lib/VM/HiddenClass.cpp (743 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #define DEBUG_TYPE "class" #include "hermes/VM/HiddenClass.h" #include "hermes/VM/ArrayStorage.h" #include "hermes/VM/GCPointer-inline.h" #include "hermes/VM/JSArray.h" #include "hermes/VM/JSObject.h" #include "hermes/VM/Operations.h" #include "hermes/VM/StringView.h" #include "llvh/Support/Debug.h" using llvh::dbgs; namespace hermes { namespace vm { namespace detail { void TransitionMap::snapshotAddNodes(GC *gc, HeapSnapshot &snap) { if (!isLarge()) { return; } // Make one node that is the sum of the sizes of the WeakValueMap and the // llvh::DenseMap to which it points. // This is based on the assumption that the WeakValueMap uniquely owns that // DenseMap. snap.beginNode(); snap.endNode( HeapSnapshot::NodeType::Native, "WeakValueMap", gc->getNativeID(large()), getMemorySize(), 0); } void TransitionMap::snapshotAddEdges(GC *gc, HeapSnapshot &snap) { if (!isLarge()) { return; } snap.addNamedEdge( HeapSnapshot::EdgeType::Internal, "transitionMap", gc->getNativeID(large())); } void TransitionMap::snapshotUntrackMemory(GC *gc) { // Untrack the memory ID in case one was created. if (isLarge()) { gc->getIDTracker().untrackNative(large()); } } void TransitionMap::insertUnsafe( Runtime &runtime, const Transition &key, WeakRefSlot *ptr) { if (isClean()) { smallKey_ = key; smallValue() = WeakRef<HiddenClass>(ptr); return; } if (!isLarge()) uncleanMakeLarge(runtime); large()->insertUnsafe(key, ptr); } size_t TransitionMap::getMemorySize() const { // Inline slot is not counted here (it counts as part of the HiddenClass). return isLarge() ? sizeof(*large()) + large()->getMemorySize() : 0; } void TransitionMap::uncleanMakeLarge(Runtime &runtime) { assert(!isClean() && "must not still be clean"); assert(!isLarge() && "must not yet be large"); auto large = new WeakValueMap<Transition, HiddenClass>(); // Move any valid entry into the allocated map. if (auto handle = smallValue().get(runtime, &runtime.getHeap())) large->insertNewLocked(&runtime.getHeap(), smallKey_, handle.getValue()); u.large_ = large; smallKey_.symbolID = SymbolID::deleted(); assert(isLarge()); } } // namespace detail const VTable HiddenClass::vt{ CellKind::HiddenClassKind, cellSize<HiddenClass>(), _finalizeImpl, _markWeakImpl, _mallocSizeImpl, nullptr, VTable::HeapSnapshotMetadata{ HeapSnapshot::NodeType::Object, HiddenClass::_snapshotNameImpl, HiddenClass::_snapshotAddEdgesImpl, HiddenClass::_snapshotAddNodesImpl, nullptr}}; void HiddenClassBuildMeta(const GCCell *cell, Metadata::Builder &mb) { const auto *self = static_cast<const HiddenClass *>(cell); mb.setVTable(&HiddenClass::vt); mb.addField("symbol", &self->symbolID_); mb.addField("parent", &self->parent_); mb.addField("propertyMap", &self->propertyMap_); mb.addField("forInCache", &self->forInCache_); } void HiddenClass::_markWeakImpl(GCCell *cell, WeakRefAcceptor &acceptor) { auto *self = reinterpret_cast<HiddenClass *>(cell); self->transitionMap_.markWeakRefs(acceptor); } void HiddenClass::_finalizeImpl(GCCell *cell, GC *gc) { auto *self = vmcast<HiddenClass>(cell); self->transitionMap_.snapshotUntrackMemory(gc); self->~HiddenClass(); } size_t HiddenClass::_mallocSizeImpl(GCCell *cell) { auto *self = vmcast<HiddenClass>(cell); return self->transitionMap_.getMemorySize(); } std::string HiddenClass::_snapshotNameImpl(GCCell *cell, GC *gc) { auto *const self = vmcast<HiddenClass>(cell); std::string name{cell->getVT()->snapshotMetaData.defaultNameForNode(self)}; if (self->isDictionary()) { return name + "(Dictionary)"; } return name; } void HiddenClass::_snapshotAddEdgesImpl( GCCell *cell, GC *gc, HeapSnapshot &snap) { auto *const self = vmcast<HiddenClass>(cell); self->transitionMap_.snapshotAddEdges(gc, snap); } void HiddenClass::_snapshotAddNodesImpl( GCCell *cell, GC *gc, HeapSnapshot &snap) { auto *const self = vmcast<HiddenClass>(cell); self->transitionMap_.snapshotAddNodes(gc, snap); } CallResult<HermesValue> HiddenClass::createRoot(Runtime &runtime) { return create( runtime, ClassFlags{}, Runtime::makeNullHandle<HiddenClass>(), SymbolID{}, PropertyFlags{}, 0); } CallResult<HermesValue> HiddenClass::create( Runtime &runtime, ClassFlags flags, Handle<HiddenClass> parent, SymbolID symbolID, PropertyFlags propertyFlags, unsigned numProperties) { assert( (flags.dictionaryMode || numProperties == 0 || *parent) && "non-empty non-dictionary orphan"); auto *obj = runtime.makeAFixed<HiddenClass, HasFinalizer::Yes, LongLived::Yes>( runtime, flags, parent, symbolID, propertyFlags, numProperties); return HermesValue::encodeObjectValue(obj); } Handle<HiddenClass> HiddenClass::copyToNewDictionary( Handle<HiddenClass> selfHandle, Runtime &runtime, bool noCache) { assert( !selfHandle->isDictionaryNoCache() && "class already in no-cache mode"); auto newFlags = selfHandle->flags_; newFlags.dictionaryMode = true; // If the requested, transition to no-cache mode. if (noCache) { newFlags.dictionaryNoCacheMode = true; } /// Allocate a new class without a parent. auto newClassHandle = runtime.makeHandle<HiddenClass>( runtime.ignoreAllocationFailure(HiddenClass::create( runtime, newFlags, Runtime::makeNullHandle<HiddenClass>(), SymbolID{}, PropertyFlags{}, selfHandle->numProperties_))); // Optionally allocate the property map and move it to the new class. if (LLVM_UNLIKELY(!selfHandle->propertyMap_)) initializeMissingPropertyMap(selfHandle, runtime); newClassHandle->propertyMap_.set( runtime, selfHandle->propertyMap_, &runtime.getHeap()); selfHandle->propertyMap_.setNull(&runtime.getHeap()); LLVM_DEBUG( dbgs() << "Converted Class:" << selfHandle->getDebugAllocationId() << " to dictionary Class:" << newClassHandle->getDebugAllocationId() << "\n"); return newClassHandle; } void HiddenClass::forEachPropertyNoAlloc( HiddenClass *self, PointerBase &base, std::function<void(SymbolID, NamedPropertyDescriptor)> callback) { std::vector<std::pair<SymbolID, NamedPropertyDescriptor>> properties; HiddenClass *curr = self; while (curr && !curr->propertyMap_) { // Skip invalid symbols stored in the hidden class chain, as well as // flag-only transitions. if (curr->symbolID_.isValid() && !curr->propertyFlags_.flagsTransition) { properties.emplace_back( curr->symbolID_, NamedPropertyDescriptor{ curr->propertyFlags_, curr->numProperties_ - 1}); } curr = curr->parent_.get(base); } // Either we reached the root hidden class and never found a property // map, or we found a property map somewhere in the HiddenClass chain. if (curr) { assert( curr->propertyMap_ && "If it's not the root class, it must have a property map"); // The DPM exists, and it can be iterated over to find some properties. DictPropertyMap::forEachPropertyNoAlloc( curr->propertyMap_.getNonNull(base), callback); } // Add any iterated properties at the end. // Since we moved backwards through HiddenClasses, the properties are in // reverse order. Iterate backwards through properties to get the original // order. for (auto it = properties.rbegin(); it != properties.rend(); ++it) { callback(it->first, it->second); } } OptValue<HiddenClass::PropertyPos> HiddenClass::findProperty( PseudoHandle<HiddenClass> self, Runtime &runtime, SymbolID name, PropertyFlags expectedFlags, NamedPropertyDescriptor &desc) { // Lazily create the property map. if (LLVM_UNLIKELY(!self->propertyMap_)) { // If expectedFlags is valid, we can check if there is an outgoing // transition with name and the flags. The presence of such a transition // indicates that this is a new property and we don't have to build the map // in order to look for it (since we wouldn't find it anyway). if (expectedFlags.isValid()) { Transition t{name, expectedFlags}; if (self->transitionMap_.containsKey(t, &runtime.getHeap())) { LLVM_DEBUG( dbgs() << "Property " << runtime.formatSymbolID(name) << " NOT FOUND in Class:" << self->getDebugAllocationId() << " due to existing transition to Class:" << (*self->transitionMap_.lookup( runtime, &runtime.getHeap(), t)) ->getDebugAllocationId() << "\n"); return llvh::None; } } auto selfHandle = runtime.makeHandle(std::move(self)); initializeMissingPropertyMap(selfHandle, runtime); self = selfHandle; } auto *propMap = self->propertyMap_.getNonNull(runtime); { // propMap is a raw pointer. We assume that find does no allocation. NoAllocScope noAlloc(runtime); auto found = DictPropertyMap::find(propMap, name); if (!found) return llvh::None; // Technically, the last use of propMap occurs before the call here, so // it would be legal for the call to allocate. If that were ever the case, // we would move "found" out of scope, and terminate the NoAllocScope here. desc = DictPropertyMap::getDescriptorPair(propMap, *found)->second; return *found; } } llvh::Optional<NamedPropertyDescriptor> HiddenClass::findPropertyNoAlloc( HiddenClass *self, PointerBase &base, SymbolID name) { for (HiddenClass *curr = self; curr; curr = curr->parent_.get(base)) { if (curr->propertyMap_) { // If a property map exists, just search this hidden class auto found = DictPropertyMap::find(curr->propertyMap_.getNonNull(base), name); if (found) { return DictPropertyMap::getDescriptorPair( curr->propertyMap_.getNonNull(base), *found) ->second; } } // Else, no property map exists. Check the current hidden class before // moving up. if (curr->symbolID_ == name) { return NamedPropertyDescriptor{ curr->propertyFlags_, curr->numProperties_ - 1}; } } // Reached the root hidden class without finding a property map or the // matching symbol, this property doesn't exist. return llvh::None; } bool HiddenClass::debugIsPropertyDefined( HiddenClass *self, PointerBase &base, SymbolID name) { do { // If we happen to have a property map, use it. if (self->propertyMap_) return DictPropertyMap::find(self->propertyMap_.getNonNull(base), name) .hasValue(); // Is the property defined in this class? if (self->symbolID_ == name) return true; self = self->parent_.get(base); } while (self); return false; } Handle<HiddenClass> HiddenClass::deleteProperty( Handle<HiddenClass> selfHandle, Runtime &runtime, PropertyPos pos) { // We convert to dictionary if we're not yet a dictionary // (transition to a cacheable dictionary), or if we are, but not yet // in no-cache mode (transition to no-cache mode). auto newHandle = LLVM_UNLIKELY(!selfHandle->isDictionaryNoCache()) ? copyToNewDictionary(selfHandle, runtime, selfHandle->isDictionary()) : selfHandle; --newHandle->numProperties_; DictPropertyMap::erase(newHandle->propertyMap_.get(runtime), runtime, pos); LLVM_DEBUG( dbgs() << "Deleting from Class:" << selfHandle->getDebugAllocationId() << " produces Class:" << newHandle->getDebugAllocationId() << "\n"); return newHandle; } CallResult<std::pair<Handle<HiddenClass>, SlotIndex>> HiddenClass::addProperty( Handle<HiddenClass> selfHandle, Runtime &runtime, SymbolID name, PropertyFlags propertyFlags) { assert(propertyFlags.isValid() && "propertyFlags must be valid"); if (LLVM_UNLIKELY(selfHandle->isDictionary())) { if (toArrayIndex( runtime.getIdentifierTable().getStringView(runtime, name))) { selfHandle->flags_.hasIndexLikeProperties = true; } // Allocate a new slot. // TODO: this changes the property map, so if we want to support OOM // handling in the future, and the following operation fails, we would have // to somehow be able to undo it, or use an approach where we peek the slot // but not consume until we are sure (which is less efficient, but more // robust). T31555339. SlotIndex newSlot = DictPropertyMap::allocatePropertySlot( selfHandle->propertyMap_.get(runtime), runtime); if (LLVM_UNLIKELY( addToPropertyMap( selfHandle, runtime, name, NamedPropertyDescriptor(propertyFlags, newSlot)) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } ++selfHandle->numProperties_; return std::make_pair(selfHandle, newSlot); } // Do we already have a transition for that property+flags pair? auto optChildHandle = selfHandle->transitionMap_.lookup( runtime, &runtime.getHeap(), {name, propertyFlags}); if (LLVM_LIKELY(optChildHandle)) { // If the child doesn't have a property map, but we do, update our map and // move it to the child. if (!optChildHandle.getValue()->propertyMap_ && selfHandle->propertyMap_) { LLVM_DEBUG( dbgs() << "Adding property " << runtime.formatSymbolID(name) << " to Class:" << selfHandle->getDebugAllocationId() << " transitions Map to existing Class:" << optChildHandle.getValue()->getDebugAllocationId() << "\n"); if (LLVM_UNLIKELY( addToPropertyMap( selfHandle, runtime, name, NamedPropertyDescriptor( propertyFlags, selfHandle->numProperties_)) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } optChildHandle.getValue()->propertyMap_.set( runtime, selfHandle->propertyMap_, &runtime.getHeap()); } else { LLVM_DEBUG( dbgs() << "Adding property " << runtime.formatSymbolID(name) << " to Class:" << selfHandle->getDebugAllocationId() << " transitions to existing Class:" << optChildHandle.getValue()->getDebugAllocationId() << "\n"); } // In any case, clear our own map. selfHandle->propertyMap_.setNull(&runtime.getHeap()); return std::make_pair(*optChildHandle, selfHandle->numProperties_); } // Do we need to convert to dictionary? if (LLVM_UNLIKELY(selfHandle->numProperties_ == kDictionaryThreshold)) { // Do it. auto childHandle = copyToNewDictionary(selfHandle, runtime); if (toArrayIndex( runtime.getIdentifierTable().getStringView(runtime, name))) { childHandle->flags_.hasIndexLikeProperties = true; } // Add the property to the child. if (LLVM_UNLIKELY( addToPropertyMap( childHandle, runtime, name, NamedPropertyDescriptor( propertyFlags, childHandle->numProperties_)) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return std::make_pair(childHandle, childHandle->numProperties_++); } // Allocate the child. auto childHandle = runtime.makeHandle<HiddenClass>( runtime.ignoreAllocationFailure(HiddenClass::create( runtime, selfHandle->flags_, selfHandle, name, propertyFlags, selfHandle->numProperties_ + 1))); // Add it to the transition table. auto inserted = selfHandle->transitionMap_.insertNew( runtime, Transition(name, propertyFlags), childHandle); (void)inserted; assert( inserted && "transition already exists when adding a new property to hidden class"); if (toArrayIndex(runtime.getIdentifierTable().getStringView(runtime, name))) { childHandle->flags_.hasIndexLikeProperties = true; } if (selfHandle->propertyMap_) { assert( !DictPropertyMap::find(selfHandle->propertyMap_.get(runtime), name) && "Adding an existing property to hidden class"); LLVM_DEBUG( dbgs() << "Adding property " << runtime.formatSymbolID(name) << " to Class:" << selfHandle->getDebugAllocationId() << " transitions Map to new Class:" << childHandle->getDebugAllocationId() << "\n"); // Move the map to the child class. childHandle->propertyMap_.set( runtime, selfHandle->propertyMap_, &runtime.getHeap()); selfHandle->propertyMap_.setNull(&runtime.getHeap()); if (LLVM_UNLIKELY( addToPropertyMap( childHandle, runtime, name, NamedPropertyDescriptor( propertyFlags, selfHandle->numProperties_)) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } } else { LLVM_DEBUG( dbgs() << "Adding property " << runtime.formatSymbolID(name) << " to Class:" << selfHandle->getDebugAllocationId() << " transitions to new Class:" << childHandle->getDebugAllocationId() << "\n"); } return std::make_pair(childHandle, selfHandle->numProperties_); } Handle<HiddenClass> HiddenClass::updateProperty( Handle<HiddenClass> selfHandle, Runtime &runtime, PropertyPos pos, PropertyFlags newFlags) { assert(newFlags.isValid() && "newFlags must be valid"); // In dictionary mode we simply update our map (which must exist). if (LLVM_UNLIKELY(selfHandle->isDictionary())) { assert( selfHandle->propertyMap_ && "propertyMap must exist in dictionary mode"); DictPropertyMap::getDescriptorPair( selfHandle->propertyMap_.getNonNull(runtime), pos) ->second.flags = newFlags; // If it's still cacheable, make it non-cacheable. if (!selfHandle->isDictionaryNoCache()) { selfHandle = copyToNewDictionary(selfHandle, runtime, /*noCache*/ true); } return selfHandle; } assert( selfHandle->propertyMap_ && "propertyMap must exist in updateProperty()"); auto *descPair = DictPropertyMap::getDescriptorPair( selfHandle->propertyMap_.get(runtime), pos); // If the property flags didn't change, do nothing. if (descPair->second.flags == newFlags) return selfHandle; auto name = descPair->first; // The transition flags must indicate that it is a "flags transition". PropertyFlags transitionFlags = newFlags; transitionFlags.flagsTransition = 1; // Do we already have a transition for that property+flags pair? auto optChildHandle = selfHandle->transitionMap_.lookup( runtime, &runtime.getHeap(), {name, transitionFlags}); if (LLVM_LIKELY(optChildHandle)) { // If the child doesn't have a property map, but we do, update our map and // move it to the child. if (!optChildHandle.getValue()->propertyMap_) { LLVM_DEBUG( dbgs() << "Updating property " << runtime.formatSymbolID(name) << " in Class:" << selfHandle->getDebugAllocationId() << " transitions Map to existing Class:" << optChildHandle.getValue()->getDebugAllocationId() << "\n"); descPair->second.flags = newFlags; optChildHandle.getValue()->propertyMap_.set( runtime, selfHandle->propertyMap_, &runtime.getHeap()); } else { LLVM_DEBUG( dbgs() << "Updating property " << runtime.formatSymbolID(name) << " in Class:" << selfHandle->getDebugAllocationId() << " transitions to existing Class:" << optChildHandle.getValue()->getDebugAllocationId() << "\n"); } // In any case, clear our own map. selfHandle->propertyMap_.setNull(&runtime.getHeap()); return *optChildHandle; } // We are updating the existing property and adding a transition to a new // hidden class. descPair->second.flags = newFlags; // Allocate the child. auto childHandle = runtime.makeHandle<HiddenClass>( runtime.ignoreAllocationFailure(HiddenClass::create( runtime, selfHandle->flags_, selfHandle, name, transitionFlags, selfHandle->numProperties_))); // Add it to the transition table. auto inserted = selfHandle->transitionMap_.insertNew( runtime, Transition(name, transitionFlags), childHandle); (void)inserted; assert( inserted && "transition already exists when updating a property in hidden class"); LLVM_DEBUG( dbgs() << "Updating property " << runtime.formatSymbolID(name) << " in Class:" << selfHandle->getDebugAllocationId() << " transitions Map to new Class:" << childHandle->getDebugAllocationId() << "\n"); // Move the updated map to the child class. childHandle->propertyMap_.set( runtime, selfHandle->propertyMap_, &runtime.getHeap()); selfHandle->propertyMap_.setNull(&runtime.getHeap()); return childHandle; } Handle<HiddenClass> HiddenClass::makeAllNonConfigurable( Handle<HiddenClass> selfHandle, Runtime &runtime) { if (selfHandle->flags_.allNonConfigurable) return selfHandle; if (!selfHandle->propertyMap_) initializeMissingPropertyMap(selfHandle, runtime); LLVM_DEBUG( dbgs() << "Class:" << selfHandle->getDebugAllocationId() << " making all non-configurable\n"); // Keep a handle to our initial map. The order of properties in it will // remain the same as long as we are only doing property updates. auto mapHandle = runtime.makeHandle(selfHandle->propertyMap_); MutableHandle<HiddenClass> curHandle{runtime, *selfHandle}; // TODO: this can be made much more efficient at the expense of moving some // logic from updateOwnProperty() here. DictPropertyMap::forEachProperty( mapHandle, runtime, [&runtime, &curHandle](SymbolID id, NamedPropertyDescriptor desc) { if (!desc.flags.configurable) return; PropertyFlags newFlags = desc.flags; newFlags.configurable = 0; assert( curHandle->propertyMap_ && "propertyMap must exist after updateOwnProperty()"); auto found = DictPropertyMap::find(curHandle->propertyMap_.get(runtime), id); assert(found && "property not found during enumeration"); curHandle = *updateProperty(curHandle, runtime, *found, newFlags); }); curHandle->flags_.allNonConfigurable = true; return std::move(curHandle); } Handle<HiddenClass> HiddenClass::makeAllReadOnly( Handle<HiddenClass> selfHandle, Runtime &runtime) { if (selfHandle->flags_.allReadOnly) return selfHandle; if (!selfHandle->propertyMap_) initializeMissingPropertyMap(selfHandle, runtime); LLVM_DEBUG( dbgs() << "Class:" << selfHandle->getDebugAllocationId() << " making all read-only\n"); // Keep a handle to our initial map. The order of properties in it will // remain the same as long as we are only doing property updates. auto mapHandle = runtime.makeHandle(selfHandle->propertyMap_); MutableHandle<HiddenClass> curHandle{runtime, *selfHandle}; // TODO: this can be made much more efficient at the expense of moving some // logic from updateOwnProperty() here. DictPropertyMap::forEachProperty( mapHandle, runtime, [&runtime, &curHandle](SymbolID id, NamedPropertyDescriptor desc) { PropertyFlags newFlags = desc.flags; if (!newFlags.accessor) { newFlags.writable = 0; newFlags.configurable = 0; } else { newFlags.configurable = 0; } if (desc.flags == newFlags) return; assert( curHandle->propertyMap_ && "propertyMap must exist after updateOwnProperty()"); auto found = DictPropertyMap::find(curHandle->propertyMap_.get(runtime), id); assert(found && "property not found during enumeration"); curHandle = *updateProperty(curHandle, runtime, *found, newFlags); }); curHandle->flags_.allNonConfigurable = true; curHandle->flags_.allReadOnly = true; return std::move(curHandle); } Handle<HiddenClass> HiddenClass::updatePropertyFlagsWithoutTransitions( Handle<HiddenClass> selfHandle, Runtime &runtime, PropertyFlags flagsToClear, PropertyFlags flagsToSet, OptValue<llvh::ArrayRef<SymbolID>> props) { // Result must be in dictionary mode, since it's a non-empty orphan. MutableHandle<HiddenClass> classHandle{runtime}; if (selfHandle->isDictionary()) { classHandle = *selfHandle; } else { classHandle = *copyToNewDictionary(selfHandle, runtime); } auto mapHandle = runtime.makeHandle<DictPropertyMap>(classHandle->propertyMap_); auto changeFlags = [&flagsToClear, &flagsToSet](NamedPropertyDescriptor &desc) { desc.flags.changeFlags(flagsToClear, flagsToSet); }; // If we have the subset of properties to update, only update them; otherwise, // update all properties. if (props) { // Iterate over the properties that exist on the property map. for (auto id : *props) { auto pos = DictPropertyMap::find(*mapHandle, id); if (!pos) { continue; } auto descPair = DictPropertyMap::getDescriptorPair(*mapHandle, *pos); changeFlags(descPair->second); } } else { DictPropertyMap::forEachMutablePropertyDescriptor( mapHandle, runtime, changeFlags); } return std::move(classHandle); } CallResult<std::pair<Handle<HiddenClass>, SlotIndex>> HiddenClass::reserveSlot( Handle<HiddenClass> selfHandle, Runtime &runtime) { assert( !selfHandle->isDictionary() && "Reserved slots can only be added in class mode"); SlotIndex index = selfHandle->numProperties_; assert( index < InternalProperty::NumAnonymousInternalProperties && "Reserved slot index is too large"); return HiddenClass::addProperty( selfHandle, runtime, InternalProperty::getSymbolID(index), PropertyFlags{}); } bool HiddenClass::areAllNonConfigurable( Handle<HiddenClass> selfHandle, Runtime &runtime) { if (selfHandle->flags_.allNonConfigurable) return true; if (!forEachPropertyWhile( selfHandle, runtime, [](Runtime &, SymbolID, NamedPropertyDescriptor desc) { return !desc.flags.configurable; })) { return false; } selfHandle->flags_.allNonConfigurable = true; return true; } bool HiddenClass::areAllReadOnly( Handle<HiddenClass> selfHandle, Runtime &runtime) { if (selfHandle->flags_.allReadOnly) return true; if (!forEachPropertyWhile( selfHandle, runtime, [](Runtime &, SymbolID, NamedPropertyDescriptor desc) { if (!desc.flags.accessor && desc.flags.writable) return false; return !desc.flags.configurable; })) { return false; } selfHandle->flags_.allNonConfigurable = true; selfHandle->flags_.allReadOnly = true; return true; } ExecutionStatus HiddenClass::addToPropertyMap( Handle<HiddenClass> selfHandle, Runtime &runtime, SymbolID name, NamedPropertyDescriptor desc) { assert(selfHandle->propertyMap_ && "the property map must be initialized"); // Add the new field to the property map. MutableHandle<DictPropertyMap> updatedMap{ runtime, selfHandle->propertyMap_.get(runtime)}; if (LLVM_UNLIKELY( DictPropertyMap::add(updatedMap, runtime, name, desc) == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } selfHandle->propertyMap_.setNonNull(runtime, *updatedMap, &runtime.getHeap()); return ExecutionStatus::RETURNED; } void HiddenClass::initializeMissingPropertyMap( Handle<HiddenClass> selfHandle, Runtime &runtime) { assert(!selfHandle->propertyMap_ && "property map is already initialized"); // Check whether we can steal our parent's map. If we can, we only need // to add or update a single property. if (selfHandle->parent_ && selfHandle->parent_.getNonNull(runtime)->propertyMap_) return stealPropertyMapFromParent(selfHandle, runtime); LLVM_DEBUG( dbgs() << "Class:" << selfHandle->getDebugAllocationId() << " allocating new map\n"); // First collect all entries in reverse order. This avoids recursion. using MapEntry = std::pair<SymbolID, PropertyFlags>; llvh::SmallVector<MapEntry, 4> entries; entries.reserve(selfHandle->numProperties_); { // Walk chain of parents using raw pointers. NoAllocScope _(runtime); for (auto *cur = *selfHandle; cur->numProperties_ > 0; cur = cur->parent_.getNonNull(runtime)) { auto tmpFlags = cur->propertyFlags_; tmpFlags.flagsTransition = 0; entries.emplace_back(cur->symbolID_, tmpFlags); } } assert( entries.size() <= DictPropertyMap::getMaxCapacity() && "There shouldn't ever be this many properties"); // Allocate the map with the correct size. auto res = DictPropertyMap::create( runtime, std::max( (DictPropertyMap::size_type)entries.size(), toRValue(DictPropertyMap::DEFAULT_CAPACITY))); assert( res != ExecutionStatus::EXCEPTION && "Since the entries would fit, there shouldn't be an exception"); MutableHandle<DictPropertyMap> mapHandle{runtime, res->get()}; // Add the collected entries in reverse order. Note that there could be // duplicates. SlotIndex slotIndex = 0; for (auto it = entries.rbegin(), e = entries.rend(); it != e; ++it) { auto inserted = DictPropertyMap::findOrAdd(mapHandle, runtime, it->first); assert( inserted != ExecutionStatus::EXCEPTION && "Space was already reserved, this couldn't have grown"); inserted->first->flags = it->second; // If it is a new property, allocate the next slot. if (LLVM_LIKELY(inserted->second)) inserted->first->slot = slotIndex++; } selfHandle->propertyMap_.setNonNull(runtime, *mapHandle, &runtime.getHeap()); } void HiddenClass::stealPropertyMapFromParent( Handle<HiddenClass> selfHandle, Runtime &runtime) { // Most of this method uses raw pointers. NoAllocScope noAlloc(runtime); auto *self = *selfHandle; assert( self->parent_ && self->parent_.getNonNull(runtime)->propertyMap_ && !self->propertyMap_ && "stealPropertyMapFromParent() must be called with a valid parent with a property map"); LLVM_DEBUG( dbgs() << "Class:" << self->getDebugAllocationId() << " stealing map from parent Class:" << self->parent_.getNonNull(runtime)->getDebugAllocationId() << "\n"); // Success! Just steal our parent's map and add our own property. self->propertyMap_.set( runtime, self->parent_.getNonNull(runtime)->propertyMap_, &runtime.getHeap()); self->parent_.getNonNull(runtime)->propertyMap_.setNull(&runtime.getHeap()); // Does our class add a new property? if (LLVM_LIKELY(!self->propertyFlags_.flagsTransition)) { // This is a new property that we must now add. assert( self->numProperties_ - 1 == self->propertyMap_.getNonNull(runtime)->size() && "propertyMap->size() must match HiddenClass::numProperties-1 in " "new prop transition"); // Create a descriptor for our property. NamedPropertyDescriptor desc{ self->propertyFlags_, self->numProperties_ - 1}; // Return to handle mode to add the property. noAlloc.release(); addToPropertyMap(selfHandle, runtime, selfHandle->symbolID_, desc); return; } // Our class is updating the flags of an existing property. So we need // to find it and update it. assert( self->numProperties_ == self->propertyMap_.getNonNull(runtime)->size() && "propertyMap->size() must match HiddenClass::numProperties in " "flag update transition"); auto pos = DictPropertyMap::find(self->propertyMap_.get(runtime), self->symbolID_); assert(pos && "property must exist in flag update transition"); auto tmpFlags = self->propertyFlags_; tmpFlags.flagsTransition = 0; DictPropertyMap::getDescriptorPair(self->propertyMap_.get(runtime), *pos) ->second.flags = tmpFlags; } } // namespace vm } // namespace hermes #undef DEBUG_TYPE