|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
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 #include "jspropertytree.h" |
|
8 |
|
9 #include "jscntxt.h" |
|
10 #include "jsgc.h" |
|
11 #include "jstypes.h" |
|
12 |
|
13 #include "vm/Shape.h" |
|
14 |
|
15 #include "jsgcinlines.h" |
|
16 |
|
17 #include "vm/Shape-inl.h" |
|
18 |
|
19 using namespace js; |
|
20 |
|
21 inline HashNumber |
|
22 ShapeHasher::hash(const Lookup &l) |
|
23 { |
|
24 return l.hash(); |
|
25 } |
|
26 |
|
27 inline bool |
|
28 ShapeHasher::match(const Key k, const Lookup &l) |
|
29 { |
|
30 return k->matches(l); |
|
31 } |
|
32 |
|
33 Shape * |
|
34 PropertyTree::newShape(ExclusiveContext *cx) |
|
35 { |
|
36 Shape *shape = js_NewGCShape(cx); |
|
37 if (!shape) |
|
38 js_ReportOutOfMemory(cx); |
|
39 return shape; |
|
40 } |
|
41 |
|
42 static KidsHash * |
|
43 HashChildren(Shape *kid1, Shape *kid2) |
|
44 { |
|
45 KidsHash *hash = js_new<KidsHash>(); |
|
46 if (!hash || !hash->init(2)) { |
|
47 js_delete(hash); |
|
48 return nullptr; |
|
49 } |
|
50 |
|
51 JS_ALWAYS_TRUE(hash->putNew(kid1, kid1)); |
|
52 JS_ALWAYS_TRUE(hash->putNew(kid2, kid2)); |
|
53 return hash; |
|
54 } |
|
55 |
|
56 bool |
|
57 PropertyTree::insertChild(ExclusiveContext *cx, Shape *parent, Shape *child) |
|
58 { |
|
59 JS_ASSERT(!parent->inDictionary()); |
|
60 JS_ASSERT(!child->parent); |
|
61 JS_ASSERT(!child->inDictionary()); |
|
62 JS_ASSERT(child->compartment() == parent->compartment()); |
|
63 JS_ASSERT(cx->isInsideCurrentCompartment(this)); |
|
64 |
|
65 KidsPointer *kidp = &parent->kids; |
|
66 |
|
67 if (kidp->isNull()) { |
|
68 child->setParent(parent); |
|
69 kidp->setShape(child); |
|
70 return true; |
|
71 } |
|
72 |
|
73 if (kidp->isShape()) { |
|
74 Shape *shape = kidp->toShape(); |
|
75 JS_ASSERT(shape != child); |
|
76 JS_ASSERT(!shape->matches(child)); |
|
77 |
|
78 KidsHash *hash = HashChildren(shape, child); |
|
79 if (!hash) { |
|
80 js_ReportOutOfMemory(cx); |
|
81 return false; |
|
82 } |
|
83 kidp->setHash(hash); |
|
84 child->setParent(parent); |
|
85 return true; |
|
86 } |
|
87 |
|
88 if (!kidp->toHash()->putNew(child, child)) { |
|
89 js_ReportOutOfMemory(cx); |
|
90 return false; |
|
91 } |
|
92 |
|
93 child->setParent(parent); |
|
94 return true; |
|
95 } |
|
96 |
|
97 void |
|
98 Shape::removeChild(Shape *child) |
|
99 { |
|
100 JS_ASSERT(!child->inDictionary()); |
|
101 JS_ASSERT(child->parent == this); |
|
102 |
|
103 KidsPointer *kidp = &kids; |
|
104 |
|
105 if (kidp->isShape()) { |
|
106 JS_ASSERT(kidp->toShape() == child); |
|
107 kidp->setNull(); |
|
108 child->parent = nullptr; |
|
109 return; |
|
110 } |
|
111 |
|
112 KidsHash *hash = kidp->toHash(); |
|
113 JS_ASSERT(hash->count() >= 2); /* otherwise kidp->isShape() should be true */ |
|
114 |
|
115 hash->remove(child); |
|
116 child->parent = nullptr; |
|
117 |
|
118 if (hash->count() == 1) { |
|
119 /* Convert from HASH form back to SHAPE form. */ |
|
120 KidsHash::Range r = hash->all(); |
|
121 Shape *otherChild = r.front(); |
|
122 JS_ASSERT((r.popFront(), r.empty())); /* No more elements! */ |
|
123 kidp->setShape(otherChild); |
|
124 js_delete(hash); |
|
125 } |
|
126 } |
|
127 |
|
128 Shape * |
|
129 PropertyTree::getChild(ExclusiveContext *cx, Shape *parentArg, StackShape &unrootedChild) |
|
130 { |
|
131 RootedShape parent(cx, parentArg); |
|
132 JS_ASSERT(parent); |
|
133 |
|
134 Shape *existingShape = nullptr; |
|
135 |
|
136 /* |
|
137 * The property tree has extremely low fan-out below its root in |
|
138 * popular embeddings with real-world workloads. Patterns such as |
|
139 * defining closures that capture a constructor's environment as |
|
140 * getters or setters on the new object that is passed in as |
|
141 * |this| can significantly increase fan-out below the property |
|
142 * tree root -- see bug 335700 for details. |
|
143 */ |
|
144 KidsPointer *kidp = &parent->kids; |
|
145 if (kidp->isShape()) { |
|
146 Shape *kid = kidp->toShape(); |
|
147 if (kid->matches(unrootedChild)) |
|
148 existingShape = kid; |
|
149 } else if (kidp->isHash()) { |
|
150 if (KidsHash::Ptr p = kidp->toHash()->lookup(unrootedChild)) |
|
151 existingShape = *p; |
|
152 } else { |
|
153 /* If kidp->isNull(), we always insert. */ |
|
154 } |
|
155 |
|
156 #ifdef JSGC_INCREMENTAL |
|
157 if (existingShape) { |
|
158 JS::Zone *zone = existingShape->zone(); |
|
159 if (zone->needsBarrier()) { |
|
160 /* |
|
161 * We need a read barrier for the shape tree, since these are weak |
|
162 * pointers. |
|
163 */ |
|
164 Shape *tmp = existingShape; |
|
165 MarkShapeUnbarriered(zone->barrierTracer(), &tmp, "read barrier"); |
|
166 JS_ASSERT(tmp == existingShape); |
|
167 } else if (zone->isGCSweeping() && !existingShape->isMarked() && |
|
168 !existingShape->arenaHeader()->allocatedDuringIncremental) |
|
169 { |
|
170 /* |
|
171 * The shape we've found is unreachable and due to be finalized, so |
|
172 * remove our weak reference to it and don't use it. |
|
173 */ |
|
174 JS_ASSERT(parent->isMarked()); |
|
175 parent->removeChild(existingShape); |
|
176 existingShape = nullptr; |
|
177 } |
|
178 } |
|
179 #endif |
|
180 |
|
181 if (existingShape) |
|
182 return existingShape; |
|
183 |
|
184 RootedGeneric<StackShape*> child(cx, &unrootedChild); |
|
185 |
|
186 Shape *shape = newShape(cx); |
|
187 if (!shape) |
|
188 return nullptr; |
|
189 |
|
190 new (shape) Shape(*child, parent->numFixedSlots()); |
|
191 |
|
192 if (!insertChild(cx, parent, shape)) |
|
193 return nullptr; |
|
194 |
|
195 return shape; |
|
196 } |
|
197 |
|
198 Shape * |
|
199 PropertyTree::lookupChild(ThreadSafeContext *cx, Shape *parent, const StackShape &child) |
|
200 { |
|
201 /* Keep this in sync with the logic of getChild above. */ |
|
202 Shape *shape = nullptr; |
|
203 |
|
204 JS_ASSERT(parent); |
|
205 |
|
206 KidsPointer *kidp = &parent->kids; |
|
207 if (kidp->isShape()) { |
|
208 Shape *kid = kidp->toShape(); |
|
209 if (kid->matches(child)) |
|
210 shape = kid; |
|
211 } else if (kidp->isHash()) { |
|
212 if (KidsHash::Ptr p = kidp->toHash()->readonlyThreadsafeLookup(child)) |
|
213 shape = *p; |
|
214 } else { |
|
215 return nullptr; |
|
216 } |
|
217 |
|
218 #if defined(JSGC_INCREMENTAL) && defined(DEBUG) |
|
219 if (shape) { |
|
220 JS::Zone *zone = shape->arenaHeader()->zone; |
|
221 JS_ASSERT(!zone->needsBarrier()); |
|
222 JS_ASSERT(!(zone->isGCSweeping() && !shape->isMarked() && |
|
223 !shape->arenaHeader()->allocatedDuringIncremental)); |
|
224 } |
|
225 #endif |
|
226 |
|
227 return shape; |
|
228 } |
|
229 |
|
230 void |
|
231 Shape::sweep() |
|
232 { |
|
233 if (inDictionary()) |
|
234 return; |
|
235 |
|
236 /* |
|
237 * We detach the child from the parent if the parent is reachable. |
|
238 * |
|
239 * Note that due to incremental sweeping, the parent pointer may point |
|
240 * to the original reachable parent, or it may point to a new live |
|
241 * object allocated in the same cell that used to hold the parent. |
|
242 * |
|
243 * There are three cases: |
|
244 * |
|
245 * Case 1: parent is not marked - parent is unreachable, may have been |
|
246 * finalized, and the cell may subsequently have been |
|
247 * reallocated to a compartment that is not being marked (cells |
|
248 * are marked when allocated in a compartment that is currenly |
|
249 * being marked by the collector). |
|
250 * |
|
251 * Case 2: parent is marked and is in a different compartment - parent |
|
252 * has been freed and reallocated to compartment that was being |
|
253 * marked. |
|
254 * |
|
255 * Case 3: parent is marked and is in the same compartment - parent is |
|
256 * stil reachable and we need to detach from it. |
|
257 */ |
|
258 if (parent && parent->isMarked() && parent->compartment() == compartment()) |
|
259 parent->removeChild(this); |
|
260 } |
|
261 |
|
262 void |
|
263 Shape::finalize(FreeOp *fop) |
|
264 { |
|
265 if (!inDictionary() && kids.isHash()) |
|
266 fop->delete_(kids.toHash()); |
|
267 } |
|
268 |
|
269 #ifdef DEBUG |
|
270 |
|
271 void |
|
272 KidsPointer::checkConsistency(Shape *aKid) const |
|
273 { |
|
274 if (isShape()) { |
|
275 JS_ASSERT(toShape() == aKid); |
|
276 } else { |
|
277 JS_ASSERT(isHash()); |
|
278 KidsHash *hash = toHash(); |
|
279 KidsHash::Ptr ptr = hash->lookup(aKid); |
|
280 JS_ASSERT(*ptr == aKid); |
|
281 } |
|
282 } |
|
283 |
|
284 void |
|
285 Shape::dump(JSContext *cx, FILE *fp) const |
|
286 { |
|
287 /* This is only used from gdb, so allowing GC here would just be confusing. */ |
|
288 gc::AutoSuppressGC suppress(cx); |
|
289 |
|
290 jsid propid = this->propid(); |
|
291 |
|
292 JS_ASSERT(!JSID_IS_VOID(propid)); |
|
293 |
|
294 if (JSID_IS_INT(propid)) { |
|
295 fprintf(fp, "[%ld]", (long) JSID_TO_INT(propid)); |
|
296 } else { |
|
297 JSLinearString *str; |
|
298 if (JSID_IS_ATOM(propid)) { |
|
299 str = JSID_TO_ATOM(propid); |
|
300 } else { |
|
301 JS_ASSERT(JSID_IS_OBJECT(propid)); |
|
302 Value v = IdToValue(propid); |
|
303 JSString *s = ToStringSlow<NoGC>(cx, v); |
|
304 fputs("object ", fp); |
|
305 str = s ? s->ensureLinear(cx) : nullptr; |
|
306 } |
|
307 if (!str) |
|
308 fputs("<error>", fp); |
|
309 else |
|
310 FileEscapedString(fp, str, '"'); |
|
311 } |
|
312 |
|
313 fprintf(fp, " g/s %p/%p slot %d attrs %x ", |
|
314 JS_FUNC_TO_DATA_PTR(void *, base()->rawGetter), |
|
315 JS_FUNC_TO_DATA_PTR(void *, base()->rawSetter), |
|
316 hasSlot() ? slot() : -1, attrs); |
|
317 |
|
318 if (attrs) { |
|
319 int first = 1; |
|
320 fputs("(", fp); |
|
321 #define DUMP_ATTR(name, display) if (attrs & JSPROP_##name) fputs(&(" " #display)[first], fp), first = 0 |
|
322 DUMP_ATTR(ENUMERATE, enumerate); |
|
323 DUMP_ATTR(READONLY, readonly); |
|
324 DUMP_ATTR(PERMANENT, permanent); |
|
325 DUMP_ATTR(GETTER, getter); |
|
326 DUMP_ATTR(SETTER, setter); |
|
327 DUMP_ATTR(SHARED, shared); |
|
328 #undef DUMP_ATTR |
|
329 fputs(") ", fp); |
|
330 } |
|
331 |
|
332 fprintf(fp, "flags %x ", flags); |
|
333 if (flags) { |
|
334 int first = 1; |
|
335 fputs("(", fp); |
|
336 #define DUMP_FLAG(name, display) if (flags & name) fputs(&(" " #display)[first], fp), first = 0 |
|
337 DUMP_FLAG(IN_DICTIONARY, in_dictionary); |
|
338 #undef DUMP_FLAG |
|
339 fputs(") ", fp); |
|
340 } |
|
341 } |
|
342 |
|
343 void |
|
344 Shape::dumpSubtree(JSContext *cx, int level, FILE *fp) const |
|
345 { |
|
346 if (!parent) { |
|
347 JS_ASSERT(level == 0); |
|
348 JS_ASSERT(JSID_IS_EMPTY(propid_)); |
|
349 fprintf(fp, "class %s emptyShape\n", getObjectClass()->name); |
|
350 } else { |
|
351 fprintf(fp, "%*sid ", level, ""); |
|
352 dump(cx, fp); |
|
353 } |
|
354 |
|
355 if (!kids.isNull()) { |
|
356 ++level; |
|
357 if (kids.isShape()) { |
|
358 Shape *kid = kids.toShape(); |
|
359 JS_ASSERT(kid->parent == this); |
|
360 kid->dumpSubtree(cx, level, fp); |
|
361 } else { |
|
362 const KidsHash &hash = *kids.toHash(); |
|
363 for (KidsHash::Range range = hash.all(); !range.empty(); range.popFront()) { |
|
364 Shape *kid = range.front(); |
|
365 |
|
366 JS_ASSERT(kid->parent == this); |
|
367 kid->dumpSubtree(cx, level, fp); |
|
368 } |
|
369 } |
|
370 } |
|
371 } |
|
372 |
|
373 #endif |