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