Skip to content

Commit

Permalink
Add a separate HiddenClass for lazy objects
Browse files Browse the repository at this point in the history
Summary:
`CacheNewObject` and caching properties on the prototype benefit from
being able to assume that the HiddenClass for a lazy object will never
compare equal to that for an ordinary object.

Reviewed By: avp

Differential Revision: D68677444

fbshipit-source-id: 87d01e5ebb7f7e7ec68ae707b4fd914bcd311319
  • Loading branch information
neildhar authored and facebook-github-bot committed Jan 31, 2025
1 parent d13de00 commit 45203e1
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 22 deletions.
12 changes: 12 additions & 0 deletions include/hermes/VM/JSObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,18 @@ class JSObject : public GCCell {
return clazz_;
}

/// Set the hidden class of this object to \p clazz. This does not allocate
/// indirect property storage, and must only be used when the current and new
/// hidden classes do not have more properties than the direct storage. Note
/// that if used incorrectly, this can create completely invalid objects.
void setClassNoAllocPropStorageUnsafe(Runtime &runtime, HiddenClass *clazz) {
// Check that there is no prop storage already allocated, and the new class
// does not require it.
assert(!propStorage_);
assert(clazz->getNumProperties() <= DIRECT_PROPERTY_SLOTS);
clazz_.set(runtime, clazz, runtime.getHeap());
}

/// \return the object ID. Assign one if not yet exist. This ID can be used
/// in Set or Map where hashing is required. We don't assign object an ID
/// until we actually need it. An exception is lazily created objects where
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 @@ -75,6 +75,7 @@ RUNTIME_HV_FIELD(throwTypeErrorAccessor, PropertyAccessor)
RUNTIME_HV_FIELD(arrayClass, HiddenClass)
RUNTIME_HV_FIELD(fastArrayClass, HiddenClass)
RUNTIME_HV_FIELD(regExpMatchClass, HiddenClass)
RUNTIME_HV_FIELD(lazyObjectClass, HiddenClass)

RUNTIME_HV_FIELD(iteratorPrototype, JSObject)
RUNTIME_HV_FIELD(arrayIteratorPrototype, JSObject)
Expand Down
52 changes: 30 additions & 22 deletions lib/VM/Callable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,29 @@ std::string Callable::_snapshotNameImpl(GCCell *cell, GC &gc) {
}
#endif

/// \return the inferred parent of a Callable based on its \p kind.
static Handle<JSObject> inferredParent(Runtime &runtime, FuncKind kind) {
if (kind == FuncKind::Generator) {
return runtime.generatorFunctionPrototype;
} else if (kind == FuncKind::Async) {
return runtime.asyncFunctionPrototype;
} else {
assert(kind == FuncKind::Normal && "Unsupported function kind");
return runtime.functionPrototype;
}
}

void Callable::defineLazyProperties(Handle<Callable> fn, Runtime &runtime) {
// lazy functions can be Bound or JS Functions.
if (auto jsFun = Handle<JSFunction>::dyn_vmcast(fn)) {
const CodeBlock *codeBlock = jsFun->getCodeBlock();

// Set the actual non-lazy hidden class.
Handle<HiddenClass> newClass = runtime.getHiddenClassForPrototype(
*inferredParent(runtime, (FuncKind)codeBlock->getHeaderFlags().kind),
numOverlapSlots<JSFunction>());
jsFun->setClassNoAllocPropStorageUnsafe(runtime, *newClass);

// Create empty object for prototype.
auto prototypeParent = Callable::isGeneratorFunction(*jsFun)
? Handle<JSObject>::vmcast(&runtime.generatorPrototype)
Expand Down Expand Up @@ -147,6 +166,12 @@ void Callable::defineLazyProperties(Handle<Callable> fn, Runtime &runtime) {
cr != ExecutionStatus::EXCEPTION && "failed to define length and name");
(void)cr;
} else if (auto nativeFun = Handle<NativeJSFunction>::dyn_vmcast(fn)) {
// Set the actual non-lazy hidden class.
Handle<HiddenClass> newClass = runtime.getHiddenClassForPrototype(
*inferredParent(runtime, (FuncKind)nativeFun->getFunctionInfo()->kind),
numOverlapSlots<NativeJSFunction>());
nativeFun->setClassNoAllocPropStorageUnsafe(runtime, *newClass);

auto prototypeParent = Callable::isGeneratorFunction(*nativeFun)
? Handle<JSObject>::vmcast(&runtime.generatorPrototype)
: Handle<JSObject>::vmcast(&runtime.objectPrototype);
Expand Down Expand Up @@ -931,8 +956,7 @@ Handle<NativeJSFunction> NativeJSFunction::create(
auto *cell = runtime.makeAFixed<NativeJSFunction>(
runtime,
parentHandle,
runtime.getHiddenClassForPrototype(
*parentHandle, numOverlapSlots<NativeJSFunction>()),
runtime.lazyObjectClass,
functionPtr,
funcInfo,
unit);
Expand All @@ -952,8 +976,7 @@ Handle<NativeJSFunction> NativeJSFunction::create(
auto *cell = runtime.makeAFixed<NativeJSFunction>(
runtime,
parentHandle,
runtime.getHiddenClassForPrototype(
*parentHandle, numOverlapSlots<NativeJSFunction>()),
runtime.lazyObjectClass,
parentEnvHandle,
functionPtr,
funcInfo,
Expand All @@ -963,18 +986,6 @@ Handle<NativeJSFunction> NativeJSFunction::create(
return selfHandle;
}

/// \return the inferred parent of a Callable based on its \p kind.
static Handle<JSObject> inferredParent(Runtime &runtime, FuncKind kind) {
if (kind == FuncKind::Generator) {
return runtime.generatorFunctionPrototype;
} else if (kind == FuncKind::Async) {
return runtime.asyncFunctionPrototype;
} else {
assert(kind == FuncKind::Normal && "Unsupported function kind");
return runtime.functionPrototype;
}
}

Handle<NativeJSFunction> NativeJSFunction::createWithInferredParent(
Runtime &runtime,
Handle<Environment> parentEnvHandle,
Expand Down Expand Up @@ -1066,8 +1077,7 @@ Handle<NativeJSDerivedClass> NativeJSDerivedClass::create(
auto *cell = runtime.makeAFixed<NativeJSDerivedClass>(
runtime,
parentHandle,
runtime.getHiddenClassForPrototype(
*parentHandle, numOverlapSlots<NativeJSDerivedClass>()),
runtime.lazyObjectClass,
parentEnvHandle,
functionPtr,
funcInfo,
Expand Down Expand Up @@ -1362,8 +1372,7 @@ PseudoHandle<JSFunction> JSFunction::create(
runtime,
domain,
parentHandle,
runtime.getHiddenClassForPrototype(
*parentHandle, numOverlapSlots<JSFunction>()),
runtime.lazyObjectClass,
envHandle,
codeBlock);
auto self = JSObjectInit::initToPseudoHandle(runtime, cell);
Expand Down Expand Up @@ -1503,8 +1512,7 @@ PseudoHandle<JSDerivedClass> JSDerivedClass::create(
runtime,
domain,
parentHandle,
runtime.getHiddenClassForPrototype(
*parentHandle, numOverlapSlots<JSDerivedClass>()),
runtime.lazyObjectClass,
envHandle,
codeBlock);
auto self = JSObjectInit::initToPseudoHandle(runtime, cell);
Expand Down
3 changes: 3 additions & 0 deletions lib/VM/JSObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ void JSObject::initializeLazyObject(
Runtime &runtime,
Handle<JSObject> lazyObject) {
assert(lazyObject->flags_.lazyObject && "object must be lazy");
assert(
lazyObject->getClass(runtime) == *runtime.lazyObjectClass &&
"lazy object must have lazy class");
// object is now assumed to be a regular object.
lazyObject->flags_.lazyObject = 0;

Expand Down
5 changes: 5 additions & 0 deletions lib/VM/Runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,11 @@ Runtime::Runtime(
clazz = *addResult->first;
rootClazzes_[i] = clazz.getHermesValue();
}

// Create a separate hidden class for lazy objects so that they never
// compare equal to ordinary objects.
lazyObjectClass = vmcast<HiddenClass>(
ignoreAllocationFailure(HiddenClass::createRoot(*this)));
}

global_ = JSObject::create(*this, makeNullHandle<JSObject>());
Expand Down

0 comments on commit 45203e1

Please sign in to comment.