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