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   mozilla::Maybe<PropertyInfo> iterProp = arrayProto->lookup(
70       cx, PropertyKey::Symbol(cx->wellKnownSymbols().iterator));
71   if (iterProp.isNothing() || !iterProp->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(iterProp->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   mozilla::Maybe<PropertyInfo> nextProp =
88       arrayIteratorProto->lookup(cx, cx->names().next);
89   if (nextProp.isNothing() || !nextProp->isDataProperty()) {
90     return true;
91   }
92 
93   // Get the referred value, ensure it holds the canonical ArrayIteratorNext
94   // function.
95   Value next = arrayIteratorProto->getSlot(nextProp->slot());
96   JSFunction* nextFun;
97   if (!IsFunctionObject(next, &nextFun)) {
98     return true;
99   }
100   if (!IsSelfHostedFunctionWithName(nextFun, cx->names().ArrayIteratorNext)) {
101     return true;
102   }
103 
104   disabled_ = false;
105   arrayProtoShape_ = arrayProto->shape();
106   arrayProtoIteratorSlot_ = iterProp->slot();
107   canonicalIteratorFunc_ = iterator;
108   arrayIteratorProtoShape_ = arrayIteratorProto->shape();
109   arrayIteratorProtoNextSlot_ = nextProp->slot();
110   canonicalNextFunc_ = next;
111   return true;
112 }
113 
tryOptimizeArray(JSContext * cx,HandleArrayObject array,bool * optimized)114 bool js::ForOfPIC::Chain::tryOptimizeArray(JSContext* cx,
115                                            HandleArrayObject array,
116                                            bool* optimized) {
117   MOZ_ASSERT(optimized);
118 
119   *optimized = false;
120 
121   if (!initialized_) {
122     // If PIC is not initialized, initialize it.
123     if (!initialize(cx)) {
124       return false;
125     }
126 
127   } else if (!disabled_ && !isArrayStateStillSane()) {
128     // Otherwise, if array state is no longer sane, reinitialize.
129     reset(cx);
130 
131     if (!initialize(cx)) {
132       return false;
133     }
134   }
135   MOZ_ASSERT(initialized_);
136 
137   // If PIC is disabled, don't bother trying to optimize.
138   if (disabled_) {
139     return true;
140   }
141 
142   // By the time we get here, we should have a sane array state to work with.
143   MOZ_ASSERT(isArrayStateStillSane());
144 
145   // Ensure array's prototype is the actual Array.prototype
146   if (array->staticPrototype() != arrayProto_) {
147     return true;
148   }
149 
150   // Check if stub already exists.
151   if (hasMatchingStub(array)) {
152     *optimized = true;
153     return true;
154   }
155 
156   // Ensure array doesn't define @@iterator directly.
157   if (array->lookup(cx, PropertyKey::Symbol(cx->wellKnownSymbols().iterator))) {
158     return true;
159   }
160 
161   // If the number of stubs is about to exceed the limit, throw away entire
162   // existing cache before adding new stubs.  We shouldn't really have heavy
163   // churn on these.
164   if (numStubs() >= MAX_STUBS) {
165     eraseChain(cx);
166   }
167 
168   // Good to optimize now, create stub to add.
169   RootedShape shape(cx, array->shape());
170   Stub* stub = cx->new_<Stub>(shape);
171   if (!stub) {
172     return false;
173   }
174 
175   // Add the stub.
176   addStub(picObject_, stub);
177 
178   *optimized = true;
179   return true;
180 }
181 
tryOptimizeArrayIteratorNext(JSContext * cx,bool * optimized)182 bool js::ForOfPIC::Chain::tryOptimizeArrayIteratorNext(JSContext* cx,
183                                                        bool* optimized) {
184   MOZ_ASSERT(optimized);
185 
186   *optimized = false;
187 
188   if (!initialized_) {
189     // If PIC is not initialized, initialize it.
190     if (!initialize(cx)) {
191       return false;
192     }
193   } else if (!disabled_ && !isArrayNextStillSane()) {
194     // Otherwise, if array iterator state is no longer sane, reinitialize.
195     reset(cx);
196 
197     if (!initialize(cx)) {
198       return false;
199     }
200   }
201   MOZ_ASSERT(initialized_);
202 
203   // If PIC is disabled, don't bother trying to optimize.
204   if (disabled_) {
205     return true;
206   }
207 
208   // By the time we get here, we should have a sane iterator state to work with.
209   MOZ_ASSERT(isArrayNextStillSane());
210 
211   *optimized = true;
212   return true;
213 }
214 
hasMatchingStub(ArrayObject * obj)215 bool js::ForOfPIC::Chain::hasMatchingStub(ArrayObject* obj) {
216   // Ensure PIC is initialized and not disabled.
217   MOZ_ASSERT(initialized_ && !disabled_);
218 
219   // Check if there is a matching stub.
220   for (Stub* stub = stubs(); stub != nullptr; stub = stub->next()) {
221     if (stub->shape() == obj->shape()) {
222       return true;
223     }
224   }
225 
226   return false;
227 }
228 
isArrayStateStillSane()229 bool js::ForOfPIC::Chain::isArrayStateStillSane() {
230   // Ensure that canonical Array.prototype has matching shape.
231   if (arrayProto_->shape() != arrayProtoShape_) {
232     return false;
233   }
234 
235   // Ensure that Array.prototype[@@iterator] contains the
236   // canonical iterator function.
237   if (arrayProto_->getSlot(arrayProtoIteratorSlot_) != canonicalIteratorFunc_) {
238     return false;
239   }
240 
241   // Chain to isArrayNextStillSane.
242   return isArrayNextStillSane();
243 }
244 
reset(JSContext * cx)245 void js::ForOfPIC::Chain::reset(JSContext* cx) {
246   // Should never reset a disabled_ stub.
247   MOZ_ASSERT(!disabled_);
248 
249   // Erase the chain.
250   eraseChain(cx);
251 
252   arrayProto_ = nullptr;
253   arrayIteratorProto_ = nullptr;
254 
255   arrayProtoShape_ = nullptr;
256   arrayProtoIteratorSlot_ = -1;
257   canonicalIteratorFunc_ = UndefinedValue();
258 
259   arrayIteratorProtoShape_ = nullptr;
260   arrayIteratorProtoNextSlot_ = -1;
261   canonicalNextFunc_ = UndefinedValue();
262 
263   initialized_ = false;
264 }
265 
eraseChain(JSContext * cx)266 void js::ForOfPIC::Chain::eraseChain(JSContext* cx) {
267   // Should never need to clear the chain of a disabled stub.
268   MOZ_ASSERT(!disabled_);
269   freeAllStubs(cx->defaultFreeOp());
270 }
271 
272 // Trace the pointers stored directly on the stub.
trace(JSTracer * trc)273 void js::ForOfPIC::Chain::trace(JSTracer* trc) {
274   TraceEdge(trc, &picObject_, "ForOfPIC object");
275 
276   if (!initialized_ || disabled_) {
277     return;
278   }
279 
280   TraceEdge(trc, &arrayProto_, "ForOfPIC Array.prototype.");
281   TraceEdge(trc, &arrayIteratorProto_, "ForOfPIC ArrayIterator.prototype.");
282 
283   TraceEdge(trc, &arrayProtoShape_, "ForOfPIC Array.prototype shape.");
284   TraceEdge(trc, &arrayIteratorProtoShape_,
285             "ForOfPIC ArrayIterator.prototype shape.");
286 
287   TraceEdge(trc, &canonicalIteratorFunc_, "ForOfPIC ArrayValues builtin.");
288   TraceEdge(trc, &canonicalNextFunc_,
289             "ForOfPIC ArrayIterator.prototype.next builtin.");
290 
291   if (trc->isMarkingTracer()) {
292     // Free all the stubs in the chain.
293     freeAllStubs(trc->runtime()->defaultFreeOp());
294   }
295 }
296 
ForOfPIC_finalize(JSFreeOp * fop,JSObject * obj)297 static void ForOfPIC_finalize(JSFreeOp* fop, JSObject* obj) {
298   MOZ_ASSERT(fop->maybeOnHelperThread());
299   if (ForOfPIC::Chain* chain =
300           ForOfPIC::fromJSObject(&obj->as<NativeObject>())) {
301     chain->finalize(fop, obj);
302   }
303 }
304 
finalize(JSFreeOp * fop,JSObject * obj)305 void js::ForOfPIC::Chain::finalize(JSFreeOp* fop, JSObject* obj) {
306   freeAllStubs(fop);
307   fop->delete_(obj, this, MemoryUse::ForOfPIC);
308 }
309 
freeAllStubs(JSFreeOp * fop)310 void js::ForOfPIC::Chain::freeAllStubs(JSFreeOp* fop) {
311   Stub* stub = stubs_;
312   while (stub) {
313     Stub* next = stub->next();
314     fop->delete_(picObject_, stub, MemoryUse::ForOfPICStub);
315     stub = next;
316   }
317   stubs_ = nullptr;
318 }
319 
ForOfPIC_traceObject(JSTracer * trc,JSObject * obj)320 static void ForOfPIC_traceObject(JSTracer* trc, JSObject* obj) {
321   if (ForOfPIC::Chain* chain =
322           ForOfPIC::fromJSObject(&obj->as<NativeObject>())) {
323     chain->trace(trc);
324   }
325 }
326 
327 static const JSClassOps ForOfPICClassOps = {
328     nullptr,               // addProperty
329     nullptr,               // delProperty
330     nullptr,               // enumerate
331     nullptr,               // newEnumerate
332     nullptr,               // resolve
333     nullptr,               // mayResolve
334     ForOfPIC_finalize,     // finalize
335     nullptr,               // call
336     nullptr,               // hasInstance
337     nullptr,               // construct
338     ForOfPIC_traceObject,  // trace
339 };
340 
341 const JSClass ForOfPICObject::class_ = {
342     "ForOfPIC",
343     JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_BACKGROUND_FINALIZE,
344     &ForOfPICClassOps};
345 
346 /* static */
createForOfPICObject(JSContext * cx,Handle<GlobalObject * > global)347 NativeObject* js::ForOfPIC::createForOfPICObject(JSContext* cx,
348                                                  Handle<GlobalObject*> global) {
349   cx->check(global);
350   ForOfPICObject* obj =
351       NewTenuredObjectWithGivenProto<ForOfPICObject>(cx, nullptr);
352   if (!obj) {
353     return nullptr;
354   }
355   ForOfPIC::Chain* chain = cx->new_<ForOfPIC::Chain>(obj);
356   if (!chain) {
357     return nullptr;
358   }
359   InitReservedSlot(obj, ForOfPICObject::ChainSlot, chain, MemoryUse::ForOfPIC);
360   return obj;
361 }
362 
create(JSContext * cx)363 /* static */ js::ForOfPIC::Chain* js::ForOfPIC::create(JSContext* cx) {
364   MOZ_ASSERT(!cx->global()->getForOfPICObject());
365   Rooted<GlobalObject*> global(cx, cx->global());
366   NativeObject* obj = GlobalObject::getOrCreateForOfPICObject(cx, global);
367   if (!obj) {
368     return nullptr;
369   }
370   return fromJSObject(obj);
371 }
372