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