Skip to content

Commit

Permalink
Create RegExp match objects using a pre-created hidden class
Browse files Browse the repository at this point in the history
Summary:
Use a pre-computed hidden class when creating regexp match objects. This
pre-computed hidden class contains the properties "index", "input", and "groups"
which according to [ES2023 22.2.7.2 RegExpBuiltinExec ( R, S )](
https://tc39.es/ecma262/#sec-regexpbuiltinexec) will always be
present in the resulting object.

Reviewed By: fbmal7

Differential Revision: D40769312

fbshipit-source-id: 5ca0b8dad7da19bf55d2aa1b86e9bc1b98cd3943
  • Loading branch information
John Porto authored and facebook-github-bot committed Nov 10, 2022
1 parent 07f9aaa commit 987627c
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 36 deletions.
11 changes: 11 additions & 0 deletions include/hermes/VM/JSArray.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,17 @@ class JSArray final : public ArrayImpl {
size_type capacity = 0,
size_type length = 0);

/// Create an instance of Array, with [[Prototype]] initialized with
/// \p prototypeHandle, with capacity for \p capacity elements and actual size
/// \p length. It also allocates the return object's property storage array
/// to hold all properties in \p classHandle.
static CallResult<Handle<JSArray>> createAndAllocPropStorage(
Runtime &runtime,
Handle<JSObject> prototypeHandle,
Handle<HiddenClass> classHandle,
size_type capacity = 0,
size_type length = 0);

static CallResult<Handle<JSArray>> create(
Runtime &runtime,
Handle<JSObject> prototypeHandle,
Expand Down
7 changes: 7 additions & 0 deletions include/hermes/VM/JSRegExp.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ class JSRegExp final : public JSObject {
return create(runtime, Handle<JSObject>::vmcast(&runtime.regExpPrototype));
}

/// Creates the hidden class for Regular Expression match objects.
/// \p arrayClass should be hidden class for JSArray objects, and it will be
/// augmented with properties "index", "input", and "groups".
static Handle<HiddenClass> createMatchClass(
Runtime &runtime,
Handle<HiddenClass> arrayClass);

/// Initializes RegExp with existing bytecode. Populates fields for the
/// pattern and flags, but performs no validation on them. It is assumed that
/// the bytecode is correct and corresponds to the given pattern/flags.
Expand Down
1 change: 1 addition & 0 deletions include/hermes/VM/RuntimeHermesValueFields.def
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ RUNTIME_HV_FIELD_PROTOTYPE(regExpLastRegExp)

RUNTIME_HV_FIELD_PROTOTYPE(throwTypeErrorAccessor)
RUNTIME_HV_FIELD_PROTOTYPE(arrayClass)
RUNTIME_HV_FIELD_PROTOTYPE(regExpMatchClass)

RUNTIME_HV_FIELD_PROTOTYPE(iteratorPrototype)
RUNTIME_HV_FIELD_PROTOTYPE(arrayIteratorPrototype)
Expand Down
21 changes: 21 additions & 0 deletions lib/VM/JSArray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,27 @@ CallResult<Handle<JSArray>> JSArray::createNoAllocPropStorage(
return self;
}

CallResult<Handle<JSArray>> JSArray::createAndAllocPropStorage(
Runtime &runtime,
Handle<JSObject> prototypeHandle,
Handle<HiddenClass> classHandle,
size_type capacity,
size_type length) {
CallResult<Handle<JSArray>> res = createNoAllocPropStorage(
runtime, prototypeHandle, classHandle, capacity, length);
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}

// Allocate property storage with size corresponding to number of properties
// in the hidden class.
Handle<JSArray> arr = std::move(*res);
runtime.ignoreAllocationFailure(JSObject::allocatePropStorage(
arr, runtime, classHandle->getNumProperties()));

return arr;
}

CallResult<Handle<JSArray>>
JSArray::create(Runtime &runtime, size_type capacity, size_type length) {
return JSArray::createNoAllocPropStorage(
Expand Down
6 changes: 6 additions & 0 deletions lib/VM/JSLib/GlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,12 @@ void initGlobalObject(Runtime &runtime, const JSLibFlags &jsLibFlags) {
runtime, Handle<JSObject>::vmcast(&runtime.arrayPrototype))
.getHermesValue();

// Declare the regexp match object class.
runtime.regExpMatchClass =
JSRegExp::createMatchClass(
runtime, Handle<HiddenClass>::vmcast(&runtime.arrayClass))
.getHermesValue();

// "Forward declaration" of ArrayBuffer.prototype. Its properties will be
// populated later.
runtime.arrayBufferPrototype =
Expand Down
76 changes: 40 additions & 36 deletions lib/VM/JSLib/RegExp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -417,18 +417,26 @@ static CallResult<Handle<JSRegExp>> regExpConstructorFastCopy(
return Handle<JSRegExp>::vmcast(*newRegexpRes);
}

static ExecutionStatus createGroupsObject(
static void createGroupsObject(
Runtime &runtime,
Handle<JSArray> matchObj,
Handle<JSObject> mappingObj) {
// matchObj is created with a HiddenClass that already has the groups
// property.
NamedPropertyDescriptor groupsDesc;
auto pos = JSObject::getOwnNamedDescriptor(
matchObj,
runtime,
Predefined::getSymbolID(Predefined::groups),
groupsDesc);
assert(pos && "match object is missing .groups property");
(void)pos;

// If there are no capture groups, then set groups to undefined.
if (!mappingObj) {
return JSObject::putNamed_RJS(
matchObj,
runtime,
Predefined::getSymbolID(Predefined::groups),
runtime.makeHandle(HermesValue::encodeUndefinedValue()))
.getStatus();
auto shv = SmallHermesValue::encodeUndefinedValue();
JSObject::setNamedSlotValueUnsafe(matchObj.get(), runtime, groupsDesc, shv);
return;
}

// The `__proto__` property on the groups object is not special,
Expand All @@ -448,14 +456,8 @@ static ExecutionStatus createGroupsObject(
*groupsObj, runtime, desc.slot, matchObj->at(runtime, groupIdx));
});

return JSObject::defineOwnProperty(
matchObj,
runtime,
Predefined::getSymbolID(Predefined::groups),
DefinePropertyFlags::getDefaultNewPropertyFlags(),
groupsObj,
PropOpFlags().plusThrowOnError())
.getStatus();
auto shv = SmallHermesValue::encodeObjectValue(*groupsObj, runtime);
JSObject::setNamedSlotValueUnsafe(matchObj.get(), runtime, groupsDesc, shv);
}

// ES6 21.2.5.2.2
Expand Down Expand Up @@ -564,32 +566,34 @@ CallResult<Handle<JSArray>> directRegExpExec(
}
}

const auto dpf = DefinePropertyFlags::getDefaultNewPropertyFlags();

auto arrRes = JSArray::create(runtime, match.size(), match.size());
auto arrRes = JSArray::createAndAllocPropStorage(
runtime,
Handle<JSObject>::vmcast(&runtime.arrayPrototype),
Handle<HiddenClass>::vmcast(&runtime.regExpMatchClass),
match.size(),
match.size());
if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
A = arrRes->get();

CallResult<bool> defineResult = JSObject::defineOwnProperty(
A,
runtime,
Predefined::getSymbolID(Predefined::index),
dpf,
runtime.makeHandle(
HermesValue::encodeNumberValue(match.front()->location)));
assert(
defineResult != ExecutionStatus::EXCEPTION &&
"defineOwnProperty() failed on a new object");
(void)defineResult;

defineResult = JSObject::defineOwnProperty(
A, runtime, Predefined::getSymbolID(Predefined::input), dpf, S);
assert(
defineResult != ExecutionStatus::EXCEPTION &&
"defineOwnProperty() failed on a new object");
(void)defineResult;
// A already has .index and .groups.
NamedPropertyDescriptor indexDesc;
bool res = JSObject::getOwnNamedDescriptor(
A, runtime, Predefined::getSymbolID(Predefined::index), indexDesc);
assert(res && "match object is missing .index property");
(void)res;
auto indexSHV =
SmallHermesValue::encodeNumberValue(match.front()->location, runtime);
JSObject::setNamedSlotValueUnsafe(*A, runtime, indexDesc, indexSHV);

NamedPropertyDescriptor inputDesc;
res = JSObject::getOwnNamedDescriptor(
A, runtime, Predefined::getSymbolID(Predefined::input), inputDesc);
assert(res && "match object is missing .input property");
(void)res;
auto inputSHV = SmallHermesValue::encodeStringValue(*S, runtime);
JSObject::setNamedSlotValueUnsafe(*A, runtime, inputDesc, inputSHV);

// Set capture groups (including the initial full match)
size_t idx = 0;
Expand Down
22 changes: 22 additions & 0 deletions lib/VM/JSRegExp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,28 @@ PseudoHandle<JSRegExp> JSRegExp::create(
return JSObjectInit::initToPseudoHandle(runtime, cell);
}

Handle<HiddenClass> JSRegExp::createMatchClass(
Runtime &runtime,
Handle<HiddenClass> arrayClass) {
// Adds the property \p name to matchClass which, upon return, will point to
// the newly created hidden class.
auto addProperty = [&](Handle<HiddenClass> clazz, Predefined::Str name) {
auto added = HiddenClass::addProperty(
clazz,
runtime,
Predefined::getSymbolID(name),
PropertyFlags::defaultNewNamedPropertyFlags());
assert(
added != ExecutionStatus::EXCEPTION &&
"Adding the first properties shouldn't cause overflow");
return added->first;
};

Handle<HiddenClass> addIndex = addProperty(arrayClass, Predefined::index);
Handle<HiddenClass> addInput = addProperty(addIndex, Predefined::input);
return addProperty(addInput, Predefined::groups);
}

void JSRegExp::initialize(
Handle<JSRegExp> selfHandle,
Runtime &runtime,
Expand Down

0 comments on commit 987627c

Please sign in to comment.