PropMap-inl.h (7507B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef vm_PropMap_inl_h 8 #define vm_PropMap_inl_h 9 10 #include "vm/PropMap.h" 11 12 #include "gc/Cell.h" 13 #include "gc/Zone.h" 14 #include "vm/JSContext.h" 15 16 #include "gc/GCContext-inl.h" 17 18 namespace js { 19 20 inline AutoKeepPropMapTables::AutoKeepPropMapTables(JSContext* cx) 21 : cx_(cx), prev_(cx->zone()->keepPropMapTables()) { 22 cx->zone()->setKeepPropMapTables(true); 23 } 24 25 inline AutoKeepPropMapTables::~AutoKeepPropMapTables() { 26 cx_->zone()->setKeepPropMapTables(prev_); 27 } 28 29 // static 30 MOZ_ALWAYS_INLINE PropMap* PropMap::lookupLinear(uint32_t mapLength, 31 PropertyKey key, 32 uint32_t* index) { 33 MOZ_ASSERT(mapLength > 0); 34 MOZ_ASSERT(mapLength <= Capacity); 35 36 // This function is very hot, so we use a macro to manually unroll the lookups 37 // below. Some compilers are able to unroll the equivalent loops, but they're 38 // not very consistent about this. The code below results in reasonable code 39 // with all compilers we tested. 40 41 static_assert(PropMap::Capacity == 8, 42 "Code below needs to change when capacity changes"); 43 44 #define LOOKUP_KEY(idx) \ 45 if (mapLength > idx && getKey(idx) == key) { \ 46 *index = idx; \ 47 return this; \ 48 } 49 LOOKUP_KEY(0); 50 LOOKUP_KEY(1); 51 LOOKUP_KEY(2); 52 LOOKUP_KEY(3); 53 LOOKUP_KEY(4); 54 LOOKUP_KEY(5); 55 LOOKUP_KEY(6); 56 LOOKUP_KEY(7); 57 #undef LOOKUP_KEY 58 59 PropMap* map = this; 60 while (map->hasPrevious()) { 61 map = map->asLinked()->previous(); 62 #define LOOKUP_KEY(idx) \ 63 if (map->getKey(idx) == key) { \ 64 *index = idx; \ 65 return map; \ 66 } 67 LOOKUP_KEY(0); 68 LOOKUP_KEY(1); 69 LOOKUP_KEY(2); 70 LOOKUP_KEY(3); 71 LOOKUP_KEY(4); 72 LOOKUP_KEY(5); 73 LOOKUP_KEY(6); 74 LOOKUP_KEY(7); 75 #undef LOOKUP_INDEX 76 } 77 78 return nullptr; 79 } 80 81 MOZ_ALWAYS_INLINE PropMap* PropMapTable::lookup(PropMap* map, 82 uint32_t mapLength, 83 PropertyKey key, 84 uint32_t* index) { 85 JS::AutoCheckCannotGC nogc; 86 MOZ_ASSERT(map->asLinked()->maybeTable(nogc) == this); 87 88 PropMapAndIndex entry; 89 if (lookupInCache(key, &entry)) { 90 if (entry.isNone()) { 91 return nullptr; 92 } 93 } else { 94 auto p = lookupRaw(key); 95 addToCache(key, p); 96 if (!p) { 97 return nullptr; 98 } 99 entry = *p; 100 } 101 102 // For the last map, only properties in [0, mapLength) are part of the object. 103 if (entry.map() == map && entry.index() >= mapLength) { 104 return nullptr; 105 } 106 107 *index = entry.index(); 108 return entry.map(); 109 } 110 111 // static 112 MOZ_ALWAYS_INLINE PropMap* PropMap::lookupPure(uint32_t mapLength, 113 PropertyKey key, 114 uint32_t* index) { 115 if (canHaveTable()) { 116 JS::AutoCheckCannotGC nogc; 117 if (PropMapTable* table = asLinked()->maybeTable(nogc)) { 118 return table->lookup(this, mapLength, key, index); 119 } 120 } 121 122 return lookupLinear(mapLength, key, index); 123 } 124 125 // static 126 MOZ_ALWAYS_INLINE PropMap* PropMap::lookup(JSContext* cx, uint32_t mapLength, 127 PropertyKey key, uint32_t* index) { 128 if (canHaveTable()) { 129 JS::AutoCheckCannotGC nogc; 130 if (PropMapTable* table = asLinked()->ensureTable(cx, nogc); 131 MOZ_LIKELY(table)) { 132 return table->lookup(this, mapLength, key, index); 133 } 134 // OOM. Do a linear lookup. 135 cx->recoverFromOutOfMemory(); 136 } 137 138 return lookupLinear(mapLength, key, index); 139 } 140 141 // static 142 inline void SharedPropMap::getPrevious(MutableHandle<SharedPropMap*> map, 143 uint32_t* mapLength) { 144 // Update the map/mapLength pointers to "remove" the last property. In most 145 // cases we can simply decrement *mapLength, but if *mapLength is 1 we have to 146 // either start at the previous map or set map/mapLength to nullptr/zero 147 // (if there is just one property). 148 149 MOZ_ASSERT(map); 150 MOZ_ASSERT(*mapLength > 0); 151 152 if (*mapLength > 1) { 153 *mapLength -= 1; 154 return; 155 } 156 157 if (map->hasPrevious()) { 158 map.set(map->asNormal()->previous()); 159 *mapLength = PropMap::Capacity; 160 return; 161 } 162 163 map.set(nullptr); 164 *mapLength = 0; 165 } 166 167 // static 168 inline bool PropMap::lookupForRemove(JSContext* cx, PropMap* map, 169 uint32_t mapLength, PropertyKey key, 170 const AutoKeepPropMapTables& keep, 171 PropMap** propMap, uint32_t* propIndex, 172 PropMapTable** table, 173 PropMapTable::Ptr* ptr) { 174 if (map->isDictionary()) { 175 *table = map->asLinked()->ensureTable(cx, keep); 176 if (!*table) { 177 return false; 178 } 179 *ptr = (*table)->lookupRaw(key); 180 *propMap = *ptr ? (*ptr)->map() : nullptr; 181 *propIndex = *ptr ? (*ptr)->index() : 0; 182 return true; 183 } 184 185 *table = nullptr; 186 *propMap = map->lookup(cx, mapLength, key, propIndex); 187 return true; 188 } 189 190 MOZ_ALWAYS_INLINE bool SharedPropMap::shouldConvertToDictionaryForAdd() const { 191 if (MOZ_LIKELY(numPreviousMaps() < NumPrevMapsConsiderDictionary)) { 192 return false; 193 } 194 if (numPreviousMaps() >= NumPrevMapsAlwaysDictionary) { 195 return true; 196 } 197 198 // More heuristics: if one of the last two maps has had a dictionary 199 // conversion before, or is branchy (indicated by parent != previous), convert 200 // to dictionary. 201 const SharedPropMap* curMap = this; 202 for (size_t i = 0; i < 2; i++) { 203 if (curMap->hadDictionaryConversion()) { 204 return true; 205 } 206 if (curMap->treeDataRef().parent.map() != curMap->asNormal()->previous()) { 207 return true; 208 } 209 curMap = curMap->asNormal()->previous(); 210 } 211 return false; 212 } 213 214 inline void SharedPropMap::sweep(JS::GCContext* gcx) { 215 // We detach the child from the parent if the parent is reachable. 216 // 217 // This test depends on PropMap arenas not being freed until after we finish 218 // incrementally sweeping them. If that were not the case the parent pointer 219 // could point to a marked cell that had been deallocated and then 220 // reallocated, since allocating a cell in a zone that is being marked will 221 // set the mark bit for that cell. 222 223 MOZ_ASSERT(zone()->isGCSweeping()); 224 MOZ_ASSERT_IF(hasPrevious(), asLinked()->previous()->zone() == zone()); 225 226 SharedPropMapAndIndex parent = treeDataRef().parent; 227 if (!parent.isNone() && TenuredThingIsMarkedAny(parent.map())) { 228 parent.map()->removeChild(gcx, this); 229 } 230 } 231 232 inline void SharedPropMap::finalize(JS::GCContext* gcx) { 233 if (canHaveTable() && asLinked()->hasTable()) { 234 asLinked()->purgeTable(gcx); 235 } 236 if (hasChildrenSet()) { 237 SharedChildrenPtr& childrenRef = treeDataRef().children; 238 gcx->delete_(this, childrenRef.toChildrenSet(), MemoryUse::PropMapChildren); 239 childrenRef.setNone(); 240 } 241 } 242 243 inline void DictionaryPropMap::finalize(JS::GCContext* gcx) { 244 if (asLinked()->hasTable()) { 245 asLinked()->purgeTable(gcx); 246 } 247 } 248 249 } // namespace js 250 251 #endif /* vm_PropMap_inl_h */