|
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 "vm/PIC.h" |
|
8 #include "jscntxt.h" |
|
9 #include "jsobj.h" |
|
10 #include "gc/Marking.h" |
|
11 |
|
12 #include "vm/GlobalObject.h" |
|
13 #include "vm/ObjectImpl.h" |
|
14 #include "vm/SelfHosting.h" |
|
15 #include "jsobjinlines.h" |
|
16 #include "vm/ObjectImpl-inl.h" |
|
17 |
|
18 using namespace js; |
|
19 using namespace js::gc; |
|
20 |
|
21 bool |
|
22 js::ForOfPIC::Chain::initialize(JSContext *cx) |
|
23 { |
|
24 JS_ASSERT(!initialized_); |
|
25 |
|
26 // Get the canonical Array.prototype |
|
27 RootedObject arrayProto(cx, GlobalObject::getOrCreateArrayPrototype(cx, cx->global())); |
|
28 if (!arrayProto) |
|
29 return false; |
|
30 |
|
31 // Get the canonical ArrayIterator.prototype |
|
32 RootedObject arrayIteratorProto(cx, |
|
33 GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global())); |
|
34 if (!arrayIteratorProto) |
|
35 return false; |
|
36 |
|
37 // From this point on, we can't fail. Set initialized and fill the fields |
|
38 // for the canonical Array.prototype and ArrayIterator.prototype objects. |
|
39 initialized_ = true; |
|
40 arrayProto_ = arrayProto; |
|
41 arrayIteratorProto_ = arrayIteratorProto; |
|
42 |
|
43 // Shortcut returns below means Array for-of will never be optimizable, |
|
44 // do set disabled_ now, and clear it later when we succeed. |
|
45 disabled_ = true; |
|
46 |
|
47 // Look up '@@iterator' on Array.prototype, ensure it's a slotful shape. |
|
48 Shape *iterShape = arrayProto->nativeLookup(cx, cx->names().std_iterator); |
|
49 if (!iterShape || !iterShape->hasSlot() || !iterShape->hasDefaultGetter()) |
|
50 return true; |
|
51 |
|
52 // Get the referred value, and ensure it holds the canonical ArrayValues function. |
|
53 Value iterator = arrayProto->getSlot(iterShape->slot()); |
|
54 JSFunction *iterFun; |
|
55 if (!IsFunctionObject(iterator, &iterFun)) |
|
56 return true; |
|
57 if (!IsSelfHostedFunctionWithName(iterFun, cx->names().ArrayValues)) |
|
58 return true; |
|
59 |
|
60 // Look up the 'next' value on ArrayIterator.prototype |
|
61 Shape *nextShape = arrayIteratorProto->nativeLookup(cx, cx->names().next); |
|
62 if (!nextShape || !nextShape->hasSlot()) |
|
63 return true; |
|
64 |
|
65 // Get the referred value, ensure it holds the canonical ArrayIteratorNext function. |
|
66 Value next = arrayIteratorProto->getSlot(nextShape->slot()); |
|
67 JSFunction *nextFun; |
|
68 if (!IsFunctionObject(next, &nextFun)) |
|
69 return true; |
|
70 if (!IsSelfHostedFunctionWithName(nextFun, cx->names().ArrayIteratorNext)) |
|
71 return true; |
|
72 |
|
73 disabled_ = false; |
|
74 arrayProtoShape_ = arrayProto->lastProperty(); |
|
75 arrayProtoIteratorSlot_ = iterShape->slot(); |
|
76 canonicalIteratorFunc_ = iterator; |
|
77 arrayIteratorProtoShape_ = arrayIteratorProto->lastProperty(); |
|
78 arrayIteratorProtoNextSlot_ = nextShape->slot(); |
|
79 canonicalNextFunc_ = next; |
|
80 return true; |
|
81 } |
|
82 |
|
83 js::ForOfPIC::Stub * |
|
84 js::ForOfPIC::Chain::isArrayOptimized(ArrayObject *obj) |
|
85 { |
|
86 Stub *stub = getMatchingStub(obj); |
|
87 if (!stub) |
|
88 return nullptr; |
|
89 |
|
90 // Ensure that this is an otherwise optimizable array. |
|
91 if (!isOptimizableArray(obj)) |
|
92 return nullptr; |
|
93 |
|
94 // Not yet enough! Ensure that the world as we know it remains sane. |
|
95 if (!isArrayStateStillSane()) |
|
96 return nullptr; |
|
97 |
|
98 return stub; |
|
99 } |
|
100 |
|
101 bool |
|
102 js::ForOfPIC::Chain::tryOptimizeArray(JSContext *cx, HandleObject array, bool *optimized) |
|
103 { |
|
104 JS_ASSERT(array->is<ArrayObject>()); |
|
105 JS_ASSERT(optimized); |
|
106 |
|
107 *optimized = false; |
|
108 |
|
109 if (!initialized_) { |
|
110 // If PIC is not initialized, initialize it. |
|
111 if (!initialize(cx)) |
|
112 return false; |
|
113 |
|
114 } else if (!disabled_ && !isArrayStateStillSane()) { |
|
115 // Otherwise, if array state is no longer sane, reinitialize. |
|
116 reset(cx); |
|
117 |
|
118 if (!initialize(cx)) |
|
119 return false; |
|
120 } |
|
121 JS_ASSERT(initialized_); |
|
122 |
|
123 // If PIC is disabled, don't bother trying to optimize. |
|
124 if (disabled_) |
|
125 return true; |
|
126 |
|
127 // By the time we get here, we should have a sane array state to work with. |
|
128 JS_ASSERT(isArrayStateStillSane()); |
|
129 |
|
130 // Check if stub already exists. |
|
131 ForOfPIC::Stub *stub = isArrayOptimized(&array->as<ArrayObject>()); |
|
132 if (stub) { |
|
133 *optimized = true; |
|
134 return true; |
|
135 } |
|
136 |
|
137 // If the number of stubs is about to exceed the limit, throw away entire |
|
138 // existing cache before adding new stubs. We shouldn't really have heavy |
|
139 // churn on these. |
|
140 if (numStubs() >= MAX_STUBS) |
|
141 eraseChain(); |
|
142 |
|
143 // Ensure array's prototype is the actual Array.prototype |
|
144 if (!isOptimizableArray(array)) |
|
145 return true; |
|
146 |
|
147 // Ensure array doesn't define '@@iterator' directly. |
|
148 if (array->nativeLookup(cx, cx->names().std_iterator)) |
|
149 return true; |
|
150 |
|
151 // Good to optimize now, create stub to add. |
|
152 RootedShape shape(cx, array->lastProperty()); |
|
153 stub = cx->new_<Stub>(shape); |
|
154 if (!stub) |
|
155 return false; |
|
156 |
|
157 // Add the stub. |
|
158 addStub(stub); |
|
159 |
|
160 *optimized = true; |
|
161 return true; |
|
162 } |
|
163 |
|
164 js::ForOfPIC::Stub * |
|
165 js::ForOfPIC::Chain::getMatchingStub(JSObject *obj) |
|
166 { |
|
167 // Ensure PIC is initialized and not disabled. |
|
168 if (!initialized_ || disabled_) |
|
169 return nullptr; |
|
170 |
|
171 // Check if there is a matching stub. |
|
172 for (Stub *stub = stubs(); stub != nullptr; stub = stub->next()) { |
|
173 if (stub->shape() == obj->lastProperty()) |
|
174 return stub; |
|
175 } |
|
176 |
|
177 return nullptr; |
|
178 } |
|
179 |
|
180 bool |
|
181 js::ForOfPIC::Chain::isOptimizableArray(JSObject *obj) |
|
182 { |
|
183 JS_ASSERT(obj->is<ArrayObject>()); |
|
184 |
|
185 // Ensure object's prototype is the actual Array.prototype |
|
186 if (!obj->getTaggedProto().isObject()) |
|
187 return false; |
|
188 if (obj->getTaggedProto().toObject() != arrayProto_) |
|
189 return false; |
|
190 |
|
191 return true; |
|
192 } |
|
193 |
|
194 bool |
|
195 js::ForOfPIC::Chain::isArrayStateStillSane() |
|
196 { |
|
197 // Ensure that canonical Array.prototype has matching shape. |
|
198 if (arrayProto_->lastProperty() != arrayProtoShape_) |
|
199 return false; |
|
200 |
|
201 // Ensure that Array.prototype['@@iterator'] contains the |
|
202 // canonical iterator function. |
|
203 if (arrayProto_->getSlot(arrayProtoIteratorSlot_) != canonicalIteratorFunc_) |
|
204 return false; |
|
205 |
|
206 // Chain to isArrayNextStillSane. |
|
207 return isArrayNextStillSane(); |
|
208 } |
|
209 |
|
210 void |
|
211 js::ForOfPIC::Chain::reset(JSContext *cx) |
|
212 { |
|
213 // Should never reset a disabled_ stub. |
|
214 JS_ASSERT(!disabled_); |
|
215 |
|
216 // Erase the chain. |
|
217 eraseChain(); |
|
218 |
|
219 arrayProto_ = nullptr; |
|
220 arrayIteratorProto_ = nullptr; |
|
221 |
|
222 arrayProtoShape_ = nullptr; |
|
223 arrayProtoIteratorSlot_ = -1; |
|
224 canonicalIteratorFunc_ = UndefinedValue(); |
|
225 |
|
226 arrayIteratorProtoShape_ = nullptr; |
|
227 arrayIteratorProtoNextSlot_ = -1; |
|
228 canonicalNextFunc_ = UndefinedValue(); |
|
229 |
|
230 initialized_ = false; |
|
231 } |
|
232 |
|
233 void |
|
234 js::ForOfPIC::Chain::eraseChain() |
|
235 { |
|
236 // Should never need to clear the chain of a disabled stub. |
|
237 JS_ASSERT(!disabled_); |
|
238 |
|
239 // Free all stubs. |
|
240 Stub *stub = stubs_; |
|
241 while (stub) { |
|
242 Stub *next = stub->next(); |
|
243 js_delete(stub); |
|
244 stub = next; |
|
245 } |
|
246 stubs_ = nullptr; |
|
247 } |
|
248 |
|
249 |
|
250 // Trace the pointers stored directly on the stub. |
|
251 void |
|
252 js::ForOfPIC::Chain::mark(JSTracer *trc) |
|
253 { |
|
254 if (!initialized_ || disabled_) |
|
255 return; |
|
256 |
|
257 gc::MarkObject(trc, &arrayProto_, "ForOfPIC Array.prototype."); |
|
258 gc::MarkObject(trc, &arrayIteratorProto_, "ForOfPIC ArrayIterator.prototype."); |
|
259 |
|
260 gc::MarkShape(trc, &arrayProtoShape_, "ForOfPIC Array.prototype shape."); |
|
261 gc::MarkShape(trc, &arrayIteratorProtoShape_, "ForOfPIC ArrayIterator.prototype shape."); |
|
262 |
|
263 gc::MarkValue(trc, &canonicalIteratorFunc_, "ForOfPIC ArrayValues builtin."); |
|
264 gc::MarkValue(trc, &canonicalNextFunc_, "ForOfPIC ArrayIterator.prototype.next builtin."); |
|
265 |
|
266 // Free all the stubs in the chain. |
|
267 while (stubs_) |
|
268 removeStub(stubs_, nullptr); |
|
269 } |
|
270 |
|
271 void |
|
272 js::ForOfPIC::Chain::sweep(FreeOp *fop) |
|
273 { |
|
274 // Free all the stubs in the chain. |
|
275 while (stubs_) { |
|
276 Stub *next = stubs_->next(); |
|
277 fop->delete_(stubs_); |
|
278 stubs_ = next; |
|
279 } |
|
280 fop->delete_(this); |
|
281 } |
|
282 |
|
283 static void |
|
284 ForOfPIC_finalize(FreeOp *fop, JSObject *obj) |
|
285 { |
|
286 if (ForOfPIC::Chain *chain = ForOfPIC::fromJSObject(obj)) |
|
287 chain->sweep(fop); |
|
288 } |
|
289 |
|
290 static void |
|
291 ForOfPIC_traceObject(JSTracer *trc, JSObject *obj) |
|
292 { |
|
293 if (ForOfPIC::Chain *chain = ForOfPIC::fromJSObject(obj)) |
|
294 chain->mark(trc); |
|
295 } |
|
296 |
|
297 const Class ForOfPIC::jsclass = { |
|
298 "ForOfPIC", JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS, |
|
299 JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, |
|
300 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, ForOfPIC_finalize, |
|
301 nullptr, /* call */ |
|
302 nullptr, /* hasInstance */ |
|
303 nullptr, /* construct */ |
|
304 ForOfPIC_traceObject |
|
305 }; |
|
306 |
|
307 /* static */ JSObject * |
|
308 js::ForOfPIC::createForOfPICObject(JSContext *cx, Handle<GlobalObject*> global) |
|
309 { |
|
310 assertSameCompartment(cx, global); |
|
311 JSObject *obj = NewObjectWithGivenProto(cx, &ForOfPIC::jsclass, nullptr, global); |
|
312 if (!obj) |
|
313 return nullptr; |
|
314 ForOfPIC::Chain *chain = cx->new_<ForOfPIC::Chain>(); |
|
315 if (!chain) |
|
316 return nullptr; |
|
317 obj->setPrivate(chain); |
|
318 return obj; |
|
319 } |
|
320 |
|
321 /* static */ js::ForOfPIC::Chain * |
|
322 js::ForOfPIC::create(JSContext *cx) |
|
323 { |
|
324 JS_ASSERT(!cx->global()->getForOfPICObject()); |
|
325 Rooted<GlobalObject *> global(cx, cx->global()); |
|
326 JSObject *obj = GlobalObject::getOrCreateForOfPICObject(cx, global); |
|
327 if (!obj) |
|
328 return nullptr; |
|
329 return fromJSObject(obj); |
|
330 } |