Source code

Revision control

Other Tools

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 "jit/CacheIR.h"
8
9
#include "mozilla/DebugOnly.h"
10
#include "mozilla/FloatingPoint.h"
11
12
#include "jit/BaselineCacheIRCompiler.h"
13
#include "jit/BaselineIC.h"
14
#include "jit/CacheIRSpewer.h"
15
#include "jit/InlinableNatives.h"
16
#include "vm/SelfHosting.h"
17
18
#include "jit/MacroAssembler-inl.h"
19
#include "vm/EnvironmentObject-inl.h"
20
#include "vm/JSContext-inl.h"
21
#include "vm/JSObject-inl.h"
22
#include "vm/JSScript-inl.h"
23
#include "vm/NativeObject-inl.h"
24
#include "vm/StringObject-inl.h"
25
#include "vm/TypeInference-inl.h"
26
27
using namespace js;
28
using namespace js::jit;
29
30
using mozilla::DebugOnly;
31
using mozilla::Maybe;
32
33
const char* const js::jit::CacheKindNames[] = {
34
#define DEFINE_KIND(kind) #kind,
35
CACHE_IR_KINDS(DEFINE_KIND)
36
#undef DEFINE_KIND
37
};
38
39
// We need to enter the namespace here so that the definition of
40
// CacheIROpFormat::ArgLengths can see CacheIROpFormat::ArgType
41
// (without defining None/Id/Field/etc everywhere else in this file.)
42
namespace js {
43
namespace jit {
44
namespace CacheIROpFormat {
45
46
static constexpr uint32_t CacheIRArgLength(ArgType arg) {
47
switch (arg) {
48
case None:
49
return 0;
50
case Id:
51
return sizeof(uint8_t);
52
case Field:
53
return sizeof(uint8_t);
54
case Byte:
55
return sizeof(uint8_t);
56
case Int32:
57
case UInt32:
58
return sizeof(uint32_t);
59
case Word:
60
return sizeof(uintptr_t);
61
}
62
}
63
template <typename... Args>
64
static constexpr uint32_t CacheIRArgLength(ArgType arg, Args... args) {
65
return CacheIRArgLength(arg) + CacheIRArgLength(args...);
66
}
67
68
const uint32_t ArgLengths[] = {
69
#define ARGLENGTH(op, ...) CacheIRArgLength(__VA_ARGS__),
70
CACHE_IR_OPS(ARGLENGTH)
71
#undef ARGLENGTH
72
};
73
74
} // namespace CacheIROpFormat
75
} // namespace jit
76
} // namespace js
77
78
void CacheIRWriter::assertSameCompartment(JSObject* obj) {
79
cx_->debugOnlyCheck(obj);
80
}
81
82
StubField CacheIRWriter::readStubFieldForIon(uint32_t offset,
83
StubField::Type type) const {
84
size_t index = 0;
85
size_t currentOffset = 0;
86
87
// If we've seen an offset earlier than this before, we know we can start the
88
// search there at least, otherwise, we start the search from the beginning.
89
if (lastOffset_ < offset) {
90
currentOffset = lastOffset_;
91
index = lastIndex_;
92
}
93
94
while (currentOffset != offset) {
95
currentOffset += StubField::sizeInBytes(stubFields_[index].type());
96
index++;
97
MOZ_ASSERT(index < stubFields_.length());
98
}
99
100
MOZ_ASSERT(stubFields_[index].type() == type);
101
102
lastOffset_ = currentOffset;
103
lastIndex_ = index;
104
105
return stubFields_[index];
106
}
107
108
IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
109
CacheKind cacheKind, ICState::Mode mode)
110
: writer(cx),
111
cx_(cx),
112
script_(script),
113
pc_(pc),
114
cacheKind_(cacheKind),
115
mode_(mode) {}
116
117
GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script,
118
jsbytecode* pc, ICState::Mode mode,
119
CacheKind cacheKind, HandleValue val,
120
HandleValue idVal, HandleValue receiver,
121
GetPropertyResultFlags resultFlags)
122
: IRGenerator(cx, script, pc, cacheKind, mode),
123
val_(val),
124
idVal_(idVal),
125
receiver_(receiver),
126
resultFlags_(resultFlags),
127
preliminaryObjectAction_(PreliminaryObjectAction::None) {}
128
129
static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderOp,
130
NativeObject* holder, Shape* shape) {
131
if (holder->isFixedSlot(shape->slot())) {
132
writer.loadFixedSlotResult(holderOp,
133
NativeObject::getFixedSlotOffset(shape->slot()));
134
} else {
135
size_t dynamicSlotOffset =
136
holder->dynamicSlotIndex(shape->slot()) * sizeof(Value);
137
writer.loadDynamicSlotResult(holderOp, dynamicSlotOffset);
138
}
139
}
140
141
// DOM proxies
142
// -----------
143
//
144
// DOM proxies are proxies that are used to implement various DOM objects like
145
// HTMLDocument and NodeList. DOM proxies may have an expando object - a native
146
// object that stores extra properties added to the object. The following
147
// CacheIR instructions are only used with DOM proxies:
148
//
149
// * LoadDOMExpandoValue: returns the Value in the proxy's expando slot. This
150
// returns either an UndefinedValue (no expando), ObjectValue (the expando
151
// object), or PrivateValue(ExpandoAndGeneration*).
152
//
153
// * LoadDOMExpandoValueGuardGeneration: guards the Value in the proxy's expando
154
// slot is the same PrivateValue(ExpandoAndGeneration*), then guards on its
155
// generation, then returns expandoAndGeneration->expando. This Value is
156
// either an UndefinedValue or ObjectValue.
157
//
158
// * LoadDOMExpandoValueIgnoreGeneration: assumes the Value in the proxy's
159
// expando slot is a PrivateValue(ExpandoAndGeneration*), unboxes it, and
160
// returns the expandoAndGeneration->expando Value.
161
//
162
// * GuardDOMExpandoMissingOrGuardShape: takes an expando Value as input, then
163
// guards it's either UndefinedValue or an object with the expected shape.
164
165
enum class ProxyStubType {
166
None,
167
DOMExpando,
168
DOMShadowed,
169
DOMUnshadowed,
170
Generic
171
};
172
173
static ProxyStubType GetProxyStubType(JSContext* cx, HandleObject obj,
174
HandleId id) {
175
if (!obj->is<ProxyObject>()) {
176
return ProxyStubType::None;
177
}
178
179
if (!IsCacheableDOMProxy(obj)) {
180
return ProxyStubType::Generic;
181
}
182
183
DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id);
184
if (shadows == ShadowCheckFailed) {
185
cx->clearPendingException();
186
return ProxyStubType::None;
187
}
188
189
if (DOMProxyIsShadowing(shadows)) {
190
if (shadows == ShadowsViaDirectExpando ||
191
shadows == ShadowsViaIndirectExpando) {
192
return ProxyStubType::DOMExpando;
193
}
194
return ProxyStubType::DOMShadowed;
195
}
196
197
MOZ_ASSERT(shadows == DoesntShadow || shadows == DoesntShadowUnique);
198
return ProxyStubType::DOMUnshadowed;
199
}
200
201
static bool ValueToNameOrSymbolId(JSContext* cx, HandleValue idval,
202
MutableHandleId id, bool* nameOrSymbol) {
203
*nameOrSymbol = false;
204
205
if (!idval.isString() && !idval.isSymbol()) {
206
return true;
207
}
208
209
if (!ValueToId<CanGC>(cx, idval, id)) {
210
return false;
211
}
212
213
if (!JSID_IS_STRING(id) && !JSID_IS_SYMBOL(id)) {
214
id.set(JSID_VOID);
215
return true;
216
}
217
218
uint32_t dummy;
219
if (JSID_IS_STRING(id) && JSID_TO_ATOM(id)->isIndex(&dummy)) {
220
id.set(JSID_VOID);
221
return true;
222
}
223
224
*nameOrSymbol = true;
225
return true;
226
}
227
228
AttachDecision GetPropIRGenerator::tryAttachStub() {
229
// Idempotent ICs should call tryAttachIdempotentStub instead.
230
MOZ_ASSERT(!idempotent());
231
232
AutoAssertNoPendingException aanpe(cx_);
233
234
// Non-object receivers are a degenerate case, so don't try to attach
235
// stubs. The stubs we do emit will still perform runtime checks and
236
// fallback as needed.
237
if (isSuper() && !receiver_.isObject()) {
238
return AttachDecision::NoAction;
239
}
240
241
ValOperandId valId(writer.setInputOperandId(0));
242
if (cacheKind_ != CacheKind::GetProp) {
243
MOZ_ASSERT_IF(cacheKind_ == CacheKind::GetPropSuper,
244
getSuperReceiverValueId().id() == 1);
245
MOZ_ASSERT_IF(cacheKind_ != CacheKind::GetPropSuper,
246
getElemKeyValueId().id() == 1);
247
writer.setInputOperandId(1);
248
}
249
if (cacheKind_ == CacheKind::GetElemSuper) {
250
MOZ_ASSERT(getSuperReceiverValueId().id() == 2);
251
writer.setInputOperandId(2);
252
}
253
254
RootedId id(cx_);
255
bool nameOrSymbol;
256
if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
257
cx_->clearPendingException();
258
return AttachDecision::NoAction;
259
}
260
261
if (val_.isObject()) {
262
RootedObject obj(cx_, &val_.toObject());
263
ObjOperandId objId = writer.guardToObject(valId);
264
if (nameOrSymbol) {
265
TRY_ATTACH(tryAttachObjectLength(obj, objId, id));
266
TRY_ATTACH(tryAttachNative(obj, objId, id));
267
TRY_ATTACH(tryAttachTypedObject(obj, objId, id));
268
TRY_ATTACH(tryAttachModuleNamespace(obj, objId, id));
269
TRY_ATTACH(tryAttachWindowProxy(obj, objId, id));
270
TRY_ATTACH(tryAttachCrossCompartmentWrapper(obj, objId, id));
271
TRY_ATTACH(tryAttachXrayCrossCompartmentWrapper(obj, objId, id));
272
TRY_ATTACH(tryAttachFunction(obj, objId, id));
273
TRY_ATTACH(tryAttachProxy(obj, objId, id));
274
275
trackAttached(IRGenerator::NotAttached);
276
return AttachDecision::NoAction;
277
}
278
279
MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
280
cacheKind_ == CacheKind::GetElemSuper);
281
282
TRY_ATTACH(tryAttachProxyElement(obj, objId));
283
284
uint32_t index;
285
Int32OperandId indexId;
286
if (maybeGuardInt32Index(idVal_, getElemKeyValueId(), &index, &indexId)) {
287
TRY_ATTACH(tryAttachTypedElement(obj, objId, index, indexId));
288
TRY_ATTACH(tryAttachDenseElement(obj, objId, index, indexId));
289
TRY_ATTACH(tryAttachDenseElementHole(obj, objId, index, indexId));
290
TRY_ATTACH(tryAttachSparseElement(obj, objId, index, indexId));
291
TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, indexId));
292
TRY_ATTACH(tryAttachGenericElement(obj, objId, index, indexId));
293
294
trackAttached(IRGenerator::NotAttached);
295
return AttachDecision::NoAction;
296
}
297
298
TRY_ATTACH(tryAttachTypedArrayNonInt32Index(obj, objId));
299
300
trackAttached(IRGenerator::NotAttached);
301
return AttachDecision::NoAction;
302
}
303
304
if (nameOrSymbol) {
305
TRY_ATTACH(tryAttachPrimitive(valId, id));
306
TRY_ATTACH(tryAttachStringLength(valId, id));
307
TRY_ATTACH(tryAttachMagicArgumentsName(valId, id));
308
309
trackAttached(IRGenerator::NotAttached);
310
return AttachDecision::NoAction;
311
}
312
313
if (idVal_.isInt32()) {
314
ValOperandId indexId = getElemKeyValueId();
315
TRY_ATTACH(tryAttachStringChar(valId, indexId));
316
TRY_ATTACH(tryAttachMagicArgument(valId, indexId));
317
318
trackAttached(IRGenerator::NotAttached);
319
return AttachDecision::NoAction;
320
}
321
322
trackAttached(IRGenerator::NotAttached);
323
return AttachDecision::NoAction;
324
}
325
326
AttachDecision GetPropIRGenerator::tryAttachIdempotentStub() {
327
// For idempotent ICs, only attach stubs which we can be sure have no side
328
// effects and produce a result which the MIR in the calling code is able
329
// to handle, since we do not have a pc to explicitly monitor the result.
330
331
MOZ_ASSERT(idempotent());
332
333
RootedObject obj(cx_, &val_.toObject());
334
RootedId id(cx_, NameToId(idVal_.toString()->asAtom().asPropertyName()));
335
336
ValOperandId valId(writer.setInputOperandId(0));
337
ObjOperandId objId = writer.guardToObject(valId);
338
339
TRY_ATTACH(tryAttachNative(obj, objId, id));
340
341
// Object lengths are supported only if int32 results are allowed.
342
TRY_ATTACH(tryAttachObjectLength(obj, objId, id));
343
344
// Also support native data properties on DOMProxy prototypes.
345
if (GetProxyStubType(cx_, obj, id) == ProxyStubType::DOMUnshadowed) {
346
return tryAttachDOMProxyUnshadowed(obj, objId, id);
347
}
348
349
return AttachDecision::NoAction;
350
}
351
352
static bool IsCacheableProtoChain(JSObject* obj, JSObject* holder) {
353
while (obj != holder) {
354
/*
355
* We cannot assume that we find the holder object on the prototype
356
* chain and must check for null proto. The prototype chain can be
357
* altered during the lookupProperty call.
358
*/
359
JSObject* proto = obj->staticPrototype();
360
if (!proto || !proto->isNative()) {
361
return false;
362
}
363
obj = proto;
364
}
365
return true;
366
}
367
368
static bool IsCacheableGetPropReadSlot(JSObject* obj, JSObject* holder,
369
PropertyResult prop) {
370
if (!prop || !IsCacheableProtoChain(obj, holder)) {
371
return false;
372
}
373
374
Shape* shape = prop.shape();
375
if (!shape->isDataProperty()) {
376
return false;
377
}
378
379
return true;
380
}
381
382
enum NativeGetPropCacheability {
383
CanAttachNone,
384
CanAttachReadSlot,
385
CanAttachNativeGetter,
386
CanAttachScriptedGetter,
387
CanAttachTemporarilyUnoptimizable
388
};
389
390
static NativeGetPropCacheability IsCacheableGetPropCall(JSObject* obj,
391
JSObject* holder,
392
Shape* shape) {
393
if (!shape || !IsCacheableProtoChain(obj, holder)) {
394
return CanAttachNone;
395
}
396
397
if (!shape->hasGetterValue() || !shape->getterValue().isObject()) {
398
return CanAttachNone;
399
}
400
401
if (!shape->getterValue().toObject().is<JSFunction>()) {
402
return CanAttachNone;
403
}
404
405
JSFunction& getter = shape->getterValue().toObject().as<JSFunction>();
406
407
if (getter.isClassConstructor()) {
408
return CanAttachNone;
409
}
410
411
// For getters that need the WindowProxy (instead of the Window) as this
412
// object, don't cache if obj is the Window, since our cache will pass that
413
// instead of the WindowProxy.
414
if (IsWindow(obj)) {
415
// Check for a getter that has jitinfo and whose jitinfo says it's
416
// OK with both inner and outer objects.
417
if (!getter.hasJitInfo() || getter.jitInfo()->needsOuterizedThisObject()) {
418
return CanAttachNone;
419
}
420
}
421
422
if (getter.isBuiltinNative()) {
423
return CanAttachNativeGetter;
424
}
425
426
if (getter.hasScript() || getter.isNativeWithJitEntry()) {
427
return CanAttachScriptedGetter;
428
}
429
430
if (getter.isInterpretedLazy()) {
431
return CanAttachTemporarilyUnoptimizable;
432
}
433
434
return CanAttachNone;
435
}
436
437
static bool CheckHasNoSuchOwnProperty(JSContext* cx, JSObject* obj, jsid id) {
438
if (obj->isNative()) {
439
// Don't handle proto chains with resolve hooks.
440
if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj)) {
441
return false;
442
}
443
if (obj->as<NativeObject>().contains(cx, id)) {
444
return false;
445
}
446
} else if (obj->is<TypedObject>()) {
447
if (obj->as<TypedObject>().typeDescr().hasProperty(cx->names(), id)) {
448
return false;
449
}
450
} else {
451
return false;
452
}
453
454
return true;
455
}
456
457
static bool CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, jsid id) {
458
JSObject* curObj = obj;
459
do {
460
if (!CheckHasNoSuchOwnProperty(cx, curObj, id)) {
461
return false;
462
}
463
464
if (!curObj->isNative()) {
465
// Non-native objects are only handled as the original receiver.
466
if (curObj != obj) {
467
return false;
468
}
469
}
470
471
curObj = curObj->staticPrototype();
472
} while (curObj);
473
474
return true;
475
}
476
477
// Return whether obj is in some PreliminaryObjectArray and has a structure
478
// that might change in the future.
479
static bool IsPreliminaryObject(JSObject* obj) {
480
if (obj->isSingleton()) {
481
return false;
482
}
483
484
AutoSweepObjectGroup sweep(obj->group());
485
TypeNewScript* newScript = obj->group()->newScript(sweep);
486
if (newScript && !newScript->analyzed()) {
487
return true;
488
}
489
490
if (obj->group()->maybePreliminaryObjects(sweep)) {
491
return true;
492
}
493
494
return false;
495
}
496
497
static bool IsCacheableNoProperty(JSContext* cx, JSObject* obj,
498
JSObject* holder, Shape* shape, jsid id,
499
jsbytecode* pc,
500
GetPropertyResultFlags resultFlags) {
501
if (shape) {
502
return false;
503
}
504
505
MOZ_ASSERT(!holder);
506
507
// Idempotent ICs may only attach missing-property stubs if undefined
508
// results are explicitly allowed, since no monitoring is done of the
509
// cache result.
510
if (!pc && !(resultFlags & GetPropertyResultFlags::AllowUndefined)) {
511
return false;
512
}
513
514
// If we're doing a name lookup, we have to throw a ReferenceError. If
515
// extra warnings are enabled, we may have to report a warning.
516
// Note that Ion does not generate idempotent caches for JSOp::GetBoundName.
517
if ((pc && JSOp(*pc) == JSOp::GetBoundName) ||
518
cx->realm()->behaviors().extraWarnings(cx)) {
519
return false;
520
}
521
522
return CheckHasNoSuchProperty(cx, obj, id);
523
}
524
525
static NativeGetPropCacheability CanAttachNativeGetProp(
526
JSContext* cx, HandleObject obj, HandleId id,
527
MutableHandleNativeObject holder, MutableHandleShape shape, jsbytecode* pc,
528
GetPropertyResultFlags resultFlags) {
529
MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
530
531
// The lookup needs to be universally pure, otherwise we risk calling hooks
532
// out of turn. We don't mind doing this even when purity isn't required,
533
// because we only miss out on shape hashification, which is only a temporary
534
// perf cost. The limits were arbitrarily set, anyways.
535
JSObject* baseHolder = nullptr;
536
PropertyResult prop;
537
if (!LookupPropertyPure(cx, obj, id, &baseHolder, &prop)) {
538
return CanAttachNone;
539
}
540
541
MOZ_ASSERT(!holder);
542
if (baseHolder) {
543
if (!baseHolder->isNative()) {
544
return CanAttachNone;
545
}
546
holder.set(&baseHolder->as<NativeObject>());
547
}
548
shape.set(prop.maybeShape());
549
550
if (IsCacheableGetPropReadSlot(obj, holder, prop)) {
551
return CanAttachReadSlot;
552
}
553
554
if (IsCacheableNoProperty(cx, obj, holder, shape, id, pc, resultFlags)) {
555
return CanAttachReadSlot;
556
}
557
558
// Idempotent ICs cannot call getters, see tryAttachIdempotentStub.
559
if (pc && (resultFlags & GetPropertyResultFlags::Monitored)) {
560
return IsCacheableGetPropCall(obj, holder, shape);
561
}
562
563
return CanAttachNone;
564
}
565
566
static void GuardGroupProto(CacheIRWriter& writer, JSObject* obj,
567
ObjOperandId objId) {
568
// Uses the group to determine if the prototype is unchanged. If the
569
// group's prototype is mutable, we must check the actual prototype,
570
// otherwise checking the group is sufficient. This can be used if object
571
// is not ShapedObject or if Shape has UNCACHEABLE_PROTO flag set.
572
573
ObjectGroup* group = obj->groupRaw();
574
575
if (group->hasUncacheableProto()) {
576
writer.guardProto(objId, obj->staticPrototype());
577
} else {
578
writer.guardGroupForProto(objId, group);
579
}
580
}
581
582
// Guard that a given object has same class and same OwnProperties (excluding
583
// dense elements and dynamic properties).
584
static void TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj,
585
ObjOperandId objId) {
586
if (obj->is<TypedObject>()) {
587
writer.guardGroupForLayout(objId, obj->group());
588
} else if (obj->is<ProxyObject>()) {
589
writer.guardShapeForClass(objId, obj->as<ProxyObject>().shape());
590
} else {
591
MOZ_ASSERT(obj->is<NativeObject>());
592
writer.guardShapeForOwnProperties(objId,
593
obj->as<NativeObject>().lastProperty());
594
}
595
}
596
597
// Similar to |TestMatchingReceiver|, but specialized for NativeObject.
598
static void TestMatchingNativeReceiver(CacheIRWriter& writer, NativeObject* obj,
599
ObjOperandId objId) {
600
writer.guardShapeForOwnProperties(objId, obj->lastProperty());
601
}
602
603
// Similar to |TestMatchingReceiver|, but specialized for ProxyObject.
604
static void TestMatchingProxyReceiver(CacheIRWriter& writer, ProxyObject* obj,
605
ObjOperandId objId) {
606
writer.guardShapeForClass(objId, obj->shape());
607
}
608
609
// Adds additional guards if TestMatchingReceiver* does not also imply the
610
// prototype.
611
static void GeneratePrototypeGuardsForReceiver(CacheIRWriter& writer,
612
JSObject* obj,
613
ObjOperandId objId) {
614
// If receiver was marked UNCACHEABLE_PROTO, the previous shape guard
615
// doesn't ensure the prototype is unchanged. In this case we must use the
616
// group to check the prototype.
617
if (obj->hasUncacheableProto()) {
618
MOZ_ASSERT(obj->is<NativeObject>());
619
GuardGroupProto(writer, obj, objId);
620
}
621
622
#ifdef DEBUG
623
// The following cases already guaranteed the prototype is unchanged.
624
if (obj->is<TypedObject>()) {
625
MOZ_ASSERT(!obj->group()->hasUncacheableProto());
626
} else if (obj->is<ProxyObject>()) {
627
MOZ_ASSERT(!obj->hasUncacheableProto());
628
}
629
#endif // DEBUG
630
}
631
632
static bool ProtoChainSupportsTeleporting(JSObject* obj, JSObject* holder) {
633
// Any non-delegate should already have been handled since its checks are
634
// always required.
635
MOZ_ASSERT(obj->isDelegate());
636
637
// Prototype chain must have cacheable prototypes to ensure the cached
638
// holder is the current holder.
639
for (JSObject* tmp = obj; tmp != holder; tmp = tmp->staticPrototype()) {
640
if (tmp->hasUncacheableProto()) {
641
return false;
642
}
643
}
644
645
// The holder itself only gets reshaped by teleportation if it is not
646
// marked UNCACHEABLE_PROTO. See: ReshapeForProtoMutation.
647
return !holder->hasUncacheableProto();
648
}
649
650
static void GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj,
651
JSObject* holder, ObjOperandId objId) {
652
// Assuming target property is on |holder|, generate appropriate guards to
653
// ensure |holder| is still on the prototype chain of |obj| and we haven't
654
// introduced any shadowing definitions.
655
//
656
// For each item in the proto chain before holder, we must ensure that
657
// [[GetPrototypeOf]] still has the expected result, and that
658
// [[GetOwnProperty]] has no definition of the target property.
659
//
660
//
661
// [SMDOC] Shape Teleporting Optimization
662
// ------------------------------
663
//
664
// Starting with the assumption (and guideline to developers) that mutating
665
// prototypes is an uncommon and fair-to-penalize operation we move cost
666
// from the access side to the mutation side.
667
//
668
// Consider the following proto chain, with B defining a property 'x':
669
//
670
// D -> C -> B{x: 3} -> A -> null
671
//
672
// When accessing |D.x| we refer to D as the "receiver", and B as the
673
// "holder". To optimize this access we need to ensure that neither D nor C
674
// has since defined a shadowing property 'x'. Since C is a prototype that
675
// we assume is rarely mutated we would like to avoid checking each time if
676
// new properties are added. To do this we require that everytime C is
677
// mutated that in addition to generating a new shape for itself, it will
678
// walk the proto chain and generate new shapes for those objects on the
679
// chain (B and A). As a result, checking the shape of D and B is
680
// sufficient. Note that we do not care if the shape or properties of A
681
// change since the lookup of 'x' will stop at B.
682
//
683
// The second condition we must verify is that the prototype chain was not
684
// mutated. The same mechanism as above is used. When the prototype link is
685
// changed, we generate a new shape for the object. If the object whose
686
// link we are mutating is itself a prototype, we regenerate shapes down
687
// the chain. This means the same two shape checks as above are sufficient.
688
//
689
// An additional wrinkle is the UNCACHEABLE_PROTO shape flag. This
690
// indicates that the shape no longer implies any specific prototype. As
691
// well, the shape will not be updated by the teleporting optimization.
692
// If any shape from receiver to holder (inclusive) is UNCACHEABLE_PROTO,
693
// we don't apply the optimization.
694
//
695
// See:
696
// - ReshapeForProtoMutation
697
// - ReshapeForShadowedProp
698
699
MOZ_ASSERT(holder);
700
MOZ_ASSERT(obj != holder);
701
702
// Only DELEGATE objects participate in teleporting so peel off the first
703
// object in the chain if needed and handle it directly.
704
JSObject* pobj = obj;
705
if (!obj->isDelegate()) {
706
// TestMatchingReceiver does not always ensure the prototype is
707
// unchanged, so generate extra guards as needed.
708
GeneratePrototypeGuardsForReceiver(writer, obj, objId);
709
710
pobj = obj->staticPrototype();
711
}
712
MOZ_ASSERT(pobj->isDelegate());
713
714
// If teleporting is supported for this prototype chain, we are done.
715
if (ProtoChainSupportsTeleporting(pobj, holder)) {
716
return;
717
}
718
719
// If already at the holder, no further proto checks are needed.
720
if (pobj == holder) {
721
return;
722
}
723
724
// NOTE: We could be clever and look for a middle prototype to shape check
725
// and elide some (but not all) of the group checks. Unless we have
726
// real-world examples, let's avoid the complexity.
727
728
// Synchronize pobj and protoId.
729
MOZ_ASSERT(pobj == obj || pobj == obj->staticPrototype());
730
ObjOperandId protoId = (pobj == obj) ? objId : writer.loadProto(objId);
731
732
// Guard prototype links from |pobj| to |holder|.
733
while (pobj != holder) {
734
pobj = pobj->staticPrototype();
735
protoId = writer.loadProto(protoId);
736
737
writer.guardSpecificObject(protoId, pobj);
738
}
739
}
740
741
static void GeneratePrototypeHoleGuards(CacheIRWriter& writer, JSObject* obj,
742
ObjOperandId objId,
743
bool alwaysGuardFirstProto) {
744
if (alwaysGuardFirstProto || obj->hasUncacheableProto()) {
745
GuardGroupProto(writer, obj, objId);
746
}
747
748
JSObject* pobj = obj->staticPrototype();
749
while (pobj) {
750
ObjOperandId protoId = writer.loadObject(pobj);
751
752
// If shape doesn't imply proto, additional guards are needed.
753
if (pobj->hasUncacheableProto()) {
754
GuardGroupProto(writer, pobj, protoId);
755
}
756
757
// Make sure the shape matches, to avoid non-dense elements or anything
758
// else that is being checked by CanAttachDenseElementHole.
759
writer.guardShape(protoId, pobj->as<NativeObject>().lastProperty());
760
761
// Also make sure there are no dense elements.
762
writer.guardNoDenseElements(protoId);
763
764
pobj = pobj->staticPrototype();
765
}
766
}
767
768
// Similar to |TestMatchingReceiver|, but for the holder object (when it
769
// differs from the receiver). The holder may also be the expando of the
770
// receiver if it exists.
771
static void TestMatchingHolder(CacheIRWriter& writer, JSObject* obj,
772
ObjOperandId objId) {
773
// The GeneratePrototypeGuards + TestMatchingHolder checks only support
774
// prototype chains composed of NativeObject (excluding the receiver
775
// itself).
776
MOZ_ASSERT(obj->is<NativeObject>());
777
778
writer.guardShapeForOwnProperties(objId,
779
obj->as<NativeObject>().lastProperty());
780
}
781
782
static bool UncacheableProtoOnChain(JSObject* obj) {
783
while (true) {
784
if (obj->hasUncacheableProto()) {
785
return true;
786
}
787
788
obj = obj->staticPrototype();
789
if (!obj) {
790
return false;
791
}
792
}
793
}
794
795
static void ShapeGuardProtoChain(CacheIRWriter& writer, JSObject* obj,
796
ObjOperandId objId) {
797
while (true) {
798
// Guard on the proto if the shape does not imply the proto.
799
bool guardProto = obj->hasUncacheableProto();
800
801
obj = obj->staticPrototype();
802
if (!obj && !guardProto) {
803
return;
804
}
805
806
objId = writer.loadProto(objId);
807
808
if (guardProto) {
809
writer.guardSpecificObject(objId, obj);
810
}
811
812
if (!obj) {
813
return;
814
}
815
816
writer.guardShape(objId, obj->as<NativeObject>().shape());
817
}
818
}
819
820
// For cross compartment guards we shape-guard the prototype chain to avoid
821
// referencing the holder object.
822
//
823
// This peels off the first layer because it's guarded against obj == holder.
824
static void ShapeGuardProtoChainForCrossCompartmentHolder(
825
CacheIRWriter& writer, JSObject* obj, ObjOperandId objId, JSObject* holder,
826
Maybe<ObjOperandId>* holderId) {
827
MOZ_ASSERT(obj != holder);
828
MOZ_ASSERT(holder);
829
while (true) {
830
obj = obj->staticPrototype();
831
MOZ_ASSERT(obj);
832
833
objId = writer.loadProto(objId);
834
if (obj == holder) {
835
TestMatchingHolder(writer, obj, objId);
836
holderId->emplace(objId);
837
return;
838
} else {
839
writer.guardShapeForOwnProperties(objId, obj->as<NativeObject>().shape());
840
}
841
}
842
}
843
844
enum class SlotReadType { Normal, CrossCompartment };
845
846
template <SlotReadType MaybeCrossCompartment = SlotReadType::Normal>
847
static void EmitReadSlotGuard(CacheIRWriter& writer, JSObject* obj,
848
JSObject* holder, ObjOperandId objId,
849
Maybe<ObjOperandId>* holderId) {
850
TestMatchingReceiver(writer, obj, objId);
851
852
if (obj != holder) {
853
if (holder) {
854
if (MaybeCrossCompartment == SlotReadType::CrossCompartment) {
855
// Guard proto chain integrity.
856
// We use a variant of guards that avoid baking in any cross-compartment
857
// object pointers.
858
ShapeGuardProtoChainForCrossCompartmentHolder(writer, obj, objId,
859
holder, holderId);
860
} else {
861
// Guard proto chain integrity.
862
GeneratePrototypeGuards(writer, obj, holder, objId);
863
864
// Guard on the holder's shape.
865
holderId->emplace(writer.loadObject(holder));
866
TestMatchingHolder(writer, holder, holderId->ref());
867
}
868
} else {
869
// The property does not exist. Guard on everything in the prototype
870
// chain. This is guaranteed to see only Native objects because of
871
// CanAttachNativeGetProp().
872
ShapeGuardProtoChain(writer, obj, objId);
873
}
874
} else {
875
holderId->emplace(objId);
876
}
877
}
878
879
template <SlotReadType MaybeCrossCompartment = SlotReadType::Normal>
880
static void EmitReadSlotResult(CacheIRWriter& writer, JSObject* obj,
881
JSObject* holder, Shape* shape,
882
ObjOperandId objId) {
883
Maybe<ObjOperandId> holderId;
884
EmitReadSlotGuard<MaybeCrossCompartment>(writer, obj, holder, objId,
885
&holderId);
886
887
// Slot access.
888
if (holder) {
889
MOZ_ASSERT(holderId->valid());
890
EmitLoadSlotResult(writer, *holderId, &holder->as<NativeObject>(), shape);
891
} else {
892
MOZ_ASSERT(holderId.isNothing());
893
writer.loadUndefinedResult();
894
}
895
}
896
897
static void EmitReadSlotReturn(CacheIRWriter& writer, JSObject*,
898
JSObject* holder, Shape* shape,
899
bool wrapResult = false) {
900
// Slot access.
901
if (holder) {
902
MOZ_ASSERT(shape);
903
if (wrapResult) {
904
writer.wrapResult();
905
}
906
writer.typeMonitorResult();
907
} else {
908
// Normally for this op, the result would have to be monitored by TI.
909
// However, since this stub ALWAYS returns UndefinedValue(), and we can be
910
// sure that undefined is already registered with the type-set, this can be
911
// avoided.
912
writer.returnFromIC();
913
}
914
}
915
916
static void EmitCallGetterResultNoGuards(CacheIRWriter& writer, JSObject* obj,
917
JSObject* holder, Shape* shape,
918
ObjOperandId receiverId) {
919
switch (IsCacheableGetPropCall(obj, holder, shape)) {
920
case CanAttachNativeGetter: {
921
JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
922
MOZ_ASSERT(target->isBuiltinNative());
923
writer.callNativeGetterResult(receiverId, target);
924
writer.typeMonitorResult();
925
break;
926
}
927
case CanAttachScriptedGetter: {
928
JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
929
MOZ_ASSERT(target->hasJitEntry());
930
writer.callScriptedGetterResult(receiverId, target);
931
writer.typeMonitorResult();
932
break;
933
}
934
default:
935
// CanAttachNativeGetProp guarantees that the getter is either a native or
936
// a scripted function.
937
MOZ_ASSERT_UNREACHABLE("Can't attach getter");
938
break;
939
}
940
}
941
942
static void EmitCallGetterResultGuards(CacheIRWriter& writer, JSObject* obj,
943
JSObject* holder, Shape* shape,
944
ObjOperandId objId, ICState::Mode mode) {
945
// Use the megamorphic guard if we're in megamorphic mode, except if |obj|
946
// is a Window as GuardHasGetterSetter doesn't support this yet (Window may
947
// require outerizing).
948
if (mode == ICState::Mode::Specialized || IsWindow(obj)) {
949
TestMatchingReceiver(writer, obj, objId);
950
951
if (obj != holder) {
952
GeneratePrototypeGuards(writer, obj, holder, objId);
953
954
// Guard on the holder's shape.
955
ObjOperandId holderId = writer.loadObject(holder);
956
TestMatchingHolder(writer, holder, holderId);
957
}
958
} else {
959
writer.guardHasGetterSetter(objId, shape);
960
}
961
}
962
963
static void EmitCallGetterResult(CacheIRWriter& writer, JSObject* obj,
964
JSObject* holder, Shape* shape,
965
ObjOperandId objId, ObjOperandId receiverId,
966
ICState::Mode mode) {
967
EmitCallGetterResultGuards(writer, obj, holder, shape, objId, mode);
968
EmitCallGetterResultNoGuards(writer, obj, holder, shape, receiverId);
969
}
970
971
static void EmitCallGetterResult(CacheIRWriter& writer, JSObject* obj,
972
JSObject* holder, Shape* shape,
973
ObjOperandId objId, ICState::Mode mode) {
974
EmitCallGetterResult(writer, obj, holder, shape, objId, objId, mode);
975
}
976
977
static void EmitCallGetterByValueResult(CacheIRWriter& writer, JSObject* obj,
978
JSObject* holder, Shape* shape,
979
ObjOperandId objId,
980
ValOperandId receiverId,
981
ICState::Mode mode) {
982
EmitCallGetterResultGuards(writer, obj, holder, shape, objId, mode);
983
984
switch (IsCacheableGetPropCall(obj, holder, shape)) {
985
case CanAttachNativeGetter: {
986
JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
987
MOZ_ASSERT(target->isBuiltinNative());
988
writer.callNativeGetterByValueResult(receiverId, target);
989
writer.typeMonitorResult();
990
break;
991
}
992
case CanAttachScriptedGetter: {
993
JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
994
MOZ_ASSERT(target->hasJitEntry());
995
writer.callScriptedGetterByValueResult(receiverId, target);
996
writer.typeMonitorResult();
997
break;
998
}
999
default:
1000
// CanAttachNativeGetProp guarantees that the getter is either a native or
1001
// a scripted function.
1002
MOZ_ASSERT_UNREACHABLE("Can't attach getter");
1003
break;
1004
}
1005
}
1006
1007
void GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId,
1008
jsid id,
1009
bool handleMissing) {
1010
MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
1011
1012
// The stub handles the missing-properties case only if we're seeing one
1013
// now, to make sure Ion ICs correctly monitor the undefined type.
1014
1015
if (cacheKind_ == CacheKind::GetProp ||
1016
cacheKind_ == CacheKind::GetPropSuper) {
1017
writer.megamorphicLoadSlotResult(objId, JSID_TO_ATOM(id)->asPropertyName(),
1018
handleMissing);
1019
} else {
1020
MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
1021
cacheKind_ == CacheKind::GetElemSuper);
1022
writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId(),
1023
handleMissing);
1024
}
1025
writer.typeMonitorResult();
1026
1027
trackAttached(handleMissing ? "MegamorphicMissingNativeSlot"
1028
: "MegamorphicNativeSlot");
1029
}
1030
1031
AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj,
1032
ObjOperandId objId,
1033
HandleId id) {
1034
RootedShape shape(cx_);
1035
RootedNativeObject holder(cx_);
1036
1037
NativeGetPropCacheability type =
1038
CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_, resultFlags_);
1039
switch (type) {
1040
case CanAttachNone:
1041
return AttachDecision::NoAction;
1042
case CanAttachTemporarilyUnoptimizable:
1043
return AttachDecision::TemporarilyUnoptimizable;
1044
case CanAttachReadSlot:
1045
if (mode_ == ICState::Mode::Megamorphic) {
1046
attachMegamorphicNativeSlot(objId, id, holder == nullptr);
1047
return AttachDecision::Attach;
1048
}
1049
1050
maybeEmitIdGuard(id);
1051
if (holder) {
1052
EnsureTrackPropertyTypes(cx_, holder, id);
1053
// See the comment in StripPreliminaryObjectStubs.
1054
if (IsPreliminaryObject(obj)) {
1055
preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
1056
} else {
1057
preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
1058
}
1059
}
1060
EmitReadSlotResult(writer, obj, holder, shape, objId);
1061
EmitReadSlotReturn(writer, obj, holder, shape);
1062
1063
trackAttached("NativeSlot");
1064
return AttachDecision::Attach;
1065
case CanAttachScriptedGetter:
1066
case CanAttachNativeGetter: {
1067
// |super.prop| accesses use a |this| value that differs from lookup
1068
// object
1069
MOZ_ASSERT(!idempotent());
1070
ObjOperandId receiverId =
1071
isSuper() ? writer.guardToObject(getSuperReceiverValueId()) : objId;
1072
maybeEmitIdGuard(id);
1073
EmitCallGetterResult(writer, obj, holder, shape, objId, receiverId,
1074
mode_);
1075
1076
trackAttached("NativeGetter");
1077
return AttachDecision::Attach;
1078
}
1079
}
1080
1081
MOZ_CRASH("Bad NativeGetPropCacheability");
1082
}
1083
1084
bool js::jit::IsWindowProxyForScriptGlobal(JSScript* script, JSObject* obj) {
1085
if (!IsWindowProxy(obj)) {
1086
return false;
1087
}
1088
1089
MOZ_ASSERT(obj->getClass() ==
1090
script->runtimeFromMainThread()->maybeWindowProxyClass());
1091
1092
JSObject* window = ToWindowIfWindowProxy(obj);
1093
1094
// Ion relies on the WindowProxy's group changing (and the group getting
1095
// marked as having unknown properties) on navigation. If we ever stop
1096
// transplanting same-compartment WindowProxies, this assert will fail and we
1097
// need to fix that code.
1098
MOZ_ASSERT(window == &obj->nonCCWGlobal());
1099
1100
// This must be a WindowProxy for a global in this compartment. Else it would
1101
// be a cross-compartment wrapper and IsWindowProxy returns false for
1102
// those.
1103
MOZ_ASSERT(script->compartment() == obj->compartment());
1104
1105
// Only optimize lookups on the WindowProxy for the current global. Other
1106
// WindowProxies in the compartment may require security checks (based on
1107
// mutable document.domain). See bug 1516775.
1108
return window == &script->global();
1109
}
1110
1111
// Guards objId is a WindowProxy for windowObj. Returns the window's operand id.
1112
static ObjOperandId GuardAndLoadWindowProxyWindow(CacheIRWriter& writer,
1113
ObjOperandId objId,
1114
GlobalObject* windowObj) {
1115
// Note: update AddCacheIRGetPropFunction in BaselineInspector.cpp when making
1116
// changes here.
1117
writer.guardClass(objId, GuardClassKind::WindowProxy);
1118
ObjOperandId windowObjId = writer.loadWrapperTarget(objId);
1119
writer.guardSpecificObject(windowObjId, windowObj);
1120
return windowObjId;
1121
}
1122
1123
AttachDecision GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
1124
ObjOperandId objId,
1125
HandleId id) {
1126
// Attach a stub when the receiver is a WindowProxy and we can do the lookup
1127
// on the Window (the global object).
1128
1129
if (!IsWindowProxyForScriptGlobal(script_, obj)) {
1130
return AttachDecision::NoAction;
1131
}
1132
1133
// If we're megamorphic prefer a generic proxy stub that handles a lot more
1134
// cases.
1135
if (mode_ == ICState::Mode::Megamorphic) {
1136
return AttachDecision::NoAction;
1137
}
1138
1139
// Now try to do the lookup on the Window (the current global).
1140
Handle<GlobalObject*> windowObj = cx_->global();
1141
RootedShape shape(cx_);
1142
RootedNativeObject holder(cx_);
1143
NativeGetPropCacheability type = CanAttachNativeGetProp(
1144
cx_, windowObj, id, &holder, &shape, pc_, resultFlags_);
1145
switch (type) {
1146
case CanAttachNone:
1147
return AttachDecision::NoAction;
1148
1149
case CanAttachReadSlot: {
1150
maybeEmitIdGuard(id);
1151
ObjOperandId windowObjId =
1152
GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
1153
EmitReadSlotResult(writer, windowObj, holder, shape, windowObjId);
1154
EmitReadSlotReturn(writer, windowObj, holder, shape);
1155
1156
trackAttached("WindowProxySlot");
1157
return AttachDecision::Attach;
1158
}
1159
1160
case CanAttachNativeGetter: {
1161
// Make sure the native getter is okay with the IC passing the Window
1162
// instead of the WindowProxy as |this| value.
1163
JSFunction* callee = &shape->getterObject()->as<JSFunction>();
1164
MOZ_ASSERT(callee->isNative());
1165
if (!callee->hasJitInfo() ||
1166
callee->jitInfo()->needsOuterizedThisObject()) {
1167
return AttachDecision::NoAction;
1168
}
1169
1170
// If a |super| access, it is not worth the complexity to attach an IC.
1171
if (isSuper()) {
1172
return AttachDecision::NoAction;
1173
}
1174
1175
// Guard the incoming object is a WindowProxy and inline a getter call
1176
// based on the Window object.
1177
maybeEmitIdGuard(id);
1178
ObjOperandId windowObjId =
1179
GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
1180
EmitCallGetterResult(writer, windowObj, holder, shape, windowObjId,
1181
mode_);
1182
1183
trackAttached("WindowProxyGetter");
1184
return AttachDecision::Attach;
1185
}
1186
1187
case CanAttachScriptedGetter:
1188
case CanAttachTemporarilyUnoptimizable:
1189
MOZ_ASSERT_UNREACHABLE("Not possible for window proxies");
1190
}
1191
1192
MOZ_CRASH("Unreachable");
1193
}
1194
1195
AttachDecision GetPropIRGenerator::tryAttachCrossCompartmentWrapper(
1196
HandleObject obj, ObjOperandId objId, HandleId id) {
1197
// We can only optimize this very wrapper-handler, because others might
1198
// have a security policy.
1199
if (!IsWrapper(obj) ||
1200
Wrapper::wrapperHandler(obj) != &CrossCompartmentWrapper::singleton) {
1201
return AttachDecision::NoAction;
1202
}
1203
1204
// If we're megamorphic prefer a generic proxy stub that handles a lot more
1205
// cases.
1206
if (mode_ == ICState::Mode::Megamorphic) {
1207
return AttachDecision::NoAction;
1208
}
1209
1210
RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj));
1211
MOZ_ASSERT(unwrapped == UnwrapOneCheckedStatic(obj));
1212
MOZ_ASSERT(!IsCrossCompartmentWrapper(unwrapped),
1213
"CCWs must not wrap other CCWs");
1214
1215
// If we allowed different zones we would have to wrap strings.
1216
if (unwrapped->compartment()->zone() != cx_->compartment()->zone()) {
1217
return AttachDecision::NoAction;
1218
}
1219
1220
// Take the unwrapped object's global, and wrap in a
1221
// this-compartment wrapper. This is what will be stored in the IC
1222
// keep the compartment alive.
1223
RootedObject wrappedTargetGlobal(cx_, &unwrapped->nonCCWGlobal());
1224
if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal)) {
1225
cx_->clearPendingException();
1226
return AttachDecision::NoAction;
1227
}
1228
1229
RootedShape shape(cx_);
1230
RootedNativeObject holder(cx_);
1231
1232
// Enter realm of target since some checks have side-effects
1233
// such as de-lazifying type info.
1234
{
1235
AutoRealm ar(cx_, unwrapped);
1236
1237
NativeGetPropCacheability canCache = CanAttachNativeGetProp(
1238
cx_, unwrapped, id, &holder, &shape, pc_, resultFlags_);
1239
if (canCache == CanAttachTemporarilyUnoptimizable) {
1240
return AttachDecision::TemporarilyUnoptimizable;
1241
}
1242
if (canCache != CanAttachReadSlot) {
1243
return AttachDecision::NoAction;
1244
}
1245
1246
if (holder) {
1247
// Need to be in the compartment of the holder to
1248
// call EnsureTrackPropertyTypes
1249
EnsureTrackPropertyTypes(cx_, holder, id);
1250
if (unwrapped == holder) {
1251
// See the comment in StripPreliminaryObjectStubs.
1252
if (IsPreliminaryObject(unwrapped)) {
1253
preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
1254
} else {
1255
preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
1256
}
1257
}
1258
} else {
1259
// UNCACHEABLE_PROTO may result in guards against specific
1260
// (cross-compartment) prototype objects, so don't try to attach IC if we
1261
// see the flag at all.
1262
if (UncacheableProtoOnChain(unwrapped)) {
1263
return AttachDecision::NoAction;
1264
}
1265
}
1266
}
1267
1268
maybeEmitIdGuard(id);
1269
writer.guardIsProxy(objId);
1270
writer.guardHasProxyHandler(objId, Wrapper::wrapperHandler(obj));
1271
1272
// Load the object wrapped by the CCW
1273
ObjOperandId wrapperTargetId = writer.loadWrapperTarget(objId);
1274
1275
// If the compartment of the wrapped object is different we should fail.
1276
writer.guardCompartment(wrapperTargetId, wrappedTargetGlobal,
1277
unwrapped->compartment());
1278
1279
ObjOperandId unwrappedId = wrapperTargetId;
1280
EmitReadSlotResult<SlotReadType::CrossCompartment>(writer, unwrapped, holder,
1281
shape, unwrappedId);
1282
EmitReadSlotReturn(writer, unwrapped, holder, shape, /* wrapResult = */ true);
1283
1284
trackAttached("CCWSlot");
1285
return AttachDecision::Attach;
1286
}
1287
1288
static bool GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray,
1289
MutableHandleObject wrapper) {
1290
Value v = GetProxyReservedSlot(xray, GetXrayJitInfo()->xrayHolderSlot);
1291
if (v.isObject()) {
1292
NativeObject* holder = &v.toObject().as<NativeObject>();
1293
v = holder->getFixedSlot(GetXrayJitInfo()->holderExpandoSlot);
1294
if (v.isObject()) {
1295
RootedNativeObject expando(
1296
cx, &UncheckedUnwrap(&v.toObject())->as<NativeObject>());
1297
wrapper.set(NewWrapperWithObjectShape(cx, expando));
1298
return wrapper != nullptr;
1299
}
1300
}
1301
wrapper.set(nullptr);
1302
return true;
1303
}
1304
1305
AttachDecision GetPropIRGenerator::tryAttachXrayCrossCompartmentWrapper(
1306
HandleObject obj, ObjOperandId objId, HandleId id) {
1307
if (!IsProxy(obj)) {
1308
return AttachDecision::NoAction;
1309
}
1310
1311
XrayJitInfo* info = GetXrayJitInfo();
1312
if (!info || !info->isCrossCompartmentXray(GetProxyHandler(obj))) {
1313
return AttachDecision::NoAction;
1314
}
1315
1316
if (!info->compartmentHasExclusiveExpandos(obj)) {
1317
return AttachDecision::NoAction;
1318
}
1319
1320
RootedObject target(cx_, UncheckedUnwrap(obj));
1321
1322
RootedObject expandoShapeWrapper(cx_);
1323
if (!GetXrayExpandoShapeWrapper(cx_, obj, &expandoShapeWrapper)) {
1324
cx_->recoverFromOutOfMemory();
1325
return AttachDecision::NoAction;
1326
}
1327
1328
// Look for a getter we can call on the xray or its prototype chain.
1329
Rooted<PropertyDescriptor> desc(cx_);
1330
RootedObject holder(cx_, obj);
1331
RootedObjectVector prototypes(cx_);
1332
RootedObjectVector prototypeExpandoShapeWrappers(cx_);
1333
while (true) {
1334
if (!GetOwnPropertyDescriptor(cx_, holder, id, &desc)) {
1335
cx_->clearPendingException();
1336
return AttachDecision::NoAction;
1337
}
1338
if (desc.object()) {
1339
break;
1340
}
1341
if (!GetPrototype(cx_, holder, &holder)) {
1342
cx_->clearPendingException();
1343
return AttachDecision::NoAction;
1344
}
1345
if (!holder || !IsProxy(holder) ||
1346
!info->isCrossCompartmentXray(GetProxyHandler(holder))) {
1347
return AttachDecision::NoAction;
1348
}
1349
RootedObject prototypeExpandoShapeWrapper(cx_);
1350
if (!GetXrayExpandoShapeWrapper(cx_, holder,
1351
&prototypeExpandoShapeWrapper) ||
1352
!prototypes.append(holder) ||
1353
!prototypeExpandoShapeWrappers.append(prototypeExpandoShapeWrapper)) {
1354
cx_->recoverFromOutOfMemory();
1355
return AttachDecision::NoAction;
1356
}
1357
}
1358
if (!desc.isAccessorDescriptor()) {
1359
return AttachDecision::NoAction;
1360
}
1361
1362
RootedObject getter(cx_, desc.getterObject());
1363
if (!getter || !getter->is<JSFunction>() ||
1364
!getter->as<JSFunction>().isNative()) {
1365
return AttachDecision::NoAction;
1366
}
1367
1368
maybeEmitIdGuard(id);
1369
writer.guardIsProxy(objId);
1370
writer.guardHasProxyHandler(objId, GetProxyHandler(obj));
1371
1372
// Load the object wrapped by the CCW
1373
ObjOperandId wrapperTargetId = writer.loadWrapperTarget(objId);
1374
1375
// Test the wrapped object's class. The properties held by xrays or their
1376
// prototypes will be invariant for objects of a given class, except for
1377
// changes due to xray expandos or xray prototype mutations.
1378
writer.guardAnyClass(wrapperTargetId, target->getClass());
1379
1380
// Make sure the expandos on the xray and its prototype chain match up with
1381
// what we expect. The expando shape needs to be consistent, to ensure it
1382
// has not had any shadowing properties added, and the expando cannot have
1383
// any custom prototype (xray prototypes are stable otherwise).
1384
//
1385
// We can only do this for xrays with exclusive access to their expandos
1386
// (as we checked earlier), which store a pointer to their expando
1387
// directly. Xrays in other compartments may share their expandos with each
1388
// other and a VM call is needed just to find the expando.
1389
writer.guardXrayExpandoShapeAndDefaultProto(objId, expandoShapeWrapper);
1390
for (size_t i = 0; i < prototypes.length(); i++) {
1391
JSObject* proto = prototypes[i];
1392
ObjOperandId protoId = writer.loadObject(proto);
1393
writer.guardXrayExpandoShapeAndDefaultProto(
1394
protoId, prototypeExpandoShapeWrappers[i]);
1395
}
1396
1397
writer.callNativeGetterResult(objId, &getter->as<JSFunction>());
1398
writer.typeMonitorResult();
1399
1400
trackAttached("XrayGetter");
1401
return AttachDecision::Attach;
1402
}
1403
1404
AttachDecision GetPropIRGenerator::tryAttachGenericProxy(
1405
HandleObject obj, ObjOperandId objId, HandleId id, bool handleDOMProxies) {
1406
MOZ_ASSERT(obj->is<ProxyObject>());
1407
1408
writer.guardIsProxy(objId);
1409
1410
if (!handleDOMProxies) {
1411
// Ensure that the incoming object is not a DOM proxy, so that we can get to
1412
// the specialized stubs
1413
writer.guardNotDOMProxy(objId);
1414
}
1415
1416
if (cacheKind_ == CacheKind::GetProp || mode_ == ICState::Mode::Specialized) {
1417
MOZ_ASSERT(!isSuper());
1418
maybeEmitIdGuard(id);
1419
writer.callProxyGetResult(objId, id);
1420
} else {
1421
// Attach a stub that handles every id.
1422
MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
1423
MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
1424
MOZ_ASSERT(!isSuper());
1425
writer.callProxyGetByValueResult(objId, getElemKeyValueId());
1426
}
1427
1428
writer.typeMonitorResult();
1429
1430
trackAttached("GenericProxy");
1431
return AttachDecision::Attach;
1432
}
1433
1434
ObjOperandId IRGenerator::guardDOMProxyExpandoObjectAndShape(
1435
JSObject* obj, ObjOperandId objId, const Value& expandoVal,
1436
JSObject* expandoObj) {
1437
MOZ_ASSERT(IsCacheableDOMProxy(obj));
1438
1439
TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
1440
1441
// Shape determines Class, so now it must be a DOM proxy.
1442
ValOperandId expandoValId;
1443
if (expandoVal.isObject()) {
1444
expandoValId = writer.loadDOMExpandoValue(objId);
1445
} else {
1446
expandoValId = writer.loadDOMExpandoValueIgnoreGeneration(objId);
1447
}
1448
1449
// Guard the expando is an object and shape guard.
1450
ObjOperandId expandoObjId = writer.guardToObject(expandoValId);
1451
TestMatchingHolder(writer, expandoObj, expandoObjId);
1452
return expandoObjId;
1453
}
1454
1455
AttachDecision GetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj,
1456
ObjOperandId objId,
1457
HandleId id) {
1458
MOZ_ASSERT(IsCacheableDOMProxy(obj));
1459
1460
RootedValue expandoVal(cx_, GetProxyPrivate(obj));
1461
RootedObject expandoObj(cx_);
1462
if (expandoVal.isObject()) {
1463
expandoObj = &expandoVal.toObject();
1464
} else {
1465
MOZ_ASSERT(!expandoVal.isUndefined(),
1466
"How did a missing expando manage to shadow things?");
1467
auto expandoAndGeneration =
1468
static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
1469
MOZ_ASSERT(expandoAndGeneration);
1470
expandoObj = &expandoAndGeneration->expando.toObject();
1471
}
1472
1473
// Try to do the lookup on the expando object.
1474
RootedNativeObject holder(cx_);
1475
RootedShape propShape(cx_);
1476
NativeGetPropCacheability canCache = CanAttachNativeGetProp(
1477
cx_, expandoObj, id, &holder, &propShape, pc_, resultFlags_);
1478
if (canCache == CanAttachNone) {
1479
return AttachDecision::NoAction;
1480
}
1481
if (canCache == CanAttachTemporarilyUnoptimizable) {
1482
return AttachDecision::TemporarilyUnoptimizable;
1483
}
1484
if (!holder) {
1485
return AttachDecision::NoAction;
1486
}
1487
1488
MOZ_ASSERT(holder == expandoObj);
1489
1490
maybeEmitIdGuard(id);
1491
ObjOperandId expandoObjId =
1492
guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj);
1493
1494
if (canCache == CanAttachReadSlot) {
1495
// Load from the expando's slots.
1496
EmitLoadSlotResult(writer, expandoObjId, &expandoObj->as<NativeObject>(),
1497
propShape);
1498
writer.typeMonitorResult();
1499
} else {
1500
// Call the getter. Note that we pass objId, the DOM proxy, as |this|
1501
// and not the expando object.
1502
MOZ_ASSERT(canCache == CanAttachNativeGetter ||
1503
canCache == CanAttachScriptedGetter);
1504
EmitCallGetterResultNoGuards(writer, expandoObj, expandoObj, propShape,
1505
objId);
1506
}
1507
1508
trackAttached("DOMProxyExpando");
1509
return AttachDecision::Attach;
1510
}
1511
1512
AttachDecision GetPropIRGenerator::tryAttachDOMProxyShadowed(HandleObject obj,
1513
ObjOperandId objId,
1514
HandleId id) {
1515
MOZ_ASSERT(!isSuper());
1516
MOZ_ASSERT(IsCacheableDOMProxy(obj));
1517
1518
maybeEmitIdGuard(id);
1519
TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
1520
writer.callProxyGetResult(objId, id);
1521
writer.typeMonitorResult();
1522
1523
trackAttached("DOMProxyShadowed");
1524
return AttachDecision::Attach;
1525
}
1526
1527
// Callers are expected to have already guarded on the shape of the
1528
// object, which guarantees the object is a DOM proxy.
1529
static void CheckDOMProxyExpandoDoesNotShadow(CacheIRWriter& writer,
1530
JSObject* obj, jsid id,
1531
ObjOperandId objId) {
1532
MOZ_ASSERT(IsCacheableDOMProxy(obj));
1533
1534
Value expandoVal = GetProxyPrivate(obj);
1535
1536
ValOperandId expandoId;
1537
if (!expandoVal.isObject() && !expandoVal.isUndefined()) {
1538
auto expandoAndGeneration =
1539
static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
1540
expandoId =
1541
writer.loadDOMExpandoValueGuardGeneration(objId, expandoAndGeneration);
1542
expandoVal = expandoAndGeneration->expando;
1543
} else {
1544
expandoId = writer.loadDOMExpandoValue(objId);
1545
}
1546
1547
if (expandoVal.isUndefined()) {
1548
// Guard there's no expando object.
1549
writer.guardType(expandoId, ValueType::Undefined);
1550
} else if (expandoVal.isObject()) {
1551
// Guard the proxy either has no expando object or, if it has one, that
1552
// the shape matches the current expando object.
1553
NativeObject& expandoObj = expandoVal.toObject().as<NativeObject>();
1554
MOZ_ASSERT(!expandoObj.containsPure(id));
1555
writer.guardDOMExpandoMissingOrGuardShape(expandoId,
1556
expandoObj.lastProperty());
1557
} else {
1558
MOZ_CRASH("Invalid expando value");
1559
}
1560
}
1561
1562
AttachDecision GetPropIRGenerator::tryAttachDOMProxyUnshadowed(
1563
HandleObject obj, ObjOperandId objId, HandleId id) {
1564
MOZ_ASSERT(IsCacheableDOMProxy(obj));
1565
1566
RootedObject checkObj(cx_, obj->staticPrototype());
1567
if (!checkObj) {
1568
return AttachDecision::NoAction;
1569
}
1570
1571
RootedNativeObject holder(cx_);
1572
RootedShape shape(cx_);
1573
NativeGetPropCacheability canCache = CanAttachNativeGetProp(
1574
cx_, checkObj, id, &holder, &shape, pc_, resultFlags_);
1575
if (canCache == CanAttachNone) {
1576
return AttachDecision::NoAction;
1577
}
1578
if (canCache == CanAttachTemporarilyUnoptimizable) {
1579
return AttachDecision::TemporarilyUnoptimizable;
1580
}
1581
1582
maybeEmitIdGuard(id);
1583
1584
// Guard that our expando object hasn't started shadowing this property.
1585
TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
1586
CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId);
1587
1588
if (holder) {
1589
// Found the property on the prototype chain. Treat it like a native
1590
// getprop.
1591
GeneratePrototypeGuards(writer, obj, holder, objId);
1592
1593
// Guard on the holder of the property.
1594
ObjOperandId holderId = writer.loadObject(holder);
1595
TestMatchingHolder(writer, holder, holderId);
1596
1597
if (canCache == CanAttachReadSlot) {
1598
EmitLoadSlotResult(writer, holderId, holder, shape);
1599
writer.typeMonitorResult();
1600
} else {
1601
// EmitCallGetterResultNoGuards expects |obj| to be the object the
1602
// property is on to do some checks. Since we actually looked at
1603
// checkObj, and no extra guards will be generated, we can just
1604
// pass that instead.
1605
MOZ_ASSERT(canCache == CanAttachNativeGetter ||
1606
canCache == CanAttachScriptedGetter);
1607
MOZ_ASSERT(!isSuper());
1608
EmitCallGetterResultNoGuards(writer, checkObj, holder, shape, objId);
1609
}
1610
} else {
1611
// Property was not found on the prototype chain. Deoptimize down to
1612
// proxy get call.
1613
MOZ_ASSERT(!isSuper());
1614
writer.callProxyGetResult(objId, id);
1615
writer.typeMonitorResult();
1616
}
1617
1618
trackAttached("DOMProxyUnshadowed");
1619
return AttachDecision::Attach;
1620
}
1621
1622
AttachDecision GetPropIRGenerator::tryAttachProxy(HandleObject obj,
1623
ObjOperandId objId,
1624
HandleId id) {
1625
ProxyStubType type = GetProxyStubType(cx_, obj, id);
1626
if (type == ProxyStubType::None) {
1627
return AttachDecision::NoAction;
1628
}
1629
1630
// The proxy stubs don't currently support |super| access.
1631
if (isSuper()) {
1632
return AttachDecision::NoAction;
1633
}
1634
1635
if (mode_ == ICState::Mode::Megamorphic) {
1636
return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ true);
1637
}
1638
1639
switch (type) {
1640
case ProxyStubType::None:
1641
break;
1642
case ProxyStubType::DOMExpando:
1643
TRY_ATTACH(tryAttachDOMProxyExpando(obj, objId, id));
1644
[[fallthrough]]; // Fall through to the generic shadowed case.
1645
case ProxyStubType::DOMShadowed:
1646
return tryAttachDOMProxyShadowed(obj, objId, id);
1647
case ProxyStubType::DOMUnshadowed:
1648
TRY_ATTACH(tryAttachDOMProxyUnshadowed(obj, objId, id));
1649
return tryAttachGenericProxy(obj, objId, id,
1650
/* handleDOMProxies = */ true);
1651
case ProxyStubType::Generic:
1652
return tryAttachGenericProxy(obj, objId, id,
1653
/* handleDOMProxies = */ false);
1654
}
1655
1656
MOZ_CRASH("Unexpected ProxyStubType");
1657
}
1658
1659
static TypedThingLayout GetTypedThingLayout(const JSClass* clasp) {
1660
if (IsTypedArrayClass(clasp)) {
1661
return Layout_TypedArray;
1662
}
1663
if (IsOutlineTypedObjectClass(clasp)) {
1664
return Layout_OutlineTypedObject;
1665
}
1666
if (IsInlineTypedObjectClass(clasp)) {
1667
return Layout_InlineTypedObject;
1668
}
1669
MOZ_CRASH("Bad object class");
1670
}
1671
1672
AttachDecision GetPropIRGenerator::tryAttachTypedObject(HandleObject obj,
1673
ObjOperandId objId,
1674
HandleId id) {
1675
if (!obj->is<TypedObject>()) {
1676
return AttachDecision::NoAction;
1677
}
1678
1679
TypedObject* typedObj = &obj->as<TypedObject>();
1680
if (!typedObj->typeDescr().is<StructTypeDescr>()) {
1681
return AttachDecision::NoAction;
1682
}
1683
1684
StructTypeDescr* structDescr = &typedObj->typeDescr().as<StructTypeDescr>();
1685
size_t fieldIndex;
1686
if (!structDescr->fieldIndex(id, &fieldIndex)) {
1687
return AttachDecision::NoAction;
1688
}
1689
1690
TypeDescr* fieldDescr = &structDescr->fieldDescr(fieldIndex);
1691
if (!fieldDescr->is<SimpleTypeDescr>()) {
1692
return AttachDecision::NoAction;
1693
}
1694
1695
TypedThingLayout layout = GetTypedThingLayout(obj->getClass());
1696
1697
uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex);
1698
uint32_t typeDescr = SimpleTypeDescrKey(&fieldDescr->as<SimpleTypeDescr>());
1699
1700
maybeEmitIdGuard(id);
1701
writer.guardGroupForLayout(objId, obj->group());
1702
writer.loadTypedObjectResult(objId, fieldOffset, layout, typeDescr);
1703
1704
// Only monitor the result if the type produced by this stub might vary.
1705
bool monitorLoad = false;
1706
if (SimpleTypeDescrKeyIsScalar(typeDescr)) {
1707
Scalar::Type type = ScalarTypeFromSimpleTypeDescrKey(typeDescr);
1708
monitorLoad = type == Scalar::Uint32;
1709
} else {
1710
ReferenceType type = ReferenceTypeFromSimpleTypeDescrKey(typeDescr);
1711
monitorLoad = type != ReferenceType::TYPE_STRING;
1712
}
1713
1714
if (monitorLoad) {
1715
writer.typeMonitorResult();
1716
} else {
1717
writer.returnFromIC();
1718
}
1719
1720