/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef vm_PropMap_inl_h #define vm_PropMap_inl_h #include "vm/PropMap.h" #include "gc/Cell.h" #include "gc/Zone.h" #include "vm/JSContext.h" #include "gc/GCContext-inl.h" namespace js { inline AutoKeepPropMapTables::AutoKeepPropMapTables(JSContext* cx) : cx_(cx), prev_(cx->zone()->keepPropMapTables()) { cx->zone()->setKeepPropMapTables(true); } inline AutoKeepPropMapTables::~AutoKeepPropMapTables() { cx_->zone()->setKeepPropMapTables(prev_); } // static MOZ_ALWAYS_INLINE PropMap* PropMap::lookupLinear(uint32_t mapLength, PropertyKey key, uint32_t* index) { MOZ_ASSERT(mapLength > 0); MOZ_ASSERT(mapLength <= Capacity); // This function is very hot, so we use a macro to manually unroll the lookups // below. Some compilers are able to unroll the equivalent loops, but they're // not very consistent about this. The code below results in reasonable code // with all compilers we tested. static_assert(PropMap::Capacity == 8, "Code below needs to change when capacity changes"); #define LOOKUP_KEY(idx) \ if (mapLength > idx && getKey(idx) == key) { \ *index = idx; \ return this; \ } LOOKUP_KEY(0); LOOKUP_KEY(1); LOOKUP_KEY(2); LOOKUP_KEY(3); LOOKUP_KEY(4); LOOKUP_KEY(5); LOOKUP_KEY(6); LOOKUP_KEY(7); #undef LOOKUP_KEY PropMap* map = this; while (map->hasPrevious()) { map = map->asLinked()->previous(); #define LOOKUP_KEY(idx) \ if (map->getKey(idx) == key) { \ *index = idx; \ return map; \ } LOOKUP_KEY(0); LOOKUP_KEY(1); LOOKUP_KEY(2); LOOKUP_KEY(3); LOOKUP_KEY(4); LOOKUP_KEY(5); LOOKUP_KEY(6); LOOKUP_KEY(7); #undef LOOKUP_INDEX } return nullptr; } MOZ_ALWAYS_INLINE PropMap* PropMapTable::lookup(PropMap* map, uint32_t mapLength, PropertyKey key, uint32_t* index) { JS::AutoCheckCannotGC nogc; MOZ_ASSERT(map->asLinked()->maybeTable(nogc) == this); PropMapAndIndex entry; if (lookupInCache(key, &entry)) { if (entry.isNone()) { return nullptr; } } else { auto p = lookupRaw(key); addToCache(key, p); if (!p) { return nullptr; } entry = *p; } // For the last map, only properties in [0, mapLength) are part of the object. if (entry.map() == map && entry.index() >= mapLength) { return nullptr; } *index = entry.index(); return entry.map(); } // static MOZ_ALWAYS_INLINE PropMap* PropMap::lookupPure(uint32_t mapLength, PropertyKey key, uint32_t* index) { if (canHaveTable()) { JS::AutoCheckCannotGC nogc; if (PropMapTable* table = asLinked()->maybeTable(nogc)) { return table->lookup(this, mapLength, key, index); } } return lookupLinear(mapLength, key, index); } // static MOZ_ALWAYS_INLINE PropMap* PropMap::lookup(JSContext* cx, uint32_t mapLength, PropertyKey key, uint32_t* index) { if (canHaveTable()) { JS::AutoCheckCannotGC nogc; if (PropMapTable* table = asLinked()->ensureTable(cx, nogc); MOZ_LIKELY(table)) { return table->lookup(this, mapLength, key, index); } // OOM. Do a linear lookup. cx->recoverFromOutOfMemory(); } return lookupLinear(mapLength, key, index); } // static inline void SharedPropMap::getPrevious(MutableHandle map, uint32_t* mapLength) { // Update the map/mapLength pointers to "remove" the last property. In most // cases we can simply decrement *mapLength, but if *mapLength is 1 we have to // either start at the previous map or set map/mapLength to nullptr/zero // (if there is just one property). MOZ_ASSERT(map); MOZ_ASSERT(*mapLength > 0); if (*mapLength > 1) { *mapLength -= 1; return; } if (map->hasPrevious()) { map.set(map->asNormal()->previous()); *mapLength = PropMap::Capacity; return; } map.set(nullptr); *mapLength = 0; } // static inline bool PropMap::lookupForRemove(JSContext* cx, PropMap* map, uint32_t mapLength, PropertyKey key, const AutoKeepPropMapTables& keep, PropMap** propMap, uint32_t* propIndex, PropMapTable** table, PropMapTable::Ptr* ptr) { if (map->isDictionary()) { *table = map->asLinked()->ensureTable(cx, keep); if (!*table) { return false; } *ptr = (*table)->lookupRaw(key); *propMap = *ptr ? (*ptr)->map() : nullptr; *propIndex = *ptr ? (*ptr)->index() : 0; return true; } *table = nullptr; *propMap = map->lookup(cx, mapLength, key, propIndex); return true; } MOZ_ALWAYS_INLINE bool SharedPropMap::shouldConvertToDictionaryForAdd() const { if (MOZ_LIKELY(numPreviousMaps() < NumPrevMapsConsiderDictionary)) { return false; } if (numPreviousMaps() >= NumPrevMapsAlwaysDictionary) { return true; } // More heuristics: if one of the last two maps has had a dictionary // conversion before, or is branchy (indicated by parent != previous), convert // to dictionary. const SharedPropMap* curMap = this; for (size_t i = 0; i < 2; i++) { if (curMap->hadDictionaryConversion()) { return true; } if (curMap->treeDataRef().parent.map() != curMap->asNormal()->previous()) { return true; } curMap = curMap->asNormal()->previous(); } return false; } inline void SharedPropMap::sweep(JS::GCContext* gcx) { // We detach the child from the parent if the parent is reachable. // // This test depends on PropMap arenas not being freed until after we finish // incrementally sweeping them. If that were not the case the parent pointer // could point to a marked cell that had been deallocated and then // reallocated, since allocating a cell in a zone that is being marked will // set the mark bit for that cell. MOZ_ASSERT(zone()->isGCSweeping()); MOZ_ASSERT_IF(hasPrevious(), asLinked()->previous()->zone() == zone()); SharedPropMapAndIndex parent = treeDataRef().parent; if (!parent.isNone() && TenuredThingIsMarkedAny(parent.map())) { parent.map()->removeChild(gcx, this); } } inline void SharedPropMap::finalize(JS::GCContext* gcx) { if (canHaveTable() && asLinked()->hasTable()) { asLinked()->purgeTable(gcx); } if (hasChildrenSet()) { SharedChildrenPtr& childrenRef = treeDataRef().children; gcx->delete_(this, childrenRef.toChildrenSet(), MemoryUse::PropMapChildren); childrenRef.setNone(); } } inline void DictionaryPropMap::finalize(JS::GCContext* gcx) { if (asLinked()->hasTable()) { asLinked()->purgeTable(gcx); } } } // namespace js #endif /* vm_PropMap_inl_h */