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