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