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 "jsapi.h"
13 #include "jsmath.h"
14
15 #include "builtin/DataViewObject.h"
16 #include "builtin/MapObject.h"
17 #include "builtin/ModuleObject.h"
18 #include "builtin/Object.h"
19 #include "jit/BaselineIC.h"
20 #include "jit/CacheIRCloner.h"
21 #include "jit/CacheIRCompiler.h"
22 #include "jit/CacheIRGenerator.h"
23 #include "jit/CacheIRSpewer.h"
24 #include "jit/CacheIRWriter.h"
25 #include "jit/InlinableNatives.h"
26 #include "jit/JitContext.h"
27 #include "js/experimental/JitInfo.h" // JSJitInfo
28 #include "js/friend/DOMProxy.h" // JS::ExpandoAndGeneration
29 #include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowIfWindowProxy
30 #include "js/friend/XrayJitInfo.h" // js::jit::GetXrayJitInfo, JS::XrayJitInfo
31 #include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis
32 #include "js/RegExpFlags.h" // JS::RegExpFlags
33 #include "js/ScalarType.h" // js::Scalar::Type
34 #include "js/Wrapper.h"
35 #include "proxy/DOMProxy.h" // js::GetDOMProxyHandlerFamily
36 #include "util/Unicode.h"
37 #include "vm/ArrayBufferObject.h"
38 #include "vm/BytecodeUtil.h"
39 #include "vm/Iteration.h"
40 #include "vm/PlainObject.h" // js::PlainObject
41 #include "vm/ProxyObject.h"
42 #include "vm/RegExpObject.h"
43 #include "vm/SelfHosting.h"
44 #include "vm/ThrowMsgKind.h" // ThrowCondition
45 #include "vm/Watchtower.h"
46 #include "wasm/WasmInstance.h"
47
48 #include "jit/BaselineFrame-inl.h"
49 #include "jit/MacroAssembler-inl.h"
50 #include "vm/ArrayBufferObject-inl.h"
51 #include "vm/BytecodeUtil-inl.h"
52 #include "vm/EnvironmentObject-inl.h"
53 #include "vm/JSContext-inl.h"
54 #include "vm/JSObject-inl.h"
55 #include "vm/JSScript-inl.h"
56 #include "vm/NativeObject-inl.h"
57 #include "vm/StringObject-inl.h"
58
59 using namespace js;
60 using namespace js::jit;
61
62 using mozilla::DebugOnly;
63 using mozilla::Maybe;
64
65 using JS::DOMProxyShadowsResult;
66 using JS::ExpandoAndGeneration;
67
68 const char* const js::jit::CacheKindNames[] = {
69 #define DEFINE_KIND(kind) #kind,
70 CACHE_IR_KINDS(DEFINE_KIND)
71 #undef DEFINE_KIND
72 };
73
74 const char* const js::jit::CacheIROpNames[] = {
75 #define OPNAME(op, ...) #op,
76 CACHE_IR_OPS(OPNAME)
77 #undef OPNAME
78 };
79
80 const CacheIROpInfo js::jit::CacheIROpInfos[] = {
81 #define OPINFO(op, len, transpile, ...) {len, transpile},
82 CACHE_IR_OPS(OPINFO)
83 #undef OPINFO
84 };
85
86 const uint32_t js::jit::CacheIROpHealth[] = {
87 #define OPHEALTH(op, len, transpile, health) health,
88 CACHE_IR_OPS(OPHEALTH)
89 #undef OPHEALTH
90 };
91
92 #ifdef DEBUG
NumInputsForCacheKind(CacheKind kind)93 size_t js::jit::NumInputsForCacheKind(CacheKind kind) {
94 switch (kind) {
95 case CacheKind::NewArray:
96 case CacheKind::NewObject:
97 case CacheKind::GetIntrinsic:
98 return 0;
99 case CacheKind::GetProp:
100 case CacheKind::TypeOf:
101 case CacheKind::ToPropertyKey:
102 case CacheKind::GetIterator:
103 case CacheKind::ToBool:
104 case CacheKind::UnaryArith:
105 case CacheKind::GetName:
106 case CacheKind::BindName:
107 case CacheKind::Call:
108 case CacheKind::OptimizeSpreadCall:
109 return 1;
110 case CacheKind::Compare:
111 case CacheKind::GetElem:
112 case CacheKind::GetPropSuper:
113 case CacheKind::SetProp:
114 case CacheKind::In:
115 case CacheKind::HasOwn:
116 case CacheKind::CheckPrivateField:
117 case CacheKind::InstanceOf:
118 case CacheKind::BinaryArith:
119 return 2;
120 case CacheKind::GetElemSuper:
121 case CacheKind::SetElem:
122 return 3;
123 }
124 MOZ_CRASH("Invalid kind");
125 }
126 #endif
127
128 #ifdef DEBUG
assertSameCompartment(JSObject * obj)129 void CacheIRWriter::assertSameCompartment(JSObject* obj) {
130 cx_->debugOnlyCheck(obj);
131 }
assertSameZone(Shape * shape)132 void CacheIRWriter::assertSameZone(Shape* shape) {
133 MOZ_ASSERT(cx_->zone() == shape->zone());
134 }
135 #endif
136
readStubFieldForIon(uint32_t offset,StubField::Type type) const137 StubField CacheIRWriter::readStubFieldForIon(uint32_t offset,
138 StubField::Type type) const {
139 size_t index = 0;
140 size_t currentOffset = 0;
141
142 // If we've seen an offset earlier than this before, we know we can start the
143 // search there at least, otherwise, we start the search from the beginning.
144 if (lastOffset_ < offset) {
145 currentOffset = lastOffset_;
146 index = lastIndex_;
147 }
148
149 while (currentOffset != offset) {
150 currentOffset += StubField::sizeInBytes(stubFields_[index].type());
151 index++;
152 MOZ_ASSERT(index < stubFields_.length());
153 }
154
155 MOZ_ASSERT(stubFields_[index].type() == type);
156
157 lastOffset_ = currentOffset;
158 lastIndex_ = index;
159
160 return stubFields_[index];
161 }
162
CacheIRCloner(ICCacheIRStub * stub)163 CacheIRCloner::CacheIRCloner(ICCacheIRStub* stub)
164 : stubInfo_(stub->stubInfo()), stubData_(stub->stubDataStart()) {}
165
cloneOp(CacheOp op,CacheIRReader & reader,CacheIRWriter & writer)166 void CacheIRCloner::cloneOp(CacheOp op, CacheIRReader& reader,
167 CacheIRWriter& writer) {
168 switch (op) {
169 #define DEFINE_OP(op, ...) \
170 case CacheOp::op: \
171 clone##op(reader, writer); \
172 break;
173 CACHE_IR_OPS(DEFINE_OP)
174 #undef DEFINE_OP
175 default:
176 MOZ_CRASH("Invalid op");
177 }
178 }
179
readStubWord(uint32_t offset)180 uintptr_t CacheIRCloner::readStubWord(uint32_t offset) {
181 return stubInfo_->getStubRawWord(stubData_, offset);
182 }
readStubInt64(uint32_t offset)183 int64_t CacheIRCloner::readStubInt64(uint32_t offset) {
184 return stubInfo_->getStubRawInt64(stubData_, offset);
185 }
186
getShapeField(uint32_t stubOffset)187 Shape* CacheIRCloner::getShapeField(uint32_t stubOffset) {
188 return reinterpret_cast<Shape*>(readStubWord(stubOffset));
189 }
getGetterSetterField(uint32_t stubOffset)190 GetterSetter* CacheIRCloner::getGetterSetterField(uint32_t stubOffset) {
191 return reinterpret_cast<GetterSetter*>(readStubWord(stubOffset));
192 }
getObjectField(uint32_t stubOffset)193 JSObject* CacheIRCloner::getObjectField(uint32_t stubOffset) {
194 return reinterpret_cast<JSObject*>(readStubWord(stubOffset));
195 }
getStringField(uint32_t stubOffset)196 JSString* CacheIRCloner::getStringField(uint32_t stubOffset) {
197 return reinterpret_cast<JSString*>(readStubWord(stubOffset));
198 }
getAtomField(uint32_t stubOffset)199 JSAtom* CacheIRCloner::getAtomField(uint32_t stubOffset) {
200 return reinterpret_cast<JSAtom*>(readStubWord(stubOffset));
201 }
getPropertyNameField(uint32_t stubOffset)202 PropertyName* CacheIRCloner::getPropertyNameField(uint32_t stubOffset) {
203 return reinterpret_cast<PropertyName*>(readStubWord(stubOffset));
204 }
getSymbolField(uint32_t stubOffset)205 JS::Symbol* CacheIRCloner::getSymbolField(uint32_t stubOffset) {
206 return reinterpret_cast<JS::Symbol*>(readStubWord(stubOffset));
207 }
getBaseScriptField(uint32_t stubOffset)208 BaseScript* CacheIRCloner::getBaseScriptField(uint32_t stubOffset) {
209 return reinterpret_cast<BaseScript*>(readStubWord(stubOffset));
210 }
getRawInt32Field(uint32_t stubOffset)211 uint32_t CacheIRCloner::getRawInt32Field(uint32_t stubOffset) {
212 return uint32_t(reinterpret_cast<uintptr_t>(readStubWord(stubOffset)));
213 }
getRawPointerField(uint32_t stubOffset)214 const void* CacheIRCloner::getRawPointerField(uint32_t stubOffset) {
215 return reinterpret_cast<const void*>(readStubWord(stubOffset));
216 }
getRawInt64Field(uint32_t stubOffset)217 uint64_t CacheIRCloner::getRawInt64Field(uint32_t stubOffset) {
218 return static_cast<uint64_t>(readStubInt64(stubOffset));
219 }
getAllocSiteField(uint32_t stubOffset)220 gc::AllocSite* CacheIRCloner::getAllocSiteField(uint32_t stubOffset) {
221 return reinterpret_cast<gc::AllocSite*>(readStubWord(stubOffset));
222 }
223
getIdField(uint32_t stubOffset)224 jsid CacheIRCloner::getIdField(uint32_t stubOffset) {
225 return jsid::fromRawBits(readStubWord(stubOffset));
226 }
getValueField(uint32_t stubOffset)227 const Value CacheIRCloner::getValueField(uint32_t stubOffset) {
228 return Value::fromRawBits(uint64_t(readStubInt64(stubOffset)));
229 }
getDoubleField(uint32_t stubOffset)230 double CacheIRCloner::getDoubleField(uint32_t stubOffset) {
231 uint64_t bits = uint64_t(readStubInt64(stubOffset));
232 return mozilla::BitwiseCast<double>(bits);
233 }
234
IRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,CacheKind cacheKind,ICState state)235 IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
236 CacheKind cacheKind, ICState state)
237 : writer(cx),
238 cx_(cx),
239 script_(script),
240 pc_(pc),
241 cacheKind_(cacheKind),
242 mode_(state.mode()),
243 isFirstStub_(state.newStubIsFirstStub()) {}
244
GetPropIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,CacheKind cacheKind,HandleValue val,HandleValue idVal)245 GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script,
246 jsbytecode* pc, ICState state,
247 CacheKind cacheKind, HandleValue val,
248 HandleValue idVal)
249 : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {}
250
EmitLoadSlotResult(CacheIRWriter & writer,ObjOperandId holderId,NativeObject * holder,PropertyInfo prop)251 static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderId,
252 NativeObject* holder, PropertyInfo prop) {
253 if (holder->isFixedSlot(prop.slot())) {
254 writer.loadFixedSlotResult(holderId,
255 NativeObject::getFixedSlotOffset(prop.slot()));
256 } else {
257 size_t dynamicSlotOffset =
258 holder->dynamicSlotIndex(prop.slot()) * sizeof(Value);
259 writer.loadDynamicSlotResult(holderId, dynamicSlotOffset);
260 }
261 }
262
263 // DOM proxies
264 // -----------
265 //
266 // DOM proxies are proxies that are used to implement various DOM objects like
267 // HTMLDocument and NodeList. DOM proxies may have an expando object - a native
268 // object that stores extra properties added to the object. The following
269 // CacheIR instructions are only used with DOM proxies:
270 //
271 // * LoadDOMExpandoValue: returns the Value in the proxy's expando slot. This
272 // returns either an UndefinedValue (no expando), ObjectValue (the expando
273 // object), or PrivateValue(ExpandoAndGeneration*).
274 //
275 // * LoadDOMExpandoValueGuardGeneration: guards the Value in the proxy's expando
276 // slot is the same PrivateValue(ExpandoAndGeneration*), then guards on its
277 // generation, then returns expandoAndGeneration->expando. This Value is
278 // either an UndefinedValue or ObjectValue.
279 //
280 // * LoadDOMExpandoValueIgnoreGeneration: assumes the Value in the proxy's
281 // expando slot is a PrivateValue(ExpandoAndGeneration*), unboxes it, and
282 // returns the expandoAndGeneration->expando Value.
283 //
284 // * GuardDOMExpandoMissingOrGuardShape: takes an expando Value as input, then
285 // guards it's either UndefinedValue or an object with the expected shape.
286
287 enum class ProxyStubType {
288 None,
289 DOMExpando,
290 DOMShadowed,
291 DOMUnshadowed,
292 Generic
293 };
294
IsCacheableDOMProxy(ProxyObject * obj)295 static bool IsCacheableDOMProxy(ProxyObject* obj) {
296 const BaseProxyHandler* handler = obj->handler();
297 if (handler->family() != GetDOMProxyHandlerFamily()) {
298 return false;
299 }
300
301 // Some DOM proxies have dynamic prototypes. We can't really cache those very
302 // well.
303 return obj->hasStaticPrototype();
304 }
305
GetProxyStubType(JSContext * cx,HandleObject obj,HandleId id)306 static ProxyStubType GetProxyStubType(JSContext* cx, HandleObject obj,
307 HandleId id) {
308 if (!obj->is<ProxyObject>()) {
309 return ProxyStubType::None;
310 }
311 auto proxy = obj.as<ProxyObject>();
312
313 if (!IsCacheableDOMProxy(proxy)) {
314 return ProxyStubType::Generic;
315 }
316
317 DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, proxy, id);
318 if (shadows == DOMProxyShadowsResult::ShadowCheckFailed) {
319 cx->clearPendingException();
320 return ProxyStubType::None;
321 }
322
323 if (DOMProxyIsShadowing(shadows)) {
324 if (shadows == DOMProxyShadowsResult::ShadowsViaDirectExpando ||
325 shadows == DOMProxyShadowsResult::ShadowsViaIndirectExpando) {
326 return ProxyStubType::DOMExpando;
327 }
328 return ProxyStubType::DOMShadowed;
329 }
330
331 MOZ_ASSERT(shadows == DOMProxyShadowsResult::DoesntShadow ||
332 shadows == DOMProxyShadowsResult::DoesntShadowUnique);
333 return ProxyStubType::DOMUnshadowed;
334 }
335
ValueToNameOrSymbolId(JSContext * cx,HandleValue idVal,MutableHandleId id,bool * nameOrSymbol)336 static bool ValueToNameOrSymbolId(JSContext* cx, HandleValue idVal,
337 MutableHandleId id, bool* nameOrSymbol) {
338 *nameOrSymbol = false;
339
340 if (!idVal.isString() && !idVal.isSymbol() && !idVal.isUndefined() &&
341 !idVal.isNull()) {
342 return true;
343 }
344
345 if (!PrimitiveValueToId<CanGC>(cx, idVal, id)) {
346 return false;
347 }
348
349 if (!id.isAtom() && !id.isSymbol()) {
350 id.set(JS::PropertyKey::Void());
351 return true;
352 }
353
354 if (id.isAtom() && id.toAtom()->isIndex()) {
355 id.set(JS::PropertyKey::Void());
356 return true;
357 }
358
359 *nameOrSymbol = true;
360 return true;
361 }
362
tryAttachStub()363 AttachDecision GetPropIRGenerator::tryAttachStub() {
364 AutoAssertNoPendingException aanpe(cx_);
365
366 ValOperandId valId(writer.setInputOperandId(0));
367 if (cacheKind_ != CacheKind::GetProp) {
368 MOZ_ASSERT_IF(cacheKind_ == CacheKind::GetPropSuper,
369 getSuperReceiverValueId().id() == 1);
370 MOZ_ASSERT_IF(cacheKind_ != CacheKind::GetPropSuper,
371 getElemKeyValueId().id() == 1);
372 writer.setInputOperandId(1);
373 }
374 if (cacheKind_ == CacheKind::GetElemSuper) {
375 MOZ_ASSERT(getSuperReceiverValueId().id() == 2);
376 writer.setInputOperandId(2);
377 }
378
379 RootedId id(cx_);
380 bool nameOrSymbol;
381 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
382 cx_->clearPendingException();
383 return AttachDecision::NoAction;
384 }
385
386 // |super.prop| getter calls use a |this| value that differs from lookup
387 // object.
388 ValOperandId receiverId = isSuper() ? getSuperReceiverValueId() : valId;
389
390 if (val_.isObject()) {
391 RootedObject obj(cx_, &val_.toObject());
392 ObjOperandId objId = writer.guardToObject(valId);
393 if (nameOrSymbol) {
394 TRY_ATTACH(tryAttachObjectLength(obj, objId, id));
395 TRY_ATTACH(tryAttachTypedArray(obj, objId, id));
396 TRY_ATTACH(tryAttachDataView(obj, objId, id));
397 TRY_ATTACH(tryAttachArrayBufferMaybeShared(obj, objId, id));
398 TRY_ATTACH(tryAttachRegExp(obj, objId, id));
399 TRY_ATTACH(tryAttachNative(obj, objId, id, receiverId));
400 TRY_ATTACH(tryAttachModuleNamespace(obj, objId, id));
401 TRY_ATTACH(tryAttachWindowProxy(obj, objId, id));
402 TRY_ATTACH(tryAttachCrossCompartmentWrapper(obj, objId, id));
403 TRY_ATTACH(
404 tryAttachXrayCrossCompartmentWrapper(obj, objId, id, receiverId));
405 TRY_ATTACH(tryAttachFunction(obj, objId, id));
406 TRY_ATTACH(tryAttachArgumentsObjectIterator(obj, objId, id));
407 TRY_ATTACH(tryAttachArgumentsObjectCallee(obj, objId, id));
408 TRY_ATTACH(tryAttachProxy(obj, objId, id, receiverId));
409
410 trackAttached(IRGenerator::NotAttached);
411 return AttachDecision::NoAction;
412 }
413
414 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
415 cacheKind_ == CacheKind::GetElemSuper);
416
417 TRY_ATTACH(tryAttachProxyElement(obj, objId));
418 TRY_ATTACH(tryAttachTypedArrayElement(obj, objId));
419
420 uint32_t index;
421 Int32OperandId indexId;
422 if (maybeGuardInt32Index(idVal_, getElemKeyValueId(), &index, &indexId)) {
423 TRY_ATTACH(tryAttachDenseElement(obj, objId, index, indexId));
424 TRY_ATTACH(tryAttachDenseElementHole(obj, objId, index, indexId));
425 TRY_ATTACH(tryAttachSparseElement(obj, objId, index, indexId));
426 TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, index, indexId));
427 TRY_ATTACH(tryAttachArgumentsObjectArgHole(obj, objId, index, indexId));
428 TRY_ATTACH(tryAttachGenericElement(obj, objId, index, indexId));
429
430 trackAttached(IRGenerator::NotAttached);
431 return AttachDecision::NoAction;
432 }
433
434 trackAttached(IRGenerator::NotAttached);
435 return AttachDecision::NoAction;
436 }
437
438 if (nameOrSymbol) {
439 TRY_ATTACH(tryAttachPrimitive(valId, id));
440 TRY_ATTACH(tryAttachStringLength(valId, id));
441
442 trackAttached(IRGenerator::NotAttached);
443 return AttachDecision::NoAction;
444 }
445
446 if (idVal_.isInt32()) {
447 ValOperandId indexId = getElemKeyValueId();
448 TRY_ATTACH(tryAttachStringChar(valId, indexId));
449
450 trackAttached(IRGenerator::NotAttached);
451 return AttachDecision::NoAction;
452 }
453
454 trackAttached(IRGenerator::NotAttached);
455 return AttachDecision::NoAction;
456 }
457
458 #ifdef DEBUG
459 // Any property lookups performed when trying to attach ICs must be pure, i.e.
460 // must use LookupPropertyPure() or similar functions. Pure lookups are
461 // guaranteed to never modify the prototype chain. This ensures that the holder
462 // object can always be found on the prototype chain.
IsCacheableProtoChain(NativeObject * obj,NativeObject * holder)463 static bool IsCacheableProtoChain(NativeObject* obj, NativeObject* holder) {
464 while (obj != holder) {
465 JSObject* proto = obj->staticPrototype();
466 if (!proto || !proto->is<NativeObject>()) {
467 return false;
468 }
469 obj = &proto->as<NativeObject>();
470 }
471 return true;
472 }
473 #endif
474
IsCacheableGetPropReadSlot(NativeObject * obj,NativeObject * holder,PropertyInfo prop)475 static bool IsCacheableGetPropReadSlot(NativeObject* obj, NativeObject* holder,
476 PropertyInfo prop) {
477 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
478
479 return prop.isDataProperty();
480 }
481
482 enum NativeGetPropCacheability {
483 CanAttachNone,
484 CanAttachReadSlot,
485 CanAttachNativeGetter,
486 CanAttachScriptedGetter,
487 };
488
IsCacheableGetPropCall(NativeObject * obj,NativeObject * holder,PropertyInfo prop)489 static NativeGetPropCacheability IsCacheableGetPropCall(NativeObject* obj,
490 NativeObject* holder,
491 PropertyInfo prop) {
492 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
493
494 if (!prop.isAccessorProperty()) {
495 return CanAttachNone;
496 }
497
498 JSObject* getterObject = holder->getGetter(prop);
499 if (!getterObject || !getterObject->is<JSFunction>()) {
500 return CanAttachNone;
501 }
502
503 JSFunction& getter = getterObject->as<JSFunction>();
504
505 if (getter.isClassConstructor()) {
506 return CanAttachNone;
507 }
508
509 // For getters that need the WindowProxy (instead of the Window) as this
510 // object, don't cache if obj is the Window, since our cache will pass that
511 // instead of the WindowProxy.
512 if (IsWindow(obj)) {
513 // Check for a getter that has jitinfo and whose jitinfo says it's
514 // OK with both inner and outer objects.
515 if (!getter.hasJitInfo() || getter.jitInfo()->needsOuterizedThisObject()) {
516 return CanAttachNone;
517 }
518 }
519
520 // Scripted functions and natives with JIT entry can use the scripted path.
521 if (getter.hasJitEntry()) {
522 return CanAttachScriptedGetter;
523 }
524
525 MOZ_ASSERT(getter.isNativeWithoutJitEntry());
526 return CanAttachNativeGetter;
527 }
528
CheckHasNoSuchOwnProperty(JSContext * cx,JSObject * obj,jsid id)529 static bool CheckHasNoSuchOwnProperty(JSContext* cx, JSObject* obj, jsid id) {
530 if (!obj->is<NativeObject>()) {
531 return false;
532 }
533 // Don't handle objects with resolve hooks.
534 if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj)) {
535 return false;
536 }
537 if (obj->as<NativeObject>().contains(cx, id)) {
538 return false;
539 }
540 return true;
541 }
542
CheckHasNoSuchProperty(JSContext * cx,JSObject * obj,jsid id)543 static bool CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, jsid id) {
544 JSObject* curObj = obj;
545 do {
546 if (!CheckHasNoSuchOwnProperty(cx, curObj, id)) {
547 return false;
548 }
549
550 curObj = curObj->staticPrototype();
551 } while (curObj);
552
553 return true;
554 }
555
IsCacheableNoProperty(JSContext * cx,NativeObject * obj,NativeObject * holder,jsid id,jsbytecode * pc)556 static bool IsCacheableNoProperty(JSContext* cx, NativeObject* obj,
557 NativeObject* holder, jsid id,
558 jsbytecode* pc) {
559 MOZ_ASSERT(!holder);
560
561 // If we're doing a name lookup, we have to throw a ReferenceError.
562 if (JSOp(*pc) == JSOp::GetBoundName) {
563 return false;
564 }
565
566 return CheckHasNoSuchProperty(cx, obj, id);
567 }
568
CanAttachNativeGetProp(JSContext * cx,JSObject * obj,PropertyKey id,NativeObject ** holder,Maybe<PropertyInfo> * propInfo,jsbytecode * pc)569 static NativeGetPropCacheability CanAttachNativeGetProp(
570 JSContext* cx, JSObject* obj, PropertyKey id, NativeObject** holder,
571 Maybe<PropertyInfo>* propInfo, jsbytecode* pc) {
572 MOZ_ASSERT(id.isString() || id.isSymbol());
573 MOZ_ASSERT(!*holder);
574
575 // The lookup needs to be universally pure, otherwise we risk calling hooks
576 // out of turn. We don't mind doing this even when purity isn't required,
577 // because we only miss out on shape hashification, which is only a temporary
578 // perf cost. The limits were arbitrarily set, anyways.
579 NativeObject* baseHolder = nullptr;
580 PropertyResult prop;
581 if (!LookupPropertyPure(cx, obj, id, &baseHolder, &prop)) {
582 return CanAttachNone;
583 }
584 auto* nobj = &obj->as<NativeObject>();
585
586 if (prop.isNativeProperty()) {
587 MOZ_ASSERT(baseHolder);
588 *holder = baseHolder;
589 *propInfo = mozilla::Some(prop.propertyInfo());
590
591 if (IsCacheableGetPropReadSlot(nobj, *holder, propInfo->ref())) {
592 return CanAttachReadSlot;
593 }
594
595 return IsCacheableGetPropCall(nobj, *holder, propInfo->ref());
596 }
597
598 if (!prop.isFound()) {
599 if (IsCacheableNoProperty(cx, nobj, *holder, id, pc)) {
600 return CanAttachReadSlot;
601 }
602 }
603
604 return CanAttachNone;
605 }
606
GuardReceiverProto(CacheIRWriter & writer,NativeObject * obj,ObjOperandId objId)607 static void GuardReceiverProto(CacheIRWriter& writer, NativeObject* obj,
608 ObjOperandId objId) {
609 // Note: we guard on the actual prototype and not on the shape because this is
610 // used for sparse elements where we expect shape changes.
611
612 if (JSObject* proto = obj->staticPrototype()) {
613 writer.guardProto(objId, proto);
614 } else {
615 writer.guardNullProto(objId);
616 }
617 }
618
619 // Guard that a given object has same class and same OwnProperties (excluding
620 // dense elements and dynamic properties).
TestMatchingNativeReceiver(CacheIRWriter & writer,NativeObject * obj,ObjOperandId objId)621 static void TestMatchingNativeReceiver(CacheIRWriter& writer, NativeObject* obj,
622 ObjOperandId objId) {
623 writer.guardShapeForOwnProperties(objId, obj->shape());
624 }
625
626 // Similar to |TestMatchingNativeReceiver|, but specialized for ProxyObject.
TestMatchingProxyReceiver(CacheIRWriter & writer,ProxyObject * obj,ObjOperandId objId)627 static void TestMatchingProxyReceiver(CacheIRWriter& writer, ProxyObject* obj,
628 ObjOperandId objId) {
629 writer.guardShapeForClass(objId, obj->shape());
630 }
631
GeneratePrototypeGuards(CacheIRWriter & writer,JSObject * obj,NativeObject * holder,ObjOperandId objId)632 static void GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj,
633 NativeObject* holder, ObjOperandId objId) {
634 // Assuming target property is on |holder|, generate appropriate guards to
635 // ensure |holder| is still on the prototype chain of |obj| and we haven't
636 // introduced any shadowing definitions.
637 //
638 // For each item in the proto chain before holder, we must ensure that
639 // [[GetPrototypeOf]] still has the expected result, and that
640 // [[GetOwnProperty]] has no definition of the target property.
641 //
642 //
643 // [SMDOC] Shape Teleporting Optimization
644 // --------------------------------------
645 //
646 // Starting with the assumption (and guideline to developers) that mutating
647 // prototypes is an uncommon and fair-to-penalize operation we move cost
648 // from the access side to the mutation side.
649 //
650 // Consider the following proto chain, with B defining a property 'x':
651 //
652 // D -> C -> B{x: 3} -> A -> null
653 //
654 // When accessing |D.x| we refer to D as the "receiver", and B as the
655 // "holder". To optimize this access we need to ensure that neither D nor C
656 // has since defined a shadowing property 'x'. Since C is a prototype that
657 // we assume is rarely mutated we would like to avoid checking each time if
658 // new properties are added. To do this we require that whenever C starts
659 // shadowing a property on its proto chain, we invalidate (and opt out of) the
660 // teleporting optimization by setting the InvalidatedTeleporting flag on the
661 // object we're shadowing, triggering a shape change of that object. As a
662 // result, checking the shape of D and B is sufficient. Note that we do not
663 // care if the shape or properties of A change since the lookup of 'x' will
664 // stop at B.
665 //
666 // The second condition we must verify is that the prototype chain was not
667 // mutated. The same mechanism as above is used. When the prototype link is
668 // changed, we generate a new shape for the object. If the object whose
669 // link we are mutating is itself a prototype, we regenerate shapes down
670 // the chain by setting the InvalidatedTeleporting flag on them. This means
671 // the same two shape checks as above are sufficient.
672 //
673 // Once the InvalidatedTeleporting flag is set, it means the shape will no
674 // longer be changed by ReshapeForProtoMutation and ReshapeForShadowedProp.
675 // In this case we can no longer apply the optimization.
676 //
677 // See:
678 // - ReshapeForProtoMutation
679 // - ReshapeForShadowedProp
680
681 MOZ_ASSERT(holder);
682 MOZ_ASSERT(obj != holder);
683
684 // Receiver guards (see TestMatchingReceiver) ensure the receiver's proto is
685 // unchanged so peel off the receiver.
686 JSObject* pobj = obj->staticPrototype();
687 MOZ_ASSERT(pobj->isUsedAsPrototype());
688
689 // If teleporting is supported for this holder, we are done.
690 if (!holder->hasInvalidatedTeleporting()) {
691 return;
692 }
693
694 // If already at the holder, no further proto checks are needed.
695 if (pobj == holder) {
696 return;
697 }
698
699 // Synchronize pobj and protoId.
700 MOZ_ASSERT(pobj == obj->staticPrototype());
701 ObjOperandId protoId = writer.loadProto(objId);
702
703 // Shape guard each prototype object between receiver and holder. This guards
704 // against both proto changes and shadowing properties.
705 while (pobj != holder) {
706 writer.guardShape(protoId, pobj->shape());
707
708 pobj = pobj->staticPrototype();
709 protoId = writer.loadProto(protoId);
710 }
711 }
712
GeneratePrototypeHoleGuards(CacheIRWriter & writer,NativeObject * obj,ObjOperandId objId,bool alwaysGuardFirstProto)713 static void GeneratePrototypeHoleGuards(CacheIRWriter& writer,
714 NativeObject* obj, ObjOperandId objId,
715 bool alwaysGuardFirstProto) {
716 if (alwaysGuardFirstProto) {
717 GuardReceiverProto(writer, obj, objId);
718 }
719
720 JSObject* pobj = obj->staticPrototype();
721 while (pobj) {
722 ObjOperandId protoId = writer.loadObject(pobj);
723
724 // Make sure the shape matches, to ensure the proto is unchanged and to
725 // avoid non-dense elements or anything else that is being checked by
726 // CanAttachDenseElementHole.
727 MOZ_ASSERT(pobj->is<NativeObject>());
728 writer.guardShape(protoId, pobj->shape());
729
730 // Also make sure there are no dense elements.
731 writer.guardNoDenseElements(protoId);
732
733 pobj = pobj->staticPrototype();
734 }
735 }
736
737 // Similar to |TestMatchingReceiver|, but for the holder object (when it
738 // differs from the receiver). The holder may also be the expando of the
739 // receiver if it exists.
TestMatchingHolder(CacheIRWriter & writer,NativeObject * obj,ObjOperandId objId)740 static void TestMatchingHolder(CacheIRWriter& writer, NativeObject* obj,
741 ObjOperandId objId) {
742 // The GeneratePrototypeGuards + TestMatchingHolder checks only support
743 // prototype chains composed of NativeObject (excluding the receiver
744 // itself).
745 writer.guardShapeForOwnProperties(objId, obj->shape());
746 }
747
748 // Emit a shape guard for all objects on the proto chain. This does NOT include
749 // the receiver; callers must ensure the receiver's proto is the first proto by
750 // either emitting a shape guard or a prototype guard for |objId|.
751 //
752 // Note: this relies on shape implying proto.
ShapeGuardProtoChain(CacheIRWriter & writer,NativeObject * obj,ObjOperandId objId)753 static void ShapeGuardProtoChain(CacheIRWriter& writer, NativeObject* obj,
754 ObjOperandId objId) {
755 while (true) {
756 JSObject* proto = obj->staticPrototype();
757 if (!proto) {
758 return;
759 }
760
761 obj = &proto->as<NativeObject>();
762 objId = writer.loadProto(objId);
763
764 writer.guardShape(objId, obj->shape());
765 }
766 }
767
768 // For cross compartment guards we shape-guard the prototype chain to avoid
769 // referencing the holder object.
770 //
771 // This peels off the first layer because it's guarded against obj == holder.
ShapeGuardProtoChainForCrossCompartmentHolder(CacheIRWriter & writer,NativeObject * obj,ObjOperandId objId,NativeObject * holder,Maybe<ObjOperandId> * holderId)772 static void ShapeGuardProtoChainForCrossCompartmentHolder(
773 CacheIRWriter& writer, NativeObject* obj, ObjOperandId objId,
774 NativeObject* holder, Maybe<ObjOperandId>* holderId) {
775 MOZ_ASSERT(obj != holder);
776 MOZ_ASSERT(holder);
777 while (true) {
778 MOZ_ASSERT(obj->staticPrototype());
779 obj = &obj->staticPrototype()->as<NativeObject>();
780
781 objId = writer.loadProto(objId);
782 if (obj == holder) {
783 TestMatchingHolder(writer, obj, objId);
784 holderId->emplace(objId);
785 return;
786 } else {
787 writer.guardShapeForOwnProperties(objId, obj->shape());
788 }
789 }
790 }
791
792 enum class SlotReadType { Normal, CrossCompartment };
793
794 template <SlotReadType MaybeCrossCompartment = SlotReadType::Normal>
EmitReadSlotGuard(CacheIRWriter & writer,NativeObject * obj,NativeObject * holder,ObjOperandId objId,Maybe<ObjOperandId> * holderId)795 static void EmitReadSlotGuard(CacheIRWriter& writer, NativeObject* obj,
796 NativeObject* holder, ObjOperandId objId,
797 Maybe<ObjOperandId>* holderId) {
798 TestMatchingNativeReceiver(writer, obj, objId);
799
800 if (obj != holder) {
801 if (holder) {
802 if (MaybeCrossCompartment == SlotReadType::CrossCompartment) {
803 // Guard proto chain integrity.
804 // We use a variant of guards that avoid baking in any cross-compartment
805 // object pointers.
806 ShapeGuardProtoChainForCrossCompartmentHolder(writer, obj, objId,
807 holder, holderId);
808 } else {
809 // Guard proto chain integrity.
810 GeneratePrototypeGuards(writer, obj, holder, objId);
811
812 // Guard on the holder's shape.
813 holderId->emplace(writer.loadObject(holder));
814 TestMatchingHolder(writer, holder, holderId->ref());
815 }
816 } else {
817 // The property does not exist. Guard on everything in the prototype
818 // chain. This is guaranteed to see only Native objects because of
819 // CanAttachNativeGetProp().
820 ShapeGuardProtoChain(writer, obj, objId);
821 }
822 } else {
823 holderId->emplace(objId);
824 }
825 }
826
827 template <SlotReadType MaybeCrossCompartment = SlotReadType::Normal>
EmitReadSlotResult(CacheIRWriter & writer,NativeObject * obj,NativeObject * holder,Maybe<PropertyInfo> prop,ObjOperandId objId)828 static void EmitReadSlotResult(CacheIRWriter& writer, NativeObject* obj,
829 NativeObject* holder, Maybe<PropertyInfo> prop,
830 ObjOperandId objId) {
831 Maybe<ObjOperandId> holderId;
832 EmitReadSlotGuard<MaybeCrossCompartment>(writer, obj, holder, objId,
833 &holderId);
834
835 // Slot access.
836 if (holder) {
837 MOZ_ASSERT(holderId->valid());
838 EmitLoadSlotResult(writer, *holderId, holder, *prop);
839 } else {
840 MOZ_ASSERT(holderId.isNothing());
841 writer.loadUndefinedResult();
842 }
843 }
844
EmitCallGetterResultNoGuards(JSContext * cx,CacheIRWriter & writer,NativeObject * obj,NativeObject * holder,PropertyInfo prop,ValOperandId receiverId)845 static void EmitCallGetterResultNoGuards(JSContext* cx, CacheIRWriter& writer,
846 NativeObject* obj,
847 NativeObject* holder,
848 PropertyInfo prop,
849 ValOperandId receiverId) {
850 JSFunction* target = &holder->getGetter(prop)->as<JSFunction>();
851 bool sameRealm = cx->realm() == target->realm();
852
853 switch (IsCacheableGetPropCall(obj, holder, prop)) {
854 case CanAttachNativeGetter: {
855 writer.callNativeGetterResult(receiverId, target, sameRealm);
856 writer.returnFromIC();
857 break;
858 }
859 case CanAttachScriptedGetter: {
860 writer.callScriptedGetterResult(receiverId, target, sameRealm);
861 writer.returnFromIC();
862 break;
863 }
864 default:
865 // CanAttachNativeGetProp guarantees that the getter is either a native or
866 // a scripted function.
867 MOZ_ASSERT_UNREACHABLE("Can't attach getter");
868 break;
869 }
870 }
871
872 // See the SMDOC comment in vm/GetterSetter.h for more info on Getter/Setter
873 // properties
EmitGuardGetterSetterSlot(CacheIRWriter & writer,NativeObject * holder,PropertyInfo prop,ObjOperandId holderId,bool holderIsConstant=false)874 static void EmitGuardGetterSetterSlot(CacheIRWriter& writer,
875 NativeObject* holder, PropertyInfo prop,
876 ObjOperandId holderId,
877 bool holderIsConstant = false) {
878 // If the holder is guaranteed to be the same object, and it never had a
879 // slot holding a GetterSetter mutated or deleted, its Shape will change when
880 // that does happen so we don't need to guard on the GetterSetter.
881 if (holderIsConstant && !holder->hadGetterSetterChange()) {
882 return;
883 }
884
885 size_t slot = prop.slot();
886 Value slotVal = holder->getSlot(slot);
887 MOZ_ASSERT(slotVal.isPrivateGCThing());
888
889 if (holder->isFixedSlot(slot)) {
890 size_t offset = NativeObject::getFixedSlotOffset(slot);
891 writer.guardFixedSlotValue(holderId, offset, slotVal);
892 } else {
893 size_t offset = holder->dynamicSlotIndex(slot) * sizeof(Value);
894 writer.guardDynamicSlotValue(holderId, offset, slotVal);
895 }
896 }
897
EmitCallGetterResultGuards(CacheIRWriter & writer,NativeObject * obj,NativeObject * holder,HandleId id,PropertyInfo prop,ObjOperandId objId,ICState::Mode mode)898 static void EmitCallGetterResultGuards(CacheIRWriter& writer, NativeObject* obj,
899 NativeObject* holder, HandleId id,
900 PropertyInfo prop, ObjOperandId objId,
901 ICState::Mode mode) {
902 // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
903 // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
904 // require outerizing).
905
906 MOZ_ASSERT(holder->containsPure(id, prop));
907
908 if (mode == ICState::Mode::Specialized || IsWindow(obj)) {
909 TestMatchingNativeReceiver(writer, obj, objId);
910
911 if (obj != holder) {
912 GeneratePrototypeGuards(writer, obj, holder, objId);
913
914 // Guard on the holder's shape.
915 ObjOperandId holderId = writer.loadObject(holder);
916 TestMatchingHolder(writer, holder, holderId);
917
918 EmitGuardGetterSetterSlot(writer, holder, prop, holderId,
919 /* holderIsConstant = */ true);
920 } else {
921 EmitGuardGetterSetterSlot(writer, holder, prop, objId);
922 }
923 } else {
924 GetterSetter* gs = holder->getGetterSetter(prop);
925 writer.guardHasGetterSetter(objId, id, gs);
926 }
927 }
928
EmitCallGetterResult(JSContext * cx,CacheIRWriter & writer,NativeObject * obj,NativeObject * holder,HandleId id,PropertyInfo prop,ObjOperandId objId,ValOperandId receiverId,ICState::Mode mode)929 static void EmitCallGetterResult(JSContext* cx, CacheIRWriter& writer,
930 NativeObject* obj, NativeObject* holder,
931 HandleId id, PropertyInfo prop,
932 ObjOperandId objId, ValOperandId receiverId,
933 ICState::Mode mode) {
934 EmitCallGetterResultGuards(writer, obj, holder, id, prop, objId, mode);
935 EmitCallGetterResultNoGuards(cx, writer, obj, holder, prop, receiverId);
936 }
937
CanAttachDOMCall(JSContext * cx,JSJitInfo::OpType type,JSObject * obj,JSFunction * fun,ICState::Mode mode)938 static bool CanAttachDOMCall(JSContext* cx, JSJitInfo::OpType type,
939 JSObject* obj, JSFunction* fun,
940 ICState::Mode mode) {
941 MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter ||
942 type == JSJitInfo::Method);
943
944 if (mode != ICState::Mode::Specialized) {
945 return false;
946 }
947
948 if (!fun->hasJitInfo()) {
949 return false;
950 }
951
952 if (cx->realm() != fun->realm()) {
953 return false;
954 }
955
956 const JSJitInfo* jitInfo = fun->jitInfo();
957 MOZ_ASSERT_IF(IsWindow(obj), !jitInfo->needsOuterizedThisObject());
958 if (jitInfo->type() != type) {
959 return false;
960 }
961
962 const JSClass* clasp = obj->getClass();
963 if (!clasp->isDOMClass()) {
964 return false;
965 }
966
967 if (type != JSJitInfo::Method && clasp->isProxyObject()) {
968 return false;
969 }
970
971 // Tell the analysis the |DOMInstanceClassHasProtoAtDepth| hook can't GC.
972 JS::AutoSuppressGCAnalysis nogc;
973
974 DOMInstanceClassHasProtoAtDepth instanceChecker =
975 cx->runtime()->DOMcallbacks->instanceClassMatchesProto;
976 return instanceChecker(clasp, jitInfo->protoID, jitInfo->depth);
977 }
978
CanAttachDOMGetterSetter(JSContext * cx,JSJitInfo::OpType type,NativeObject * obj,NativeObject * holder,PropertyInfo prop,ICState::Mode mode)979 static bool CanAttachDOMGetterSetter(JSContext* cx, JSJitInfo::OpType type,
980 NativeObject* obj, NativeObject* holder,
981 PropertyInfo prop, ICState::Mode mode) {
982 MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter);
983
984 JSObject* accessor = type == JSJitInfo::Getter ? holder->getGetter(prop)
985 : holder->getSetter(prop);
986 JSFunction* fun = &accessor->as<JSFunction>();
987
988 return CanAttachDOMCall(cx, type, obj, fun, mode);
989 }
990
EmitCallDOMGetterResultNoGuards(CacheIRWriter & writer,NativeObject * holder,PropertyInfo prop,ObjOperandId objId)991 static void EmitCallDOMGetterResultNoGuards(CacheIRWriter& writer,
992 NativeObject* holder,
993 PropertyInfo prop,
994 ObjOperandId objId) {
995 JSFunction* getter = &holder->getGetter(prop)->as<JSFunction>();
996 writer.callDOMGetterResult(objId, getter->jitInfo());
997 writer.returnFromIC();
998 }
999
EmitCallDOMGetterResult(JSContext * cx,CacheIRWriter & writer,NativeObject * obj,NativeObject * holder,HandleId id,PropertyInfo prop,ObjOperandId objId)1000 static void EmitCallDOMGetterResult(JSContext* cx, CacheIRWriter& writer,
1001 NativeObject* obj, NativeObject* holder,
1002 HandleId id, PropertyInfo prop,
1003 ObjOperandId objId) {
1004 // Note: this relies on EmitCallGetterResultGuards emitting a shape guard
1005 // for specialized stubs.
1006 // The shape guard ensures the receiver's Class is valid for this DOM getter.
1007 EmitCallGetterResultGuards(writer, obj, holder, id, prop, objId,
1008 ICState::Mode::Specialized);
1009 EmitCallDOMGetterResultNoGuards(writer, holder, prop, objId);
1010 }
1011
attachMegamorphicNativeSlot(ObjOperandId objId,jsid id)1012 void GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId,
1013 jsid id) {
1014 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
1015
1016 if (cacheKind_ == CacheKind::GetProp ||
1017 cacheKind_ == CacheKind::GetPropSuper) {
1018 writer.megamorphicLoadSlotResult(objId, id.toAtom()->asPropertyName());
1019 } else {
1020 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
1021 cacheKind_ == CacheKind::GetElemSuper);
1022 writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId());
1023 }
1024 writer.returnFromIC();
1025
1026 trackAttached("MegamorphicNativeSlot");
1027 }
1028
tryAttachNative(HandleObject obj,ObjOperandId objId,HandleId id,ValOperandId receiverId)1029 AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj,
1030 ObjOperandId objId,
1031 HandleId id,
1032 ValOperandId receiverId) {
1033 Maybe<PropertyInfo> prop;
1034 NativeObject* holder = nullptr;
1035
1036 NativeGetPropCacheability type =
1037 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
1038 switch (type) {
1039 case CanAttachNone:
1040 return AttachDecision::NoAction;
1041 case CanAttachReadSlot: {
1042 auto* nobj = &obj->as<NativeObject>();
1043
1044 if (mode_ == ICState::Mode::Megamorphic) {
1045 attachMegamorphicNativeSlot(objId, id);
1046 return AttachDecision::Attach;
1047 }
1048
1049 maybeEmitIdGuard(id);
1050 EmitReadSlotResult(writer, nobj, holder, prop, objId);
1051 writer.returnFromIC();
1052
1053 trackAttached("NativeSlot");
1054 return AttachDecision::Attach;
1055 }
1056 case CanAttachScriptedGetter:
1057 case CanAttachNativeGetter: {
1058 auto* nobj = &obj->as<NativeObject>();
1059
1060 maybeEmitIdGuard(id);
1061
1062 if (!isSuper() && CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, nobj,
1063 holder, *prop, mode_)) {
1064 EmitCallDOMGetterResult(cx_, writer, nobj, holder, id, *prop, objId);
1065
1066 trackAttached("DOMGetter");
1067 return AttachDecision::Attach;
1068 }
1069
1070 EmitCallGetterResult(cx_, writer, nobj, holder, id, *prop, objId,
1071 receiverId, mode_);
1072
1073 trackAttached("NativeGetter");
1074 return AttachDecision::Attach;
1075 }
1076 }
1077
1078 MOZ_CRASH("Bad NativeGetPropCacheability");
1079 }
1080
1081 // Returns whether obj is a WindowProxy wrapping the script's global.
IsWindowProxyForScriptGlobal(JSScript * script,JSObject * obj)1082 static bool IsWindowProxyForScriptGlobal(JSScript* script, JSObject* obj) {
1083 if (!IsWindowProxy(obj)) {
1084 return false;
1085 }
1086
1087 MOZ_ASSERT(obj->getClass() ==
1088 script->runtimeFromMainThread()->maybeWindowProxyClass());
1089
1090 JSObject* window = ToWindowIfWindowProxy(obj);
1091
1092 // Ion relies on the WindowProxy's group changing (and the group getting
1093 // marked as having unknown properties) on navigation. If we ever stop
1094 // transplanting same-compartment WindowProxies, this assert will fail and we
1095 // need to fix that code.
1096 MOZ_ASSERT(window == &obj->nonCCWGlobal());
1097
1098 // This must be a WindowProxy for a global in this compartment. Else it would
1099 // be a cross-compartment wrapper and IsWindowProxy returns false for
1100 // those.
1101 MOZ_ASSERT(script->compartment() == obj->compartment());
1102
1103 // Only optimize lookups on the WindowProxy for the current global. Other
1104 // WindowProxies in the compartment may require security checks (based on
1105 // mutable document.domain). See bug 1516775.
1106 return window == &script->global();
1107 }
1108
1109 // Guards objId is a WindowProxy for windowObj. Returns the window's operand id.
GuardAndLoadWindowProxyWindow(CacheIRWriter & writer,ObjOperandId objId,GlobalObject * windowObj)1110 static ObjOperandId GuardAndLoadWindowProxyWindow(CacheIRWriter& writer,
1111 ObjOperandId objId,
1112 GlobalObject* windowObj) {
1113 // Note: update AddCacheIRGetPropFunction in BaselineInspector.cpp when making
1114 // changes here.
1115 writer.guardClass(objId, GuardClassKind::WindowProxy);
1116 ObjOperandId windowObjId = writer.loadWrapperTarget(objId);
1117 writer.guardSpecificObject(windowObjId, windowObj);
1118 return windowObjId;
1119 }
1120
tryAttachWindowProxy(HandleObject obj,ObjOperandId objId,HandleId id)1121 AttachDecision GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
1122 ObjOperandId objId,
1123 HandleId id) {
1124 // Attach a stub when the receiver is a WindowProxy and we can do the lookup
1125 // on the Window (the global object).
1126
1127 if (!IsWindowProxyForScriptGlobal(script_, obj)) {
1128 return AttachDecision::NoAction;
1129 }
1130
1131 // If we're megamorphic prefer a generic proxy stub that handles a lot more
1132 // cases.
1133 if (mode_ == ICState::Mode::Megamorphic) {
1134 return AttachDecision::NoAction;
1135 }
1136
1137 // Now try to do the lookup on the Window (the current global).
1138 GlobalObject* windowObj = cx_->global();
1139 NativeObject* holder = nullptr;
1140 Maybe<PropertyInfo> prop;
1141 NativeGetPropCacheability type =
1142 CanAttachNativeGetProp(cx_, windowObj, id, &holder, &prop, pc_);
1143 switch (type) {
1144 case CanAttachNone:
1145 return AttachDecision::NoAction;
1146
1147 case CanAttachReadSlot: {
1148 maybeEmitIdGuard(id);
1149 ObjOperandId windowObjId =
1150 GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
1151 EmitReadSlotResult(writer, windowObj, holder, prop, windowObjId);
1152 writer.returnFromIC();
1153
1154 trackAttached("WindowProxySlot");
1155 return AttachDecision::Attach;
1156 }
1157
1158 case CanAttachNativeGetter: {
1159 // Make sure the native getter is okay with the IC passing the Window
1160 // instead of the WindowProxy as |this| value.
1161 JSFunction* callee = &holder->getGetter(*prop)->as<JSFunction>();
1162 MOZ_ASSERT(callee->isNativeWithoutJitEntry());
1163 if (!callee->hasJitInfo() ||
1164 callee->jitInfo()->needsOuterizedThisObject()) {
1165 return AttachDecision::NoAction;
1166 }
1167
1168 // If a |super| access, it is not worth the complexity to attach an IC.
1169 if (isSuper()) {
1170 return AttachDecision::NoAction;
1171 }
1172
1173 // Guard the incoming object is a WindowProxy and inline a getter call
1174 // based on the Window object.
1175 maybeEmitIdGuard(id);
1176 ObjOperandId windowObjId =
1177 GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
1178
1179 if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, windowObj, holder,
1180 *prop, mode_)) {
1181 EmitCallDOMGetterResult(cx_, writer, windowObj, holder, id, *prop,
1182 windowObjId);
1183 trackAttached("WindowProxyDOMGetter");
1184 } else {
1185 ValOperandId receiverId = writer.boxObject(windowObjId);
1186 EmitCallGetterResult(cx_, writer, windowObj, holder, id, *prop,
1187 windowObjId, receiverId, mode_);
1188 trackAttached("WindowProxyGetter");
1189 }
1190
1191 return AttachDecision::Attach;
1192 }
1193
1194 case CanAttachScriptedGetter:
1195 MOZ_ASSERT_UNREACHABLE("Not possible for window proxies");
1196 }
1197
1198 MOZ_CRASH("Unreachable");
1199 }
1200
tryAttachCrossCompartmentWrapper(HandleObject obj,ObjOperandId objId,HandleId id)1201 AttachDecision GetPropIRGenerator::tryAttachCrossCompartmentWrapper(
1202 HandleObject obj, ObjOperandId objId, HandleId id) {
1203 // We can only optimize this very wrapper-handler, because others might
1204 // have a security policy.
1205 if (!IsWrapper(obj) ||
1206 Wrapper::wrapperHandler(obj) != &CrossCompartmentWrapper::singleton) {
1207 return AttachDecision::NoAction;
1208 }
1209
1210 // If we're megamorphic prefer a generic proxy stub that handles a lot more
1211 // cases.
1212 if (mode_ == ICState::Mode::Megamorphic) {
1213 return AttachDecision::NoAction;
1214 }
1215
1216 RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj));
1217 MOZ_ASSERT(unwrapped == UnwrapOneCheckedStatic(obj));
1218 MOZ_ASSERT(!IsCrossCompartmentWrapper(unwrapped),
1219 "CCWs must not wrap other CCWs");
1220
1221 // If we allowed different zones we would have to wrap strings.
1222 if (unwrapped->compartment()->zone() != cx_->compartment()->zone()) {
1223 return AttachDecision::NoAction;
1224 }
1225
1226 // Take the unwrapped object's global, and wrap in a
1227 // this-compartment wrapper. This is what will be stored in the IC
1228 // keep the compartment alive.
1229 RootedObject wrappedTargetGlobal(cx_, &unwrapped->nonCCWGlobal());
1230 if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal)) {
1231 cx_->clearPendingException();
1232 return AttachDecision::NoAction;
1233 }
1234
1235 NativeObject* holder = nullptr;
1236 Maybe<PropertyInfo> prop;
1237
1238 // Enter realm of target to prevent failing compartment assertions when doing
1239 // the lookup.
1240 {
1241 AutoRealm ar(cx_, unwrapped);
1242
1243 NativeGetPropCacheability canCache =
1244 CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &prop, pc_);
1245 if (canCache != CanAttachReadSlot) {
1246 return AttachDecision::NoAction;
1247 }
1248 }
1249 auto* unwrappedNative = &unwrapped->as<NativeObject>();
1250
1251 maybeEmitIdGuard(id);
1252 writer.guardIsProxy(objId);
1253 writer.guardHasProxyHandler(objId, Wrapper::wrapperHandler(obj));
1254
1255 // Load the object wrapped by the CCW
1256 ObjOperandId wrapperTargetId = writer.loadWrapperTarget(objId);
1257
1258 // If the compartment of the wrapped object is different we should fail.
1259 writer.guardCompartment(wrapperTargetId, wrappedTargetGlobal,
1260 unwrappedNative->compartment());
1261
1262 ObjOperandId unwrappedId = wrapperTargetId;
1263 EmitReadSlotResult<SlotReadType::CrossCompartment>(writer, unwrappedNative,
1264 holder, prop, unwrappedId);
1265 writer.wrapResult();
1266 writer.returnFromIC();
1267
1268 trackAttached("CCWSlot");
1269 return AttachDecision::Attach;
1270 }
1271
1272 static JSObject* NewWrapperWithObjectShape(JSContext* cx,
1273 HandleNativeObject obj);
1274
GetXrayExpandoShapeWrapper(JSContext * cx,HandleObject xray,MutableHandleObject wrapper)1275 static bool GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray,
1276 MutableHandleObject wrapper) {
1277 Value v = GetProxyReservedSlot(xray, GetXrayJitInfo()->xrayHolderSlot);
1278 if (v.isObject()) {
1279 NativeObject* holder = &v.toObject().as<NativeObject>();
1280 v = holder->getFixedSlot(GetXrayJitInfo()->holderExpandoSlot);
1281 if (v.isObject()) {
1282 RootedNativeObject expando(
1283 cx, &UncheckedUnwrap(&v.toObject())->as<NativeObject>());
1284 wrapper.set(NewWrapperWithObjectShape(cx, expando));
1285 return wrapper != nullptr;
1286 }
1287 }
1288 wrapper.set(nullptr);
1289 return true;
1290 }
1291
tryAttachXrayCrossCompartmentWrapper(HandleObject obj,ObjOperandId objId,HandleId id,ValOperandId receiverId)1292 AttachDecision GetPropIRGenerator::tryAttachXrayCrossCompartmentWrapper(
1293 HandleObject obj, ObjOperandId objId, HandleId id,
1294 ValOperandId receiverId) {
1295 if (!obj->is<ProxyObject>()) {
1296 return AttachDecision::NoAction;
1297 }
1298
1299 JS::XrayJitInfo* info = GetXrayJitInfo();
1300 if (!info || !info->isCrossCompartmentXray(GetProxyHandler(obj))) {
1301 return AttachDecision::NoAction;
1302 }
1303
1304 if (!info->compartmentHasExclusiveExpandos(obj)) {
1305 return AttachDecision::NoAction;
1306 }
1307
1308 RootedObject target(cx_, UncheckedUnwrap(obj));
1309
1310 RootedObject expandoShapeWrapper(cx_);
1311 if (!GetXrayExpandoShapeWrapper(cx_, obj, &expandoShapeWrapper)) {
1312 cx_->recoverFromOutOfMemory();
1313 return AttachDecision::NoAction;
1314 }
1315
1316 // Look for a getter we can call on the xray or its prototype chain.
1317 Rooted<Maybe<PropertyDescriptor>> desc(cx_);
1318 RootedObject holder(cx_, obj);
1319 RootedObjectVector prototypes(cx_);
1320 RootedObjectVector prototypeExpandoShapeWrappers(cx_);
1321 while (true) {
1322 if (!GetOwnPropertyDescriptor(cx_, holder, id, &desc)) {
1323 cx_->clearPendingException();
1324 return AttachDecision::NoAction;
1325 }
1326 if (desc.isSome()) {
1327 break;
1328 }
1329 if (!GetPrototype(cx_, holder, &holder)) {
1330 cx_->clearPendingException();
1331 return AttachDecision::NoAction;
1332 }
1333 if (!holder || !holder->is<ProxyObject>() ||
1334 !info->isCrossCompartmentXray(GetProxyHandler(holder))) {
1335 return AttachDecision::NoAction;
1336 }
1337 RootedObject prototypeExpandoShapeWrapper(cx_);
1338 if (!GetXrayExpandoShapeWrapper(cx_, holder,
1339 &prototypeExpandoShapeWrapper) ||
1340 !prototypes.append(holder) ||
1341 !prototypeExpandoShapeWrappers.append(prototypeExpandoShapeWrapper)) {
1342 cx_->recoverFromOutOfMemory();
1343 return AttachDecision::NoAction;
1344 }
1345 }
1346 if (!desc->isAccessorDescriptor()) {
1347 return AttachDecision::NoAction;
1348 }
1349
1350 RootedObject getter(cx_, desc->getter());
1351 if (!getter || !getter->is<JSFunction>() ||
1352 !getter->as<JSFunction>().isNativeWithoutJitEntry()) {
1353 return AttachDecision::NoAction;
1354 }
1355
1356 maybeEmitIdGuard(id);
1357 writer.guardIsProxy(objId);
1358 writer.guardHasProxyHandler(objId, GetProxyHandler(obj));
1359
1360 // Load the object wrapped by the CCW
1361 ObjOperandId wrapperTargetId = writer.loadWrapperTarget(objId);
1362
1363 // Test the wrapped object's class. The properties held by xrays or their
1364 // prototypes will be invariant for objects of a given class, except for
1365 // changes due to xray expandos or xray prototype mutations.
1366 writer.guardAnyClass(wrapperTargetId, target->getClass());
1367
1368 // Make sure the expandos on the xray and its prototype chain match up with
1369 // what we expect. The expando shape needs to be consistent, to ensure it
1370 // has not had any shadowing properties added, and the expando cannot have
1371 // any custom prototype (xray prototypes are stable otherwise).
1372 //
1373 // We can only do this for xrays with exclusive access to their expandos
1374 // (as we checked earlier), which store a pointer to their expando
1375 // directly. Xrays in other compartments may share their expandos with each
1376 // other and a VM call is needed just to find the expando.
1377 if (expandoShapeWrapper) {
1378 writer.guardXrayExpandoShapeAndDefaultProto(objId, expandoShapeWrapper);
1379 } else {
1380 writer.guardXrayNoExpando(objId);
1381 }
1382 for (size_t i = 0; i < prototypes.length(); i++) {
1383 JSObject* proto = prototypes[i];
1384 ObjOperandId protoId = writer.loadObject(proto);
1385 if (JSObject* protoShapeWrapper = prototypeExpandoShapeWrappers[i]) {
1386 writer.guardXrayExpandoShapeAndDefaultProto(protoId, protoShapeWrapper);
1387 } else {
1388 writer.guardXrayNoExpando(protoId);
1389 }
1390 }
1391
1392 bool sameRealm = cx_->realm() == getter->as<JSFunction>().realm();
1393 writer.callNativeGetterResult(receiverId, &getter->as<JSFunction>(),
1394 sameRealm);
1395 writer.returnFromIC();
1396
1397 trackAttached("XrayGetter");
1398 return AttachDecision::Attach;
1399 }
1400
tryAttachGenericProxy(Handle<ProxyObject * > obj,ObjOperandId objId,HandleId id,bool handleDOMProxies)1401 AttachDecision GetPropIRGenerator::tryAttachGenericProxy(
1402 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
1403 bool handleDOMProxies) {
1404 writer.guardIsProxy(objId);
1405
1406 if (!handleDOMProxies) {
1407 // Ensure that the incoming object is not a DOM proxy, so that we can get to
1408 // the specialized stubs
1409 writer.guardIsNotDOMProxy(objId);
1410 }
1411
1412 if (cacheKind_ == CacheKind::GetProp || mode_ == ICState::Mode::Specialized) {
1413 MOZ_ASSERT(!isSuper());
1414 maybeEmitIdGuard(id);
1415 writer.proxyGetResult(objId, id);
1416 } else {
1417 // Attach a stub that handles every id.
1418 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
1419 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
1420 MOZ_ASSERT(!isSuper());
1421 writer.proxyGetByValueResult(objId, getElemKeyValueId());
1422 }
1423
1424 writer.returnFromIC();
1425
1426 trackAttached("GenericProxy");
1427 return AttachDecision::Attach;
1428 }
1429
ValueIsInt64Index(const Value & val,int64_t * index)1430 static bool ValueIsInt64Index(const Value& val, int64_t* index) {
1431 // Try to convert the Value to a TypedArray index or DataView offset.
1432
1433 if (val.isInt32()) {
1434 *index = val.toInt32();
1435 return true;
1436 }
1437
1438 if (val.isDouble()) {
1439 // Use NumberEqualsInt64 because ToPropertyKey(-0) is 0.
1440 return mozilla::NumberEqualsInt64(val.toDouble(), index);
1441 }
1442
1443 return false;
1444 }
1445
guardToIntPtrIndex(const Value & index,ValOperandId indexId,bool supportOOB)1446 IntPtrOperandId IRGenerator::guardToIntPtrIndex(const Value& index,
1447 ValOperandId indexId,
1448 bool supportOOB) {
1449 #ifdef DEBUG
1450 int64_t indexInt64;
1451 MOZ_ASSERT_IF(!supportOOB, ValueIsInt64Index(index, &indexInt64));
1452 #endif
1453
1454 if (index.isInt32()) {
1455 Int32OperandId int32IndexId = writer.guardToInt32(indexId);
1456 return writer.int32ToIntPtr(int32IndexId);
1457 }
1458
1459 MOZ_ASSERT(index.isNumber());
1460 NumberOperandId numberIndexId = writer.guardIsNumber(indexId);
1461 return writer.guardNumberToIntPtrIndex(numberIndexId, supportOOB);
1462 }
1463
guardDOMProxyExpandoObjectAndShape(ProxyObject * obj,ObjOperandId objId,const Value & expandoVal,NativeObject * expandoObj)1464 ObjOperandId IRGenerator::guardDOMProxyExpandoObjectAndShape(
1465 ProxyObject* obj, ObjOperandId objId, const Value& expandoVal,
1466 NativeObject* expandoObj) {
1467 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1468
1469 TestMatchingProxyReceiver(writer, obj, objId);
1470
1471 // Shape determines Class, so now it must be a DOM proxy.
1472 ValOperandId expandoValId;
1473 if (expandoVal.isObject()) {
1474 expandoValId = writer.loadDOMExpandoValue(objId);
1475 } else {
1476 expandoValId = writer.loadDOMExpandoValueIgnoreGeneration(objId);
1477 }
1478
1479 // Guard the expando is an object and shape guard.
1480 ObjOperandId expandoObjId = writer.guardToObject(expandoValId);
1481 TestMatchingHolder(writer, expandoObj, expandoObjId);
1482 return expandoObjId;
1483 }
1484
tryAttachDOMProxyExpando(Handle<ProxyObject * > obj,ObjOperandId objId,HandleId id,ValOperandId receiverId)1485 AttachDecision GetPropIRGenerator::tryAttachDOMProxyExpando(
1486 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
1487 ValOperandId receiverId) {
1488 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1489
1490 Value expandoVal = GetProxyPrivate(obj);
1491 JSObject* expandoObj;
1492 if (expandoVal.isObject()) {
1493 expandoObj = &expandoVal.toObject();
1494 } else {
1495 MOZ_ASSERT(!expandoVal.isUndefined(),
1496 "How did a missing expando manage to shadow things?");
1497 auto expandoAndGeneration =
1498 static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
1499 MOZ_ASSERT(expandoAndGeneration);
1500 expandoObj = &expandoAndGeneration->expando.toObject();
1501 }
1502
1503 // Try to do the lookup on the expando object.
1504 NativeObject* holder = nullptr;
1505 Maybe<PropertyInfo> prop;
1506 NativeGetPropCacheability canCache =
1507 CanAttachNativeGetProp(cx_, expandoObj, id, &holder, &prop, pc_);
1508 if (canCache == CanAttachNone) {
1509 return AttachDecision::NoAction;
1510 }
1511 if (!holder) {
1512 return AttachDecision::NoAction;
1513 }
1514 auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
1515
1516 MOZ_ASSERT(holder == nativeExpandoObj);
1517
1518 maybeEmitIdGuard(id);
1519 ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
1520 obj, objId, expandoVal, nativeExpandoObj);
1521
1522 if (canCache == CanAttachReadSlot) {
1523 // Load from the expando's slots.
1524 EmitLoadSlotResult(writer, expandoObjId, nativeExpandoObj, *prop);
1525 writer.returnFromIC();
1526 } else {
1527 // Call the getter. Note that we pass objId, the DOM proxy, as |this|
1528 // and not the expando object.
1529 MOZ_ASSERT(canCache == CanAttachNativeGetter ||
1530 canCache == CanAttachScriptedGetter);
1531 EmitGuardGetterSetterSlot(writer, nativeExpandoObj, *prop, expandoObjId);
1532 EmitCallGetterResultNoGuards(cx_, writer, nativeExpandoObj,
1533 nativeExpandoObj, *prop, receiverId);
1534 }
1535
1536 trackAttached("DOMProxyExpando");
1537 return AttachDecision::Attach;
1538 }
1539
tryAttachDOMProxyShadowed(Handle<ProxyObject * > obj,ObjOperandId objId,HandleId id)1540 AttachDecision GetPropIRGenerator::tryAttachDOMProxyShadowed(
1541 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id) {
1542 MOZ_ASSERT(!isSuper());
1543 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1544
1545 maybeEmitIdGuard(id);
1546 TestMatchingProxyReceiver(writer, obj, objId);
1547 writer.proxyGetResult(objId, id);
1548 writer.returnFromIC();
1549
1550 trackAttached("DOMProxyShadowed");
1551 return AttachDecision::Attach;
1552 }
1553
1554 // Callers are expected to have already guarded on the shape of the
1555 // object, which guarantees the object is a DOM proxy.
CheckDOMProxyExpandoDoesNotShadow(CacheIRWriter & writer,ProxyObject * obj,jsid id,ObjOperandId objId)1556 static void CheckDOMProxyExpandoDoesNotShadow(CacheIRWriter& writer,
1557 ProxyObject* obj, jsid id,
1558 ObjOperandId objId) {
1559 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1560
1561 Value expandoVal = GetProxyPrivate(obj);
1562
1563 ValOperandId expandoId;
1564 if (!expandoVal.isObject() && !expandoVal.isUndefined()) {
1565 auto expandoAndGeneration =
1566 static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
1567 uint64_t generation = expandoAndGeneration->generation;
1568 expandoId = writer.loadDOMExpandoValueGuardGeneration(
1569 objId, expandoAndGeneration, generation);
1570 expandoVal = expandoAndGeneration->expando;
1571 } else {
1572 expandoId = writer.loadDOMExpandoValue(objId);
1573 }
1574
1575 if (expandoVal.isUndefined()) {
1576 // Guard there's no expando object.
1577 writer.guardNonDoubleType(expandoId, ValueType::Undefined);
1578 } else if (expandoVal.isObject()) {
1579 // Guard the proxy either has no expando object or, if it has one, that
1580 // the shape matches the current expando object.
1581 NativeObject& expandoObj = expandoVal.toObject().as<NativeObject>();
1582 MOZ_ASSERT(!expandoObj.containsPure(id));
1583 writer.guardDOMExpandoMissingOrGuardShape(expandoId, expandoObj.shape());
1584 } else {
1585 MOZ_CRASH("Invalid expando value");
1586 }
1587 }
1588
tryAttachDOMProxyUnshadowed(Handle<ProxyObject * > obj,ObjOperandId objId,HandleId id,ValOperandId receiverId)1589 AttachDecision GetPropIRGenerator::tryAttachDOMProxyUnshadowed(
1590 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
1591 ValOperandId receiverId) {
1592 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1593
1594 JSObject* checkObj = obj->staticPrototype();
1595 if (!checkObj) {
1596 return AttachDecision::NoAction;
1597 }
1598
1599 NativeObject* holder = nullptr;
1600 Maybe<PropertyInfo> prop;
1601 NativeGetPropCacheability canCache =
1602 CanAttachNativeGetProp(cx_, checkObj, id, &holder, &prop, pc_);
1603 if (canCache == CanAttachNone) {
1604 return AttachDecision::NoAction;
1605 }
1606 auto* nativeCheckObj = &checkObj->as<NativeObject>();
1607
1608 maybeEmitIdGuard(id);
1609
1610 // Guard that our expando object hasn't started shadowing this property.
1611 TestMatchingProxyReceiver(writer, obj, objId);
1612 CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId);
1613
1614 if (holder) {
1615 // Found the property on the prototype chain. Treat it like a native
1616 // getprop.
1617 GeneratePrototypeGuards(writer, obj, holder, objId);
1618
1619 // Guard on the holder of the property.
1620 ObjOperandId holderId = writer.loadObject(holder);
1621 TestMatchingHolder(writer, holder, holderId);
1622
1623 if (canCache == CanAttachReadSlot) {
1624 EmitLoadSlotResult(writer, holderId, holder, *prop);
1625 writer.returnFromIC();
1626 } else {
1627 // EmitCallGetterResultNoGuards expects |obj| to be the object the
1628 // property is on to do some checks. Since we actually looked at
1629 // checkObj, and no extra guards will be generated, we can just
1630 // pass that instead.
1631 MOZ_ASSERT(canCache == CanAttachNativeGetter ||
1632 canCache == CanAttachScriptedGetter);
1633 MOZ_ASSERT(!isSuper());
1634 EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
1635 /* holderIsConstant = */ true);
1636 EmitCallGetterResultNoGuards(cx_, writer, nativeCheckObj, holder, *prop,
1637 receiverId);
1638 }
1639 } else {
1640 // Property was not found on the prototype chain. Deoptimize down to
1641 // proxy get call.
1642 MOZ_ASSERT(!isSuper());
1643 writer.proxyGetResult(objId, id);
1644 writer.returnFromIC();
1645 }
1646
1647 trackAttached("DOMProxyUnshadowed");
1648 return AttachDecision::Attach;
1649 }
1650
tryAttachProxy(HandleObject obj,ObjOperandId objId,HandleId id,ValOperandId receiverId)1651 AttachDecision GetPropIRGenerator::tryAttachProxy(HandleObject obj,
1652 ObjOperandId objId,
1653 HandleId id,
1654 ValOperandId receiverId) {
1655 ProxyStubType type = GetProxyStubType(cx_, obj, id);
1656 if (type == ProxyStubType::None) {
1657 return AttachDecision::NoAction;
1658 }
1659 auto proxy = obj.as<ProxyObject>();
1660
1661 // The proxy stubs don't currently support |super| access.
1662 if (isSuper()) {
1663 return AttachDecision::NoAction;
1664 }
1665
1666 if (mode_ == ICState::Mode::Megamorphic) {
1667 return tryAttachGenericProxy(proxy, objId, id,
1668 /* handleDOMProxies = */ true);
1669 }
1670
1671 switch (type) {
1672 case ProxyStubType::None:
1673 break;
1674 case ProxyStubType::DOMExpando:
1675 TRY_ATTACH(tryAttachDOMProxyExpando(proxy, objId, id, receiverId));
1676 [[fallthrough]]; // Fall through to the generic shadowed case.
1677 case ProxyStubType::DOMShadowed:
1678 return tryAttachDOMProxyShadowed(proxy, objId, id);
1679 case ProxyStubType::DOMUnshadowed:
1680 TRY_ATTACH(tryAttachDOMProxyUnshadowed(proxy, objId, id, receiverId));
1681 return tryAttachGenericProxy(proxy, objId, id,
1682 /* handleDOMProxies = */ true);
1683 case ProxyStubType::Generic:
1684 return tryAttachGenericProxy(proxy, objId, id,
1685 /* handleDOMProxies = */ false);
1686 }
1687
1688 MOZ_CRASH("Unexpected ProxyStubType");
1689 }
1690
tryAttachObjectLength(HandleObject obj,ObjOperandId objId,HandleId id)1691 AttachDecision GetPropIRGenerator::tryAttachObjectLength(HandleObject obj,
1692 ObjOperandId objId,
1693 HandleId id) {
1694 if (!id.isAtom(cx_->names().length)) {
1695 return AttachDecision::NoAction;
1696 }
1697
1698 if (obj->is<ArrayObject>()) {
1699 if (obj->as<ArrayObject>().length() > INT32_MAX) {
1700 return AttachDecision::NoAction;
1701 }
1702
1703 maybeEmitIdGuard(id);
1704 writer.guardClass(objId, GuardClassKind::Array);
1705 writer.loadInt32ArrayLengthResult(objId);
1706 writer.returnFromIC();
1707
1708 trackAttached("ArrayLength");
1709 return AttachDecision::Attach;
1710 }
1711
1712 if (obj->is<ArgumentsObject>() &&
1713 !obj->as<ArgumentsObject>().hasOverriddenLength()) {
1714 maybeEmitIdGuard(id);
1715 if (obj->is<MappedArgumentsObject>()) {
1716 writer.guardClass(objId, GuardClassKind::MappedArguments);
1717 } else {
1718 MOZ_ASSERT(obj->is<UnmappedArgumentsObject>());
1719 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
1720 }
1721 writer.loadArgumentsObjectLengthResult(objId);
1722 writer.returnFromIC();
1723
1724 trackAttached("ArgumentsObjectLength");
1725 return AttachDecision::Attach;
1726 }
1727
1728 return AttachDecision::NoAction;
1729 }
1730
tryAttachTypedArray(HandleObject obj,ObjOperandId objId,HandleId id)1731 AttachDecision GetPropIRGenerator::tryAttachTypedArray(HandleObject obj,
1732 ObjOperandId objId,
1733 HandleId id) {
1734 if (!obj->is<TypedArrayObject>()) {
1735 return AttachDecision::NoAction;
1736 }
1737
1738 if (mode_ != ICState::Mode::Specialized) {
1739 return AttachDecision::NoAction;
1740 }
1741
1742 // Receiver should be the object.
1743 if (isSuper()) {
1744 return AttachDecision::NoAction;
1745 }
1746
1747 bool isLength = id.isAtom(cx_->names().length);
1748 bool isByteOffset = id.isAtom(cx_->names().byteOffset);
1749 if (!isLength && !isByteOffset && !id.isAtom(cx_->names().byteLength)) {
1750 return AttachDecision::NoAction;
1751 }
1752
1753 NativeObject* holder = nullptr;
1754 Maybe<PropertyInfo> prop;
1755 NativeGetPropCacheability type =
1756 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
1757 if (type != CanAttachNativeGetter) {
1758 return AttachDecision::NoAction;
1759 }
1760
1761 JSFunction& fun = holder->getGetter(*prop)->as<JSFunction>();
1762 if (isLength) {
1763 if (!TypedArrayObject::isOriginalLengthGetter(fun.native())) {
1764 return AttachDecision::NoAction;
1765 }
1766 } else if (isByteOffset) {
1767 if (!TypedArrayObject::isOriginalByteOffsetGetter(fun.native())) {
1768 return AttachDecision::NoAction;
1769 }
1770 } else {
1771 if (!TypedArrayObject::isOriginalByteLengthGetter(fun.native())) {
1772 return AttachDecision::NoAction;
1773 }
1774 }
1775
1776 auto* tarr = &obj->as<TypedArrayObject>();
1777
1778 maybeEmitIdGuard(id);
1779 // Emit all the normal guards for calling this native, but specialize
1780 // callNativeGetterResult.
1781 EmitCallGetterResultGuards(writer, tarr, holder, id, *prop, objId, mode_);
1782 if (isLength) {
1783 if (tarr->length() <= INT32_MAX) {
1784 writer.loadArrayBufferViewLengthInt32Result(objId);
1785 } else {
1786 writer.loadArrayBufferViewLengthDoubleResult(objId);
1787 }
1788 trackAttached("TypedArrayLength");
1789 } else if (isByteOffset) {
1790 if (tarr->byteOffset() <= INT32_MAX) {
1791 writer.arrayBufferViewByteOffsetInt32Result(objId);
1792 } else {
1793 writer.arrayBufferViewByteOffsetDoubleResult(objId);
1794 }
1795 trackAttached("TypedArrayByteOffset");
1796 } else {
1797 if (tarr->byteLength() <= INT32_MAX) {
1798 writer.typedArrayByteLengthInt32Result(objId);
1799 } else {
1800 writer.typedArrayByteLengthDoubleResult(objId);
1801 }
1802 trackAttached("TypedArrayByteLength");
1803 }
1804 writer.returnFromIC();
1805
1806 return AttachDecision::Attach;
1807 }
1808
tryAttachDataView(HandleObject obj,ObjOperandId objId,HandleId id)1809 AttachDecision GetPropIRGenerator::tryAttachDataView(HandleObject obj,
1810 ObjOperandId objId,
1811 HandleId id) {
1812 if (!obj->is<DataViewObject>()) {
1813 return AttachDecision::NoAction;
1814 }
1815 auto* dv = &obj->as<DataViewObject>();
1816
1817 if (mode_ != ICState::Mode::Specialized) {
1818 return AttachDecision::NoAction;
1819 }
1820
1821 // Receiver should be the object.
1822 if (isSuper()) {
1823 return AttachDecision::NoAction;
1824 }
1825
1826 bool isByteOffset = id.isAtom(cx_->names().byteOffset);
1827 if (!isByteOffset && !id.isAtom(cx_->names().byteLength)) {
1828 return AttachDecision::NoAction;
1829 }
1830
1831 // byteOffset and byteLength both throw when the ArrayBuffer is detached.
1832 if (dv->hasDetachedBuffer()) {
1833 return AttachDecision::NoAction;
1834 }
1835
1836 NativeObject* holder = nullptr;
1837 Maybe<PropertyInfo> prop;
1838 NativeGetPropCacheability type =
1839 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
1840 if (type != CanAttachNativeGetter) {
1841 return AttachDecision::NoAction;
1842 }
1843
1844 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
1845 if (isByteOffset) {
1846 if (!DataViewObject::isOriginalByteOffsetGetter(fun.native())) {
1847 return AttachDecision::NoAction;
1848 }
1849 } else {
1850 if (!DataViewObject::isOriginalByteLengthGetter(fun.native())) {
1851 return AttachDecision::NoAction;
1852 }
1853 }
1854
1855 maybeEmitIdGuard(id);
1856 // Emit all the normal guards for calling this native, but specialize
1857 // callNativeGetterResult.
1858 EmitCallGetterResultGuards(writer, dv, holder, id, *prop, objId, mode_);
1859 writer.guardHasAttachedArrayBuffer(objId);
1860 if (isByteOffset) {
1861 if (dv->byteOffset() <= INT32_MAX) {
1862 writer.arrayBufferViewByteOffsetInt32Result(objId);
1863 } else {
1864 writer.arrayBufferViewByteOffsetDoubleResult(objId);
1865 }
1866 trackAttached("DataViewByteOffset");
1867 } else {
1868 if (dv->byteLength() <= INT32_MAX) {
1869 writer.loadArrayBufferViewLengthInt32Result(objId);
1870 } else {
1871 writer.loadArrayBufferViewLengthDoubleResult(objId);
1872 }
1873 trackAttached("DataViewByteLength");
1874 }
1875 writer.returnFromIC();
1876
1877 return AttachDecision::Attach;
1878 }
1879
tryAttachArrayBufferMaybeShared(HandleObject obj,ObjOperandId objId,HandleId id)1880 AttachDecision GetPropIRGenerator::tryAttachArrayBufferMaybeShared(
1881 HandleObject obj, ObjOperandId objId, HandleId id) {
1882 if (!obj->is<ArrayBufferObjectMaybeShared>()) {
1883 return AttachDecision::NoAction;
1884 }
1885 auto* buf = &obj->as<ArrayBufferObjectMaybeShared>();
1886
1887 if (mode_ != ICState::Mode::Specialized) {
1888 return AttachDecision::NoAction;
1889 }
1890
1891 // Receiver should be the object.
1892 if (isSuper()) {
1893 return AttachDecision::NoAction;
1894 }
1895
1896 if (!id.isAtom(cx_->names().byteLength)) {
1897 return AttachDecision::NoAction;
1898 }
1899
1900 NativeObject* holder = nullptr;
1901 Maybe<PropertyInfo> prop;
1902 NativeGetPropCacheability type =
1903 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
1904 if (type != CanAttachNativeGetter) {
1905 return AttachDecision::NoAction;
1906 }
1907
1908 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
1909 if (buf->is<ArrayBufferObject>()) {
1910 if (!ArrayBufferObject::isOriginalByteLengthGetter(fun.native())) {
1911 return AttachDecision::NoAction;
1912 }
1913 } else {
1914 if (!SharedArrayBufferObject::isOriginalByteLengthGetter(fun.native())) {
1915 return AttachDecision::NoAction;
1916 }
1917 }
1918
1919 maybeEmitIdGuard(id);
1920 // Emit all the normal guards for calling this native, but specialize
1921 // callNativeGetterResult.
1922 EmitCallGetterResultGuards(writer, buf, holder, id, *prop, objId, mode_);
1923 if (buf->byteLength() <= INT32_MAX) {
1924 writer.loadArrayBufferByteLengthInt32Result(objId);
1925 } else {
1926 writer.loadArrayBufferByteLengthDoubleResult(objId);
1927 }
1928 writer.returnFromIC();
1929
1930 trackAttached("ArrayBufferMaybeSharedByteLength");
1931 return AttachDecision::Attach;
1932 }
1933
tryAttachRegExp(HandleObject obj,ObjOperandId objId,HandleId id)1934 AttachDecision GetPropIRGenerator::tryAttachRegExp(HandleObject obj,
1935 ObjOperandId objId,
1936 HandleId id) {
1937 if (!obj->is<RegExpObject>()) {
1938 return AttachDecision::NoAction;
1939 }
1940 auto* regExp = &obj->as<RegExpObject>();
1941
1942 if (mode_ != ICState::Mode::Specialized) {
1943 return AttachDecision::NoAction;
1944 }
1945
1946 // Receiver should be the object.
1947 if (isSuper()) {
1948 return AttachDecision::NoAction;
1949 }
1950
1951 NativeObject* holder = nullptr;
1952 Maybe<PropertyInfo> prop;
1953 NativeGetPropCacheability type =
1954 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
1955 if (type != CanAttachNativeGetter) {
1956 return AttachDecision::NoAction;
1957 }
1958
1959 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
1960 JS::RegExpFlags flags = JS::RegExpFlag::NoFlags;
1961 if (!RegExpObject::isOriginalFlagGetter(fun.native(), &flags)) {
1962 return AttachDecision::NoAction;
1963 }
1964
1965 maybeEmitIdGuard(id);
1966 // Emit all the normal guards for calling this native, but specialize
1967 // callNativeGetterResult.
1968 EmitCallGetterResultGuards(writer, regExp, holder, id, *prop, objId, mode_);
1969
1970 writer.regExpFlagResult(objId, flags.value());
1971 writer.returnFromIC();
1972
1973 trackAttached("RegExpFlag");
1974 return AttachDecision::Attach;
1975 }
1976
tryAttachFunction(HandleObject obj,ObjOperandId objId,HandleId id)1977 AttachDecision GetPropIRGenerator::tryAttachFunction(HandleObject obj,
1978 ObjOperandId objId,
1979 HandleId id) {
1980 // Function properties are lazily resolved so they might not be defined yet.
1981 // And we might end up in a situation where we always have a fresh function
1982 // object during the IC generation.
1983 if (!obj->is<JSFunction>()) {
1984 return AttachDecision::NoAction;
1985 }
1986
1987 bool isLength = id.isAtom(cx_->names().length);
1988 if (!isLength && !id.isAtom(cx_->names().name)) {
1989 return AttachDecision::NoAction;
1990 }
1991
1992 NativeObject* holder = nullptr;
1993 PropertyResult prop;
1994 // If this property exists already, don't attach the stub.
1995 if (LookupPropertyPure(cx_, obj, id, &holder, &prop)) {
1996 return AttachDecision::NoAction;
1997 }
1998
1999 JSFunction* fun = &obj->as<JSFunction>();
2000
2001 if (isLength) {
2002 // length was probably deleted from the function.
2003 if (fun->hasResolvedLength()) {
2004 return AttachDecision::NoAction;
2005 }
2006
2007 // Lazy functions don't store the length.
2008 if (!fun->hasBytecode()) {
2009 return AttachDecision::NoAction;
2010 }
2011
2012 // Length can be non-int32 for bound functions.
2013 if (fun->isBoundFunction()) {
2014 constexpr auto lengthSlot = FunctionExtended::BOUND_FUNCTION_LENGTH_SLOT;
2015 if (!fun->getExtendedSlot(lengthSlot).isInt32()) {
2016 return AttachDecision::NoAction;
2017 }
2018 }
2019 } else {
2020 // name was probably deleted from the function.
2021 if (fun->hasResolvedName()) {
2022 return AttachDecision::NoAction;
2023 }
2024
2025 // Unless the bound function name prefix is present, we need to call into
2026 // the VM to compute the full name.
2027 if (fun->isBoundFunction() && !fun->hasBoundFunctionNamePrefix()) {
2028 return AttachDecision::NoAction;
2029 }
2030 }
2031
2032 maybeEmitIdGuard(id);
2033 writer.guardClass(objId, GuardClassKind::JSFunction);
2034 if (isLength) {
2035 writer.loadFunctionLengthResult(objId);
2036 writer.returnFromIC();
2037 trackAttached("FunctionLength");
2038 } else {
2039 writer.loadFunctionNameResult(objId);
2040 writer.returnFromIC();
2041 trackAttached("FunctionName");
2042 }
2043 return AttachDecision::Attach;
2044 }
2045
tryAttachArgumentsObjectIterator(HandleObject obj,ObjOperandId objId,HandleId id)2046 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectIterator(
2047 HandleObject obj, ObjOperandId objId, HandleId id) {
2048 if (!obj->is<ArgumentsObject>()) {
2049 return AttachDecision::NoAction;
2050 }
2051
2052 if (!id.isWellKnownSymbol(JS::SymbolCode::iterator)) {
2053 return AttachDecision::NoAction;
2054 }
2055
2056 Handle<ArgumentsObject*> args = obj.as<ArgumentsObject>();
2057 if (args->hasOverriddenIterator()) {
2058 return AttachDecision::NoAction;
2059 }
2060
2061 RootedValue iterator(cx_);
2062 if (!ArgumentsObject::getArgumentsIterator(cx_, &iterator)) {
2063 cx_->recoverFromOutOfMemory();
2064 return AttachDecision::NoAction;
2065 }
2066 MOZ_ASSERT(iterator.isObject());
2067
2068 maybeEmitIdGuard(id);
2069 if (args->is<MappedArgumentsObject>()) {
2070 writer.guardClass(objId, GuardClassKind::MappedArguments);
2071 } else {
2072 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
2073 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
2074 }
2075 uint32_t flags = ArgumentsObject::ITERATOR_OVERRIDDEN_BIT;
2076 writer.guardArgumentsObjectFlags(objId, flags);
2077
2078 ObjOperandId iterId = writer.loadObject(&iterator.toObject());
2079 writer.loadObjectResult(iterId);
2080 writer.returnFromIC();
2081
2082 trackAttached("ArgumentsObjectIterator");
2083 return AttachDecision::Attach;
2084 }
2085
tryAttachModuleNamespace(HandleObject obj,ObjOperandId objId,HandleId id)2086 AttachDecision GetPropIRGenerator::tryAttachModuleNamespace(HandleObject obj,
2087 ObjOperandId objId,
2088 HandleId id) {
2089 if (!obj->is<ModuleNamespaceObject>()) {
2090 return AttachDecision::NoAction;
2091 }
2092
2093 auto* ns = &obj->as<ModuleNamespaceObject>();
2094 ModuleEnvironmentObject* env = nullptr;
2095 Maybe<PropertyInfo> prop;
2096 if (!ns->bindings().lookup(id, &env, &prop)) {
2097 return AttachDecision::NoAction;
2098 }
2099
2100 // Don't emit a stub until the target binding has been initialized.
2101 if (env->getSlot(prop->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) {
2102 return AttachDecision::NoAction;
2103 }
2104
2105 // Check for the specific namespace object.
2106 maybeEmitIdGuard(id);
2107 writer.guardSpecificObject(objId, ns);
2108
2109 ObjOperandId envId = writer.loadObject(env);
2110 EmitLoadSlotResult(writer, envId, env, *prop);
2111 writer.returnFromIC();
2112
2113 trackAttached("ModuleNamespace");
2114 return AttachDecision::Attach;
2115 }
2116
tryAttachPrimitive(ValOperandId valId,HandleId id)2117 AttachDecision GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId,
2118 HandleId id) {
2119 MOZ_ASSERT(!isSuper(), "SuperBase is guaranteed to be an object");
2120
2121 JSProtoKey protoKey;
2122 switch (val_.type()) {
2123 case ValueType::String:
2124 if (id.isAtom(cx_->names().length)) {
2125 // String length is special-cased, see js::GetProperty.
2126 return AttachDecision::NoAction;
2127 }
2128 protoKey = JSProto_String;
2129 break;
2130 case ValueType::Int32:
2131 case ValueType::Double:
2132 protoKey = JSProto_Number;
2133 break;
2134 case ValueType::Boolean:
2135 protoKey = JSProto_Boolean;
2136 break;
2137 case ValueType::Symbol:
2138 protoKey = JSProto_Symbol;
2139 break;
2140 case ValueType::BigInt:
2141 protoKey = JSProto_BigInt;
2142 break;
2143 case ValueType::Null:
2144 case ValueType::Undefined:
2145 case ValueType::Magic:
2146 return AttachDecision::NoAction;
2147 #ifdef ENABLE_RECORD_TUPLE
2148 case ValueType::ExtendedPrimitive:
2149 #endif
2150 case ValueType::Object:
2151 case ValueType::PrivateGCThing:
2152 MOZ_CRASH("unexpected type");
2153 }
2154
2155 JSObject* proto = cx_->global()->maybeGetPrototype(protoKey);
2156 if (!proto) {
2157 return AttachDecision::NoAction;
2158 }
2159
2160 NativeObject* holder = nullptr;
2161 Maybe<PropertyInfo> prop;
2162 NativeGetPropCacheability type =
2163 CanAttachNativeGetProp(cx_, proto, id, &holder, &prop, pc_);
2164 switch (type) {
2165 case CanAttachNone:
2166 return AttachDecision::NoAction;
2167 case CanAttachReadSlot: {
2168 auto* nproto = &proto->as<NativeObject>();
2169
2170 if (val_.isNumber()) {
2171 writer.guardIsNumber(valId);
2172 } else {
2173 writer.guardNonDoubleType(valId, val_.type());
2174 }
2175 maybeEmitIdGuard(id);
2176
2177 ObjOperandId protoId = writer.loadObject(nproto);
2178 EmitReadSlotResult(writer, nproto, holder, prop, protoId);
2179 writer.returnFromIC();
2180
2181 trackAttached("PrimitiveSlot");
2182 return AttachDecision::Attach;
2183 }
2184 case CanAttachScriptedGetter:
2185 case CanAttachNativeGetter: {
2186 auto* nproto = &proto->as<NativeObject>();
2187
2188 if (val_.isNumber()) {
2189 writer.guardIsNumber(valId);
2190 } else {
2191 writer.guardNonDoubleType(valId, val_.type());
2192 }
2193 maybeEmitIdGuard(id);
2194
2195 ObjOperandId protoId = writer.loadObject(nproto);
2196 EmitCallGetterResult(cx_, writer, nproto, holder, id, *prop, protoId,
2197 valId, mode_);
2198
2199 trackAttached("PrimitiveGetter");
2200 return AttachDecision::Attach;
2201 }
2202 }
2203
2204 MOZ_CRASH("Bad NativeGetPropCacheability");
2205 }
2206
tryAttachStringLength(ValOperandId valId,HandleId id)2207 AttachDecision GetPropIRGenerator::tryAttachStringLength(ValOperandId valId,
2208 HandleId id) {
2209 if (!val_.isString() || !id.isAtom(cx_->names().length)) {
2210 return AttachDecision::NoAction;
2211 }
2212
2213 StringOperandId strId = writer.guardToString(valId);
2214 maybeEmitIdGuard(id);
2215 writer.loadStringLengthResult(strId);
2216 writer.returnFromIC();
2217
2218 trackAttached("StringLength");
2219 return AttachDecision::Attach;
2220 }
2221
CanAttachStringChar(const Value & val,const Value & idVal)2222 static bool CanAttachStringChar(const Value& val, const Value& idVal) {
2223 if (!val.isString() || !idVal.isInt32()) {
2224 return false;
2225 }
2226
2227 int32_t index = idVal.toInt32();
2228 if (index < 0) {
2229 return false;
2230 }
2231
2232 JSString* str = val.toString();
2233 if (size_t(index) >= str->length()) {
2234 return false;
2235 }
2236
2237 // This follows JSString::getChar, otherwise we fail to attach getChar in a
2238 // lot of cases.
2239 if (str->isRope()) {
2240 JSRope* rope = &str->asRope();
2241
2242 // Make sure the left side contains the index.
2243 if (size_t(index) >= rope->leftChild()->length()) {
2244 return false;
2245 }
2246
2247 str = rope->leftChild();
2248 }
2249
2250 if (!str->isLinear()) {
2251 return false;
2252 }
2253
2254 return true;
2255 }
2256
tryAttachStringChar(ValOperandId valId,ValOperandId indexId)2257 AttachDecision GetPropIRGenerator::tryAttachStringChar(ValOperandId valId,
2258 ValOperandId indexId) {
2259 MOZ_ASSERT(idVal_.isInt32());
2260
2261 if (!CanAttachStringChar(val_, idVal_)) {
2262 return AttachDecision::NoAction;
2263 }
2264
2265 StringOperandId strId = writer.guardToString(valId);
2266 Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
2267 writer.loadStringCharResult(strId, int32IndexId);
2268 writer.returnFromIC();
2269
2270 trackAttached("StringChar");
2271 return AttachDecision::Attach;
2272 }
2273
ClassCanHaveExtraProperties(const JSClass * clasp)2274 static bool ClassCanHaveExtraProperties(const JSClass* clasp) {
2275 return clasp->getResolve() || clasp->getOpsLookupProperty() ||
2276 clasp->getOpsGetProperty() || IsTypedArrayClass(clasp);
2277 }
2278
2279 enum class OwnProperty : bool { No, Yes };
2280 enum class AllowIndexedReceiver : bool { No, Yes };
2281 enum class AllowExtraReceiverProperties : bool { No, Yes };
2282
CanAttachDenseElementHole(NativeObject * obj,OwnProperty ownProp,AllowIndexedReceiver allowIndexedReceiver=AllowIndexedReceiver::No,AllowExtraReceiverProperties allowExtraReceiverProperties=AllowExtraReceiverProperties::No)2283 static bool CanAttachDenseElementHole(
2284 NativeObject* obj, OwnProperty ownProp,
2285 AllowIndexedReceiver allowIndexedReceiver = AllowIndexedReceiver::No,
2286 AllowExtraReceiverProperties allowExtraReceiverProperties =
2287 AllowExtraReceiverProperties::No) {
2288 // Make sure the objects on the prototype don't have any indexed properties
2289 // or that such properties can't appear without a shape change.
2290 // Otherwise returning undefined for holes would obviously be incorrect,
2291 // because we would have to lookup a property on the prototype instead.
2292 do {
2293 // The first two checks are also relevant to the receiver object.
2294 if (allowIndexedReceiver == AllowIndexedReceiver::No && obj->isIndexed()) {
2295 return false;
2296 }
2297 allowIndexedReceiver = AllowIndexedReceiver::No;
2298
2299 if (allowExtraReceiverProperties == AllowExtraReceiverProperties::No &&
2300 ClassCanHaveExtraProperties(obj->getClass())) {
2301 return false;
2302 }
2303 allowExtraReceiverProperties = AllowExtraReceiverProperties::No;
2304
2305 // Don't need to check prototype for OwnProperty checks
2306 if (ownProp == OwnProperty::Yes) {
2307 return true;
2308 }
2309
2310 JSObject* proto = obj->staticPrototype();
2311 if (!proto) {
2312 break;
2313 }
2314
2315 if (!proto->is<NativeObject>()) {
2316 return false;
2317 }
2318
2319 // Make sure objects on the prototype don't have dense elements.
2320 if (proto->as<NativeObject>().getDenseInitializedLength() != 0) {
2321 return false;
2322 }
2323
2324 obj = &proto->as<NativeObject>();
2325 } while (true);
2326
2327 return true;
2328 }
2329
tryAttachArgumentsObjectArg(HandleObject obj,ObjOperandId objId,uint32_t index,Int32OperandId indexId)2330 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectArg(
2331 HandleObject obj, ObjOperandId objId, uint32_t index,
2332 Int32OperandId indexId) {
2333 if (!obj->is<ArgumentsObject>()) {
2334 return AttachDecision::NoAction;
2335 }
2336 auto* args = &obj->as<ArgumentsObject>();
2337
2338 // No elements must have been overridden or deleted.
2339 if (args->hasOverriddenElement()) {
2340 return AttachDecision::NoAction;
2341 }
2342
2343 // Check bounds.
2344 if (index >= args->initialLength()) {
2345 return AttachDecision::NoAction;
2346 }
2347
2348 // And finally also check that the argument isn't forwarded.
2349 if (args->argIsForwarded(index)) {
2350 return AttachDecision::NoAction;
2351 }
2352
2353 if (args->is<MappedArgumentsObject>()) {
2354 writer.guardClass(objId, GuardClassKind::MappedArguments);
2355 } else {
2356 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
2357 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
2358 }
2359
2360 writer.loadArgumentsObjectArgResult(objId, indexId);
2361 writer.returnFromIC();
2362
2363 trackAttached("ArgumentsObjectArg");
2364 return AttachDecision::Attach;
2365 }
2366
tryAttachArgumentsObjectArgHole(HandleObject obj,ObjOperandId objId,uint32_t index,Int32OperandId indexId)2367 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectArgHole(
2368 HandleObject obj, ObjOperandId objId, uint32_t index,
2369 Int32OperandId indexId) {
2370 if (!obj->is<ArgumentsObject>()) {
2371 return AttachDecision::NoAction;
2372 }
2373 auto* args = &obj->as<ArgumentsObject>();
2374
2375 // No elements must have been overridden or deleted.
2376 if (args->hasOverriddenElement()) {
2377 return AttachDecision::NoAction;
2378 }
2379
2380 // And also check that the argument isn't forwarded.
2381 if (index < args->initialLength() && args->argIsForwarded(index)) {
2382 return AttachDecision::NoAction;
2383 }
2384
2385 if (!CanAttachDenseElementHole(args, OwnProperty::No,
2386 AllowIndexedReceiver::Yes,
2387 AllowExtraReceiverProperties::Yes)) {
2388 return AttachDecision::NoAction;
2389 }
2390
2391 // We don't need to guard on the shape, because we check if any element is
2392 // overridden. Elements are marked as overridden iff any element is defined,
2393 // irrespective of whether the element is in-bounds or out-of-bounds. So when
2394 // that flag isn't set, we can guarantee that the arguments object doesn't
2395 // have any additional own elements.
2396
2397 if (args->is<MappedArgumentsObject>()) {
2398 writer.guardClass(objId, GuardClassKind::MappedArguments);
2399 } else {
2400 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
2401 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
2402 }
2403
2404 GeneratePrototypeHoleGuards(writer, args, objId,
2405 /* alwaysGuardFirstProto = */ true);
2406
2407 writer.loadArgumentsObjectArgHoleResult(objId, indexId);
2408 writer.returnFromIC();
2409
2410 trackAttached("ArgumentsObjectArgHole");
2411 return AttachDecision::Attach;
2412 }
2413
tryAttachArgumentsObjectCallee(HandleObject obj,ObjOperandId objId,HandleId id)2414 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectCallee(
2415 HandleObject obj, ObjOperandId objId, HandleId id) {
2416 // Only mapped arguments objects have a `callee` property.
2417 if (!obj->is<MappedArgumentsObject>()) {
2418 return AttachDecision::NoAction;
2419 }
2420
2421 if (!id.isAtom(cx_->names().callee)) {
2422 return AttachDecision::NoAction;
2423 }
2424
2425 // The callee must not have been overridden or deleted.
2426 if (obj->as<MappedArgumentsObject>().hasOverriddenCallee()) {
2427 return AttachDecision::NoAction;
2428 }
2429
2430 maybeEmitIdGuard(id);
2431 writer.guardClass(objId, GuardClassKind::MappedArguments);
2432
2433 uint32_t flags = ArgumentsObject::CALLEE_OVERRIDDEN_BIT;
2434 writer.guardArgumentsObjectFlags(objId, flags);
2435
2436 writer.loadFixedSlotResult(objId,
2437 MappedArgumentsObject::getCalleeSlotOffset());
2438 writer.returnFromIC();
2439
2440 trackAttached("ArgumentsObjectCallee");
2441 return AttachDecision::Attach;
2442 }
2443
tryAttachDenseElement(HandleObject obj,ObjOperandId objId,uint32_t index,Int32OperandId indexId)2444 AttachDecision GetPropIRGenerator::tryAttachDenseElement(
2445 HandleObject obj, ObjOperandId objId, uint32_t index,
2446 Int32OperandId indexId) {
2447 if (!obj->is<NativeObject>()) {
2448 return AttachDecision::NoAction;
2449 }
2450
2451 NativeObject* nobj = &obj->as<NativeObject>();
2452 if (!nobj->containsDenseElement(index)) {
2453 return AttachDecision::NoAction;
2454 }
2455
2456 TestMatchingNativeReceiver(writer, nobj, objId);
2457 writer.loadDenseElementResult(objId, indexId);
2458 writer.returnFromIC();
2459
2460 trackAttached("DenseElement");
2461 return AttachDecision::Attach;
2462 }
2463
tryAttachDenseElementHole(HandleObject obj,ObjOperandId objId,uint32_t index,Int32OperandId indexId)2464 AttachDecision GetPropIRGenerator::tryAttachDenseElementHole(
2465 HandleObject obj, ObjOperandId objId, uint32_t index,
2466 Int32OperandId indexId) {
2467 if (!obj->is<NativeObject>()) {
2468 return AttachDecision::NoAction;
2469 }
2470
2471 NativeObject* nobj = &obj->as<NativeObject>();
2472 if (nobj->containsDenseElement(index)) {
2473 return AttachDecision::NoAction;
2474 }
2475 if (!CanAttachDenseElementHole(nobj, OwnProperty::No)) {
2476 return AttachDecision::NoAction;
2477 }
2478
2479 // Guard on the shape, to prevent non-dense elements from appearing.
2480 TestMatchingNativeReceiver(writer, nobj, objId);
2481 GeneratePrototypeHoleGuards(writer, nobj, objId,
2482 /* alwaysGuardFirstProto = */ false);
2483 writer.loadDenseElementHoleResult(objId, indexId);
2484 writer.returnFromIC();
2485
2486 trackAttached("DenseElementHole");
2487 return AttachDecision::Attach;
2488 }
2489
tryAttachSparseElement(HandleObject obj,ObjOperandId objId,uint32_t index,Int32OperandId indexId)2490 AttachDecision GetPropIRGenerator::tryAttachSparseElement(
2491 HandleObject obj, ObjOperandId objId, uint32_t index,
2492 Int32OperandId indexId) {
2493 if (!obj->is<NativeObject>()) {
2494 return AttachDecision::NoAction;
2495 }
2496 NativeObject* nobj = &obj->as<NativeObject>();
2497
2498 // Stub doesn't handle negative indices.
2499 if (index > INT_MAX) {
2500 return AttachDecision::NoAction;
2501 }
2502
2503 // We also need to be past the end of the dense capacity, to ensure sparse.
2504 if (index < nobj->getDenseInitializedLength()) {
2505 return AttachDecision::NoAction;
2506 }
2507
2508 // Only handle Array objects in this stub.
2509 if (!nobj->is<ArrayObject>()) {
2510 return AttachDecision::NoAction;
2511 }
2512
2513 // Here, we ensure that the prototype chain does not define any sparse
2514 // indexed properties on the shape lineage. This allows us to guard on
2515 // the shapes up the prototype chain to ensure that no indexed properties
2516 // exist outside of the dense elements.
2517 //
2518 // The `GeneratePrototypeHoleGuards` call below will guard on the shapes,
2519 // as well as ensure that no prototypes contain dense elements, allowing
2520 // us to perform a pure shape-search for out-of-bounds integer-indexed
2521 // properties on the recevier object.
2522 if ((nobj->staticPrototype() != nullptr) &&
2523 ObjectMayHaveExtraIndexedProperties(nobj->staticPrototype())) {
2524 return AttachDecision::NoAction;
2525 }
2526
2527 // Ensure that obj is an Array.
2528 writer.guardClass(objId, GuardClassKind::Array);
2529
2530 // The helper we are going to call only applies to non-dense elements.
2531 writer.guardIndexGreaterThanDenseInitLength(objId, indexId);
2532
2533 // Ensures we are able to efficiently able to map to an integral jsid.
2534 writer.guardInt32IsNonNegative(indexId);
2535
2536 // Shape guard the prototype chain to avoid shadowing indexes from appearing.
2537 // The helper function also ensures that the index does not appear within the
2538 // dense element set of the prototypes.
2539 GeneratePrototypeHoleGuards(writer, nobj, objId,
2540 /* alwaysGuardFirstProto = */ true);
2541
2542 // At this point, we are guaranteed that the indexed property will not
2543 // be found on one of the prototypes. We are assured that we only have
2544 // to check that the receiving object has the property.
2545
2546 writer.callGetSparseElementResult(objId, indexId);
2547 writer.returnFromIC();
2548
2549 trackAttached("GetSparseElement");
2550 return AttachDecision::Attach;
2551 }
2552
2553 // For Uint32Array we let the stub return an Int32 if we have not seen a
2554 // double, to allow better codegen in Warp while avoiding bailout loops.
ForceDoubleForUint32Array(TypedArrayObject * tarr,uint64_t index)2555 static bool ForceDoubleForUint32Array(TypedArrayObject* tarr, uint64_t index) {
2556 MOZ_ASSERT(index < tarr->length());
2557
2558 if (tarr->type() != Scalar::Type::Uint32) {
2559 // Return value is only relevant for Uint32Array.
2560 return false;
2561 }
2562
2563 Value res;
2564 MOZ_ALWAYS_TRUE(tarr->getElementPure(index, &res));
2565 MOZ_ASSERT(res.isNumber());
2566 return res.isDouble();
2567 }
2568
tryAttachTypedArrayElement(HandleObject obj,ObjOperandId objId)2569 AttachDecision GetPropIRGenerator::tryAttachTypedArrayElement(
2570 HandleObject obj, ObjOperandId objId) {
2571 if (!obj->is<TypedArrayObject>()) {
2572 return AttachDecision::NoAction;
2573 }
2574
2575 if (!idVal_.isNumber()) {
2576 return AttachDecision::NoAction;
2577 }
2578
2579 TypedArrayObject* tarr = &obj->as<TypedArrayObject>();
2580
2581 bool handleOOB = false;
2582 int64_t indexInt64;
2583 if (!ValueIsInt64Index(idVal_, &indexInt64) || indexInt64 < 0 ||
2584 uint64_t(indexInt64) >= tarr->length()) {
2585 handleOOB = true;
2586 }
2587
2588 // If the number is not representable as an integer the result will be
2589 // |undefined| so we leave |forceDoubleForUint32| as false.
2590 bool forceDoubleForUint32 = false;
2591 if (!handleOOB) {
2592 uint64_t index = uint64_t(indexInt64);
2593 forceDoubleForUint32 = ForceDoubleForUint32Array(tarr, index);
2594 }
2595
2596 writer.guardShapeForClass(objId, tarr->shape());
2597
2598 ValOperandId keyId = getElemKeyValueId();
2599 IntPtrOperandId intPtrIndexId = guardToIntPtrIndex(idVal_, keyId, handleOOB);
2600
2601 writer.loadTypedArrayElementResult(objId, intPtrIndexId, tarr->type(),
2602 handleOOB, forceDoubleForUint32);
2603 writer.returnFromIC();
2604
2605 trackAttached("TypedElement");
2606 return AttachDecision::Attach;
2607 }
2608
tryAttachGenericElement(HandleObject obj,ObjOperandId objId,uint32_t index,Int32OperandId indexId)2609 AttachDecision GetPropIRGenerator::tryAttachGenericElement(
2610 HandleObject obj, ObjOperandId objId, uint32_t index,
2611 Int32OperandId indexId) {
2612 if (!obj->is<NativeObject>()) {
2613 return AttachDecision::NoAction;
2614 }
2615
2616 // To allow other types to attach in the non-megamorphic case we test the
2617 // specific matching native receiver; however, once megamorphic we can attach
2618 // for any native
2619 if (mode_ == ICState::Mode::Megamorphic) {
2620 writer.guardIsNativeObject(objId);
2621 } else {
2622 NativeObject* nobj = &obj->as<NativeObject>();
2623 TestMatchingNativeReceiver(writer, nobj, objId);
2624 }
2625 writer.guardIndexGreaterThanDenseInitLength(objId, indexId);
2626 writer.callNativeGetElementResult(objId, indexId);
2627 writer.returnFromIC();
2628
2629 trackAttached(mode_ == ICState::Mode::Megamorphic
2630 ? "GenericElementMegamorphic"
2631 : "GenericElement");
2632 return AttachDecision::Attach;
2633 }
2634
tryAttachProxyElement(HandleObject obj,ObjOperandId objId)2635 AttachDecision GetPropIRGenerator::tryAttachProxyElement(HandleObject obj,
2636 ObjOperandId objId) {
2637 if (!obj->is<ProxyObject>()) {
2638 return AttachDecision::NoAction;
2639 }
2640
2641 // The proxy stubs don't currently support |super| access.
2642 if (isSuper()) {
2643 return AttachDecision::NoAction;
2644 }
2645
2646 writer.guardIsProxy(objId);
2647
2648 // We are not guarding against DOM proxies here, because there is no other
2649 // specialized DOM IC we could attach.
2650 // We could call maybeEmitIdGuard here and then emit ProxyGetResult,
2651 // but for GetElem we prefer to attach a stub that can handle any Value
2652 // so we don't attach a new stub for every id.
2653 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
2654 MOZ_ASSERT(!isSuper());
2655 writer.proxyGetByValueResult(objId, getElemKeyValueId());
2656 writer.returnFromIC();
2657
2658 trackAttached("ProxyElement");
2659 return AttachDecision::Attach;
2660 }
2661
trackAttached(const char * name)2662 void GetPropIRGenerator::trackAttached(const char* name) {
2663 #ifdef JS_CACHEIR_SPEW
2664 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
2665 sp.valueProperty("base", val_);
2666 sp.valueProperty("property", idVal_);
2667 }
2668 #endif
2669 }
2670
emitIdGuard(ValOperandId valId,const Value & idVal,jsid id)2671 void IRGenerator::emitIdGuard(ValOperandId valId, const Value& idVal, jsid id) {
2672 if (id.isSymbol()) {
2673 MOZ_ASSERT(idVal.toSymbol() == id.toSymbol());
2674 SymbolOperandId symId = writer.guardToSymbol(valId);
2675 writer.guardSpecificSymbol(symId, id.toSymbol());
2676 } else {
2677 MOZ_ASSERT(id.isAtom());
2678 if (idVal.isUndefined()) {
2679 MOZ_ASSERT(id.isAtom(cx_->names().undefined));
2680 writer.guardIsUndefined(valId);
2681 } else if (idVal.isNull()) {
2682 MOZ_ASSERT(id.isAtom(cx_->names().null));
2683 writer.guardIsNull(valId);
2684 } else {
2685 MOZ_ASSERT(idVal.isString());
2686 StringOperandId strId = writer.guardToString(valId);
2687 writer.guardSpecificAtom(strId, id.toAtom());
2688 }
2689 }
2690 }
2691
maybeEmitIdGuard(jsid id)2692 void GetPropIRGenerator::maybeEmitIdGuard(jsid id) {
2693 if (cacheKind_ == CacheKind::GetProp ||
2694 cacheKind_ == CacheKind::GetPropSuper) {
2695 // Constant PropertyName, no guards necessary.
2696 MOZ_ASSERT(&idVal_.toString()->asAtom() == id.toAtom());
2697 return;
2698 }
2699
2700 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
2701 cacheKind_ == CacheKind::GetElemSuper);
2702 emitIdGuard(getElemKeyValueId(), idVal_, id);
2703 }
2704
maybeEmitIdGuard(jsid id)2705 void SetPropIRGenerator::maybeEmitIdGuard(jsid id) {
2706 if (cacheKind_ == CacheKind::SetProp) {
2707 // Constant PropertyName, no guards necessary.
2708 MOZ_ASSERT(&idVal_.toString()->asAtom() == id.toAtom());
2709 return;
2710 }
2711
2712 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
2713 emitIdGuard(setElemKeyValueId(), idVal_, id);
2714 }
2715
GetNameIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,HandleObject env,HandlePropertyName name)2716 GetNameIRGenerator::GetNameIRGenerator(JSContext* cx, HandleScript script,
2717 jsbytecode* pc, ICState state,
2718 HandleObject env,
2719 HandlePropertyName name)
2720 : IRGenerator(cx, script, pc, CacheKind::GetName, state),
2721 env_(env),
2722 name_(name) {}
2723
tryAttachStub()2724 AttachDecision GetNameIRGenerator::tryAttachStub() {
2725 MOZ_ASSERT(cacheKind_ == CacheKind::GetName);
2726
2727 AutoAssertNoPendingException aanpe(cx_);
2728
2729 ObjOperandId envId(writer.setInputOperandId(0));
2730 RootedId id(cx_, NameToId(name_));
2731
2732 TRY_ATTACH(tryAttachGlobalNameValue(envId, id));
2733 TRY_ATTACH(tryAttachGlobalNameGetter(envId, id));
2734 TRY_ATTACH(tryAttachEnvironmentName(envId, id));
2735
2736 trackAttached(IRGenerator::NotAttached);
2737 return AttachDecision::NoAction;
2738 }
2739
CanAttachGlobalName(JSContext * cx,GlobalLexicalEnvironmentObject * globalLexical,PropertyKey id,NativeObject ** holder,Maybe<PropertyInfo> * prop)2740 static bool CanAttachGlobalName(JSContext* cx,
2741 GlobalLexicalEnvironmentObject* globalLexical,
2742 PropertyKey id, NativeObject** holder,
2743 Maybe<PropertyInfo>* prop) {
2744 // The property must be found, and it must be found as a normal data property.
2745 NativeObject* current = globalLexical;
2746 while (true) {
2747 *prop = current->lookup(cx, id);
2748 if (prop->isSome()) {
2749 break;
2750 }
2751
2752 if (current == globalLexical) {
2753 current = &globalLexical->global();
2754 } else {
2755 // In the browser the global prototype chain should be immutable.
2756 if (!current->staticPrototypeIsImmutable()) {
2757 return false;
2758 }
2759
2760 JSObject* proto = current->staticPrototype();
2761 if (!proto || !proto->is<NativeObject>()) {
2762 return false;
2763 }
2764
2765 current = &proto->as<NativeObject>();
2766 }
2767 }
2768
2769 *holder = current;
2770 return true;
2771 }
2772
tryAttachGlobalNameValue(ObjOperandId objId,HandleId id)2773 AttachDecision GetNameIRGenerator::tryAttachGlobalNameValue(ObjOperandId objId,
2774 HandleId id) {
2775 if (!IsGlobalOp(JSOp(*pc_))) {
2776 return AttachDecision::NoAction;
2777 }
2778 MOZ_ASSERT(!script_->hasNonSyntacticScope());
2779
2780 auto* globalLexical = &env_->as<GlobalLexicalEnvironmentObject>();
2781
2782 NativeObject* holder = nullptr;
2783 Maybe<PropertyInfo> prop;
2784 if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &prop)) {
2785 return AttachDecision::NoAction;
2786 }
2787
2788 // The property must be found, and it must be found as a normal data property.
2789 if (!prop->isDataProperty()) {
2790 return AttachDecision::NoAction;
2791 }
2792
2793 // This might still be an uninitialized lexical.
2794 if (holder->getSlot(prop->slot()).isMagic()) {
2795 return AttachDecision::NoAction;
2796 }
2797
2798 if (holder == globalLexical) {
2799 // There is no need to guard on the shape. Lexical bindings are
2800 // non-configurable, and this stub cannot be shared across globals.
2801 size_t dynamicSlotOffset =
2802 holder->dynamicSlotIndex(prop->slot()) * sizeof(Value);
2803 writer.loadDynamicSlotResult(objId, dynamicSlotOffset);
2804 } else {
2805 // Check the prototype chain from the global to the holder
2806 // prototype. Ignore the global lexical scope as it doesn't figure
2807 // into the prototype chain. We guard on the global lexical
2808 // scope's shape independently.
2809 if (!IsCacheableGetPropReadSlot(&globalLexical->global(), holder, *prop)) {
2810 return AttachDecision::NoAction;
2811 }
2812
2813 // Shape guard for global lexical.
2814 writer.guardShape(objId, globalLexical->shape());
2815
2816 // Guard on the shape of the GlobalObject.
2817 ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
2818 writer.guardShape(globalId, globalLexical->global().shape());
2819
2820 ObjOperandId holderId = globalId;
2821 if (holder != &globalLexical->global()) {
2822 // Shape guard holder.
2823 holderId = writer.loadObject(holder);
2824 writer.guardShape(holderId, holder->shape());
2825 }
2826
2827 EmitLoadSlotResult(writer, holderId, holder, *prop);
2828 }
2829
2830 writer.returnFromIC();
2831
2832 trackAttached("GlobalNameValue");
2833 return AttachDecision::Attach;
2834 }
2835
tryAttachGlobalNameGetter(ObjOperandId objId,HandleId id)2836 AttachDecision GetNameIRGenerator::tryAttachGlobalNameGetter(ObjOperandId objId,
2837 HandleId id) {
2838 if (!IsGlobalOp(JSOp(*pc_))) {
2839 return AttachDecision::NoAction;
2840 }
2841 MOZ_ASSERT(!script_->hasNonSyntacticScope());
2842
2843 Handle<GlobalLexicalEnvironmentObject*> globalLexical =
2844 env_.as<GlobalLexicalEnvironmentObject>();
2845 MOZ_ASSERT(globalLexical->isGlobal());
2846
2847 NativeObject* holder = nullptr;
2848 Maybe<PropertyInfo> prop;
2849 if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &prop)) {
2850 return AttachDecision::NoAction;
2851 }
2852
2853 if (holder == globalLexical) {
2854 return AttachDecision::NoAction;
2855 }
2856
2857 GlobalObject* global = &globalLexical->global();
2858
2859 if (IsCacheableGetPropCall(global, holder, *prop) != CanAttachNativeGetter) {
2860 return AttachDecision::NoAction;
2861 }
2862
2863 // Shape guard for global lexical.
2864 writer.guardShape(objId, globalLexical->shape());
2865
2866 // Guard on the shape of the GlobalObject.
2867 ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
2868 writer.guardShape(globalId, global->shape());
2869
2870 if (holder != global) {
2871 // Shape guard holder.
2872 ObjOperandId holderId = writer.loadObject(holder);
2873 writer.guardShape(holderId, holder->shape());
2874 EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
2875 /* holderIsConstant = */ true);
2876 } else {
2877 // Note: pass true for |holderIsConstant| because the holder must be the
2878 // current global object.
2879 EmitGuardGetterSetterSlot(writer, holder, *prop, globalId,
2880 /* holderIsConstant = */ true);
2881 }
2882
2883 if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, global, holder, *prop,
2884 mode_)) {
2885 // The global shape guard above ensures the instance JSClass is correct.
2886 EmitCallDOMGetterResultNoGuards(writer, holder, *prop, globalId);
2887 trackAttached("GlobalNameDOMGetter");
2888 } else {
2889 ValOperandId receiverId = writer.boxObject(globalId);
2890 EmitCallGetterResultNoGuards(cx_, writer, global, holder, *prop,
2891 receiverId);
2892 trackAttached("GlobalNameGetter");
2893 }
2894
2895 return AttachDecision::Attach;
2896 }
2897
NeedEnvironmentShapeGuard(JSObject * envObj)2898 static bool NeedEnvironmentShapeGuard(JSObject* envObj) {
2899 if (!envObj->is<CallObject>()) {
2900 return true;
2901 }
2902
2903 // We can skip a guard on the call object if the script's bindings are
2904 // guaranteed to be immutable (and thus cannot introduce shadowing variables).
2905 // If the function is a relazified self-hosted function it has no BaseScript
2906 // and we pessimistically create the guard.
2907 CallObject* callObj = &envObj->as<CallObject>();
2908 JSFunction* fun = &callObj->callee();
2909 if (!fun->hasBaseScript() || fun->baseScript()->funHasExtensibleScope()) {
2910 return true;
2911 }
2912
2913 return false;
2914 }
2915
tryAttachEnvironmentName(ObjOperandId objId,HandleId id)2916 AttachDecision GetNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
2917 HandleId id) {
2918 if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
2919 return AttachDecision::NoAction;
2920 }
2921
2922 JSObject* env = env_;
2923 Maybe<PropertyInfo> prop;
2924 NativeObject* holder = nullptr;
2925
2926 while (env) {
2927 if (env->is<GlobalObject>()) {
2928 prop = env->as<GlobalObject>().lookup(cx_, id);
2929 if (prop.isSome()) {
2930 break;
2931 }
2932 return AttachDecision::NoAction;
2933 }
2934
2935 if (!env->is<EnvironmentObject>() || env->is<WithEnvironmentObject>()) {
2936 return AttachDecision::NoAction;
2937 }
2938
2939 // Check for an 'own' property on the env. There is no need to
2940 // check the prototype as non-with scopes do not inherit properties
2941 // from any prototype.
2942 prop = env->as<NativeObject>().lookup(cx_, id);
2943 if (prop.isSome()) {
2944 break;
2945 }
2946
2947 env = env->enclosingEnvironment();
2948 }
2949
2950 holder = &env->as<NativeObject>();
2951 if (!IsCacheableGetPropReadSlot(holder, holder, *prop)) {
2952 return AttachDecision::NoAction;
2953 }
2954 if (holder->getSlot(prop->slot()).isMagic()) {
2955 return AttachDecision::NoAction;
2956 }
2957
2958 ObjOperandId lastObjId = objId;
2959 env = env_;
2960 while (env) {
2961 if (NeedEnvironmentShapeGuard(env)) {
2962 writer.guardShape(lastObjId, env->shape());
2963 }
2964
2965 if (env == holder) {
2966 break;
2967 }
2968
2969 lastObjId = writer.loadEnclosingEnvironment(lastObjId);
2970 env = env->enclosingEnvironment();
2971 }
2972
2973 if (holder->isFixedSlot(prop->slot())) {
2974 writer.loadEnvironmentFixedSlotResult(
2975 lastObjId, NativeObject::getFixedSlotOffset(prop->slot()));
2976 } else {
2977 size_t dynamicSlotOffset =
2978 holder->dynamicSlotIndex(prop->slot()) * sizeof(Value);
2979 writer.loadEnvironmentDynamicSlotResult(lastObjId, dynamicSlotOffset);
2980 }
2981 writer.returnFromIC();
2982
2983 trackAttached("EnvironmentName");
2984 return AttachDecision::Attach;
2985 }
2986
trackAttached(const char * name)2987 void GetNameIRGenerator::trackAttached(const char* name) {
2988 #ifdef JS_CACHEIR_SPEW
2989 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
2990 sp.valueProperty("base", ObjectValue(*env_));
2991 sp.valueProperty("property", StringValue(name_));
2992 }
2993 #endif
2994 }
2995
BindNameIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,HandleObject env,HandlePropertyName name)2996 BindNameIRGenerator::BindNameIRGenerator(JSContext* cx, HandleScript script,
2997 jsbytecode* pc, ICState state,
2998 HandleObject env,
2999 HandlePropertyName name)
3000 : IRGenerator(cx, script, pc, CacheKind::BindName, state),
3001 env_(env),
3002 name_(name) {}
3003
tryAttachStub()3004 AttachDecision BindNameIRGenerator::tryAttachStub() {
3005 MOZ_ASSERT(cacheKind_ == CacheKind::BindName);
3006
3007 AutoAssertNoPendingException aanpe(cx_);
3008
3009 ObjOperandId envId(writer.setInputOperandId(0));
3010 RootedId id(cx_, NameToId(name_));
3011
3012 TRY_ATTACH(tryAttachGlobalName(envId, id));
3013 TRY_ATTACH(tryAttachEnvironmentName(envId, id));
3014
3015 trackAttached(IRGenerator::NotAttached);
3016 return AttachDecision::NoAction;
3017 }
3018
tryAttachGlobalName(ObjOperandId objId,HandleId id)3019 AttachDecision BindNameIRGenerator::tryAttachGlobalName(ObjOperandId objId,
3020 HandleId id) {
3021 if (!IsGlobalOp(JSOp(*pc_))) {
3022 return AttachDecision::NoAction;
3023 }
3024 MOZ_ASSERT(!script_->hasNonSyntacticScope());
3025
3026 Handle<GlobalLexicalEnvironmentObject*> globalLexical =
3027 env_.as<GlobalLexicalEnvironmentObject>();
3028 MOZ_ASSERT(globalLexical->isGlobal());
3029
3030 JSObject* result = nullptr;
3031 if (Maybe<PropertyInfo> prop = globalLexical->lookup(cx_, id)) {
3032 // If this is an uninitialized lexical or a const, we need to return a
3033 // RuntimeLexicalErrorObject.
3034 if (globalLexical->getSlot(prop->slot()).isMagic() || !prop->writable()) {
3035 return AttachDecision::NoAction;
3036 }
3037 result = globalLexical;
3038 } else {
3039 result = &globalLexical->global();
3040 }
3041
3042 if (result == globalLexical) {
3043 // Lexical bindings are non-configurable so we can just return the
3044 // global lexical.
3045 writer.loadObjectResult(objId);
3046 } else {
3047 // If the property exists on the global and is non-configurable, it cannot
3048 // be shadowed by the lexical scope so we can just return the global without
3049 // a shape guard.
3050 Maybe<PropertyInfo> prop = result->as<GlobalObject>().lookup(cx_, id);
3051 if (prop.isNothing() || prop->configurable()) {
3052 writer.guardShape(objId, globalLexical->shape());
3053 }
3054 ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
3055 writer.loadObjectResult(globalId);
3056 }
3057 writer.returnFromIC();
3058
3059 trackAttached("GlobalName");
3060 return AttachDecision::Attach;
3061 }
3062
tryAttachEnvironmentName(ObjOperandId objId,HandleId id)3063 AttachDecision BindNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
3064 HandleId id) {
3065 if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
3066 return AttachDecision::NoAction;
3067 }
3068
3069 JSObject* env = env_;
3070 Maybe<PropertyInfo> prop;
3071 while (true) {
3072 if (!env->is<GlobalObject>() && !env->is<EnvironmentObject>()) {
3073 return AttachDecision::NoAction;
3074 }
3075 if (env->is<WithEnvironmentObject>()) {
3076 return AttachDecision::NoAction;
3077 }
3078
3079 // When we reach an unqualified variables object (like the global) we
3080 // have to stop looking and return that object.
3081 if (env->isUnqualifiedVarObj()) {
3082 break;
3083 }
3084
3085 // Check for an 'own' property on the env. There is no need to
3086 // check the prototype as non-with scopes do not inherit properties
3087 // from any prototype.
3088 prop = env->as<NativeObject>().lookup(cx_, id);
3089 if (prop.isSome()) {
3090 break;
3091 }
3092
3093 env = env->enclosingEnvironment();
3094 }
3095
3096 // If this is an uninitialized lexical or a const, we need to return a
3097 // RuntimeLexicalErrorObject.
3098 auto* holder = &env->as<NativeObject>();
3099 if (prop.isSome() && holder->is<EnvironmentObject>() &&
3100 (holder->getSlot(prop->slot()).isMagic() || !prop->writable())) {
3101 return AttachDecision::NoAction;
3102 }
3103
3104 ObjOperandId lastObjId = objId;
3105 env = env_;
3106 while (env) {
3107 if (NeedEnvironmentShapeGuard(env) && !env->is<GlobalObject>()) {
3108 writer.guardShape(lastObjId, env->shape());
3109 }
3110
3111 if (env == holder) {
3112 break;
3113 }
3114
3115 lastObjId = writer.loadEnclosingEnvironment(lastObjId);
3116 env = env->enclosingEnvironment();
3117 }
3118 writer.loadObjectResult(lastObjId);
3119 writer.returnFromIC();
3120
3121 trackAttached("EnvironmentName");
3122 return AttachDecision::Attach;
3123 }
3124
trackAttached(const char * name)3125 void BindNameIRGenerator::trackAttached(const char* name) {
3126 #ifdef JS_CACHEIR_SPEW
3127 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
3128 sp.valueProperty("base", ObjectValue(*env_));
3129 sp.valueProperty("property", StringValue(name_));
3130 }
3131 #endif
3132 }
3133
HasPropIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,CacheKind cacheKind,HandleValue idVal,HandleValue val)3134 HasPropIRGenerator::HasPropIRGenerator(JSContext* cx, HandleScript script,
3135 jsbytecode* pc, ICState state,
3136 CacheKind cacheKind, HandleValue idVal,
3137 HandleValue val)
3138 : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {}
3139
tryAttachDense(HandleObject obj,ObjOperandId objId,uint32_t index,Int32OperandId indexId)3140 AttachDecision HasPropIRGenerator::tryAttachDense(HandleObject obj,
3141 ObjOperandId objId,
3142 uint32_t index,
3143 Int32OperandId indexId) {
3144 if (!obj->is<NativeObject>()) {
3145 return AttachDecision::NoAction;
3146 }
3147
3148 NativeObject* nobj = &obj->as<NativeObject>();
3149 if (!nobj->containsDenseElement(index)) {
3150 return AttachDecision::NoAction;
3151 }
3152
3153 // Guard shape to ensure object class is NativeObject.
3154 TestMatchingNativeReceiver(writer, nobj, objId);
3155 writer.loadDenseElementExistsResult(objId, indexId);
3156 writer.returnFromIC();
3157
3158 trackAttached("DenseHasProp");
3159 return AttachDecision::Attach;
3160 }
3161
tryAttachDenseHole(HandleObject obj,ObjOperandId objId,uint32_t index,Int32OperandId indexId)3162 AttachDecision HasPropIRGenerator::tryAttachDenseHole(HandleObject obj,
3163 ObjOperandId objId,
3164 uint32_t index,
3165 Int32OperandId indexId) {
3166 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3167 OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No;
3168
3169 if (!obj->is<NativeObject>()) {
3170 return AttachDecision::NoAction;
3171 }
3172
3173 NativeObject* nobj = &obj->as<NativeObject>();
3174 if (nobj->containsDenseElement(index)) {
3175 return AttachDecision::NoAction;
3176 }
3177 if (!CanAttachDenseElementHole(nobj, ownProp)) {
3178 return AttachDecision::NoAction;
3179 }
3180
3181 // Guard shape to ensure class is NativeObject and to prevent non-dense
3182 // elements being added. Also ensures prototype doesn't change if dynamic
3183 // checks aren't emitted.
3184 TestMatchingNativeReceiver(writer, nobj, objId);
3185
3186 // Generate prototype guards if needed. This includes monitoring that
3187 // properties were not added in the chain.
3188 if (!hasOwn) {
3189 GeneratePrototypeHoleGuards(writer, nobj, objId,
3190 /* alwaysGuardFirstProto = */ false);
3191 }
3192
3193 writer.loadDenseElementHoleExistsResult(objId, indexId);
3194 writer.returnFromIC();
3195
3196 trackAttached("DenseHasPropHole");
3197 return AttachDecision::Attach;
3198 }
3199
tryAttachSparse(HandleObject obj,ObjOperandId objId,Int32OperandId indexId)3200 AttachDecision HasPropIRGenerator::tryAttachSparse(HandleObject obj,
3201 ObjOperandId objId,
3202 Int32OperandId indexId) {
3203 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3204 OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No;
3205
3206 if (!obj->is<NativeObject>()) {
3207 return AttachDecision::NoAction;
3208 }
3209 auto* nobj = &obj->as<NativeObject>();
3210
3211 if (!nobj->isIndexed()) {
3212 return AttachDecision::NoAction;
3213 }
3214 if (!CanAttachDenseElementHole(nobj, ownProp, AllowIndexedReceiver::Yes)) {
3215 return AttachDecision::NoAction;
3216 }
3217
3218 // Guard that this is a native object.
3219 writer.guardIsNativeObject(objId);
3220
3221 // Generate prototype guards if needed. This includes monitoring that
3222 // properties were not added in the chain.
3223 if (!hasOwn) {
3224 GeneratePrototypeHoleGuards(writer, nobj, objId,
3225 /* alwaysGuardFirstProto = */ true);
3226 }
3227
3228 // Because of the prototype guard we know that the prototype chain
3229 // does not include any dense or sparse (i.e indexed) properties.
3230 writer.callObjectHasSparseElementResult(objId, indexId);
3231 writer.returnFromIC();
3232
3233 trackAttached("Sparse");
3234 return AttachDecision::Attach;
3235 }
3236
tryAttachArgumentsObjectArg(HandleObject obj,ObjOperandId objId,Int32OperandId indexId)3237 AttachDecision HasPropIRGenerator::tryAttachArgumentsObjectArg(
3238 HandleObject obj, ObjOperandId objId, Int32OperandId indexId) {
3239 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3240 OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No;
3241
3242 if (!obj->is<ArgumentsObject>()) {
3243 return AttachDecision::NoAction;
3244 }
3245 auto* args = &obj->as<ArgumentsObject>();
3246
3247 // No elements must have been overridden or deleted.
3248 if (args->hasOverriddenElement()) {
3249 return AttachDecision::NoAction;
3250 }
3251
3252 if (!CanAttachDenseElementHole(args, ownProp, AllowIndexedReceiver::Yes,
3253 AllowExtraReceiverProperties::Yes)) {
3254 return AttachDecision::NoAction;
3255 }
3256
3257 if (args->is<MappedArgumentsObject>()) {
3258 writer.guardClass(objId, GuardClassKind::MappedArguments);
3259 } else {
3260 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
3261 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
3262 }
3263
3264 if (!hasOwn) {
3265 GeneratePrototypeHoleGuards(writer, args, objId,
3266 /* alwaysGuardFirstProto = */ true);
3267 }
3268
3269 writer.loadArgumentsObjectArgExistsResult(objId, indexId);
3270 writer.returnFromIC();
3271
3272 trackAttached("ArgumentsObjectArg");
3273 return AttachDecision::Attach;
3274 }
3275
tryAttachNamedProp(HandleObject obj,ObjOperandId objId,HandleId key,ValOperandId keyId)3276 AttachDecision HasPropIRGenerator::tryAttachNamedProp(HandleObject obj,
3277 ObjOperandId objId,
3278 HandleId key,
3279 ValOperandId keyId) {
3280 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3281
3282 NativeObject* holder = nullptr;
3283 PropertyResult prop;
3284
3285 if (hasOwn) {
3286 if (!LookupOwnPropertyPure(cx_, obj, key, &prop)) {
3287 return AttachDecision::NoAction;
3288 }
3289
3290 holder = &obj->as<NativeObject>();
3291 } else {
3292 if (!LookupPropertyPure(cx_, obj, key, &holder, &prop)) {
3293 return AttachDecision::NoAction;
3294 }
3295 }
3296 if (prop.isNotFound()) {
3297 return AttachDecision::NoAction;
3298 }
3299 auto* nobj = &obj->as<NativeObject>();
3300
3301 TRY_ATTACH(tryAttachMegamorphic(objId, keyId));
3302 TRY_ATTACH(tryAttachNative(nobj, objId, key, keyId, prop, holder));
3303
3304 return AttachDecision::NoAction;
3305 }
3306
tryAttachMegamorphic(ObjOperandId objId,ValOperandId keyId)3307 AttachDecision HasPropIRGenerator::tryAttachMegamorphic(ObjOperandId objId,
3308 ValOperandId keyId) {
3309 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3310
3311 if (mode_ != ICState::Mode::Megamorphic) {
3312 return AttachDecision::NoAction;
3313 }
3314
3315 writer.megamorphicHasPropResult(objId, keyId, hasOwn);
3316 writer.returnFromIC();
3317 trackAttached("MegamorphicHasProp");
3318 return AttachDecision::Attach;
3319 }
3320
tryAttachNative(NativeObject * obj,ObjOperandId objId,jsid key,ValOperandId keyId,PropertyResult prop,NativeObject * holder)3321 AttachDecision HasPropIRGenerator::tryAttachNative(NativeObject* obj,
3322 ObjOperandId objId, jsid key,
3323 ValOperandId keyId,
3324 PropertyResult prop,
3325 NativeObject* holder) {
3326 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
3327
3328 if (!prop.isNativeProperty()) {
3329 return AttachDecision::NoAction;
3330 }
3331
3332 Maybe<ObjOperandId> tempId;
3333 emitIdGuard(keyId, idVal_, key);
3334 EmitReadSlotGuard(writer, obj, holder, objId, &tempId);
3335 writer.loadBooleanResult(true);
3336 writer.returnFromIC();
3337
3338 trackAttached("NativeHasProp");
3339 return AttachDecision::Attach;
3340 }
3341
tryAttachTypedArray(HandleObject obj,ObjOperandId objId,ValOperandId keyId)3342 AttachDecision HasPropIRGenerator::tryAttachTypedArray(HandleObject obj,
3343 ObjOperandId objId,
3344 ValOperandId keyId) {
3345 if (!obj->is<TypedArrayObject>()) {
3346 return AttachDecision::NoAction;
3347 }
3348
3349 int64_t index;
3350 if (!ValueIsInt64Index(idVal_, &index)) {
3351 return AttachDecision::NoAction;
3352 }
3353
3354 writer.guardIsTypedArray(objId);
3355 IntPtrOperandId intPtrIndexId =
3356 guardToIntPtrIndex(idVal_, keyId, /* supportOOB = */ true);
3357 writer.loadTypedArrayElementExistsResult(objId, intPtrIndexId);
3358 writer.returnFromIC();
3359
3360 trackAttached("TypedArrayObject");
3361 return AttachDecision::Attach;
3362 }
3363
tryAttachSlotDoesNotExist(NativeObject * obj,ObjOperandId objId,jsid key,ValOperandId keyId)3364 AttachDecision HasPropIRGenerator::tryAttachSlotDoesNotExist(
3365 NativeObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId) {
3366 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3367
3368 emitIdGuard(keyId, idVal_, key);
3369 if (hasOwn) {
3370 TestMatchingNativeReceiver(writer, obj, objId);
3371 } else {
3372 Maybe<ObjOperandId> tempId;
3373 EmitReadSlotGuard(writer, obj, nullptr, objId, &tempId);
3374 }
3375 writer.loadBooleanResult(false);
3376 writer.returnFromIC();
3377
3378 trackAttached("DoesNotExist");
3379 return AttachDecision::Attach;
3380 }
3381
tryAttachDoesNotExist(HandleObject obj,ObjOperandId objId,HandleId key,ValOperandId keyId)3382 AttachDecision HasPropIRGenerator::tryAttachDoesNotExist(HandleObject obj,
3383 ObjOperandId objId,
3384 HandleId key,
3385 ValOperandId keyId) {
3386 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3387
3388 // Check that property doesn't exist on |obj| or it's prototype chain. These
3389 // checks allow NativeObjects with a NativeObject prototype chain. They return
3390 // NoAction if unknown such as resolve hooks or proxies.
3391 if (hasOwn) {
3392 if (!CheckHasNoSuchOwnProperty(cx_, obj, key)) {
3393 return AttachDecision::NoAction;
3394 }
3395 } else {
3396 if (!CheckHasNoSuchProperty(cx_, obj, key)) {
3397 return AttachDecision::NoAction;
3398 }
3399 }
3400 auto* nobj = &obj->as<NativeObject>();
3401
3402 TRY_ATTACH(tryAttachMegamorphic(objId, keyId));
3403 TRY_ATTACH(tryAttachSlotDoesNotExist(nobj, objId, key, keyId));
3404
3405 return AttachDecision::NoAction;
3406 }
3407
tryAttachProxyElement(HandleObject obj,ObjOperandId objId,ValOperandId keyId)3408 AttachDecision HasPropIRGenerator::tryAttachProxyElement(HandleObject obj,
3409 ObjOperandId objId,
3410 ValOperandId keyId) {
3411 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3412
3413 if (!obj->is<ProxyObject>()) {
3414 return AttachDecision::NoAction;
3415 }
3416
3417 writer.guardIsProxy(objId);
3418 writer.proxyHasPropResult(objId, keyId, hasOwn);
3419 writer.returnFromIC();
3420
3421 trackAttached("ProxyHasProp");
3422 return AttachDecision::Attach;
3423 }
3424
tryAttachStub()3425 AttachDecision HasPropIRGenerator::tryAttachStub() {
3426 MOZ_ASSERT(cacheKind_ == CacheKind::In || cacheKind_ == CacheKind::HasOwn);
3427
3428 AutoAssertNoPendingException aanpe(cx_);
3429
3430 // NOTE: Argument order is PROPERTY, OBJECT
3431 ValOperandId keyId(writer.setInputOperandId(0));
3432 ValOperandId valId(writer.setInputOperandId(1));
3433
3434 if (!val_.isObject()) {
3435 trackAttached(IRGenerator::NotAttached);
3436 return AttachDecision::NoAction;
3437 }
3438 RootedObject obj(cx_, &val_.toObject());
3439 ObjOperandId objId = writer.guardToObject(valId);
3440
3441 // Optimize Proxies
3442 TRY_ATTACH(tryAttachProxyElement(obj, objId, keyId));
3443
3444 RootedId id(cx_);
3445 bool nameOrSymbol;
3446 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
3447 cx_->clearPendingException();
3448 return AttachDecision::NoAction;
3449 }
3450
3451 if (nameOrSymbol) {
3452 TRY_ATTACH(tryAttachNamedProp(obj, objId, id, keyId));
3453 TRY_ATTACH(tryAttachDoesNotExist(obj, objId, id, keyId));
3454
3455 trackAttached(IRGenerator::NotAttached);
3456 return AttachDecision::NoAction;
3457 }
3458
3459 TRY_ATTACH(tryAttachTypedArray(obj, objId, keyId));
3460
3461 uint32_t index;
3462 Int32OperandId indexId;
3463 if (maybeGuardInt32Index(idVal_, keyId, &index, &indexId)) {
3464 TRY_ATTACH(tryAttachDense(obj, objId, index, indexId));
3465 TRY_ATTACH(tryAttachDenseHole(obj, objId, index, indexId));
3466 TRY_ATTACH(tryAttachSparse(obj, objId, indexId));
3467 TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, indexId));
3468
3469 trackAttached(IRGenerator::NotAttached);
3470 return AttachDecision::NoAction;
3471 }
3472
3473 trackAttached(IRGenerator::NotAttached);
3474 return AttachDecision::NoAction;
3475 }
3476
trackAttached(const char * name)3477 void HasPropIRGenerator::trackAttached(const char* name) {
3478 #ifdef JS_CACHEIR_SPEW
3479 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
3480 sp.valueProperty("base", val_);
3481 sp.valueProperty("property", idVal_);
3482 }
3483 #endif
3484 }
3485
CheckPrivateFieldIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,CacheKind cacheKind,HandleValue idVal,HandleValue val)3486 CheckPrivateFieldIRGenerator::CheckPrivateFieldIRGenerator(
3487 JSContext* cx, HandleScript script, jsbytecode* pc, ICState state,
3488 CacheKind cacheKind, HandleValue idVal, HandleValue val)
3489 : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {
3490 MOZ_ASSERT(idVal.isSymbol() && idVal.toSymbol()->isPrivateName());
3491 }
3492
tryAttachStub()3493 AttachDecision CheckPrivateFieldIRGenerator::tryAttachStub() {
3494 AutoAssertNoPendingException aanpe(cx_);
3495
3496 ValOperandId valId(writer.setInputOperandId(0));
3497 ValOperandId keyId(writer.setInputOperandId(1));
3498
3499 if (!val_.isObject()) {
3500 trackAttached(IRGenerator::NotAttached);
3501 return AttachDecision::NoAction;
3502 }
3503 JSObject* obj = &val_.toObject();
3504 ObjOperandId objId = writer.guardToObject(valId);
3505 PropertyKey key = PropertyKey::Symbol(idVal_.toSymbol());
3506
3507 ThrowCondition condition;
3508 ThrowMsgKind msgKind;
3509 GetCheckPrivateFieldOperands(pc_, &condition, &msgKind);
3510
3511 bool hasOwn = false;
3512 if (!HasOwnDataPropertyPure(cx_, obj, key, &hasOwn)) {
3513 // Can't determine if HasOwnProperty purely.
3514 return AttachDecision::NoAction;
3515 }
3516
3517 if (CheckPrivateFieldWillThrow(condition, hasOwn)) {
3518 // Don't attach a stub if the operation will throw.
3519 return AttachDecision::NoAction;
3520 }
3521
3522 TRY_ATTACH(tryAttachNative(obj, objId, key, keyId, hasOwn));
3523
3524 return AttachDecision::NoAction;
3525 }
3526
tryAttachNative(JSObject * obj,ObjOperandId objId,jsid key,ValOperandId keyId,bool hasOwn)3527 AttachDecision CheckPrivateFieldIRGenerator::tryAttachNative(JSObject* obj,
3528 ObjOperandId objId,
3529 jsid key,
3530 ValOperandId keyId,
3531 bool hasOwn) {
3532 if (!obj->is<NativeObject>()) {
3533 return AttachDecision::NoAction;
3534 }
3535 auto* nobj = &obj->as<NativeObject>();
3536
3537 Maybe<ObjOperandId> tempId;
3538 emitIdGuard(keyId, idVal_, key);
3539 EmitReadSlotGuard(writer, nobj, nobj, objId, &tempId);
3540 writer.loadBooleanResult(hasOwn);
3541 writer.returnFromIC();
3542
3543 trackAttached("NativeCheckPrivateField");
3544 return AttachDecision::Attach;
3545 }
3546
trackAttached(const char * name)3547 void CheckPrivateFieldIRGenerator::trackAttached(const char* name) {
3548 #ifdef JS_CACHEIR_SPEW
3549 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
3550 sp.valueProperty("base", val_);
3551 sp.valueProperty("property", idVal_);
3552 }
3553 #endif
3554 }
3555
maybeGuardInt32Index(const Value & index,ValOperandId indexId,uint32_t * int32Index,Int32OperandId * int32IndexId)3556 bool IRGenerator::maybeGuardInt32Index(const Value& index, ValOperandId indexId,
3557 uint32_t* int32Index,
3558 Int32OperandId* int32IndexId) {
3559 if (index.isNumber()) {
3560 int32_t indexSigned;
3561 if (index.isInt32()) {
3562 indexSigned = index.toInt32();
3563 } else {
3564 // We allow negative zero here.
3565 if (!mozilla::NumberEqualsInt32(index.toDouble(), &indexSigned)) {
3566 return false;
3567 }
3568 }
3569
3570 if (indexSigned < 0) {
3571 return false;
3572 }
3573
3574 *int32Index = uint32_t(indexSigned);
3575 *int32IndexId = writer.guardToInt32Index(indexId);
3576 return true;
3577 }
3578
3579 if (index.isString()) {
3580 int32_t indexSigned = GetIndexFromString(index.toString());
3581 if (indexSigned < 0) {
3582 return false;
3583 }
3584
3585 StringOperandId strId = writer.guardToString(indexId);
3586 *int32Index = uint32_t(indexSigned);
3587 *int32IndexId = writer.guardStringToIndex(strId);
3588 return true;
3589 }
3590
3591 return false;
3592 }
3593
SetPropIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,CacheKind cacheKind,ICState state,HandleValue lhsVal,HandleValue idVal,HandleValue rhsVal)3594 SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, HandleScript script,
3595 jsbytecode* pc, CacheKind cacheKind,
3596 ICState state, HandleValue lhsVal,
3597 HandleValue idVal, HandleValue rhsVal)
3598 : IRGenerator(cx, script, pc, cacheKind, state),
3599 lhsVal_(lhsVal),
3600 idVal_(idVal),
3601 rhsVal_(rhsVal) {}
3602
tryAttachStub()3603 AttachDecision SetPropIRGenerator::tryAttachStub() {
3604 AutoAssertNoPendingException aanpe(cx_);
3605
3606 ValOperandId objValId(writer.setInputOperandId(0));
3607 ValOperandId rhsValId;
3608 if (cacheKind_ == CacheKind::SetProp) {
3609 rhsValId = ValOperandId(writer.setInputOperandId(1));
3610 } else {
3611 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
3612 MOZ_ASSERT(setElemKeyValueId().id() == 1);
3613 writer.setInputOperandId(1);
3614 rhsValId = ValOperandId(writer.setInputOperandId(2));
3615 }
3616
3617 RootedId id(cx_);
3618 bool nameOrSymbol;
3619 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
3620 cx_->clearPendingException();
3621 return AttachDecision::NoAction;
3622 }
3623
3624 if (lhsVal_.isObject()) {
3625 RootedObject obj(cx_, &lhsVal_.toObject());
3626
3627 ObjOperandId objId = writer.guardToObject(objValId);
3628 if (IsPropertySetOp(JSOp(*pc_))) {
3629 TRY_ATTACH(tryAttachMegamorphicSetElement(obj, objId, rhsValId));
3630 }
3631 if (nameOrSymbol) {
3632 TRY_ATTACH(tryAttachNativeSetSlot(obj, objId, id, rhsValId));
3633 if (IsPropertySetOp(JSOp(*pc_))) {
3634 TRY_ATTACH(tryAttachSetArrayLength(obj, objId, id, rhsValId));
3635 TRY_ATTACH(tryAttachSetter(obj, objId, id, rhsValId));
3636 TRY_ATTACH(tryAttachWindowProxy(obj, objId, id, rhsValId));
3637 TRY_ATTACH(tryAttachProxy(obj, objId, id, rhsValId));
3638 }
3639 if (canAttachAddSlotStub(obj, id)) {
3640 deferType_ = DeferType::AddSlot;
3641 return AttachDecision::Deferred;
3642 }
3643 return AttachDecision::NoAction;
3644 }
3645
3646 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
3647
3648 if (IsPropertySetOp(JSOp(*pc_))) {
3649 TRY_ATTACH(tryAttachProxyElement(obj, objId, rhsValId));
3650 }
3651
3652 TRY_ATTACH(tryAttachSetTypedArrayElement(obj, objId, rhsValId));
3653
3654 uint32_t index;
3655 Int32OperandId indexId;
3656 if (maybeGuardInt32Index(idVal_, setElemKeyValueId(), &index, &indexId)) {
3657 TRY_ATTACH(
3658 tryAttachSetDenseElement(obj, objId, index, indexId, rhsValId));
3659 TRY_ATTACH(
3660 tryAttachSetDenseElementHole(obj, objId, index, indexId, rhsValId));
3661 TRY_ATTACH(tryAttachAddOrUpdateSparseElement(obj, objId, index, indexId,
3662 rhsValId));
3663 return AttachDecision::NoAction;
3664 }
3665 }
3666 return AttachDecision::NoAction;
3667 }
3668
EmitStoreSlotAndReturn(CacheIRWriter & writer,ObjOperandId objId,NativeObject * nobj,PropertyInfo prop,ValOperandId rhsId)3669 static void EmitStoreSlotAndReturn(CacheIRWriter& writer, ObjOperandId objId,
3670 NativeObject* nobj, PropertyInfo prop,
3671 ValOperandId rhsId) {
3672 if (nobj->isFixedSlot(prop.slot())) {
3673 size_t offset = NativeObject::getFixedSlotOffset(prop.slot());
3674 writer.storeFixedSlot(objId, offset, rhsId);
3675 } else {
3676 size_t offset = nobj->dynamicSlotIndex(prop.slot()) * sizeof(Value);
3677 writer.storeDynamicSlot(objId, offset, rhsId);
3678 }
3679 writer.returnFromIC();
3680 }
3681
LookupShapeForSetSlot(JSOp op,NativeObject * obj,jsid id)3682 static Maybe<PropertyInfo> LookupShapeForSetSlot(JSOp op, NativeObject* obj,
3683 jsid id) {
3684 Maybe<PropertyInfo> prop = obj->lookupPure(id);
3685 if (prop.isNothing() || !prop->isDataProperty() || !prop->writable()) {
3686 return mozilla::Nothing();
3687 }
3688
3689 // If this is an op like JSOp::InitElem / [[DefineOwnProperty]], the
3690 // property's attributes may have to be changed too, so make sure it's a
3691 // simple data property.
3692 if (IsPropertyInitOp(op) && (!prop->configurable() || !prop->enumerable())) {
3693 return mozilla::Nothing();
3694 }
3695
3696 return prop;
3697 }
3698
CanAttachNativeSetSlot(JSOp op,JSObject * obj,PropertyKey id,Maybe<PropertyInfo> * prop)3699 static bool CanAttachNativeSetSlot(JSOp op, JSObject* obj, PropertyKey id,
3700 Maybe<PropertyInfo>* prop) {
3701 if (!obj->is<NativeObject>()) {
3702 return false;
3703 }
3704
3705 *prop = LookupShapeForSetSlot(op, &obj->as<NativeObject>(), id);
3706 return prop->isSome();
3707 }
3708
3709 // There is no need to guard on the shape. Global lexical bindings are
3710 // non-configurable and can not be shadowed.
IsGlobalLexicalSetGName(JSOp op,NativeObject * obj,PropertyInfo prop)3711 static bool IsGlobalLexicalSetGName(JSOp op, NativeObject* obj,
3712 PropertyInfo prop) {
3713 // Ensure that the env can't change.
3714 if (op != JSOp::SetGName && op != JSOp::StrictSetGName) {
3715 return false;
3716 }
3717
3718 if (!obj->is<GlobalLexicalEnvironmentObject>()) {
3719 return false;
3720 }
3721
3722 // Uninitialized let bindings use a RuntimeLexicalErrorObject.
3723 MOZ_ASSERT(!obj->getSlot(prop.slot()).isMagic());
3724 MOZ_ASSERT(prop.writable());
3725 MOZ_ASSERT(!prop.configurable());
3726 return true;
3727 }
3728
tryAttachNativeSetSlot(HandleObject obj,ObjOperandId objId,HandleId id,ValOperandId rhsId)3729 AttachDecision SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj,
3730 ObjOperandId objId,
3731 HandleId id,
3732 ValOperandId rhsId) {
3733 Maybe<PropertyInfo> prop;
3734 if (!CanAttachNativeSetSlot(JSOp(*pc_), obj, id, &prop)) {
3735 return AttachDecision::NoAction;
3736 }
3737
3738 // Don't attach a megamorphic store slot stub for ops like JSOp::InitElem.
3739 if (mode_ == ICState::Mode::Megamorphic && cacheKind_ == CacheKind::SetProp &&
3740 IsPropertySetOp(JSOp(*pc_))) {
3741 writer.megamorphicStoreSlot(objId, id.toAtom()->asPropertyName(), rhsId);
3742 writer.returnFromIC();
3743 trackAttached("MegamorphicNativeSlot");
3744 return AttachDecision::Attach;
3745 }
3746
3747 maybeEmitIdGuard(id);
3748
3749 NativeObject* nobj = &obj->as<NativeObject>();
3750 if (!IsGlobalLexicalSetGName(JSOp(*pc_), nobj, *prop)) {
3751 TestMatchingNativeReceiver(writer, nobj, objId);
3752 }
3753
3754 EmitStoreSlotAndReturn(writer, objId, nobj, *prop, rhsId);
3755
3756 trackAttached("NativeSlot");
3757 return AttachDecision::Attach;
3758 }
3759
emitNumericGuard(ValOperandId valId,Scalar::Type type)3760 OperandId IRGenerator::emitNumericGuard(ValOperandId valId, Scalar::Type type) {
3761 switch (type) {
3762 case Scalar::Int8:
3763 case Scalar::Uint8:
3764 case Scalar::Int16:
3765 case Scalar::Uint16:
3766 case Scalar::Int32:
3767 case Scalar::Uint32:
3768 return writer.guardToInt32ModUint32(valId);
3769
3770 case Scalar::Float32:
3771 case Scalar::Float64:
3772 return writer.guardIsNumber(valId);
3773
3774 case Scalar::Uint8Clamped:
3775 return writer.guardToUint8Clamped(valId);
3776
3777 case Scalar::BigInt64:
3778 case Scalar::BigUint64:
3779 return writer.guardToBigInt(valId);
3780
3781 case Scalar::MaxTypedArrayViewType:
3782 case Scalar::Int64:
3783 case Scalar::Simd128:
3784 break;
3785 }
3786 MOZ_CRASH("Unsupported TypedArray type");
3787 }
3788
ValueIsNumeric(Scalar::Type type,const Value & val)3789 static bool ValueIsNumeric(Scalar::Type type, const Value& val) {
3790 if (Scalar::isBigIntType(type)) {
3791 return val.isBigInt();
3792 }
3793 return val.isNumber();
3794 }
3795
trackAttached(const char * name)3796 void SetPropIRGenerator::trackAttached(const char* name) {
3797 #ifdef JS_CACHEIR_SPEW
3798 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
3799 sp.opcodeProperty("op", JSOp(*pc_));
3800 sp.valueProperty("base", lhsVal_);
3801 sp.valueProperty("property", idVal_);
3802 sp.valueProperty("value", rhsVal_);
3803 }
3804 #endif
3805 }
3806
IsCacheableSetPropCallNative(NativeObject * obj,NativeObject * holder,PropertyInfo prop)3807 static bool IsCacheableSetPropCallNative(NativeObject* obj,
3808 NativeObject* holder,
3809 PropertyInfo prop) {
3810 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
3811
3812 if (!prop.isAccessorProperty()) {
3813 return false;
3814 }
3815
3816 JSObject* setterObject = holder->getSetter(prop);
3817 if (!setterObject || !setterObject->is<JSFunction>()) {
3818 return false;
3819 }
3820
3821 JSFunction& setter = setterObject->as<JSFunction>();
3822 if (!setter.isNativeWithoutJitEntry()) {
3823 return false;
3824 }
3825
3826 if (setter.isClassConstructor()) {
3827 return false;
3828 }
3829
3830 if (setter.hasJitInfo() && !setter.jitInfo()->needsOuterizedThisObject()) {
3831 return true;
3832 }
3833
3834 return !IsWindow(obj);
3835 }
3836
IsCacheableSetPropCallScripted(NativeObject * obj,NativeObject * holder,PropertyInfo prop)3837 static bool IsCacheableSetPropCallScripted(NativeObject* obj,
3838 NativeObject* holder,
3839 PropertyInfo prop) {
3840 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
3841
3842 if (IsWindow(obj)) {
3843 return false;
3844 }
3845
3846 if (!prop.isAccessorProperty()) {
3847 return false;
3848 }
3849
3850 JSObject* setterObject = holder->getSetter(prop);
3851 if (!setterObject || !setterObject->is<JSFunction>()) {
3852 return false;
3853 }
3854
3855 JSFunction& setter = setterObject->as<JSFunction>();
3856 if (setter.isClassConstructor()) {
3857 return false;
3858 }
3859
3860 // Scripted functions and natives with JIT entry can use the scripted path.
3861 return setter.hasJitEntry();
3862 }
3863
CanAttachSetter(JSContext * cx,jsbytecode * pc,JSObject * obj,PropertyKey id,NativeObject ** holder,Maybe<PropertyInfo> * propInfo)3864 static bool CanAttachSetter(JSContext* cx, jsbytecode* pc, JSObject* obj,
3865 PropertyKey id, NativeObject** holder,
3866 Maybe<PropertyInfo>* propInfo) {
3867 // Don't attach a setter stub for ops like JSOp::InitElem.
3868 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc)));
3869
3870 PropertyResult prop;
3871 if (!LookupPropertyPure(cx, obj, id, holder, &prop)) {
3872 return false;
3873 }
3874 auto* nobj = &obj->as<NativeObject>();
3875
3876 if (!prop.isNativeProperty()) {
3877 return false;
3878 }
3879
3880 if (!IsCacheableSetPropCallScripted(nobj, *holder, prop.propertyInfo()) &&
3881 !IsCacheableSetPropCallNative(nobj, *holder, prop.propertyInfo())) {
3882 return false;
3883 }
3884
3885 *propInfo = mozilla::Some(prop.propertyInfo());
3886 return true;
3887 }
3888
EmitCallSetterNoGuards(JSContext * cx,CacheIRWriter & writer,NativeObject * obj,NativeObject * holder,PropertyInfo prop,ObjOperandId objId,ValOperandId rhsId)3889 static void EmitCallSetterNoGuards(JSContext* cx, CacheIRWriter& writer,
3890 NativeObject* obj, NativeObject* holder,
3891 PropertyInfo prop, ObjOperandId objId,
3892 ValOperandId rhsId) {
3893 JSFunction* target = &holder->getSetter(prop)->as<JSFunction>();
3894 bool sameRealm = cx->realm() == target->realm();
3895
3896 if (target->isNativeWithoutJitEntry()) {
3897 MOZ_ASSERT(IsCacheableSetPropCallNative(obj, holder, prop));
3898 writer.callNativeSetter(objId, target, rhsId, sameRealm);
3899 writer.returnFromIC();
3900 return;
3901 }
3902
3903 MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, prop));
3904 writer.callScriptedSetter(objId, target, rhsId, sameRealm);
3905 writer.returnFromIC();
3906 }
3907
EmitCallDOMSetterNoGuards(JSContext * cx,CacheIRWriter & writer,NativeObject * holder,PropertyInfo prop,ObjOperandId objId,ValOperandId rhsId)3908 static void EmitCallDOMSetterNoGuards(JSContext* cx, CacheIRWriter& writer,
3909 NativeObject* holder, PropertyInfo prop,
3910 ObjOperandId objId, ValOperandId rhsId) {
3911 JSFunction* setter = &holder->getSetter(prop)->as<JSFunction>();
3912 MOZ_ASSERT(cx->realm() == setter->realm());
3913
3914 writer.callDOMSetter(objId, setter->jitInfo(), rhsId);
3915 writer.returnFromIC();
3916 }
3917
tryAttachSetter(HandleObject obj,ObjOperandId objId,HandleId id,ValOperandId rhsId)3918 AttachDecision SetPropIRGenerator::tryAttachSetter(HandleObject obj,
3919 ObjOperandId objId,
3920 HandleId id,
3921 ValOperandId rhsId) {
3922 NativeObject* holder = nullptr;
3923 Maybe<PropertyInfo> prop;
3924 if (!CanAttachSetter(cx_, pc_, obj, id, &holder, &prop)) {
3925 return AttachDecision::NoAction;
3926 }
3927 auto* nobj = &obj->as<NativeObject>();
3928
3929 maybeEmitIdGuard(id);
3930
3931 // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
3932 // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
3933 // require outerizing).
3934 if (mode_ == ICState::Mode::Specialized || IsWindow(nobj)) {
3935 TestMatchingNativeReceiver(writer, nobj, objId);
3936
3937 if (nobj != holder) {
3938 GeneratePrototypeGuards(writer, nobj, holder, objId);
3939
3940 // Guard on the holder's shape.
3941 ObjOperandId holderId = writer.loadObject(holder);
3942 TestMatchingHolder(writer, holder, holderId);
3943
3944 EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
3945 /* holderIsConstant = */ true);
3946 } else {
3947 EmitGuardGetterSetterSlot(writer, holder, *prop, objId);
3948 }
3949 } else {
3950 GetterSetter* gs = holder->getGetterSetter(*prop);
3951 writer.guardHasGetterSetter(objId, id, gs);
3952 }
3953
3954 if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Setter, nobj, holder, *prop,
3955 mode_)) {
3956 EmitCallDOMSetterNoGuards(cx_, writer, holder, *prop, objId, rhsId);
3957
3958 trackAttached("DOMSetter");
3959 return AttachDecision::Attach;
3960 }
3961
3962 EmitCallSetterNoGuards(cx_, writer, nobj, holder, *prop, objId, rhsId);
3963
3964 trackAttached("Setter");
3965 return AttachDecision::Attach;
3966 }
3967
tryAttachSetArrayLength(HandleObject obj,ObjOperandId objId,HandleId id,ValOperandId rhsId)3968 AttachDecision SetPropIRGenerator::tryAttachSetArrayLength(HandleObject obj,
3969 ObjOperandId objId,
3970 HandleId id,
3971 ValOperandId rhsId) {
3972 // Don't attach an array length stub for ops like JSOp::InitElem.
3973 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
3974
3975 if (!obj->is<ArrayObject>() || !id.isAtom(cx_->names().length) ||
3976 !obj->as<ArrayObject>().lengthIsWritable()) {
3977 return AttachDecision::NoAction;
3978 }
3979
3980 maybeEmitIdGuard(id);
3981 writer.guardClass(objId, GuardClassKind::Array);
3982 writer.callSetArrayLength(objId, IsStrictSetPC(pc_), rhsId);
3983 writer.returnFromIC();
3984
3985 trackAttached("SetArrayLength");
3986 return AttachDecision::Attach;
3987 }
3988
tryAttachSetDenseElement(HandleObject obj,ObjOperandId objId,uint32_t index,Int32OperandId indexId,ValOperandId rhsId)3989 AttachDecision SetPropIRGenerator::tryAttachSetDenseElement(
3990 HandleObject obj, ObjOperandId objId, uint32_t index,
3991 Int32OperandId indexId, ValOperandId rhsId) {
3992 if (!obj->is<NativeObject>()) {
3993 return AttachDecision::NoAction;
3994 }
3995
3996 NativeObject* nobj = &obj->as<NativeObject>();
3997 if (!nobj->containsDenseElement(index) || nobj->denseElementsAreFrozen()) {
3998 return AttachDecision::NoAction;
3999 }
4000
4001 // Setting holes requires extra code for marking the elements non-packed.
4002 MOZ_ASSERT(!rhsVal_.isMagic(JS_ELEMENTS_HOLE));
4003
4004 // Don't optimize InitElem (DefineProperty) on non-extensible objects: when
4005 // the elements are sealed, we have to throw an exception. Note that we have
4006 // to check !isExtensible instead of denseElementsAreSealed because sealing
4007 // a (non-extensible) object does not necessarily trigger a Shape change.
4008 if (IsPropertyInitOp(JSOp(*pc_)) && !nobj->isExtensible()) {
4009 return AttachDecision::NoAction;
4010 }
4011
4012 TestMatchingNativeReceiver(writer, nobj, objId);
4013
4014 writer.storeDenseElement(objId, indexId, rhsId);
4015 writer.returnFromIC();
4016
4017 trackAttached("SetDenseElement");
4018 return AttachDecision::Attach;
4019 }
4020
CanAttachAddElement(NativeObject * obj,bool isInit)4021 static bool CanAttachAddElement(NativeObject* obj, bool isInit) {
4022 // Make sure the objects on the prototype don't have any indexed properties
4023 // or that such properties can't appear without a shape change.
4024 do {
4025 // The first two checks are also relevant to the receiver object.
4026 if (obj->isIndexed()) {
4027 return false;
4028 }
4029
4030 const JSClass* clasp = obj->getClass();
4031 if (clasp != &ArrayObject::class_ &&
4032 (clasp->getAddProperty() || clasp->getResolve() ||
4033 clasp->getOpsLookupProperty() || clasp->getOpsSetProperty())) {
4034 return false;
4035 }
4036
4037 // If we're initializing a property instead of setting one, the objects
4038 // on the prototype are not relevant.
4039 if (isInit) {
4040 break;
4041 }
4042
4043 JSObject* proto = obj->staticPrototype();
4044 if (!proto) {
4045 break;
4046 }
4047
4048 if (!proto->is<NativeObject>()) {
4049 return false;
4050 }
4051
4052 // We have to make sure the proto has no non-writable (frozen) elements
4053 // because we're not allowed to shadow them.
4054 NativeObject* nproto = &proto->as<NativeObject>();
4055 if (nproto->denseElementsAreFrozen() &&
4056 nproto->getDenseInitializedLength() > 0) {
4057 return false;
4058 }
4059
4060 obj = nproto;
4061 } while (true);
4062
4063 return true;
4064 }
4065
tryAttachSetDenseElementHole(HandleObject obj,ObjOperandId objId,uint32_t index,Int32OperandId indexId,ValOperandId rhsId)4066 AttachDecision SetPropIRGenerator::tryAttachSetDenseElementHole(
4067 HandleObject obj, ObjOperandId objId, uint32_t index,
4068 Int32OperandId indexId, ValOperandId rhsId) {
4069 if (!obj->is<NativeObject>()) {
4070 return AttachDecision::NoAction;
4071 }
4072
4073 // Setting holes requires extra code for marking the elements non-packed.
4074 if (rhsVal_.isMagic(JS_ELEMENTS_HOLE)) {
4075 return AttachDecision::NoAction;
4076 }
4077
4078 JSOp op = JSOp(*pc_);
4079 MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
4080
4081 if (op == JSOp::InitHiddenElem) {
4082 return AttachDecision::NoAction;
4083 }
4084
4085 NativeObject* nobj = &obj->as<NativeObject>();
4086 if (!nobj->isExtensible()) {
4087 return AttachDecision::NoAction;
4088 }
4089
4090 MOZ_ASSERT(!nobj->denseElementsAreFrozen(),
4091 "Extensible objects should not have frozen elements");
4092
4093 uint32_t initLength = nobj->getDenseInitializedLength();
4094
4095 // Optimize if we're adding an element at initLength or writing to a hole.
4096 //
4097 // In the case where index > initLength, we need noteHasDenseAdd to be called
4098 // to ensure Ion is aware that writes have occurred to-out-of-bound indexes
4099 // before.
4100 bool isAdd = index == initLength;
4101 bool isHoleInBounds =
4102 index < initLength && !nobj->containsDenseElement(index);
4103 if (!isAdd && !isHoleInBounds) {
4104 return AttachDecision::NoAction;
4105 }
4106
4107 // Can't add new elements to arrays with non-writable length.
4108 if (isAdd && nobj->is<ArrayObject>() &&
4109 !nobj->as<ArrayObject>().lengthIsWritable()) {
4110 return AttachDecision::NoAction;
4111 }
4112
4113 // Typed arrays don't have dense elements.
4114 if (nobj->is<TypedArrayObject>()) {
4115 return AttachDecision::NoAction;
4116 }
4117
4118 // Check for other indexed properties or class hooks.
4119 if (!CanAttachAddElement(nobj, IsPropertyInitOp(op))) {
4120 return AttachDecision::NoAction;
4121 }
4122
4123 TestMatchingNativeReceiver(writer, nobj, objId);
4124
4125 // Also shape guard the proto chain, unless this is an InitElem.
4126 if (IsPropertySetOp(op)) {
4127 ShapeGuardProtoChain(writer, nobj, objId);
4128 }
4129
4130 writer.storeDenseElementHole(objId, indexId, rhsId, isAdd);
4131 writer.returnFromIC();
4132
4133 trackAttached(isAdd ? "AddDenseElement" : "StoreDenseElementHole");
4134 return AttachDecision::Attach;
4135 }
4136
4137 // Add an IC for adding or updating a sparse array element.
tryAttachAddOrUpdateSparseElement(HandleObject obj,ObjOperandId objId,uint32_t index,Int32OperandId indexId,ValOperandId rhsId)4138 AttachDecision SetPropIRGenerator::tryAttachAddOrUpdateSparseElement(
4139 HandleObject obj, ObjOperandId objId, uint32_t index,
4140 Int32OperandId indexId, ValOperandId rhsId) {
4141 JSOp op = JSOp(*pc_);
4142 MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
4143
4144 if (op != JSOp::SetElem && op != JSOp::StrictSetElem) {
4145 return AttachDecision::NoAction;
4146 }
4147
4148 if (!obj->is<NativeObject>()) {
4149 return AttachDecision::NoAction;
4150 }
4151 NativeObject* nobj = &obj->as<NativeObject>();
4152
4153 // We cannot attach a stub to a non-extensible object
4154 if (!nobj->isExtensible()) {
4155 return AttachDecision::NoAction;
4156 }
4157
4158 // Stub doesn't handle negative indices.
4159 if (index > INT_MAX) {
4160 return AttachDecision::NoAction;
4161 }
4162
4163 // We also need to be past the end of the dense capacity, to ensure sparse.
4164 if (index < nobj->getDenseInitializedLength()) {
4165 return AttachDecision::NoAction;
4166 }
4167
4168 // Only handle Array objects in this stub.
4169 if (!nobj->is<ArrayObject>()) {
4170 return AttachDecision::NoAction;
4171 }
4172 ArrayObject* aobj = &nobj->as<ArrayObject>();
4173
4174 // Don't attach if we're adding to an array with non-writable length.
4175 bool isAdd = (index >= aobj->length());
4176 if (isAdd && !aobj->lengthIsWritable()) {
4177 return AttachDecision::NoAction;
4178 }
4179
4180 // Indexed properties on the prototype chain aren't handled by the helper.
4181 if ((aobj->staticPrototype() != nullptr) &&
4182 ObjectMayHaveExtraIndexedProperties(aobj->staticPrototype())) {
4183 return AttachDecision::NoAction;
4184 }
4185
4186 // Ensure we are still talking about an array class.
4187 writer.guardClass(objId, GuardClassKind::Array);
4188
4189 // The helper we are going to call only applies to non-dense elements.
4190 writer.guardIndexGreaterThanDenseInitLength(objId, indexId);
4191
4192 // Guard extensible: We may be trying to add a new element, and so we'd best
4193 // be able to do so safely.
4194 writer.guardIsExtensible(objId);
4195
4196 // Ensures we are able to efficiently able to map to an integral jsid.
4197 writer.guardInt32IsNonNegative(indexId);
4198
4199 // Shape guard the prototype chain to avoid shadowing indexes from appearing.
4200 // Guard the prototype of the receiver explicitly, because the receiver's
4201 // shape is not being guarded as a proxy for that.
4202 GuardReceiverProto(writer, aobj, objId);
4203
4204 // Dense elements may appear on the prototype chain (and prototypes may
4205 // have a different notion of which elements are dense), but they can
4206 // only be data properties, so our specialized Set handler is ok to bind
4207 // to them.
4208 ShapeGuardProtoChain(writer, aobj, objId);
4209
4210 // Ensure that if we're adding an element to the object, the object's
4211 // length is writable.
4212 writer.guardIndexIsValidUpdateOrAdd(objId, indexId);
4213
4214 writer.callAddOrUpdateSparseElementHelper(
4215 objId, indexId, rhsId,
4216 /* strict = */ op == JSOp::StrictSetElem);
4217 writer.returnFromIC();
4218
4219 trackAttached("AddOrUpdateSparseElement");
4220 return AttachDecision::Attach;
4221 }
4222
tryAttachSetTypedArrayElement(HandleObject obj,ObjOperandId objId,ValOperandId rhsId)4223 AttachDecision SetPropIRGenerator::tryAttachSetTypedArrayElement(
4224 HandleObject obj, ObjOperandId objId, ValOperandId rhsId) {
4225 if (!obj->is<TypedArrayObject>()) {
4226 return AttachDecision::NoAction;
4227 }
4228 if (!idVal_.isNumber()) {
4229 return AttachDecision::NoAction;
4230 }
4231
4232 TypedArrayObject* tarr = &obj->as<TypedArrayObject>();
4233 Scalar::Type elementType = tarr->type();
4234
4235 // Don't attach if the input type doesn't match the guard added below.
4236 if (!ValueIsNumeric(elementType, rhsVal_)) {
4237 return AttachDecision::NoAction;
4238 }
4239
4240 bool handleOOB = false;
4241 int64_t indexInt64;
4242 if (!ValueIsInt64Index(idVal_, &indexInt64) || indexInt64 < 0 ||
4243 uint64_t(indexInt64) >= tarr->length()) {
4244 handleOOB = true;
4245 }
4246
4247 // InitElem (DefineProperty) has to throw an exception on out-of-bounds.
4248 if (handleOOB && IsPropertyInitOp(JSOp(*pc_))) {
4249 return AttachDecision::NoAction;
4250 }
4251
4252 writer.guardShapeForClass(objId, tarr->shape());
4253
4254 OperandId rhsValId = emitNumericGuard(rhsId, elementType);
4255
4256 ValOperandId keyId = setElemKeyValueId();
4257 IntPtrOperandId indexId = guardToIntPtrIndex(idVal_, keyId, handleOOB);
4258
4259 writer.storeTypedArrayElement(objId, elementType, indexId, rhsValId,
4260 handleOOB);
4261 writer.returnFromIC();
4262
4263 trackAttached(handleOOB ? "SetTypedElementOOB" : "SetTypedElement");
4264 return AttachDecision::Attach;
4265 }
4266
tryAttachGenericProxy(Handle<ProxyObject * > obj,ObjOperandId objId,HandleId id,ValOperandId rhsId,bool handleDOMProxies)4267 AttachDecision SetPropIRGenerator::tryAttachGenericProxy(
4268 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
4269 ValOperandId rhsId, bool handleDOMProxies) {
4270 writer.guardIsProxy(objId);
4271
4272 if (!handleDOMProxies) {
4273 // Ensure that the incoming object is not a DOM proxy, so that we can
4274 // get to the specialized stubs. If handleDOMProxies is true, we were
4275 // unable to attach a specialized DOM stub, so we just handle all
4276 // proxies here.
4277 writer.guardIsNotDOMProxy(objId);
4278 }
4279
4280 if (cacheKind_ == CacheKind::SetProp || mode_ == ICState::Mode::Specialized) {
4281 maybeEmitIdGuard(id);
4282 writer.proxySet(objId, id, rhsId, IsStrictSetPC(pc_));
4283 } else {
4284 // Attach a stub that handles every id.
4285 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
4286 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
4287 writer.proxySetByValue(objId, setElemKeyValueId(), rhsId,
4288 IsStrictSetPC(pc_));
4289 }
4290
4291 writer.returnFromIC();
4292
4293 trackAttached("GenericProxy");
4294 return AttachDecision::Attach;
4295 }
4296
tryAttachDOMProxyShadowed(Handle<ProxyObject * > obj,ObjOperandId objId,HandleId id,ValOperandId rhsId)4297 AttachDecision SetPropIRGenerator::tryAttachDOMProxyShadowed(
4298 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
4299 ValOperandId rhsId) {
4300 MOZ_ASSERT(IsCacheableDOMProxy(obj));
4301
4302 maybeEmitIdGuard(id);
4303 TestMatchingProxyReceiver(writer, obj, objId);
4304 writer.proxySet(objId, id, rhsId, IsStrictSetPC(pc_));
4305 writer.returnFromIC();
4306
4307 trackAttached("DOMProxyShadowed");
4308 return AttachDecision::Attach;
4309 }
4310
tryAttachDOMProxyUnshadowed(Handle<ProxyObject * > obj,ObjOperandId objId,HandleId id,ValOperandId rhsId)4311 AttachDecision SetPropIRGenerator::tryAttachDOMProxyUnshadowed(
4312 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
4313 ValOperandId rhsId) {
4314 MOZ_ASSERT(IsCacheableDOMProxy(obj));
4315
4316 JSObject* proto = obj->staticPrototype();
4317 if (!proto) {
4318 return AttachDecision::NoAction;
4319 }
4320
4321 NativeObject* holder = nullptr;
4322 Maybe<PropertyInfo> prop;
4323 if (!CanAttachSetter(cx_, pc_, proto, id, &holder, &prop)) {
4324 return AttachDecision::NoAction;
4325 }
4326 auto* nproto = &proto->as<NativeObject>();
4327
4328 maybeEmitIdGuard(id);
4329
4330 // Guard that our expando object hasn't started shadowing this property.
4331 TestMatchingProxyReceiver(writer, obj, objId);
4332 CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId);
4333
4334 GeneratePrototypeGuards(writer, obj, holder, objId);
4335
4336 // Guard on the holder of the property.
4337 ObjOperandId holderId = writer.loadObject(holder);
4338 TestMatchingHolder(writer, holder, holderId);
4339
4340 EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
4341 /* holderIsConstant = */ true);
4342
4343 // EmitCallSetterNoGuards expects |obj| to be the object the property is
4344 // on to do some checks. Since we actually looked at proto, and no extra
4345 // guards will be generated, we can just pass that instead.
4346 EmitCallSetterNoGuards(cx_, writer, nproto, holder, *prop, objId, rhsId);
4347
4348 trackAttached("DOMProxyUnshadowed");
4349 return AttachDecision::Attach;
4350 }
4351
tryAttachDOMProxyExpando(Handle<ProxyObject * > obj,ObjOperandId objId,HandleId id,ValOperandId rhsId)4352 AttachDecision SetPropIRGenerator::tryAttachDOMProxyExpando(
4353 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
4354 ValOperandId rhsId) {
4355 MOZ_ASSERT(IsCacheableDOMProxy(obj));
4356
4357 Value expandoVal = GetProxyPrivate(obj);
4358 JSObject* expandoObj;
4359 if (expandoVal.isObject()) {
4360 expandoObj = &expandoVal.toObject();
4361 } else {
4362 MOZ_ASSERT(!expandoVal.isUndefined(),
4363 "How did a missing expando manage to shadow things?");
4364 auto expandoAndGeneration =
4365 static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
4366 MOZ_ASSERT(expandoAndGeneration);
4367 expandoObj = &expandoAndGeneration->expando.toObject();
4368 }
4369
4370 Maybe<PropertyInfo> prop;
4371 if (CanAttachNativeSetSlot(JSOp(*pc_), expandoObj, id, &prop)) {
4372 auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
4373
4374 maybeEmitIdGuard(id);
4375 ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
4376 obj, objId, expandoVal, nativeExpandoObj);
4377
4378 EmitStoreSlotAndReturn(writer, expandoObjId, nativeExpandoObj, *prop,
4379 rhsId);
4380 trackAttached("DOMProxyExpandoSlot");
4381 return AttachDecision::Attach;
4382 }
4383
4384 NativeObject* holder = nullptr;
4385 if (CanAttachSetter(cx_, pc_, expandoObj, id, &holder, &prop)) {
4386 auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
4387
4388 // Call the setter. Note that we pass objId, the DOM proxy, as |this|
4389 // and not the expando object.
4390 maybeEmitIdGuard(id);
4391 ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
4392 obj, objId, expandoVal, nativeExpandoObj);
4393
4394 MOZ_ASSERT(holder == nativeExpandoObj);
4395 EmitGuardGetterSetterSlot(writer, nativeExpandoObj, *prop, expandoObjId);
4396 EmitCallSetterNoGuards(cx_, writer, nativeExpandoObj, nativeExpandoObj,
4397 *prop, objId, rhsId);
4398 trackAttached("DOMProxyExpandoSetter");
4399 return AttachDecision::Attach;
4400 }
4401
4402 return AttachDecision::NoAction;
4403 }
4404
tryAttachProxy(HandleObject obj,ObjOperandId objId,HandleId id,ValOperandId rhsId)4405 AttachDecision SetPropIRGenerator::tryAttachProxy(HandleObject obj,
4406 ObjOperandId objId,
4407 HandleId id,
4408 ValOperandId rhsId) {
4409 // Don't attach a proxy stub for ops like JSOp::InitElem.
4410 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
4411
4412 ProxyStubType type = GetProxyStubType(cx_, obj, id);
4413 if (type == ProxyStubType::None) {
4414 return AttachDecision::NoAction;
4415 }
4416 auto proxy = obj.as<ProxyObject>();
4417
4418 if (mode_ == ICState::Mode::Megamorphic) {
4419 return tryAttachGenericProxy(proxy, objId, id, rhsId,
4420 /* handleDOMProxies = */ true);
4421 }
4422
4423 switch (type) {
4424 case ProxyStubType::None:
4425 break;
4426 case ProxyStubType::DOMExpando:
4427 TRY_ATTACH(tryAttachDOMProxyExpando(proxy, objId, id, rhsId));
4428 [[fallthrough]]; // Fall through to the generic shadowed case.
4429 case ProxyStubType::DOMShadowed:
4430 return tryAttachDOMProxyShadowed(proxy, objId, id, rhsId);
4431 case ProxyStubType::DOMUnshadowed:
4432 TRY_ATTACH(tryAttachDOMProxyUnshadowed(proxy, objId, id, rhsId));
4433 return tryAttachGenericProxy(proxy, objId, id, rhsId,
4434 /* handleDOMProxies = */ true);
4435 case ProxyStubType::Generic:
4436 return tryAttachGenericProxy(proxy, objId, id, rhsId,
4437 /* handleDOMProxies = */ false);
4438 }
4439
4440 MOZ_CRASH("Unexpected ProxyStubType");
4441 }
4442
tryAttachProxyElement(HandleObject obj,ObjOperandId objId,ValOperandId rhsId)4443 AttachDecision SetPropIRGenerator::tryAttachProxyElement(HandleObject obj,
4444 ObjOperandId objId,
4445 ValOperandId rhsId) {
4446 // Don't attach a proxy stub for ops like JSOp::InitElem.
4447 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
4448
4449 if (!obj->is<ProxyObject>()) {
4450 return AttachDecision::NoAction;
4451 }
4452
4453 writer.guardIsProxy(objId);
4454
4455 // Like GetPropIRGenerator::tryAttachProxyElement, don't check for DOM
4456 // proxies here as we don't have specialized DOM stubs for this.
4457 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
4458 writer.proxySetByValue(objId, setElemKeyValueId(), rhsId, IsStrictSetPC(pc_));
4459 writer.returnFromIC();
4460
4461 trackAttached("ProxyElement");
4462 return AttachDecision::Attach;
4463 }
4464
tryAttachMegamorphicSetElement(HandleObject obj,ObjOperandId objId,ValOperandId rhsId)4465 AttachDecision SetPropIRGenerator::tryAttachMegamorphicSetElement(
4466 HandleObject obj, ObjOperandId objId, ValOperandId rhsId) {
4467 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
4468
4469 if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetElem) {
4470 return AttachDecision::NoAction;
4471 }
4472
4473 // The generic proxy stubs are faster.
4474 if (obj->is<ProxyObject>()) {
4475 return AttachDecision::NoAction;
4476 }
4477
4478 writer.megamorphicSetElement(objId, setElemKeyValueId(), rhsId,
4479 IsStrictSetPC(pc_));
4480 writer.returnFromIC();
4481
4482 trackAttached("MegamorphicSetElement");
4483 return AttachDecision::Attach;
4484 }
4485
tryAttachWindowProxy(HandleObject obj,ObjOperandId objId,HandleId id,ValOperandId rhsId)4486 AttachDecision SetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
4487 ObjOperandId objId,
4488 HandleId id,
4489 ValOperandId rhsId) {
4490 // Attach a stub when the receiver is a WindowProxy and we can do the set
4491 // on the Window (the global object).
4492
4493 if (!IsWindowProxyForScriptGlobal(script_, obj)) {
4494 return AttachDecision::NoAction;
4495 }
4496
4497 // If we're megamorphic prefer a generic proxy stub that handles a lot more
4498 // cases.
4499 if (mode_ == ICState::Mode::Megamorphic) {
4500 return AttachDecision::NoAction;
4501 }
4502
4503 // Now try to do the set on the Window (the current global).
4504 GlobalObject* windowObj = cx_->global();
4505
4506 Maybe<PropertyInfo> prop;
4507 if (!CanAttachNativeSetSlot(JSOp(*pc_), windowObj, id, &prop)) {
4508 return AttachDecision::NoAction;
4509 }
4510
4511 maybeEmitIdGuard(id);
4512
4513 ObjOperandId windowObjId =
4514 GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
4515 writer.guardShape(windowObjId, windowObj->shape());
4516
4517 EmitStoreSlotAndReturn(writer, windowObjId, windowObj, *prop, rhsId);
4518
4519 trackAttached("WindowProxySlot");
4520 return AttachDecision::Attach;
4521 }
4522
canAttachAddSlotStub(HandleObject obj,HandleId id)4523 bool SetPropIRGenerator::canAttachAddSlotStub(HandleObject obj, HandleId id) {
4524 // Special-case JSFunction resolve hook to allow redefining the 'prototype'
4525 // property without triggering lazy expansion of property and object
4526 // allocation.
4527 if (obj->is<JSFunction>() && id.isAtom(cx_->names().prototype)) {
4528 MOZ_ASSERT(ClassMayResolveId(cx_->names(), obj->getClass(), id, obj));
4529
4530 // We're only interested in functions that have a builtin .prototype
4531 // property (needsPrototypeProperty). The stub will guard on this because
4532 // the builtin .prototype property is non-configurable/non-enumerable and it
4533 // would be wrong to add a property with those attributes to a function that
4534 // doesn't have a builtin .prototype.
4535 //
4536 // Inlining needsPrototypeProperty in JIT code is complicated so we use
4537 // isNonBuiltinConstructor as a stronger condition that's easier to check
4538 // from JIT code.
4539 JSFunction* fun = &obj->as<JSFunction>();
4540 if (!fun->isNonBuiltinConstructor()) {
4541 return false;
4542 }
4543 MOZ_ASSERT(fun->needsPrototypeProperty());
4544
4545 // If property exists this isn't an "add".
4546 if (fun->lookupPure(id)) {
4547 return false;
4548 }
4549 } else {
4550 // Normal Case: If property exists this isn't an "add"
4551 PropertyResult prop;
4552 if (!LookupOwnPropertyPure(cx_, obj, id, &prop)) {
4553 return false;
4554 }
4555 if (prop.isFound()) {
4556 return false;
4557 }
4558 }
4559
4560 // For now we don't optimize Watchtower-monitored objects.
4561 if (Watchtower::watchesPropertyAdd(obj.as<NativeObject>())) {
4562 return false;
4563 }
4564
4565 // Object must be extensible, or we must be initializing a private
4566 // elem.
4567 bool canAddNewProperty = obj->nonProxyIsExtensible() || id.isPrivateName();
4568 if (!canAddNewProperty) {
4569 return false;
4570 }
4571
4572 // Walk up the object prototype chain and ensure that all prototypes are
4573 // native, and that all prototypes have no setter defined on the property.
4574 for (JSObject* proto = obj->staticPrototype(); proto;
4575 proto = proto->staticPrototype()) {
4576 if (!proto->is<NativeObject>()) {
4577 return false;
4578 }
4579
4580 // If prototype defines this property in a non-plain way, don't optimize.
4581 Maybe<PropertyInfo> protoProp = proto->as<NativeObject>().lookup(cx_, id);
4582 if (protoProp.isSome() && !protoProp->isDataProperty()) {
4583 return false;
4584 }
4585
4586 // Otherwise, if there's no such property, watch out for a resolve hook
4587 // that would need to be invoked and thus prevent inlining of property
4588 // addition. Allow the JSFunction resolve hook as it only defines plain
4589 // data properties and we don't need to invoke it for objects on the
4590 // proto chain.
4591 if (ClassMayResolveId(cx_->names(), proto->getClass(), id, proto) &&
4592 !proto->is<JSFunction>()) {
4593 return false;
4594 }
4595 }
4596
4597 return true;
4598 }
4599
tryAttachAddSlotStub(HandleShape oldShape)4600 AttachDecision SetPropIRGenerator::tryAttachAddSlotStub(HandleShape oldShape) {
4601 ValOperandId objValId(writer.setInputOperandId(0));
4602 ValOperandId rhsValId;
4603 if (cacheKind_ == CacheKind::SetProp) {
4604 rhsValId = ValOperandId(writer.setInputOperandId(1));
4605 } else {
4606 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
4607 MOZ_ASSERT(setElemKeyValueId().id() == 1);
4608 writer.setInputOperandId(1);
4609 rhsValId = ValOperandId(writer.setInputOperandId(2));
4610 }
4611
4612 RootedId id(cx_);
4613 bool nameOrSymbol;
4614 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
4615 cx_->clearPendingException();
4616 return AttachDecision::NoAction;
4617 }
4618
4619 if (!lhsVal_.isObject() || !nameOrSymbol) {
4620 return AttachDecision::NoAction;
4621 }
4622
4623 JSObject* obj = &lhsVal_.toObject();
4624
4625 PropertyResult prop;
4626 if (!LookupOwnPropertyPure(cx_, obj, id, &prop)) {
4627 return AttachDecision::NoAction;
4628 }
4629 if (prop.isNotFound()) {
4630 return AttachDecision::NoAction;
4631 }
4632
4633 if (!obj->is<NativeObject>()) {
4634 return AttachDecision::NoAction;
4635 }
4636 auto* nobj = &obj->as<NativeObject>();
4637
4638 PropertyInfo propInfo = prop.propertyInfo();
4639 NativeObject* holder = nobj;
4640
4641 // The property must be the last added property of the object.
4642 Shape* newShape = holder->shape();
4643 MOZ_RELEASE_ASSERT(newShape->lastProperty() == propInfo);
4644
4645 #ifdef DEBUG
4646 // Verify exactly one property was added by comparing the property map
4647 // lengths.
4648 if (oldShape->propMapLength() == PropMap::Capacity) {
4649 MOZ_ASSERT(newShape->propMapLength() == 1);
4650 } else {
4651 MOZ_ASSERT(newShape->propMapLength() == oldShape->propMapLength() + 1);
4652 }
4653 #endif
4654
4655 // Basic shape checks.
4656 if (newShape->isDictionary() || !propInfo.isDataProperty() ||
4657 !propInfo.writable()) {
4658 return AttachDecision::NoAction;
4659 }
4660
4661 ObjOperandId objId = writer.guardToObject(objValId);
4662 maybeEmitIdGuard(id);
4663
4664 // Shape guard the object.
4665 writer.guardShape(objId, oldShape);
4666
4667 // If this is the special function.prototype case, we need to guard the
4668 // function is a non-builtin constructor. See canAttachAddSlotStub.
4669 if (nobj->is<JSFunction>() && id.isAtom(cx_->names().prototype)) {
4670 MOZ_ASSERT(nobj->as<JSFunction>().isNonBuiltinConstructor());
4671 writer.guardFunctionIsNonBuiltinCtor(objId);
4672 }
4673
4674 ShapeGuardProtoChain(writer, nobj, objId);
4675
4676 // If the JSClass has an addProperty hook, we need to call a VM function to
4677 // invoke this hook. Ignore the Array addProperty hook, because it doesn't do
4678 // anything for non-index properties.
4679 DebugOnly<uint32_t> index;
4680 MOZ_ASSERT_IF(obj->is<ArrayObject>(), !IdIsIndex(id, &index));
4681 bool mustCallAddPropertyHook =
4682 obj->getClass()->getAddProperty() && !obj->is<ArrayObject>();
4683
4684 if (mustCallAddPropertyHook) {
4685 writer.addSlotAndCallAddPropHook(objId, rhsValId, newShape);
4686 trackAttached("AddSlotWithAddPropertyHook");
4687 } else if (holder->isFixedSlot(propInfo.slot())) {
4688 size_t offset = NativeObject::getFixedSlotOffset(propInfo.slot());
4689 writer.addAndStoreFixedSlot(objId, offset, rhsValId, newShape);
4690 trackAttached("AddSlot");
4691 } else {
4692 size_t offset = holder->dynamicSlotIndex(propInfo.slot()) * sizeof(Value);
4693 uint32_t numOldSlots = NativeObject::calculateDynamicSlots(oldShape);
4694 uint32_t numNewSlots = holder->numDynamicSlots();
4695 if (numOldSlots == numNewSlots) {
4696 writer.addAndStoreDynamicSlot(objId, offset, rhsValId, newShape);
4697 trackAttached("AddSlot");
4698 } else {
4699 MOZ_ASSERT(numNewSlots > numOldSlots);
4700 writer.allocateAndStoreDynamicSlot(objId, offset, rhsValId, newShape,
4701 numNewSlots);
4702 trackAttached("AllocateSlot");
4703 }
4704 }
4705 writer.returnFromIC();
4706
4707 return AttachDecision::Attach;
4708 }
4709
InstanceOfIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,HandleValue lhs,HandleObject rhs)4710 InstanceOfIRGenerator::InstanceOfIRGenerator(JSContext* cx, HandleScript script,
4711 jsbytecode* pc, ICState state,
4712 HandleValue lhs, HandleObject rhs)
4713 : IRGenerator(cx, script, pc, CacheKind::InstanceOf, state),
4714 lhsVal_(lhs),
4715 rhsObj_(rhs) {}
4716
tryAttachStub()4717 AttachDecision InstanceOfIRGenerator::tryAttachStub() {
4718 MOZ_ASSERT(cacheKind_ == CacheKind::InstanceOf);
4719 AutoAssertNoPendingException aanpe(cx_);
4720
4721 // Ensure RHS is a function -- could be a Proxy, which the IC isn't prepared
4722 // to handle.
4723 if (!rhsObj_->is<JSFunction>()) {
4724 trackAttached(IRGenerator::NotAttached);
4725 return AttachDecision::NoAction;
4726 }
4727
4728 HandleFunction fun = rhsObj_.as<JSFunction>();
4729
4730 if (fun->isBoundFunction()) {
4731 trackAttached(IRGenerator::NotAttached);
4732 return AttachDecision::NoAction;
4733 }
4734
4735 // Look up the @@hasInstance property, and check that Function.__proto__ is
4736 // the property holder, and that no object further down the prototype chain
4737 // (including this function) has shadowed it; together with the fact that
4738 // Function.__proto__[@@hasInstance] is immutable, this ensures that the
4739 // hasInstance hook will not change without the need to guard on the actual
4740 // property value.
4741 PropertyResult hasInstanceProp;
4742 NativeObject* hasInstanceHolder = nullptr;
4743 jsid hasInstanceID = PropertyKey::Symbol(cx_->wellKnownSymbols().hasInstance);
4744 if (!LookupPropertyPure(cx_, fun, hasInstanceID, &hasInstanceHolder,
4745 &hasInstanceProp) ||
4746 !hasInstanceProp.isNativeProperty()) {
4747 trackAttached(IRGenerator::NotAttached);
4748 return AttachDecision::NoAction;
4749 }
4750
4751 JSObject& funProto = cx_->global()->getPrototype(JSProto_Function);
4752 if (hasInstanceHolder != &funProto) {
4753 trackAttached(IRGenerator::NotAttached);
4754 return AttachDecision::NoAction;
4755 }
4756
4757 // If the above succeeded, then these should be true about @@hasInstance,
4758 // because the property on Function.__proto__ is an immutable data property:
4759 MOZ_ASSERT(hasInstanceProp.propertyInfo().isDataProperty());
4760 MOZ_ASSERT(!hasInstanceProp.propertyInfo().configurable());
4761 MOZ_ASSERT(!hasInstanceProp.propertyInfo().writable());
4762
4763 MOZ_ASSERT(IsCacheableProtoChain(fun, hasInstanceHolder));
4764
4765 // Ensure that the function's prototype slot is the same.
4766 Maybe<PropertyInfo> prop = fun->lookupPure(cx_->names().prototype);
4767 if (prop.isNothing() || !prop->isDataProperty()) {
4768 trackAttached(IRGenerator::NotAttached);
4769 return AttachDecision::NoAction;
4770 }
4771
4772 uint32_t slot = prop->slot();
4773 MOZ_ASSERT(slot >= fun->numFixedSlots(), "Stub code relies on this");
4774 if (!fun->getSlot(slot).isObject()) {
4775 trackAttached(IRGenerator::NotAttached);
4776 return AttachDecision::NoAction;
4777 }
4778
4779 JSObject* prototypeObject = &fun->getSlot(slot).toObject();
4780
4781 // Abstract Objects
4782 ValOperandId lhs(writer.setInputOperandId(0));
4783 ValOperandId rhs(writer.setInputOperandId(1));
4784
4785 ObjOperandId rhsId = writer.guardToObject(rhs);
4786 writer.guardShape(rhsId, fun->shape());
4787
4788 // Ensure that the shapes up the prototype chain for the RHS remain the same
4789 // so that @@hasInstance is not shadowed by some intermediate prototype
4790 // object.
4791 if (hasInstanceHolder != fun) {
4792 GeneratePrototypeGuards(writer, fun, hasInstanceHolder, rhsId);
4793 ObjOperandId holderId = writer.loadObject(hasInstanceHolder);
4794 TestMatchingHolder(writer, hasInstanceHolder, holderId);
4795 }
4796
4797 // Load prototypeObject into the cache -- consumed twice in the IC
4798 ObjOperandId protoId = writer.loadObject(prototypeObject);
4799 // Ensure that rhs[slot] == prototypeObject.
4800 writer.guardDynamicSlotIsSpecificObject(rhsId, protoId,
4801 slot - fun->numFixedSlots());
4802
4803 // Needn't guard LHS is object, because the actual stub can handle that
4804 // and correctly return false.
4805 writer.loadInstanceOfObjectResult(lhs, protoId);
4806 writer.returnFromIC();
4807 trackAttached("InstanceOf");
4808 return AttachDecision::Attach;
4809 }
4810
trackAttached(const char * name)4811 void InstanceOfIRGenerator::trackAttached(const char* name) {
4812 #ifdef JS_CACHEIR_SPEW
4813 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
4814 sp.valueProperty("lhs", lhsVal_);
4815 sp.valueProperty("rhs", ObjectValue(*rhsObj_));
4816 }
4817 #else
4818 // Silence Clang -Wunused-private-field warning.
4819 (void)lhsVal_;
4820 #endif
4821 }
4822
TypeOfIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,HandleValue value)4823 TypeOfIRGenerator::TypeOfIRGenerator(JSContext* cx, HandleScript script,
4824 jsbytecode* pc, ICState state,
4825 HandleValue value)
4826 : IRGenerator(cx, script, pc, CacheKind::TypeOf, state), val_(value) {}
4827
trackAttached(const char * name)4828 void TypeOfIRGenerator::trackAttached(const char* name) {
4829 #ifdef JS_CACHEIR_SPEW
4830 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
4831 sp.valueProperty("val", val_);
4832 }
4833 #endif
4834 }
4835
tryAttachStub()4836 AttachDecision TypeOfIRGenerator::tryAttachStub() {
4837 MOZ_ASSERT(cacheKind_ == CacheKind::TypeOf);
4838
4839 AutoAssertNoPendingException aanpe(cx_);
4840
4841 ValOperandId valId(writer.setInputOperandId(0));
4842
4843 TRY_ATTACH(tryAttachPrimitive(valId));
4844 TRY_ATTACH(tryAttachObject(valId));
4845
4846 MOZ_ASSERT_UNREACHABLE("Failed to attach TypeOf");
4847 return AttachDecision::NoAction;
4848 }
4849
tryAttachPrimitive(ValOperandId valId)4850 AttachDecision TypeOfIRGenerator::tryAttachPrimitive(ValOperandId valId) {
4851 if (!val_.isPrimitive()) {
4852 return AttachDecision::NoAction;
4853 }
4854
4855 // Note: we don't use GuardIsNumber for int32 values because it's less
4856 // efficient in Warp (unboxing to double instead of int32).
4857 if (val_.isDouble()) {
4858 writer.guardIsNumber(valId);
4859 } else {
4860 writer.guardNonDoubleType(valId, val_.type());
4861 }
4862
4863 writer.loadConstantStringResult(
4864 TypeName(js::TypeOfValue(val_), cx_->names()));
4865 writer.returnFromIC();
4866 writer.setTypeData(TypeData(JSValueType(val_.type())));
4867 trackAttached("Primitive");
4868 return AttachDecision::Attach;
4869 }
4870
tryAttachObject(ValOperandId valId)4871 AttachDecision TypeOfIRGenerator::tryAttachObject(ValOperandId valId) {
4872 if (!val_.isObject()) {
4873 return AttachDecision::NoAction;
4874 }
4875
4876 ObjOperandId objId = writer.guardToObject(valId);
4877 writer.loadTypeOfObjectResult(objId);
4878 writer.returnFromIC();
4879 writer.setTypeData(TypeData(JSValueType(val_.type())));
4880 trackAttached("Object");
4881 return AttachDecision::Attach;
4882 }
4883
GetIteratorIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,HandleValue value)4884 GetIteratorIRGenerator::GetIteratorIRGenerator(JSContext* cx,
4885 HandleScript script,
4886 jsbytecode* pc, ICState state,
4887 HandleValue value)
4888 : IRGenerator(cx, script, pc, CacheKind::GetIterator, state), val_(value) {}
4889
tryAttachStub()4890 AttachDecision GetIteratorIRGenerator::tryAttachStub() {
4891 MOZ_ASSERT(cacheKind_ == CacheKind::GetIterator);
4892
4893 AutoAssertNoPendingException aanpe(cx_);
4894
4895 ValOperandId valId(writer.setInputOperandId(0));
4896
4897 if (mode_ == ICState::Mode::Megamorphic) {
4898 TRY_ATTACH(tryAttachMegamorphic(valId));
4899 return AttachDecision::NoAction;
4900 }
4901
4902 TRY_ATTACH(tryAttachNativeIterator(valId));
4903 TRY_ATTACH(tryAttachNullOrUndefined(valId));
4904
4905 trackAttached(IRGenerator::NotAttached);
4906 return AttachDecision::NoAction;
4907 }
4908
tryAttachNativeIterator(ValOperandId valId)4909 AttachDecision GetIteratorIRGenerator::tryAttachNativeIterator(
4910 ValOperandId valId) {
4911 MOZ_ASSERT(JSOp(*pc_) == JSOp::Iter);
4912
4913 if (!val_.isObject()) {
4914 return AttachDecision::NoAction;
4915 }
4916
4917 RootedObject obj(cx_, &val_.toObject());
4918 ObjOperandId objId = writer.guardToObject(valId);
4919
4920 PropertyIteratorObject* iterobj = LookupInIteratorCache(cx_, obj);
4921 if (!iterobj) {
4922 return AttachDecision::NoAction;
4923 }
4924 auto* nobj = &obj->as<NativeObject>();
4925
4926 // Guard on the receiver's shape.
4927 TestMatchingNativeReceiver(writer, nobj, objId);
4928
4929 // Ensure the receiver has no dense elements.
4930 writer.guardNoDenseElements(objId);
4931
4932 // Do the same for the objects on the proto chain.
4933 GeneratePrototypeHoleGuards(writer, nobj, objId,
4934 /* alwaysGuardFirstProto = */ false);
4935
4936 ObjOperandId iterId = writer.guardAndGetIterator(
4937 objId, iterobj, &ObjectRealm::get(obj).enumerators);
4938 writer.loadObjectResult(iterId);
4939 writer.returnFromIC();
4940
4941 trackAttached("NativeIterator");
4942 return AttachDecision::Attach;
4943 }
4944
tryAttachMegamorphic(ValOperandId valId)4945 AttachDecision GetIteratorIRGenerator::tryAttachMegamorphic(
4946 ValOperandId valId) {
4947 MOZ_ASSERT(JSOp(*pc_) == JSOp::Iter);
4948
4949 writer.valueToIteratorResult(valId);
4950 writer.returnFromIC();
4951
4952 trackAttached("Megamorphic");
4953 return AttachDecision::Attach;
4954 }
4955
tryAttachNullOrUndefined(ValOperandId valId)4956 AttachDecision GetIteratorIRGenerator::tryAttachNullOrUndefined(
4957 ValOperandId valId) {
4958 MOZ_ASSERT(JSOp(*pc_) == JSOp::Iter);
4959
4960 // For null/undefined we can simply return the empty iterator singleton. This
4961 // works because this iterator is unlinked and immutable.
4962
4963 if (!val_.isNullOrUndefined()) {
4964 return AttachDecision::NoAction;
4965 }
4966
4967 PropertyIteratorObject* emptyIter = cx_->global()->maybeEmptyIterator();
4968 if (!emptyIter) {
4969 return AttachDecision::NoAction;
4970 }
4971
4972 writer.guardIsNullOrUndefined(valId);
4973
4974 ObjOperandId iterId = writer.loadObject(emptyIter);
4975 writer.loadObjectResult(iterId);
4976 writer.returnFromIC();
4977
4978 trackAttached("NullOrUndefined");
4979 return AttachDecision::Attach;
4980 }
4981
trackAttached(const char * name)4982 void GetIteratorIRGenerator::trackAttached(const char* name) {
4983 #ifdef JS_CACHEIR_SPEW
4984 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
4985 sp.valueProperty("val", val_);
4986 }
4987 #endif
4988 }
4989
OptimizeSpreadCallIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,HandleValue value)4990 OptimizeSpreadCallIRGenerator::OptimizeSpreadCallIRGenerator(
4991 JSContext* cx, HandleScript script, jsbytecode* pc, ICState state,
4992 HandleValue value)
4993 : IRGenerator(cx, script, pc, CacheKind::OptimizeSpreadCall, state),
4994 val_(value) {}
4995
tryAttachStub()4996 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachStub() {
4997 MOZ_ASSERT(cacheKind_ == CacheKind::OptimizeSpreadCall);
4998
4999 AutoAssertNoPendingException aanpe(cx_);
5000
5001 TRY_ATTACH(tryAttachArray());
5002 TRY_ATTACH(tryAttachArguments());
5003 TRY_ATTACH(tryAttachNotOptimizable());
5004
5005 trackAttached(IRGenerator::NotAttached);
5006 return AttachDecision::NoAction;
5007 }
5008
IsArrayPrototypeOptimizable(JSContext * cx,ArrayObject * arr,NativeObject ** arrProto,uint32_t * slot,JSFunction ** iterFun)5009 static bool IsArrayPrototypeOptimizable(JSContext* cx, ArrayObject* arr,
5010 NativeObject** arrProto, uint32_t* slot,
5011 JSFunction** iterFun) {
5012 // Prototype must be Array.prototype.
5013 auto* proto = cx->global()->maybeGetArrayPrototype();
5014 if (!proto || arr->staticPrototype() != proto) {
5015 return false;
5016 }
5017 *arrProto = proto;
5018
5019 // The object must not have an own @@iterator property.
5020 PropertyKey iteratorKey =
5021 PropertyKey::Symbol(cx->wellKnownSymbols().iterator);
5022 if (arr->lookupPure(iteratorKey)) {
5023 return false;
5024 }
5025
5026 // Ensure that Array.prototype's @@iterator slot is unchanged.
5027 Maybe<PropertyInfo> prop = proto->lookupPure(iteratorKey);
5028 if (prop.isNothing() || !prop->isDataProperty()) {
5029 return false;
5030 }
5031
5032 *slot = prop->slot();
5033 MOZ_ASSERT(proto->numFixedSlots() == 0, "Stub code relies on this");
5034
5035 const Value& iterVal = proto->getSlot(*slot);
5036 if (!iterVal.isObject() || !iterVal.toObject().is<JSFunction>()) {
5037 return false;
5038 }
5039
5040 *iterFun = &iterVal.toObject().as<JSFunction>();
5041 return IsSelfHostedFunctionWithName(*iterFun, cx->names().ArrayValues);
5042 }
5043
IsArrayIteratorPrototypeOptimizable(JSContext * cx,NativeObject ** arrIterProto,uint32_t * slot,JSFunction ** nextFun)5044 static bool IsArrayIteratorPrototypeOptimizable(JSContext* cx,
5045 NativeObject** arrIterProto,
5046 uint32_t* slot,
5047 JSFunction** nextFun) {
5048 auto* proto = cx->global()->maybeGetArrayIteratorPrototype();
5049 if (!proto) {
5050 return false;
5051 }
5052 *arrIterProto = proto;
5053
5054 // Ensure that %ArrayIteratorPrototype%'s "next" slot is unchanged.
5055 Maybe<PropertyInfo> prop = proto->lookupPure(cx->names().next);
5056 if (prop.isNothing() || !prop->isDataProperty()) {
5057 return false;
5058 }
5059
5060 *slot = prop->slot();
5061 MOZ_ASSERT(proto->numFixedSlots() == 0, "Stub code relies on this");
5062
5063 const Value& nextVal = proto->getSlot(*slot);
5064 if (!nextVal.isObject() || !nextVal.toObject().is<JSFunction>()) {
5065 return false;
5066 }
5067
5068 *nextFun = &nextVal.toObject().as<JSFunction>();
5069 return IsSelfHostedFunctionWithName(*nextFun, cx->names().ArrayIteratorNext);
5070 }
5071
tryAttachArray()5072 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArray() {
5073 if (!isFirstStub_) {
5074 return AttachDecision::NoAction;
5075 }
5076
5077 // The value must be a packed array.
5078 if (!val_.isObject()) {
5079 return AttachDecision::NoAction;
5080 }
5081 JSObject* obj = &val_.toObject();
5082 if (!IsPackedArray(obj)) {
5083 return AttachDecision::NoAction;
5084 }
5085
5086 // Prototype must be Array.prototype and Array.prototype[@@iterator] must not
5087 // be modified.
5088 NativeObject* arrProto;
5089 uint32_t arrProtoIterSlot;
5090 JSFunction* iterFun;
5091 if (!IsArrayPrototypeOptimizable(cx_, &obj->as<ArrayObject>(), &arrProto,
5092 &arrProtoIterSlot, &iterFun)) {
5093 return AttachDecision::NoAction;
5094 }
5095
5096 // %ArrayIteratorPrototype%.next must not be modified.
5097 NativeObject* arrayIteratorProto;
5098 uint32_t iterNextSlot;
5099 JSFunction* nextFun;
5100 if (!IsArrayIteratorPrototypeOptimizable(cx_, &arrayIteratorProto,
5101 &iterNextSlot, &nextFun)) {
5102 return AttachDecision::NoAction;
5103 }
5104
5105 ValOperandId valId(writer.setInputOperandId(0));
5106 ObjOperandId objId = writer.guardToObject(valId);
5107
5108 // Guard the object is a packed array with Array.prototype as proto.
5109 MOZ_ASSERT(obj->is<ArrayObject>());
5110 writer.guardShape(objId, obj->shape());
5111 writer.guardArrayIsPacked(objId);
5112
5113 // Guard on Array.prototype[@@iterator].
5114 ObjOperandId arrProtoId = writer.loadObject(arrProto);
5115 ObjOperandId iterId = writer.loadObject(iterFun);
5116 writer.guardShape(arrProtoId, arrProto->shape());
5117 writer.guardDynamicSlotIsSpecificObject(arrProtoId, iterId, arrProtoIterSlot);
5118
5119 // Guard on %ArrayIteratorPrototype%.next.
5120 ObjOperandId iterProtoId = writer.loadObject(arrayIteratorProto);
5121 ObjOperandId nextId = writer.loadObject(nextFun);
5122 writer.guardShape(iterProtoId, arrayIteratorProto->shape());
5123 writer.guardDynamicSlotIsSpecificObject(iterProtoId, nextId, iterNextSlot);
5124
5125 writer.loadObjectResult(objId);
5126 writer.returnFromIC();
5127
5128 trackAttached("Array");
5129 return AttachDecision::Attach;
5130 }
5131
tryAttachArguments()5132 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArguments() {
5133 // The value must be an arguments object.
5134 if (!val_.isObject()) {
5135 return AttachDecision::NoAction;
5136 }
5137 RootedObject obj(cx_, &val_.toObject());
5138 if (!obj->is<ArgumentsObject>()) {
5139 return AttachDecision::NoAction;
5140 }
5141 auto args = obj.as<ArgumentsObject>();
5142
5143 // Ensure neither elements, nor the length, nor the iterator has been
5144 // overridden. Also ensure no args are forwarded to allow reading them
5145 // directly from the frame.
5146 if (args->hasOverriddenElement() || args->hasOverriddenLength() ||
5147 args->hasOverriddenIterator() || args->anyArgIsForwarded()) {
5148 return AttachDecision::NoAction;
5149 }
5150
5151 RootedShape shape(cx_, GlobalObject::getArrayShapeWithDefaultProto(cx_));
5152 if (!shape) {
5153 cx_->recoverFromOutOfMemory();
5154 return AttachDecision::NoAction;
5155 }
5156
5157 NativeObject* arrayIteratorProto;
5158 uint32_t slot;
5159 JSFunction* nextFun;
5160 if (!IsArrayIteratorPrototypeOptimizable(cx_, &arrayIteratorProto, &slot,
5161 &nextFun)) {
5162 return AttachDecision::NoAction;
5163 }
5164
5165 ValOperandId valId(writer.setInputOperandId(0));
5166 ObjOperandId objId = writer.guardToObject(valId);
5167
5168 if (args->is<MappedArgumentsObject>()) {
5169 writer.guardClass(objId, GuardClassKind::MappedArguments);
5170 } else {
5171 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
5172 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
5173 }
5174 uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT |
5175 ArgumentsObject::LENGTH_OVERRIDDEN_BIT |
5176 ArgumentsObject::ITERATOR_OVERRIDDEN_BIT |
5177 ArgumentsObject::FORWARDED_ARGUMENTS_BIT;
5178 writer.guardArgumentsObjectFlags(objId, flags);
5179
5180 ObjOperandId protoId = writer.loadObject(arrayIteratorProto);
5181 ObjOperandId nextId = writer.loadObject(nextFun);
5182
5183 writer.guardShape(protoId, arrayIteratorProto->shape());
5184
5185 // Ensure that proto[slot] == nextFun.
5186 writer.guardDynamicSlotIsSpecificObject(protoId, nextId, slot);
5187
5188 writer.arrayFromArgumentsObjectResult(objId, shape);
5189 writer.returnFromIC();
5190
5191 trackAttached("Arguments");
5192 return AttachDecision::Attach;
5193 }
5194
tryAttachNotOptimizable()5195 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachNotOptimizable() {
5196 ValOperandId valId(writer.setInputOperandId(0));
5197
5198 writer.loadUndefinedResult();
5199 writer.returnFromIC();
5200
5201 trackAttached("NotOptimizable");
5202 return AttachDecision::Attach;
5203 }
5204
trackAttached(const char * name)5205 void OptimizeSpreadCallIRGenerator::trackAttached(const char* name) {
5206 #ifdef JS_CACHEIR_SPEW
5207 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
5208 sp.valueProperty("val", val_);
5209 }
5210 #endif
5211 }
5212
CallIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,JSOp op,ICState state,uint32_t argc,HandleValue callee,HandleValue thisval,HandleValue newTarget,HandleValueArray args)5213 CallIRGenerator::CallIRGenerator(JSContext* cx, HandleScript script,
5214 jsbytecode* pc, JSOp op, ICState state,
5215 uint32_t argc, HandleValue callee,
5216 HandleValue thisval, HandleValue newTarget,
5217 HandleValueArray args)
5218 : IRGenerator(cx, script, pc, CacheKind::Call, state),
5219 op_(op),
5220 argc_(argc),
5221 callee_(callee),
5222 thisval_(thisval),
5223 newTarget_(newTarget),
5224 args_(args) {}
5225
emitNativeCalleeGuard(JSFunction * callee)5226 void CallIRGenerator::emitNativeCalleeGuard(JSFunction* callee) {
5227 // Note: we rely on GuardSpecificFunction to also guard against the same
5228 // native from a different realm.
5229 MOZ_ASSERT(callee->isNativeWithoutJitEntry());
5230
5231 bool isConstructing = IsConstructPC(pc_);
5232 CallFlags flags(isConstructing, IsSpreadPC(pc_));
5233
5234 ValOperandId calleeValId =
5235 writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags);
5236 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
5237 writer.guardSpecificFunction(calleeObjId, callee);
5238
5239 // If we're constructing we also need to guard newTarget == callee.
5240 if (isConstructing) {
5241 MOZ_ASSERT(&newTarget_.toObject() == callee);
5242 ValOperandId newTargetValId =
5243 writer.loadArgumentFixedSlot(ArgumentKind::NewTarget, argc_, flags);
5244 ObjOperandId newTargetObjId = writer.guardToObject(newTargetValId);
5245 writer.guardSpecificFunction(newTargetObjId, callee);
5246 }
5247 }
5248
emitCalleeGuard(ObjOperandId calleeId,JSFunction * callee)5249 void CallIRGenerator::emitCalleeGuard(ObjOperandId calleeId,
5250 JSFunction* callee) {
5251 // Guarding on the callee JSFunction* is most efficient, but doesn't work well
5252 // for lambda clones (multiple functions with the same BaseScript). We guard
5253 // on the function's BaseScript if the callee is scripted and this isn't the
5254 // first IC stub.
5255 if (isFirstStub_ || !callee->hasBaseScript() ||
5256 callee->isSelfHostedBuiltin()) {
5257 writer.guardSpecificFunction(calleeId, callee);
5258 } else {
5259 writer.guardClass(calleeId, GuardClassKind::JSFunction);
5260 writer.guardFunctionScript(calleeId, callee->baseScript());
5261 }
5262 }
5263
tryAttachArrayPush(HandleFunction callee)5264 AttachDecision CallIRGenerator::tryAttachArrayPush(HandleFunction callee) {
5265 // Only optimize on obj.push(val);
5266 if (argc_ != 1 || !thisval_.isObject()) {
5267 return AttachDecision::NoAction;
5268 }
5269
5270 // Where |obj| is a native array.
5271 JSObject* thisobj = &thisval_.toObject();
5272 if (!thisobj->is<ArrayObject>()) {
5273 return AttachDecision::NoAction;
5274 }
5275
5276 auto* thisarray = &thisobj->as<ArrayObject>();
5277
5278 // Check for other indexed properties or class hooks.
5279 if (!CanAttachAddElement(thisarray, /* isInit = */ false)) {
5280 return AttachDecision::NoAction;
5281 }
5282
5283 // Can't add new elements to arrays with non-writable length.
5284 if (!thisarray->lengthIsWritable()) {
5285 return AttachDecision::NoAction;
5286 }
5287
5288 // Check that array is extensible.
5289 if (!thisarray->isExtensible()) {
5290 return AttachDecision::NoAction;
5291 }
5292
5293 // Check that the array is completely initialized (no holes).
5294 if (thisarray->getDenseInitializedLength() != thisarray->length()) {
5295 return AttachDecision::NoAction;
5296 }
5297
5298 MOZ_ASSERT(!thisarray->denseElementsAreFrozen(),
5299 "Extensible arrays should not have frozen elements");
5300
5301 // After this point, we can generate code fine.
5302
5303 // Initialize the input operand.
5304 Int32OperandId argcId(writer.setInputOperandId(0));
5305
5306 // Guard callee is the 'push' native function.
5307 emitNativeCalleeGuard(callee);
5308
5309 // Guard this is an array object.
5310 ValOperandId thisValId =
5311 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
5312 ObjOperandId thisObjId = writer.guardToObject(thisValId);
5313
5314 // Guard that the shape matches.
5315 TestMatchingNativeReceiver(writer, thisarray, thisObjId);
5316
5317 // Guard proto chain shapes.
5318 ShapeGuardProtoChain(writer, thisarray, thisObjId);
5319
5320 // arr.push(x) is equivalent to arr[arr.length] = x for regular arrays.
5321 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5322 writer.arrayPush(thisObjId, argId);
5323
5324 writer.returnFromIC();
5325
5326 trackAttached("ArrayPush");
5327 return AttachDecision::Attach;
5328 }
5329
tryAttachArrayPopShift(HandleFunction callee,InlinableNative native)5330 AttachDecision CallIRGenerator::tryAttachArrayPopShift(HandleFunction callee,
5331 InlinableNative native) {
5332 // Expecting no arguments.
5333 if (argc_ != 0) {
5334 return AttachDecision::NoAction;
5335 }
5336
5337 // Only optimize if |this| is a packed array.
5338 if (!thisval_.isObject() || !IsPackedArray(&thisval_.toObject())) {
5339 return AttachDecision::NoAction;
5340 }
5341
5342 // Other conditions:
5343 //
5344 // * The array length needs to be writable because we're changing it.
5345 // * The array must be extensible. Non-extensible arrays require preserving
5346 // the |initializedLength == capacity| invariant on ObjectElements.
5347 // See NativeObject::shrinkCapacityToInitializedLength.
5348 // This also ensures the elements aren't sealed/frozen.
5349 // * There must not be a for-in iterator for the elements because the IC stub
5350 // does not suppress deleted properties.
5351 ArrayObject* arr = &thisval_.toObject().as<ArrayObject>();
5352 if (!arr->lengthIsWritable() || !arr->isExtensible() ||
5353 arr->denseElementsHaveMaybeInIterationFlag()) {
5354 return AttachDecision::NoAction;
5355 }
5356
5357 // Initialize the input operand.
5358 Int32OperandId argcId(writer.setInputOperandId(0));
5359
5360 // Guard callee is the 'pop' or 'shift' native function.
5361 emitNativeCalleeGuard(callee);
5362
5363 ValOperandId thisValId =
5364 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
5365 ObjOperandId objId = writer.guardToObject(thisValId);
5366 writer.guardClass(objId, GuardClassKind::Array);
5367
5368 if (native == InlinableNative::ArrayPop) {
5369 writer.packedArrayPopResult(objId);
5370 } else {
5371 MOZ_ASSERT(native == InlinableNative::ArrayShift);
5372 writer.packedArrayShiftResult(objId);
5373 }
5374
5375 writer.returnFromIC();
5376
5377 trackAttached("ArrayPopShift");
5378 return AttachDecision::Attach;
5379 }
5380
tryAttachArrayJoin(HandleFunction callee)5381 AttachDecision CallIRGenerator::tryAttachArrayJoin(HandleFunction callee) {
5382 // Only handle argc <= 1.
5383 if (argc_ > 1) {
5384 return AttachDecision::NoAction;
5385 }
5386
5387 // Only optimize if |this| is an array.
5388 if (!thisval_.isObject() || !thisval_.toObject().is<ArrayObject>()) {
5389 return AttachDecision::NoAction;
5390 }
5391
5392 // The separator argument must be a string, if present.
5393 if (argc_ > 0 && !args_[0].isString()) {
5394 return AttachDecision::NoAction;
5395 }
5396
5397 // IC stub code can handle non-packed array.
5398
5399 // Initialize the input operand.
5400 Int32OperandId argcId(writer.setInputOperandId(0));
5401
5402 // Guard callee is the 'join' native function.
5403 emitNativeCalleeGuard(callee);
5404
5405 // Guard this is an array object.
5406 ValOperandId thisValId =
5407 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
5408 ObjOperandId thisObjId = writer.guardToObject(thisValId);
5409 writer.guardClass(thisObjId, GuardClassKind::Array);
5410
5411 StringOperandId sepId;
5412 if (argc_ == 1) {
5413 // If argcount is 1, guard that the argument is a string.
5414 ValOperandId argValId =
5415 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5416 sepId = writer.guardToString(argValId);
5417 } else {
5418 sepId = writer.loadConstantString(cx_->names().comma);
5419 }
5420
5421 // Do the join.
5422 writer.arrayJoinResult(thisObjId, sepId);
5423
5424 writer.returnFromIC();
5425
5426 trackAttached("ArrayJoin");
5427 return AttachDecision::Attach;
5428 }
5429
tryAttachArraySlice(HandleFunction callee)5430 AttachDecision CallIRGenerator::tryAttachArraySlice(HandleFunction callee) {
5431 // Only handle argc <= 2.
5432 if (argc_ > 2) {
5433 return AttachDecision::NoAction;
5434 }
5435
5436 // Only optimize if |this| is a packed array.
5437 if (!thisval_.isObject() || !IsPackedArray(&thisval_.toObject())) {
5438 return AttachDecision::NoAction;
5439 }
5440
5441 // Arguments for the sliced region must be integers.
5442 if (argc_ > 0 && !args_[0].isInt32()) {
5443 return AttachDecision::NoAction;
5444 }
5445 if (argc_ > 1 && !args_[1].isInt32()) {
5446 return AttachDecision::NoAction;
5447 }
5448
5449 RootedArrayObject arr(cx_, &thisval_.toObject().as<ArrayObject>());
5450
5451 JSObject* templateObj = NewDenseFullyAllocatedArray(cx_, 0, TenuredObject);
5452 if (!templateObj) {
5453 cx_->recoverFromOutOfMemory();
5454 return AttachDecision::NoAction;
5455 }
5456
5457 // Initialize the input operand.
5458 Int32OperandId argcId(writer.setInputOperandId(0));
5459
5460 // Guard callee is the 'slice' native function.
5461 emitNativeCalleeGuard(callee);
5462
5463 ValOperandId thisValId =
5464 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
5465 ObjOperandId objId = writer.guardToObject(thisValId);
5466 writer.guardClass(objId, GuardClassKind::Array);
5467
5468 Int32OperandId int32BeginId;
5469 if (argc_ > 0) {
5470 ValOperandId beginId =
5471 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5472 int32BeginId = writer.guardToInt32(beginId);
5473 } else {
5474 int32BeginId = writer.loadInt32Constant(0);
5475 }
5476
5477 Int32OperandId int32EndId;
5478 if (argc_ > 1) {
5479 ValOperandId endId =
5480 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
5481 int32EndId = writer.guardToInt32(endId);
5482 } else {
5483 int32EndId = writer.loadInt32ArrayLength(objId);
5484 }
5485
5486 writer.packedArraySliceResult(templateObj, objId, int32BeginId, int32EndId);
5487 writer.returnFromIC();
5488
5489 trackAttached("ArraySlice");
5490 return AttachDecision::Attach;
5491 }
5492
tryAttachArrayIsArray(HandleFunction callee)5493 AttachDecision CallIRGenerator::tryAttachArrayIsArray(HandleFunction callee) {
5494 // Need a single argument.
5495 if (argc_ != 1) {
5496 return AttachDecision::NoAction;
5497 }
5498
5499 // Initialize the input operand.
5500 Int32OperandId argcId(writer.setInputOperandId(0));
5501
5502 // Guard callee is the 'isArray' native function.
5503 emitNativeCalleeGuard(callee);
5504
5505 // Check if the argument is an Array and return result.
5506 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5507 writer.isArrayResult(argId);
5508 writer.returnFromIC();
5509
5510 trackAttached("ArrayIsArray");
5511 return AttachDecision::Attach;
5512 }
5513
tryAttachDataViewGet(HandleFunction callee,Scalar::Type type)5514 AttachDecision CallIRGenerator::tryAttachDataViewGet(HandleFunction callee,
5515 Scalar::Type type) {
5516 // Ensure |this| is a DataViewObject.
5517 if (!thisval_.isObject() || !thisval_.toObject().is<DataViewObject>()) {
5518 return AttachDecision::NoAction;
5519 }
5520
5521 // Expected arguments: offset (number), optional littleEndian (boolean).
5522 if (argc_ < 1 || argc_ > 2) {
5523 return AttachDecision::NoAction;
5524 }
5525 int64_t offsetInt64;
5526 if (!ValueIsInt64Index(args_[0], &offsetInt64)) {
5527 return AttachDecision::NoAction;
5528 }
5529 if (argc_ > 1 && !args_[1].isBoolean()) {
5530 return AttachDecision::NoAction;
5531 }
5532
5533 DataViewObject* dv = &thisval_.toObject().as<DataViewObject>();
5534
5535 // Bounds check the offset.
5536 if (offsetInt64 < 0 ||
5537 !dv->offsetIsInBounds(Scalar::byteSize(type), offsetInt64)) {
5538 return AttachDecision::NoAction;
5539 }
5540
5541 // For getUint32 we let the stub return an Int32 if we have not seen a
5542 // double, to allow better codegen in Warp while avoiding bailout loops.
5543 bool forceDoubleForUint32 = false;
5544 if (type == Scalar::Uint32) {
5545 bool isLittleEndian = argc_ > 1 && args_[1].toBoolean();
5546 uint32_t res = dv->read<uint32_t>(offsetInt64, isLittleEndian);
5547 forceDoubleForUint32 = res >= INT32_MAX;
5548 }
5549
5550 // Initialize the input operand.
5551 Int32OperandId argcId(writer.setInputOperandId(0));
5552
5553 // Guard callee is this DataView native function.
5554 emitNativeCalleeGuard(callee);
5555
5556 // Guard |this| is a DataViewObject.
5557 ValOperandId thisValId =
5558 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
5559 ObjOperandId objId = writer.guardToObject(thisValId);
5560 writer.guardClass(objId, GuardClassKind::DataView);
5561
5562 // Convert offset to intPtr.
5563 ValOperandId offsetId =
5564 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5565 IntPtrOperandId intPtrOffsetId =
5566 guardToIntPtrIndex(args_[0], offsetId, /* supportOOB = */ false);
5567
5568 BooleanOperandId boolLittleEndianId;
5569 if (argc_ > 1) {
5570 ValOperandId littleEndianId =
5571 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
5572 boolLittleEndianId = writer.guardToBoolean(littleEndianId);
5573 } else {
5574 boolLittleEndianId = writer.loadBooleanConstant(false);
5575 }
5576
5577 writer.loadDataViewValueResult(objId, intPtrOffsetId, boolLittleEndianId,
5578 type, forceDoubleForUint32);
5579 writer.returnFromIC();
5580
5581 trackAttached("DataViewGet");
5582 return AttachDecision::Attach;
5583 }
5584
tryAttachDataViewSet(HandleFunction callee,Scalar::Type type)5585 AttachDecision CallIRGenerator::tryAttachDataViewSet(HandleFunction callee,
5586 Scalar::Type type) {
5587 // Ensure |this| is a DataViewObject.
5588 if (!thisval_.isObject() || !thisval_.toObject().is<DataViewObject>()) {
5589 return AttachDecision::NoAction;
5590 }
5591
5592 // Expected arguments: offset (number), value, optional littleEndian (boolean)
5593 if (argc_ < 2 || argc_ > 3) {
5594 return AttachDecision::NoAction;
5595 }
5596 int64_t offsetInt64;
5597 if (!ValueIsInt64Index(args_[0], &offsetInt64)) {
5598 return AttachDecision::NoAction;
5599 }
5600 if (!ValueIsNumeric(type, args_[1])) {
5601 return AttachDecision::NoAction;
5602 }
5603 if (argc_ > 2 && !args_[2].isBoolean()) {
5604 return AttachDecision::NoAction;
5605 }
5606
5607 DataViewObject* dv = &thisval_.toObject().as<DataViewObject>();
5608
5609 // Bounds check the offset.
5610 if (offsetInt64 < 0 ||
5611 !dv->offsetIsInBounds(Scalar::byteSize(type), offsetInt64)) {
5612 return AttachDecision::NoAction;
5613 }
5614
5615 // Initialize the input operand.
5616 Int32OperandId argcId(writer.setInputOperandId(0));
5617
5618 // Guard callee is this DataView native function.
5619 emitNativeCalleeGuard(callee);
5620
5621 // Guard |this| is a DataViewObject.
5622 ValOperandId thisValId =
5623 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
5624 ObjOperandId objId = writer.guardToObject(thisValId);
5625 writer.guardClass(objId, GuardClassKind::DataView);
5626
5627 // Convert offset to intPtr.
5628 ValOperandId offsetId =
5629 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5630 IntPtrOperandId intPtrOffsetId =
5631 guardToIntPtrIndex(args_[0], offsetId, /* supportOOB = */ false);
5632
5633 // Convert value to number or BigInt.
5634 ValOperandId valueId =
5635 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
5636 OperandId numericValueId = emitNumericGuard(valueId, type);
5637
5638 BooleanOperandId boolLittleEndianId;
5639 if (argc_ > 2) {
5640 ValOperandId littleEndianId =
5641 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
5642 boolLittleEndianId = writer.guardToBoolean(littleEndianId);
5643 } else {
5644 boolLittleEndianId = writer.loadBooleanConstant(false);
5645 }
5646
5647 writer.storeDataViewValueResult(objId, intPtrOffsetId, numericValueId,
5648 boolLittleEndianId, type);
5649 writer.returnFromIC();
5650
5651 trackAttached("DataViewSet");
5652 return AttachDecision::Attach;
5653 }
5654
tryAttachUnsafeGetReservedSlot(HandleFunction callee,InlinableNative native)5655 AttachDecision CallIRGenerator::tryAttachUnsafeGetReservedSlot(
5656 HandleFunction callee, InlinableNative native) {
5657 // Self-hosted code calls this with (object, int32) arguments.
5658 MOZ_ASSERT(argc_ == 2);
5659 MOZ_ASSERT(args_[0].isObject());
5660 MOZ_ASSERT(args_[1].isInt32());
5661 MOZ_ASSERT(args_[1].toInt32() >= 0);
5662
5663 uint32_t slot = uint32_t(args_[1].toInt32());
5664 if (slot >= NativeObject::MAX_FIXED_SLOTS) {
5665 return AttachDecision::NoAction;
5666 }
5667 size_t offset = NativeObject::getFixedSlotOffset(slot);
5668
5669 // Initialize the input operand.
5670 Int32OperandId argcId(writer.setInputOperandId(0));
5671
5672 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
5673
5674 // Guard that the first argument is an object.
5675 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5676 ObjOperandId objId = writer.guardToObject(arg0Id);
5677
5678 // BytecodeEmitter::checkSelfHostedUnsafeGetReservedSlot ensures that the
5679 // slot argument is constant. (At least for direct calls)
5680
5681 switch (native) {
5682 case InlinableNative::IntrinsicUnsafeGetReservedSlot:
5683 writer.loadFixedSlotResult(objId, offset);
5684 break;
5685 case InlinableNative::IntrinsicUnsafeGetObjectFromReservedSlot:
5686 writer.loadFixedSlotTypedResult(objId, offset, ValueType::Object);
5687 break;
5688 case InlinableNative::IntrinsicUnsafeGetInt32FromReservedSlot:
5689 writer.loadFixedSlotTypedResult(objId, offset, ValueType::Int32);
5690 break;
5691 case InlinableNative::IntrinsicUnsafeGetStringFromReservedSlot:
5692 writer.loadFixedSlotTypedResult(objId, offset, ValueType::String);
5693 break;
5694 case InlinableNative::IntrinsicUnsafeGetBooleanFromReservedSlot:
5695 writer.loadFixedSlotTypedResult(objId, offset, ValueType::Boolean);
5696 break;
5697 default:
5698 MOZ_CRASH("unexpected native");
5699 }
5700
5701 writer.returnFromIC();
5702
5703 trackAttached("UnsafeGetReservedSlot");
5704 return AttachDecision::Attach;
5705 }
5706
tryAttachUnsafeSetReservedSlot(HandleFunction callee)5707 AttachDecision CallIRGenerator::tryAttachUnsafeSetReservedSlot(
5708 HandleFunction callee) {
5709 // Self-hosted code calls this with (object, int32, value) arguments.
5710 MOZ_ASSERT(argc_ == 3);
5711 MOZ_ASSERT(args_[0].isObject());
5712 MOZ_ASSERT(args_[1].isInt32());
5713 MOZ_ASSERT(args_[1].toInt32() >= 0);
5714
5715 uint32_t slot = uint32_t(args_[1].toInt32());
5716 if (slot >= NativeObject::MAX_FIXED_SLOTS) {
5717 return AttachDecision::NoAction;
5718 }
5719 size_t offset = NativeObject::getFixedSlotOffset(slot);
5720
5721 // Initialize the input operand.
5722 Int32OperandId argcId(writer.setInputOperandId(0));
5723
5724 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
5725
5726 // Guard that the first argument is an object.
5727 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5728 ObjOperandId objId = writer.guardToObject(arg0Id);
5729
5730 // BytecodeEmitter::checkSelfHostedUnsafeSetReservedSlot ensures that the
5731 // slot argument is constant. (At least for direct calls)
5732
5733 // Get the value to set.
5734 ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
5735
5736 // Set the fixed slot and return undefined.
5737 writer.storeFixedSlotUndefinedResult(objId, offset, valId);
5738
5739 // This stub always returns undefined.
5740 writer.returnFromIC();
5741
5742 trackAttached("UnsafeSetReservedSlot");
5743 return AttachDecision::Attach;
5744 }
5745
tryAttachIsSuspendedGenerator(HandleFunction callee)5746 AttachDecision CallIRGenerator::tryAttachIsSuspendedGenerator(
5747 HandleFunction callee) {
5748 // The IsSuspendedGenerator intrinsic is only called in
5749 // self-hosted code, so it's safe to assume we have a single
5750 // argument and the callee is our intrinsic.
5751
5752 MOZ_ASSERT(argc_ == 1);
5753
5754 Int32OperandId argcId(writer.setInputOperandId(0));
5755
5756 // Stack layout here is (bottom to top):
5757 // 2: Callee
5758 // 1: ThisValue
5759 // 0: Arg <-- Top of stack.
5760 // We only care about the argument.
5761 ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5762
5763 // Check whether the argument is a suspended generator.
5764 // We don't need guards, because IsSuspendedGenerator returns
5765 // false for values that are not generator objects.
5766 writer.callIsSuspendedGeneratorResult(valId);
5767 writer.returnFromIC();
5768
5769 trackAttached("IsSuspendedGenerator");
5770 return AttachDecision::Attach;
5771 }
5772
tryAttachToObject(HandleFunction callee,InlinableNative native)5773 AttachDecision CallIRGenerator::tryAttachToObject(HandleFunction callee,
5774 InlinableNative native) {
5775 // Self-hosted code calls this with a single argument.
5776 MOZ_ASSERT_IF(native == InlinableNative::IntrinsicToObject, argc_ == 1);
5777
5778 // Need a single object argument.
5779 // TODO(Warp): Support all or more conversions to object.
5780 // Note: ToObject and Object differ in their behavior for undefined/null.
5781 if (argc_ != 1 || !args_[0].isObject()) {
5782 return AttachDecision::NoAction;
5783 }
5784
5785 // Initialize the input operand.
5786 Int32OperandId argcId(writer.setInputOperandId(0));
5787
5788 // Guard callee is the 'Object' function.
5789 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
5790 if (native == InlinableNative::Object) {
5791 emitNativeCalleeGuard(callee);
5792 }
5793
5794 // Guard that the argument is an object.
5795 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5796 ObjOperandId objId = writer.guardToObject(argId);
5797
5798 // Return the object.
5799 writer.loadObjectResult(objId);
5800 writer.returnFromIC();
5801
5802 if (native == InlinableNative::IntrinsicToObject) {
5803 trackAttached("ToObject");
5804 } else {
5805 MOZ_ASSERT(native == InlinableNative::Object);
5806 trackAttached("Object");
5807 }
5808 return AttachDecision::Attach;
5809 }
5810
tryAttachToInteger(HandleFunction callee)5811 AttachDecision CallIRGenerator::tryAttachToInteger(HandleFunction callee) {
5812 // Self-hosted code calls this with a single argument.
5813 MOZ_ASSERT(argc_ == 1);
5814
5815 // Need a single int32 argument.
5816 // TODO(Warp): Support all or more conversions to integer.
5817 // Make sure to update this code correctly if we ever start
5818 // returning non-int32 integers.
5819 if (!args_[0].isInt32()) {
5820 return AttachDecision::NoAction;
5821 }
5822
5823 // Initialize the input operand.
5824 Int32OperandId argcId(writer.setInputOperandId(0));
5825
5826 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
5827
5828 // Guard that the argument is an int32.
5829 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5830 Int32OperandId int32Id = writer.guardToInt32(argId);
5831
5832 // Return the int32.
5833 writer.loadInt32Result(int32Id);
5834 writer.returnFromIC();
5835
5836 trackAttached("ToInteger");
5837 return AttachDecision::Attach;
5838 }
5839
tryAttachToLength(HandleFunction callee)5840 AttachDecision CallIRGenerator::tryAttachToLength(HandleFunction callee) {
5841 // Self-hosted code calls this with a single argument.
5842 MOZ_ASSERT(argc_ == 1);
5843
5844 // Need a single int32 argument.
5845 if (!args_[0].isInt32()) {
5846 return AttachDecision::NoAction;
5847 }
5848
5849 // Initialize the input operand.
5850 Int32OperandId argcId(writer.setInputOperandId(0));
5851
5852 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
5853
5854 // ToLength(int32) is equivalent to max(int32, 0).
5855 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5856 Int32OperandId int32ArgId = writer.guardToInt32(argId);
5857 Int32OperandId zeroId = writer.loadInt32Constant(0);
5858 bool isMax = true;
5859 Int32OperandId maxId = writer.int32MinMax(isMax, int32ArgId, zeroId);
5860 writer.loadInt32Result(maxId);
5861 writer.returnFromIC();
5862
5863 trackAttached("ToLength");
5864 return AttachDecision::Attach;
5865 }
5866
tryAttachIsObject(HandleFunction callee)5867 AttachDecision CallIRGenerator::tryAttachIsObject(HandleFunction callee) {
5868 // Self-hosted code calls this with a single argument.
5869 MOZ_ASSERT(argc_ == 1);
5870
5871 // Initialize the input operand.
5872 Int32OperandId argcId(writer.setInputOperandId(0));
5873
5874 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
5875
5876 // Type check the argument and return result.
5877 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5878 writer.isObjectResult(argId);
5879 writer.returnFromIC();
5880
5881 trackAttached("IsObject");
5882 return AttachDecision::Attach;
5883 }
5884
tryAttachIsPackedArray(HandleFunction callee)5885 AttachDecision CallIRGenerator::tryAttachIsPackedArray(HandleFunction callee) {
5886 // Self-hosted code calls this with a single object argument.
5887 MOZ_ASSERT(argc_ == 1);
5888 MOZ_ASSERT(args_[0].isObject());
5889
5890 // Initialize the input operand.
5891 Int32OperandId argcId(writer.setInputOperandId(0));
5892
5893 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
5894
5895 // Check if the argument is packed and return result.
5896 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5897 ObjOperandId objArgId = writer.guardToObject(argId);
5898 writer.isPackedArrayResult(objArgId);
5899 writer.returnFromIC();
5900
5901 trackAttached("IsPackedArray");
5902 return AttachDecision::Attach;
5903 }
5904
tryAttachIsCallable(HandleFunction callee)5905 AttachDecision CallIRGenerator::tryAttachIsCallable(HandleFunction callee) {
5906 // Self-hosted code calls this with a single argument.
5907 MOZ_ASSERT(argc_ == 1);
5908
5909 // Initialize the input operand.
5910 Int32OperandId argcId(writer.setInputOperandId(0));
5911
5912 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
5913
5914 // Check if the argument is callable and return result.
5915 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5916 writer.isCallableResult(argId);
5917 writer.returnFromIC();
5918
5919 trackAttached("IsCallable");
5920 return AttachDecision::Attach;
5921 }
5922
tryAttachIsConstructor(HandleFunction callee)5923 AttachDecision CallIRGenerator::tryAttachIsConstructor(HandleFunction callee) {
5924 // Self-hosted code calls this with a single argument.
5925 MOZ_ASSERT(argc_ == 1);
5926
5927 // Need a single object argument.
5928 if (!args_[0].isObject()) {
5929 return AttachDecision::NoAction;
5930 }
5931
5932 // Initialize the input operand.
5933 Int32OperandId argcId(writer.setInputOperandId(0));
5934
5935 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
5936
5937 // Guard that the argument is an object.
5938 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5939 ObjOperandId objId = writer.guardToObject(argId);
5940
5941 // Check if the argument is a constructor and return result.
5942 writer.isConstructorResult(objId);
5943 writer.returnFromIC();
5944
5945 trackAttached("IsConstructor");
5946 return AttachDecision::Attach;
5947 }
5948
tryAttachIsCrossRealmArrayConstructor(HandleFunction callee)5949 AttachDecision CallIRGenerator::tryAttachIsCrossRealmArrayConstructor(
5950 HandleFunction callee) {
5951 // Self-hosted code calls this with an object argument.
5952 MOZ_ASSERT(argc_ == 1);
5953 MOZ_ASSERT(args_[0].isObject());
5954
5955 if (args_[0].toObject().is<ProxyObject>()) {
5956 return AttachDecision::NoAction;
5957 }
5958
5959 // Initialize the input operand.
5960 Int32OperandId argcId(writer.setInputOperandId(0));
5961
5962 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
5963
5964 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5965 ObjOperandId objId = writer.guardToObject(argId);
5966 writer.guardIsNotProxy(objId);
5967 writer.isCrossRealmArrayConstructorResult(objId);
5968 writer.returnFromIC();
5969
5970 trackAttached("IsCrossRealmArrayConstructor");
5971 return AttachDecision::Attach;
5972 }
5973
tryAttachGuardToClass(HandleFunction callee,InlinableNative native)5974 AttachDecision CallIRGenerator::tryAttachGuardToClass(HandleFunction callee,
5975 InlinableNative native) {
5976 // Self-hosted code calls this with an object argument.
5977 MOZ_ASSERT(argc_ == 1);
5978 MOZ_ASSERT(args_[0].isObject());
5979
5980 // Class must match.
5981 const JSClass* clasp = InlinableNativeGuardToClass(native);
5982 if (args_[0].toObject().getClass() != clasp) {
5983 return AttachDecision::NoAction;
5984 }
5985
5986 // Initialize the input operand.
5987 Int32OperandId argcId(writer.setInputOperandId(0));
5988
5989 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
5990
5991 // Guard that the argument is an object.
5992 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
5993 ObjOperandId objId = writer.guardToObject(argId);
5994
5995 // Guard that the object has the correct class.
5996 writer.guardAnyClass(objId, clasp);
5997
5998 // Return the object.
5999 writer.loadObjectResult(objId);
6000 writer.returnFromIC();
6001
6002 trackAttached("GuardToClass");
6003 return AttachDecision::Attach;
6004 }
6005
tryAttachHasClass(HandleFunction callee,const JSClass * clasp,bool isPossiblyWrapped)6006 AttachDecision CallIRGenerator::tryAttachHasClass(HandleFunction callee,
6007 const JSClass* clasp,
6008 bool isPossiblyWrapped) {
6009 // Self-hosted code calls this with an object argument.
6010 MOZ_ASSERT(argc_ == 1);
6011 MOZ_ASSERT(args_[0].isObject());
6012
6013 // Only optimize when the object isn't a proxy.
6014 if (isPossiblyWrapped && args_[0].toObject().is<ProxyObject>()) {
6015 return AttachDecision::NoAction;
6016 }
6017
6018 // Initialize the input operand.
6019 Int32OperandId argcId(writer.setInputOperandId(0));
6020
6021 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6022
6023 // Perform the Class check.
6024 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6025 ObjOperandId objId = writer.guardToObject(argId);
6026
6027 if (isPossiblyWrapped) {
6028 writer.guardIsNotProxy(objId);
6029 }
6030
6031 writer.hasClassResult(objId, clasp);
6032 writer.returnFromIC();
6033
6034 trackAttached("HasClass");
6035 return AttachDecision::Attach;
6036 }
6037
tryAttachRegExpMatcherSearcherTester(HandleFunction callee,InlinableNative native)6038 AttachDecision CallIRGenerator::tryAttachRegExpMatcherSearcherTester(
6039 HandleFunction callee, InlinableNative native) {
6040 // Self-hosted code calls this with (object, string, number) arguments.
6041 MOZ_ASSERT(argc_ == 3);
6042 MOZ_ASSERT(args_[0].isObject());
6043 MOZ_ASSERT(args_[1].isString());
6044 MOZ_ASSERT(args_[2].isNumber());
6045
6046 // It's not guaranteed that the JITs have typed |lastIndex| as an Int32.
6047 if (!args_[2].isInt32()) {
6048 return AttachDecision::NoAction;
6049 }
6050
6051 // Initialize the input operand.
6052 Int32OperandId argcId(writer.setInputOperandId(0));
6053
6054 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6055
6056 // Guard argument types.
6057 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6058 ObjOperandId reId = writer.guardToObject(arg0Id);
6059
6060 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6061 StringOperandId inputId = writer.guardToString(arg1Id);
6062
6063 ValOperandId arg2Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
6064 Int32OperandId lastIndexId = writer.guardToInt32(arg2Id);
6065
6066 switch (native) {
6067 case InlinableNative::RegExpMatcher:
6068 writer.callRegExpMatcherResult(reId, inputId, lastIndexId);
6069 writer.returnFromIC();
6070 trackAttached("RegExpMatcher");
6071 break;
6072
6073 case InlinableNative::RegExpSearcher:
6074 writer.callRegExpSearcherResult(reId, inputId, lastIndexId);
6075 writer.returnFromIC();
6076 trackAttached("RegExpSearcher");
6077 break;
6078
6079 case InlinableNative::RegExpTester:
6080 writer.callRegExpTesterResult(reId, inputId, lastIndexId);
6081 writer.returnFromIC();
6082 trackAttached("RegExpTester");
6083 break;
6084
6085 default:
6086 MOZ_CRASH("Unexpected native");
6087 }
6088
6089 return AttachDecision::Attach;
6090 }
6091
tryAttachRegExpPrototypeOptimizable(HandleFunction callee)6092 AttachDecision CallIRGenerator::tryAttachRegExpPrototypeOptimizable(
6093 HandleFunction callee) {
6094 // Self-hosted code calls this with a single object argument.
6095 MOZ_ASSERT(argc_ == 1);
6096 MOZ_ASSERT(args_[0].isObject());
6097
6098 // Initialize the input operand.
6099 Int32OperandId argcId(writer.setInputOperandId(0));
6100
6101 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6102
6103 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6104 ObjOperandId protoId = writer.guardToObject(arg0Id);
6105
6106 writer.regExpPrototypeOptimizableResult(protoId);
6107 writer.returnFromIC();
6108
6109 trackAttached("RegExpPrototypeOptimizable");
6110 return AttachDecision::Attach;
6111 }
6112
tryAttachRegExpInstanceOptimizable(HandleFunction callee)6113 AttachDecision CallIRGenerator::tryAttachRegExpInstanceOptimizable(
6114 HandleFunction callee) {
6115 // Self-hosted code calls this with two object arguments.
6116 MOZ_ASSERT(argc_ == 2);
6117 MOZ_ASSERT(args_[0].isObject());
6118 MOZ_ASSERT(args_[1].isObject());
6119
6120 // Initialize the input operand.
6121 Int32OperandId argcId(writer.setInputOperandId(0));
6122
6123 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6124
6125 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6126 ObjOperandId regexpId = writer.guardToObject(arg0Id);
6127
6128 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6129 ObjOperandId protoId = writer.guardToObject(arg1Id);
6130
6131 writer.regExpInstanceOptimizableResult(regexpId, protoId);
6132 writer.returnFromIC();
6133
6134 trackAttached("RegExpInstanceOptimizable");
6135 return AttachDecision::Attach;
6136 }
6137
tryAttachGetFirstDollarIndex(HandleFunction callee)6138 AttachDecision CallIRGenerator::tryAttachGetFirstDollarIndex(
6139 HandleFunction callee) {
6140 // Self-hosted code calls this with a single string argument.
6141 MOZ_ASSERT(argc_ == 1);
6142 MOZ_ASSERT(args_[0].isString());
6143
6144 // Initialize the input operand.
6145 Int32OperandId argcId(writer.setInputOperandId(0));
6146
6147 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6148
6149 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6150 StringOperandId strId = writer.guardToString(arg0Id);
6151
6152 writer.getFirstDollarIndexResult(strId);
6153 writer.returnFromIC();
6154
6155 trackAttached("GetFirstDollarIndex");
6156 return AttachDecision::Attach;
6157 }
6158
tryAttachSubstringKernel(HandleFunction callee)6159 AttachDecision CallIRGenerator::tryAttachSubstringKernel(
6160 HandleFunction callee) {
6161 // Self-hosted code calls this with (string, int32, int32) arguments.
6162 MOZ_ASSERT(argc_ == 3);
6163 MOZ_ASSERT(args_[0].isString());
6164 MOZ_ASSERT(args_[1].isInt32());
6165 MOZ_ASSERT(args_[2].isInt32());
6166
6167 // Initialize the input operand.
6168 Int32OperandId argcId(writer.setInputOperandId(0));
6169
6170 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6171
6172 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6173 StringOperandId strId = writer.guardToString(arg0Id);
6174
6175 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6176 Int32OperandId beginId = writer.guardToInt32(arg1Id);
6177
6178 ValOperandId arg2Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
6179 Int32OperandId lengthId = writer.guardToInt32(arg2Id);
6180
6181 writer.callSubstringKernelResult(strId, beginId, lengthId);
6182 writer.returnFromIC();
6183
6184 trackAttached("SubstringKernel");
6185 return AttachDecision::Attach;
6186 }
6187
tryAttachObjectHasPrototype(HandleFunction callee)6188 AttachDecision CallIRGenerator::tryAttachObjectHasPrototype(
6189 HandleFunction callee) {
6190 // Self-hosted code calls this with (object, object) arguments.
6191 MOZ_ASSERT(argc_ == 2);
6192 MOZ_ASSERT(args_[0].isObject());
6193 MOZ_ASSERT(args_[1].isObject());
6194
6195 auto* obj = &args_[0].toObject().as<NativeObject>();
6196 auto* proto = &args_[1].toObject().as<NativeObject>();
6197
6198 // Only attach when obj.__proto__ is proto.
6199 if (obj->staticPrototype() != proto) {
6200 return AttachDecision::NoAction;
6201 }
6202
6203 // Initialize the input operand.
6204 Int32OperandId argcId(writer.setInputOperandId(0));
6205
6206 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6207
6208 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6209 ObjOperandId objId = writer.guardToObject(arg0Id);
6210
6211 writer.guardProto(objId, proto);
6212 writer.loadBooleanResult(true);
6213 writer.returnFromIC();
6214
6215 trackAttached("ObjectHasPrototype");
6216 return AttachDecision::Attach;
6217 }
6218
tryAttachString(HandleFunction callee)6219 AttachDecision CallIRGenerator::tryAttachString(HandleFunction callee) {
6220 // Need a single argument that is or can be converted to a string.
6221 if (argc_ != 1 || !(args_[0].isString() || args_[0].isNumber())) {
6222 return AttachDecision::NoAction;
6223 }
6224
6225 // Initialize the input operand.
6226 Int32OperandId argcId(writer.setInputOperandId(0));
6227
6228 // Guard callee is the 'String' function.
6229 emitNativeCalleeGuard(callee);
6230
6231 // Guard that the argument is a string or can be converted to one.
6232 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6233 StringOperandId strId = emitToStringGuard(argId, args_[0]);
6234
6235 // Return the string.
6236 writer.loadStringResult(strId);
6237 writer.returnFromIC();
6238
6239 trackAttached("String");
6240 return AttachDecision::Attach;
6241 }
6242
tryAttachStringConstructor(HandleFunction callee)6243 AttachDecision CallIRGenerator::tryAttachStringConstructor(
6244 HandleFunction callee) {
6245 // Need a single argument that is or can be converted to a string.
6246 if (argc_ != 1 || !(args_[0].isString() || args_[0].isNumber())) {
6247 return AttachDecision::NoAction;
6248 }
6249
6250 RootedString emptyString(cx_, cx_->runtime()->emptyString);
6251 JSObject* templateObj = StringObject::create(
6252 cx_, emptyString, /* proto = */ nullptr, TenuredObject);
6253 if (!templateObj) {
6254 cx_->recoverFromOutOfMemory();
6255 return AttachDecision::NoAction;
6256 }
6257
6258 // Initialize the input operand.
6259 Int32OperandId argcId(writer.setInputOperandId(0));
6260
6261 // Guard callee is the 'String' function.
6262 emitNativeCalleeGuard(callee);
6263
6264 CallFlags flags(IsConstructPC(pc_), IsSpreadPC(pc_));
6265
6266 // Guard on number and convert to string.
6267 ValOperandId argId =
6268 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags);
6269 StringOperandId strId = emitToStringGuard(argId, args_[0]);
6270
6271 writer.newStringObjectResult(templateObj, strId);
6272 writer.returnFromIC();
6273
6274 trackAttached("StringConstructor");
6275 return AttachDecision::Attach;
6276 }
6277
tryAttachStringToStringValueOf(HandleFunction callee)6278 AttachDecision CallIRGenerator::tryAttachStringToStringValueOf(
6279 HandleFunction callee) {
6280 // Expecting no arguments.
6281 if (argc_ != 0) {
6282 return AttachDecision::NoAction;
6283 }
6284
6285 // Ensure |this| is a primitive string value.
6286 if (!thisval_.isString()) {
6287 return AttachDecision::NoAction;
6288 }
6289
6290 // Initialize the input operand.
6291 Int32OperandId argcId(writer.setInputOperandId(0));
6292
6293 // Guard callee is the 'toString' OR 'valueOf' native function.
6294 emitNativeCalleeGuard(callee);
6295
6296 // Guard |this| is a string.
6297 ValOperandId thisValId =
6298 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6299 StringOperandId strId = writer.guardToString(thisValId);
6300
6301 // Return the string
6302 writer.loadStringResult(strId);
6303 writer.returnFromIC();
6304
6305 trackAttached("StringToStringValueOf");
6306 return AttachDecision::Attach;
6307 }
6308
tryAttachStringReplaceString(HandleFunction callee)6309 AttachDecision CallIRGenerator::tryAttachStringReplaceString(
6310 HandleFunction callee) {
6311 // Self-hosted code calls this with (string, string, string) arguments.
6312 MOZ_ASSERT(argc_ == 3);
6313 MOZ_ASSERT(args_[0].isString());
6314 MOZ_ASSERT(args_[1].isString());
6315 MOZ_ASSERT(args_[2].isString());
6316
6317 // Initialize the input operand.
6318 Int32OperandId argcId(writer.setInputOperandId(0));
6319
6320 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6321
6322 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6323 StringOperandId strId = writer.guardToString(arg0Id);
6324
6325 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6326 StringOperandId patternId = writer.guardToString(arg1Id);
6327
6328 ValOperandId arg2Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
6329 StringOperandId replacementId = writer.guardToString(arg2Id);
6330
6331 writer.stringReplaceStringResult(strId, patternId, replacementId);
6332 writer.returnFromIC();
6333
6334 trackAttached("StringReplaceString");
6335 return AttachDecision::Attach;
6336 }
6337
tryAttachStringSplitString(HandleFunction callee)6338 AttachDecision CallIRGenerator::tryAttachStringSplitString(
6339 HandleFunction callee) {
6340 // Self-hosted code calls this with (string, string) arguments.
6341 MOZ_ASSERT(argc_ == 2);
6342 MOZ_ASSERT(args_[0].isString());
6343 MOZ_ASSERT(args_[1].isString());
6344
6345 // Initialize the input operand.
6346 Int32OperandId argcId(writer.setInputOperandId(0));
6347
6348 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6349
6350 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6351 StringOperandId strId = writer.guardToString(arg0Id);
6352
6353 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6354 StringOperandId separatorId = writer.guardToString(arg1Id);
6355
6356 writer.stringSplitStringResult(strId, separatorId);
6357 writer.returnFromIC();
6358
6359 trackAttached("StringSplitString");
6360 return AttachDecision::Attach;
6361 }
6362
tryAttachStringChar(HandleFunction callee,StringChar kind)6363 AttachDecision CallIRGenerator::tryAttachStringChar(HandleFunction callee,
6364 StringChar kind) {
6365 // Need one argument.
6366 if (argc_ != 1) {
6367 return AttachDecision::NoAction;
6368 }
6369
6370 if (!CanAttachStringChar(thisval_, args_[0])) {
6371 return AttachDecision::NoAction;
6372 }
6373
6374 // Initialize the input operand.
6375 Int32OperandId argcId(writer.setInputOperandId(0));
6376
6377 // Guard callee is the 'charCodeAt' or 'charAt' native function.
6378 emitNativeCalleeGuard(callee);
6379
6380 // Guard this is a string.
6381 ValOperandId thisValId =
6382 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6383 StringOperandId strId = writer.guardToString(thisValId);
6384
6385 // Guard int32 index.
6386 ValOperandId indexId =
6387 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6388 Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
6389
6390 // Load string char or code.
6391 if (kind == StringChar::CodeAt) {
6392 writer.loadStringCharCodeResult(strId, int32IndexId);
6393 } else {
6394 writer.loadStringCharResult(strId, int32IndexId);
6395 }
6396
6397 writer.returnFromIC();
6398
6399 if (kind == StringChar::CodeAt) {
6400 trackAttached("StringCharCodeAt");
6401 } else {
6402 trackAttached("StringCharAt");
6403 }
6404
6405 return AttachDecision::Attach;
6406 }
6407
tryAttachStringCharCodeAt(HandleFunction callee)6408 AttachDecision CallIRGenerator::tryAttachStringCharCodeAt(
6409 HandleFunction callee) {
6410 return tryAttachStringChar(callee, StringChar::CodeAt);
6411 }
6412
tryAttachStringCharAt(HandleFunction callee)6413 AttachDecision CallIRGenerator::tryAttachStringCharAt(HandleFunction callee) {
6414 return tryAttachStringChar(callee, StringChar::At);
6415 }
6416
tryAttachStringFromCharCode(HandleFunction callee)6417 AttachDecision CallIRGenerator::tryAttachStringFromCharCode(
6418 HandleFunction callee) {
6419 // Need one int32 argument.
6420 if (argc_ != 1 || !args_[0].isInt32()) {
6421 return AttachDecision::NoAction;
6422 }
6423
6424 // Initialize the input operand.
6425 Int32OperandId argcId(writer.setInputOperandId(0));
6426
6427 // Guard callee is the 'fromCharCode' native function.
6428 emitNativeCalleeGuard(callee);
6429
6430 // Guard int32 argument.
6431 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6432 Int32OperandId codeId = writer.guardToInt32(argId);
6433
6434 // Return string created from code.
6435 writer.stringFromCharCodeResult(codeId);
6436 writer.returnFromIC();
6437
6438 trackAttached("StringFromCharCode");
6439 return AttachDecision::Attach;
6440 }
6441
tryAttachStringFromCodePoint(HandleFunction callee)6442 AttachDecision CallIRGenerator::tryAttachStringFromCodePoint(
6443 HandleFunction callee) {
6444 // Need one int32 argument.
6445 if (argc_ != 1 || !args_[0].isInt32()) {
6446 return AttachDecision::NoAction;
6447 }
6448
6449 // String.fromCodePoint throws for invalid code points.
6450 int32_t codePoint = args_[0].toInt32();
6451 if (codePoint < 0 || codePoint > int32_t(unicode::NonBMPMax)) {
6452 return AttachDecision::NoAction;
6453 }
6454
6455 // Initialize the input operand.
6456 Int32OperandId argcId(writer.setInputOperandId(0));
6457
6458 // Guard callee is the 'fromCodePoint' native function.
6459 emitNativeCalleeGuard(callee);
6460
6461 // Guard int32 argument.
6462 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6463 Int32OperandId codeId = writer.guardToInt32(argId);
6464
6465 // Return string created from code point.
6466 writer.stringFromCodePointResult(codeId);
6467 writer.returnFromIC();
6468
6469 trackAttached("StringFromCodePoint");
6470 return AttachDecision::Attach;
6471 }
6472
tryAttachStringToLowerCase(HandleFunction callee)6473 AttachDecision CallIRGenerator::tryAttachStringToLowerCase(
6474 HandleFunction callee) {
6475 // Expecting no arguments.
6476 if (argc_ != 0) {
6477 return AttachDecision::NoAction;
6478 }
6479
6480 // Ensure |this| is a primitive string value.
6481 if (!thisval_.isString()) {
6482 return AttachDecision::NoAction;
6483 }
6484
6485 // Initialize the input operand.
6486 Int32OperandId argcId(writer.setInputOperandId(0));
6487
6488 // Guard callee is the 'toLowerCase' native function.
6489 emitNativeCalleeGuard(callee);
6490
6491 // Guard this is a string.
6492 ValOperandId thisValId =
6493 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6494 StringOperandId strId = writer.guardToString(thisValId);
6495
6496 // Return string converted to lower-case.
6497 writer.stringToLowerCaseResult(strId);
6498 writer.returnFromIC();
6499
6500 trackAttached("StringToLowerCase");
6501 return AttachDecision::Attach;
6502 }
6503
tryAttachStringToUpperCase(HandleFunction callee)6504 AttachDecision CallIRGenerator::tryAttachStringToUpperCase(
6505 HandleFunction callee) {
6506 // Expecting no arguments.
6507 if (argc_ != 0) {
6508 return AttachDecision::NoAction;
6509 }
6510
6511 // Ensure |this| is a primitive string value.
6512 if (!thisval_.isString()) {
6513 return AttachDecision::NoAction;
6514 }
6515
6516 // Initialize the input operand.
6517 Int32OperandId argcId(writer.setInputOperandId(0));
6518
6519 // Guard callee is the 'toUpperCase' native function.
6520 emitNativeCalleeGuard(callee);
6521
6522 // Guard this is a string.
6523 ValOperandId thisValId =
6524 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6525 StringOperandId strId = writer.guardToString(thisValId);
6526
6527 // Return string converted to upper-case.
6528 writer.stringToUpperCaseResult(strId);
6529 writer.returnFromIC();
6530
6531 trackAttached("StringToUpperCase");
6532 return AttachDecision::Attach;
6533 }
6534
tryAttachMathRandom(HandleFunction callee)6535 AttachDecision CallIRGenerator::tryAttachMathRandom(HandleFunction callee) {
6536 // Expecting no arguments.
6537 if (argc_ != 0) {
6538 return AttachDecision::NoAction;
6539 }
6540
6541 MOZ_ASSERT(cx_->realm() == callee->realm(),
6542 "Shouldn't inline cross-realm Math.random because per-realm RNG");
6543
6544 // Initialize the input operand.
6545 Int32OperandId argcId(writer.setInputOperandId(0));
6546
6547 // Guard callee is the 'random' native function.
6548 emitNativeCalleeGuard(callee);
6549
6550 mozilla::non_crypto::XorShift128PlusRNG* rng =
6551 &cx_->realm()->getOrCreateRandomNumberGenerator();
6552 writer.mathRandomResult(rng);
6553
6554 writer.returnFromIC();
6555
6556 trackAttached("MathRandom");
6557 return AttachDecision::Attach;
6558 }
6559
tryAttachMathAbs(HandleFunction callee)6560 AttachDecision CallIRGenerator::tryAttachMathAbs(HandleFunction callee) {
6561 // Need one argument.
6562 if (argc_ != 1) {
6563 return AttachDecision::NoAction;
6564 }
6565
6566 if (!args_[0].isNumber()) {
6567 return AttachDecision::NoAction;
6568 }
6569
6570 // Initialize the input operand.
6571 Int32OperandId argcId(writer.setInputOperandId(0));
6572
6573 // Guard callee is the 'abs' native function.
6574 emitNativeCalleeGuard(callee);
6575
6576 ValOperandId argumentId =
6577 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6578
6579 // abs(INT_MIN) is a double.
6580 if (args_[0].isInt32() && args_[0].toInt32() != INT_MIN) {
6581 Int32OperandId int32Id = writer.guardToInt32(argumentId);
6582 writer.mathAbsInt32Result(int32Id);
6583 } else {
6584 NumberOperandId numberId = writer.guardIsNumber(argumentId);
6585 writer.mathAbsNumberResult(numberId);
6586 }
6587
6588 writer.returnFromIC();
6589
6590 trackAttached("MathAbs");
6591 return AttachDecision::Attach;
6592 }
6593
tryAttachMathClz32(HandleFunction callee)6594 AttachDecision CallIRGenerator::tryAttachMathClz32(HandleFunction callee) {
6595 // Need one (number) argument.
6596 if (argc_ != 1 || !args_[0].isNumber()) {
6597 return AttachDecision::NoAction;
6598 }
6599
6600 // Initialize the input operand.
6601 Int32OperandId argcId(writer.setInputOperandId(0));
6602
6603 // Guard callee is the 'clz32' native function.
6604 emitNativeCalleeGuard(callee);
6605
6606 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6607
6608 Int32OperandId int32Id;
6609 if (args_[0].isInt32()) {
6610 int32Id = writer.guardToInt32(argId);
6611 } else {
6612 MOZ_ASSERT(args_[0].isDouble());
6613 NumberOperandId numId = writer.guardIsNumber(argId);
6614 int32Id = writer.truncateDoubleToUInt32(numId);
6615 }
6616 writer.mathClz32Result(int32Id);
6617 writer.returnFromIC();
6618
6619 trackAttached("MathClz32");
6620 return AttachDecision::Attach;
6621 }
6622
tryAttachMathSign(HandleFunction callee)6623 AttachDecision CallIRGenerator::tryAttachMathSign(HandleFunction callee) {
6624 // Need one (number) argument.
6625 if (argc_ != 1 || !args_[0].isNumber()) {
6626 return AttachDecision::NoAction;
6627 }
6628
6629 // Initialize the input operand.
6630 Int32OperandId argcId(writer.setInputOperandId(0));
6631
6632 // Guard callee is the 'sign' native function.
6633 emitNativeCalleeGuard(callee);
6634
6635 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6636
6637 if (args_[0].isInt32()) {
6638 Int32OperandId int32Id = writer.guardToInt32(argId);
6639 writer.mathSignInt32Result(int32Id);
6640 } else {
6641 // Math.sign returns a double only if the input is -0 or NaN so try to
6642 // optimize the common Number => Int32 case.
6643 double d = math_sign_impl(args_[0].toDouble());
6644 int32_t unused;
6645 bool resultIsInt32 = mozilla::NumberIsInt32(d, &unused);
6646
6647 NumberOperandId numId = writer.guardIsNumber(argId);
6648 if (resultIsInt32) {
6649 writer.mathSignNumberToInt32Result(numId);
6650 } else {
6651 writer.mathSignNumberResult(numId);
6652 }
6653 }
6654
6655 writer.returnFromIC();
6656
6657 trackAttached("MathSign");
6658 return AttachDecision::Attach;
6659 }
6660
tryAttachMathImul(HandleFunction callee)6661 AttachDecision CallIRGenerator::tryAttachMathImul(HandleFunction callee) {
6662 // Need two (number) arguments.
6663 if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) {
6664 return AttachDecision::NoAction;
6665 }
6666
6667 // Initialize the input operand.
6668 Int32OperandId argcId(writer.setInputOperandId(0));
6669
6670 // Guard callee is the 'imul' native function.
6671 emitNativeCalleeGuard(callee);
6672
6673 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6674 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6675
6676 Int32OperandId int32Arg0Id, int32Arg1Id;
6677 if (args_[0].isInt32() && args_[1].isInt32()) {
6678 int32Arg0Id = writer.guardToInt32(arg0Id);
6679 int32Arg1Id = writer.guardToInt32(arg1Id);
6680 } else {
6681 // Treat both arguments as numbers if at least one of them is non-int32.
6682 NumberOperandId numArg0Id = writer.guardIsNumber(arg0Id);
6683 NumberOperandId numArg1Id = writer.guardIsNumber(arg1Id);
6684 int32Arg0Id = writer.truncateDoubleToUInt32(numArg0Id);
6685 int32Arg1Id = writer.truncateDoubleToUInt32(numArg1Id);
6686 }
6687 writer.mathImulResult(int32Arg0Id, int32Arg1Id);
6688 writer.returnFromIC();
6689
6690 trackAttached("MathImul");
6691 return AttachDecision::Attach;
6692 }
6693
tryAttachMathFloor(HandleFunction callee)6694 AttachDecision CallIRGenerator::tryAttachMathFloor(HandleFunction callee) {
6695 // Need one (number) argument.
6696 if (argc_ != 1 || !args_[0].isNumber()) {
6697 return AttachDecision::NoAction;
6698 }
6699
6700 // Check if the result fits in int32.
6701 double res = math_floor_impl(args_[0].toNumber());
6702 int32_t unused;
6703 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
6704
6705 // Initialize the input operand.
6706 Int32OperandId argcId(writer.setInputOperandId(0));
6707
6708 // Guard callee is the 'floor' native function.
6709 emitNativeCalleeGuard(callee);
6710
6711 ValOperandId argumentId =
6712 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6713
6714 if (args_[0].isInt32()) {
6715 MOZ_ASSERT(resultIsInt32);
6716
6717 // Use an indirect truncation to inform the optimizer it needs to preserve
6718 // a bailout when the input can't be represented as an int32, even if the
6719 // final result is fully truncated.
6720 Int32OperandId intId = writer.guardToInt32(argumentId);
6721 writer.indirectTruncateInt32Result(intId);
6722 } else {
6723 NumberOperandId numberId = writer.guardIsNumber(argumentId);
6724
6725 if (resultIsInt32) {
6726 writer.mathFloorToInt32Result(numberId);
6727 } else {
6728 writer.mathFloorNumberResult(numberId);
6729 }
6730 }
6731
6732 writer.returnFromIC();
6733
6734 trackAttached("MathFloor");
6735 return AttachDecision::Attach;
6736 }
6737
tryAttachMathCeil(HandleFunction callee)6738 AttachDecision CallIRGenerator::tryAttachMathCeil(HandleFunction callee) {
6739 // Need one (number) argument.
6740 if (argc_ != 1 || !args_[0].isNumber()) {
6741 return AttachDecision::NoAction;
6742 }
6743
6744 // Check if the result fits in int32.
6745 double res = math_ceil_impl(args_[0].toNumber());
6746 int32_t unused;
6747 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
6748
6749 // Initialize the input operand.
6750 Int32OperandId argcId(writer.setInputOperandId(0));
6751
6752 // Guard callee is the 'ceil' native function.
6753 emitNativeCalleeGuard(callee);
6754
6755 ValOperandId argumentId =
6756 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6757
6758 if (args_[0].isInt32()) {
6759 MOZ_ASSERT(resultIsInt32);
6760
6761 // Use an indirect truncation to inform the optimizer it needs to preserve
6762 // a bailout when the input can't be represented as an int32, even if the
6763 // final result is fully truncated.
6764 Int32OperandId intId = writer.guardToInt32(argumentId);
6765 writer.indirectTruncateInt32Result(intId);
6766 } else {
6767 NumberOperandId numberId = writer.guardIsNumber(argumentId);
6768
6769 if (resultIsInt32) {
6770 writer.mathCeilToInt32Result(numberId);
6771 } else {
6772 writer.mathCeilNumberResult(numberId);
6773 }
6774 }
6775
6776 writer.returnFromIC();
6777
6778 trackAttached("MathCeil");
6779 return AttachDecision::Attach;
6780 }
6781
tryAttachMathTrunc(HandleFunction callee)6782 AttachDecision CallIRGenerator::tryAttachMathTrunc(HandleFunction callee) {
6783 // Need one (number) argument.
6784 if (argc_ != 1 || !args_[0].isNumber()) {
6785 return AttachDecision::NoAction;
6786 }
6787
6788 // Check if the result fits in int32.
6789 double res = math_trunc_impl(args_[0].toNumber());
6790 int32_t unused;
6791 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
6792
6793 // Initialize the input operand.
6794 Int32OperandId argcId(writer.setInputOperandId(0));
6795
6796 // Guard callee is the 'trunc' native function.
6797 emitNativeCalleeGuard(callee);
6798
6799 ValOperandId argumentId =
6800 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6801
6802 if (args_[0].isInt32()) {
6803 MOZ_ASSERT(resultIsInt32);
6804
6805 // We don't need an indirect truncation barrier here, because Math.trunc
6806 // always truncates, but never rounds its input away from zero.
6807 Int32OperandId intId = writer.guardToInt32(argumentId);
6808 writer.loadInt32Result(intId);
6809 } else {
6810 NumberOperandId numberId = writer.guardIsNumber(argumentId);
6811
6812 if (resultIsInt32) {
6813 writer.mathTruncToInt32Result(numberId);
6814 } else {
6815 writer.mathTruncNumberResult(numberId);
6816 }
6817 }
6818
6819 writer.returnFromIC();
6820
6821 trackAttached("MathTrunc");
6822 return AttachDecision::Attach;
6823 }
6824
tryAttachMathRound(HandleFunction callee)6825 AttachDecision CallIRGenerator::tryAttachMathRound(HandleFunction callee) {
6826 // Need one (number) argument.
6827 if (argc_ != 1 || !args_[0].isNumber()) {
6828 return AttachDecision::NoAction;
6829 }
6830
6831 // Check if the result fits in int32.
6832 double res = math_round_impl(args_[0].toNumber());
6833 int32_t unused;
6834 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
6835
6836 // Initialize the input operand.
6837 Int32OperandId argcId(writer.setInputOperandId(0));
6838
6839 // Guard callee is the 'round' native function.
6840 emitNativeCalleeGuard(callee);
6841
6842 ValOperandId argumentId =
6843 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6844
6845 if (args_[0].isInt32()) {
6846 MOZ_ASSERT(resultIsInt32);
6847
6848 // Use an indirect truncation to inform the optimizer it needs to preserve
6849 // a bailout when the input can't be represented as an int32, even if the
6850 // final result is fully truncated.
6851 Int32OperandId intId = writer.guardToInt32(argumentId);
6852 writer.indirectTruncateInt32Result(intId);
6853 } else {
6854 NumberOperandId numberId = writer.guardIsNumber(argumentId);
6855
6856 if (resultIsInt32) {
6857 writer.mathRoundToInt32Result(numberId);
6858 } else {
6859 writer.mathFunctionNumberResult(numberId, UnaryMathFunction::Round);
6860 }
6861 }
6862
6863 writer.returnFromIC();
6864
6865 trackAttached("MathRound");
6866 return AttachDecision::Attach;
6867 }
6868
tryAttachMathSqrt(HandleFunction callee)6869 AttachDecision CallIRGenerator::tryAttachMathSqrt(HandleFunction callee) {
6870 // Need one (number) argument.
6871 if (argc_ != 1 || !args_[0].isNumber()) {
6872 return AttachDecision::NoAction;
6873 }
6874
6875 // Initialize the input operand.
6876 Int32OperandId argcId(writer.setInputOperandId(0));
6877
6878 // Guard callee is the 'sqrt' native function.
6879 emitNativeCalleeGuard(callee);
6880
6881 ValOperandId argumentId =
6882 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6883 NumberOperandId numberId = writer.guardIsNumber(argumentId);
6884 writer.mathSqrtNumberResult(numberId);
6885 writer.returnFromIC();
6886
6887 trackAttached("MathSqrt");
6888 return AttachDecision::Attach;
6889 }
6890
tryAttachMathFRound(HandleFunction callee)6891 AttachDecision CallIRGenerator::tryAttachMathFRound(HandleFunction callee) {
6892 // Need one (number) argument.
6893 if (argc_ != 1 || !args_[0].isNumber()) {
6894 return AttachDecision::NoAction;
6895 }
6896
6897 // Initialize the input operand.
6898 Int32OperandId argcId(writer.setInputOperandId(0));
6899
6900 // Guard callee is the 'fround' native function.
6901 emitNativeCalleeGuard(callee);
6902
6903 ValOperandId argumentId =
6904 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6905 NumberOperandId numberId = writer.guardIsNumber(argumentId);
6906 writer.mathFRoundNumberResult(numberId);
6907 writer.returnFromIC();
6908
6909 trackAttached("MathFRound");
6910 return AttachDecision::Attach;
6911 }
6912
CanAttachInt32Pow(const Value & baseVal,const Value & powerVal)6913 static bool CanAttachInt32Pow(const Value& baseVal, const Value& powerVal) {
6914 auto valToInt32 = [](const Value& v) {
6915 if (v.isInt32()) {
6916 return v.toInt32();
6917 }
6918 if (v.isBoolean()) {
6919 return int32_t(v.toBoolean());
6920 }
6921 MOZ_ASSERT(v.isNull());
6922 return 0;
6923 };
6924 int32_t base = valToInt32(baseVal);
6925 int32_t power = valToInt32(powerVal);
6926
6927 // x^y where y < 0 is most of the time not an int32, except when x is 1 or y
6928 // gets large enough. It's hard to determine when exactly y is "large enough",
6929 // so we don't use Int32PowResult when x != 1 and y < 0.
6930 // Note: it's important for this condition to match the code generated by
6931 // MacroAssembler::pow32 to prevent failure loops.
6932 if (power < 0) {
6933 return base == 1;
6934 }
6935
6936 double res = powi(base, power);
6937 int32_t unused;
6938 return mozilla::NumberIsInt32(res, &unused);
6939 }
6940
tryAttachMathPow(HandleFunction callee)6941 AttachDecision CallIRGenerator::tryAttachMathPow(HandleFunction callee) {
6942 // Need two number arguments.
6943 if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) {
6944 return AttachDecision::NoAction;
6945 }
6946
6947 // Initialize the input operand.
6948 Int32OperandId argcId(writer.setInputOperandId(0));
6949
6950 // Guard callee is the 'pow' function.
6951 emitNativeCalleeGuard(callee);
6952
6953 ValOperandId baseId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6954 ValOperandId exponentId =
6955 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6956
6957 if (args_[0].isInt32() && args_[1].isInt32() &&
6958 CanAttachInt32Pow(args_[0], args_[1])) {
6959 Int32OperandId baseInt32Id = writer.guardToInt32(baseId);
6960 Int32OperandId exponentInt32Id = writer.guardToInt32(exponentId);
6961 writer.int32PowResult(baseInt32Id, exponentInt32Id);
6962 } else {
6963 NumberOperandId baseNumberId = writer.guardIsNumber(baseId);
6964 NumberOperandId exponentNumberId = writer.guardIsNumber(exponentId);
6965 writer.doublePowResult(baseNumberId, exponentNumberId);
6966 }
6967
6968 writer.returnFromIC();
6969
6970 trackAttached("MathPow");
6971 return AttachDecision::Attach;
6972 }
6973
tryAttachMathHypot(HandleFunction callee)6974 AttachDecision CallIRGenerator::tryAttachMathHypot(HandleFunction callee) {
6975 // Only optimize if there are 2-4 arguments.
6976 if (argc_ < 2 || argc_ > 4) {
6977 return AttachDecision::NoAction;
6978 }
6979
6980 for (size_t i = 0; i < argc_; i++) {
6981 if (!args_[i].isNumber()) {
6982 return AttachDecision::NoAction;
6983 }
6984 }
6985
6986 // Initialize the input operand.
6987 Int32OperandId argcId(writer.setInputOperandId(0));
6988
6989 // Guard callee is the 'hypot' native function.
6990 emitNativeCalleeGuard(callee);
6991
6992 ValOperandId firstId =
6993 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6994 ValOperandId secondId =
6995 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6996
6997 NumberOperandId firstNumId = writer.guardIsNumber(firstId);
6998 NumberOperandId secondNumId = writer.guardIsNumber(secondId);
6999
7000 ValOperandId thirdId;
7001 ValOperandId fourthId;
7002 NumberOperandId thirdNumId;
7003 NumberOperandId fourthNumId;
7004
7005 switch (argc_) {
7006 case 2:
7007 writer.mathHypot2NumberResult(firstNumId, secondNumId);
7008 break;
7009 case 3:
7010 thirdId = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
7011 thirdNumId = writer.guardIsNumber(thirdId);
7012 writer.mathHypot3NumberResult(firstNumId, secondNumId, thirdNumId);
7013 break;
7014 case 4:
7015 thirdId = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
7016 fourthId = writer.loadArgumentFixedSlot(ArgumentKind::Arg3, argc_);
7017 thirdNumId = writer.guardIsNumber(thirdId);
7018 fourthNumId = writer.guardIsNumber(fourthId);
7019 writer.mathHypot4NumberResult(firstNumId, secondNumId, thirdNumId,
7020 fourthNumId);
7021 break;
7022 default:
7023 MOZ_CRASH("Unexpected number of arguments to hypot function.");
7024 }
7025
7026 writer.returnFromIC();
7027
7028 trackAttached("MathHypot");
7029 return AttachDecision::Attach;
7030 }
7031
tryAttachMathATan2(HandleFunction callee)7032 AttachDecision CallIRGenerator::tryAttachMathATan2(HandleFunction callee) {
7033 // Requires two numbers as arguments.
7034 if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) {
7035 return AttachDecision::NoAction;
7036 }
7037
7038 // Initialize the input operand.
7039 Int32OperandId argcId(writer.setInputOperandId(0));
7040
7041 // Guard callee is the 'atan2' native function.
7042 emitNativeCalleeGuard(callee);
7043
7044 ValOperandId yId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7045 ValOperandId xId = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7046
7047 NumberOperandId yNumberId = writer.guardIsNumber(yId);
7048 NumberOperandId xNumberId = writer.guardIsNumber(xId);
7049
7050 writer.mathAtan2NumberResult(yNumberId, xNumberId);
7051 writer.returnFromIC();
7052
7053 trackAttached("MathAtan2");
7054 return AttachDecision::Attach;
7055 }
7056
tryAttachMathMinMax(HandleFunction callee,bool isMax)7057 AttachDecision CallIRGenerator::tryAttachMathMinMax(HandleFunction callee,
7058 bool isMax) {
7059 // For now only optimize if there are 1-4 arguments.
7060 if (argc_ < 1 || argc_ > 4) {
7061 return AttachDecision::NoAction;
7062 }
7063
7064 // Ensure all arguments are numbers.
7065 bool allInt32 = true;
7066 for (size_t i = 0; i < argc_; i++) {
7067 if (!args_[i].isNumber()) {
7068 return AttachDecision::NoAction;
7069 }
7070 if (!args_[i].isInt32()) {
7071 allInt32 = false;
7072 }
7073 }
7074
7075 // Initialize the input operand.
7076 Int32OperandId argcId(writer.setInputOperandId(0));
7077
7078 // Guard callee is this Math function.
7079 emitNativeCalleeGuard(callee);
7080
7081 if (allInt32) {
7082 ValOperandId valId =
7083 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7084 Int32OperandId resId = writer.guardToInt32(valId);
7085 for (size_t i = 1; i < argc_; i++) {
7086 ValOperandId argId =
7087 writer.loadArgumentFixedSlot(ArgumentKindForArgIndex(i), argc_);
7088 Int32OperandId argInt32Id = writer.guardToInt32(argId);
7089 resId = writer.int32MinMax(isMax, resId, argInt32Id);
7090 }
7091 writer.loadInt32Result(resId);
7092 } else {
7093 ValOperandId valId =
7094 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7095 NumberOperandId resId = writer.guardIsNumber(valId);
7096 for (size_t i = 1; i < argc_; i++) {
7097 ValOperandId argId =
7098 writer.loadArgumentFixedSlot(ArgumentKindForArgIndex(i), argc_);
7099 NumberOperandId argNumId = writer.guardIsNumber(argId);
7100 resId = writer.numberMinMax(isMax, resId, argNumId);
7101 }
7102 writer.loadDoubleResult(resId);
7103 }
7104
7105 writer.returnFromIC();
7106
7107 trackAttached(isMax ? "MathMax" : "MathMin");
7108 return AttachDecision::Attach;
7109 }
7110
tryAttachSpreadMathMinMax(HandleFunction callee,bool isMax)7111 AttachDecision CallIRGenerator::tryAttachSpreadMathMinMax(HandleFunction callee,
7112 bool isMax) {
7113 MOZ_ASSERT(op_ == JSOp::SpreadCall);
7114
7115 // The result will be an int32 if there is at least one argument,
7116 // and all the arguments are int32.
7117 bool int32Result = args_.length() > 0;
7118 for (size_t i = 0; i < args_.length(); i++) {
7119 if (!args_[i].isNumber()) {
7120 return AttachDecision::NoAction;
7121 }
7122 if (!args_[i].isInt32()) {
7123 int32Result = false;
7124 }
7125 }
7126
7127 // Initialize the input operand.
7128 Int32OperandId argcId(writer.setInputOperandId(0));
7129
7130 // Guard callee is this Math function.
7131 emitNativeCalleeGuard(callee);
7132
7133 // Load the argument array
7134 ObjOperandId argsId = writer.loadSpreadArgs();
7135
7136 if (int32Result) {
7137 writer.int32MinMaxArrayResult(argsId, isMax);
7138 } else {
7139 writer.numberMinMaxArrayResult(argsId, isMax);
7140 }
7141
7142 writer.returnFromIC();
7143
7144 trackAttached(isMax ? "MathMax" : "MathMin");
7145 return AttachDecision::Attach;
7146 }
7147
tryAttachMathFunction(HandleFunction callee,UnaryMathFunction fun)7148 AttachDecision CallIRGenerator::tryAttachMathFunction(HandleFunction callee,
7149 UnaryMathFunction fun) {
7150 // Need one argument.
7151 if (argc_ != 1) {
7152 return AttachDecision::NoAction;
7153 }
7154
7155 if (!args_[0].isNumber()) {
7156 return AttachDecision::NoAction;
7157 }
7158
7159 // Initialize the input operand.
7160 Int32OperandId argcId(writer.setInputOperandId(0));
7161
7162 // Guard callee is this Math function.
7163 emitNativeCalleeGuard(callee);
7164
7165 ValOperandId argumentId =
7166 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7167 NumberOperandId numberId = writer.guardIsNumber(argumentId);
7168 writer.mathFunctionNumberResult(numberId, fun);
7169 writer.returnFromIC();
7170
7171 trackAttached("MathFunction");
7172 return AttachDecision::Attach;
7173 }
7174
emitToStringGuard(ValOperandId id,const Value & v)7175 StringOperandId IRGenerator::emitToStringGuard(ValOperandId id,
7176 const Value& v) {
7177 if (v.isString()) {
7178 return writer.guardToString(id);
7179 }
7180 if (v.isInt32()) {
7181 Int32OperandId intId = writer.guardToInt32(id);
7182 return writer.callInt32ToString(intId);
7183 }
7184 // At this point we are creating an IC that will handle
7185 // both Int32 and Double cases.
7186 MOZ_ASSERT(v.isNumber());
7187 NumberOperandId numId = writer.guardIsNumber(id);
7188 return writer.callNumberToString(numId);
7189 }
7190
tryAttachNumberToString(HandleFunction callee)7191 AttachDecision CallIRGenerator::tryAttachNumberToString(HandleFunction callee) {
7192 // Expecting no arguments, which means base 10.
7193 if (argc_ != 0) {
7194 return AttachDecision::NoAction;
7195 }
7196
7197 // Ensure |this| is a primitive number value.
7198 if (!thisval_.isNumber()) {
7199 return AttachDecision::NoAction;
7200 }
7201
7202 // Initialize the input operand.
7203 Int32OperandId argcId(writer.setInputOperandId(0));
7204
7205 // Guard callee is the 'toString' native function.
7206 emitNativeCalleeGuard(callee);
7207
7208 // Initialize the |this| operand.
7209 ValOperandId thisValId =
7210 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
7211
7212 // Guard on number and convert to string.
7213 StringOperandId strId = emitToStringGuard(thisValId, thisval_);
7214
7215 // Return the string.
7216 writer.loadStringResult(strId);
7217 writer.returnFromIC();
7218
7219 trackAttached("NumberToString");
7220 return AttachDecision::Attach;
7221 }
7222
tryAttachReflectGetPrototypeOf(HandleFunction callee)7223 AttachDecision CallIRGenerator::tryAttachReflectGetPrototypeOf(
7224 HandleFunction callee) {
7225 // Need one argument.
7226 if (argc_ != 1) {
7227 return AttachDecision::NoAction;
7228 }
7229
7230 if (!args_[0].isObject()) {
7231 return AttachDecision::NoAction;
7232 }
7233
7234 // Initialize the input operand.
7235 Int32OperandId argcId(writer.setInputOperandId(0));
7236
7237 // Guard callee is the 'getPrototypeOf' native function.
7238 emitNativeCalleeGuard(callee);
7239
7240 ValOperandId argumentId =
7241 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7242 ObjOperandId objId = writer.guardToObject(argumentId);
7243
7244 writer.reflectGetPrototypeOfResult(objId);
7245 writer.returnFromIC();
7246
7247 trackAttached("ReflectGetPrototypeOf");
7248 return AttachDecision::Attach;
7249 }
7250
AtomicsMeetsPreconditions(TypedArrayObject * typedArray,const Value & index)7251 static bool AtomicsMeetsPreconditions(TypedArrayObject* typedArray,
7252 const Value& index) {
7253 switch (typedArray->type()) {
7254 case Scalar::Int8:
7255 case Scalar::Uint8:
7256 case Scalar::Int16:
7257 case Scalar::Uint16:
7258 case Scalar::Int32:
7259 case Scalar::Uint32:
7260 case Scalar::BigInt64:
7261 case Scalar::BigUint64:
7262 break;
7263
7264 case Scalar::Float32:
7265 case Scalar::Float64:
7266 case Scalar::Uint8Clamped:
7267 // Exclude floating types and Uint8Clamped.
7268 return false;
7269
7270 case Scalar::MaxTypedArrayViewType:
7271 case Scalar::Int64:
7272 case Scalar::Simd128:
7273 MOZ_CRASH("Unsupported TypedArray type");
7274 }
7275
7276 // Bounds check the index argument.
7277 int64_t indexInt64;
7278 if (!ValueIsInt64Index(index, &indexInt64)) {
7279 return false;
7280 }
7281 if (indexInt64 < 0 || uint64_t(indexInt64) >= typedArray->length()) {
7282 return false;
7283 }
7284
7285 return true;
7286 }
7287
tryAttachAtomicsCompareExchange(HandleFunction callee)7288 AttachDecision CallIRGenerator::tryAttachAtomicsCompareExchange(
7289 HandleFunction callee) {
7290 if (!JitSupportsAtomics()) {
7291 return AttachDecision::NoAction;
7292 }
7293
7294 // Need four arguments.
7295 if (argc_ != 4) {
7296 return AttachDecision::NoAction;
7297 }
7298
7299 // Arguments: typedArray, index (number), expected, replacement.
7300 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
7301 return AttachDecision::NoAction;
7302 }
7303 if (!args_[1].isNumber()) {
7304 return AttachDecision::NoAction;
7305 }
7306
7307 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
7308 if (!AtomicsMeetsPreconditions(typedArray, args_[1])) {
7309 return AttachDecision::NoAction;
7310 }
7311
7312 Scalar::Type elementType = typedArray->type();
7313 if (!ValueIsNumeric(elementType, args_[2])) {
7314 return AttachDecision::NoAction;
7315 }
7316 if (!ValueIsNumeric(elementType, args_[3])) {
7317 return AttachDecision::NoAction;
7318 }
7319
7320 // Initialize the input operand.
7321 Int32OperandId argcId(writer.setInputOperandId(0));
7322
7323 // Guard callee is the `compareExchange` native function.
7324 emitNativeCalleeGuard(callee);
7325
7326 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7327 ObjOperandId objId = writer.guardToObject(arg0Id);
7328 writer.guardShapeForClass(objId, typedArray->shape());
7329
7330 // Convert index to intPtr.
7331 ValOperandId indexId =
7332 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7333 IntPtrOperandId intPtrIndexId =
7334 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false);
7335
7336 // Convert expected value to int32/BigInt.
7337 ValOperandId expectedId =
7338 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
7339 OperandId numericExpectedId = emitNumericGuard(expectedId, elementType);
7340
7341 // Convert replacement value to int32/BigInt.
7342 ValOperandId replacementId =
7343 writer.loadArgumentFixedSlot(ArgumentKind::Arg3, argc_);
7344 OperandId numericReplacementId = emitNumericGuard(replacementId, elementType);
7345
7346 writer.atomicsCompareExchangeResult(objId, intPtrIndexId, numericExpectedId,
7347 numericReplacementId, typedArray->type());
7348 writer.returnFromIC();
7349
7350 trackAttached("AtomicsCompareExchange");
7351 return AttachDecision::Attach;
7352 }
7353
canAttachAtomicsReadWriteModify()7354 bool CallIRGenerator::canAttachAtomicsReadWriteModify() {
7355 if (!JitSupportsAtomics()) {
7356 return false;
7357 }
7358
7359 // Need three arguments.
7360 if (argc_ != 3) {
7361 return false;
7362 }
7363
7364 // Arguments: typedArray, index (number), value.
7365 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
7366 return false;
7367 }
7368 if (!args_[1].isNumber()) {
7369 return false;
7370 }
7371
7372 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
7373 if (!AtomicsMeetsPreconditions(typedArray, args_[1])) {
7374 return false;
7375 }
7376 if (!ValueIsNumeric(typedArray->type(), args_[2])) {
7377 return false;
7378 }
7379 return true;
7380 }
7381
7382 CallIRGenerator::AtomicsReadWriteModifyOperands
emitAtomicsReadWriteModifyOperands(HandleFunction callee)7383 CallIRGenerator::emitAtomicsReadWriteModifyOperands(HandleFunction callee) {
7384 MOZ_ASSERT(canAttachAtomicsReadWriteModify());
7385
7386 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
7387
7388 // Initialize the input operand.
7389 Int32OperandId argcId(writer.setInputOperandId(0));
7390
7391 // Guard callee is this Atomics function.
7392 emitNativeCalleeGuard(callee);
7393
7394 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7395 ObjOperandId objId = writer.guardToObject(arg0Id);
7396 writer.guardShapeForClass(objId, typedArray->shape());
7397
7398 // Convert index to intPtr.
7399 ValOperandId indexId =
7400 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7401 IntPtrOperandId intPtrIndexId =
7402 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false);
7403
7404 // Convert value to int32/BigInt.
7405 ValOperandId valueId =
7406 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
7407 OperandId numericValueId = emitNumericGuard(valueId, typedArray->type());
7408
7409 return {objId, intPtrIndexId, numericValueId};
7410 }
7411
tryAttachAtomicsExchange(HandleFunction callee)7412 AttachDecision CallIRGenerator::tryAttachAtomicsExchange(
7413 HandleFunction callee) {
7414 if (!canAttachAtomicsReadWriteModify()) {
7415 return AttachDecision::NoAction;
7416 }
7417
7418 auto [objId, intPtrIndexId, numericValueId] =
7419 emitAtomicsReadWriteModifyOperands(callee);
7420
7421 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
7422
7423 writer.atomicsExchangeResult(objId, intPtrIndexId, numericValueId,
7424 typedArray->type());
7425 writer.returnFromIC();
7426
7427 trackAttached("AtomicsExchange");
7428 return AttachDecision::Attach;
7429 }
7430
tryAttachAtomicsAdd(HandleFunction callee)7431 AttachDecision CallIRGenerator::tryAttachAtomicsAdd(HandleFunction callee) {
7432 if (!canAttachAtomicsReadWriteModify()) {
7433 return AttachDecision::NoAction;
7434 }
7435
7436 auto [objId, intPtrIndexId, numericValueId] =
7437 emitAtomicsReadWriteModifyOperands(callee);
7438
7439 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
7440 bool forEffect = (op_ == JSOp::CallIgnoresRv);
7441
7442 writer.atomicsAddResult(objId, intPtrIndexId, numericValueId,
7443 typedArray->type(), forEffect);
7444 writer.returnFromIC();
7445
7446 trackAttached("AtomicsAdd");
7447 return AttachDecision::Attach;
7448 }
7449
tryAttachAtomicsSub(HandleFunction callee)7450 AttachDecision CallIRGenerator::tryAttachAtomicsSub(HandleFunction callee) {
7451 if (!canAttachAtomicsReadWriteModify()) {
7452 return AttachDecision::NoAction;
7453 }
7454
7455 auto [objId, intPtrIndexId, numericValueId] =
7456 emitAtomicsReadWriteModifyOperands(callee);
7457
7458 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
7459 bool forEffect = (op_ == JSOp::CallIgnoresRv);
7460
7461 writer.atomicsSubResult(objId, intPtrIndexId, numericValueId,
7462 typedArray->type(), forEffect);
7463 writer.returnFromIC();
7464
7465 trackAttached("AtomicsSub");
7466 return AttachDecision::Attach;
7467 }
7468
tryAttachAtomicsAnd(HandleFunction callee)7469 AttachDecision CallIRGenerator::tryAttachAtomicsAnd(HandleFunction callee) {
7470 if (!canAttachAtomicsReadWriteModify()) {
7471 return AttachDecision::NoAction;
7472 }
7473
7474 auto [objId, intPtrIndexId, numericValueId] =
7475 emitAtomicsReadWriteModifyOperands(callee);
7476
7477 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
7478 bool forEffect = (op_ == JSOp::CallIgnoresRv);
7479
7480 writer.atomicsAndResult(objId, intPtrIndexId, numericValueId,
7481 typedArray->type(), forEffect);
7482 writer.returnFromIC();
7483
7484 trackAttached("AtomicsAnd");
7485 return AttachDecision::Attach;
7486 }
7487
tryAttachAtomicsOr(HandleFunction callee)7488 AttachDecision CallIRGenerator::tryAttachAtomicsOr(HandleFunction callee) {
7489 if (!canAttachAtomicsReadWriteModify()) {
7490 return AttachDecision::NoAction;
7491 }
7492
7493 auto [objId, intPtrIndexId, numericValueId] =
7494 emitAtomicsReadWriteModifyOperands(callee);
7495
7496 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
7497 bool forEffect = (op_ == JSOp::CallIgnoresRv);
7498
7499 writer.atomicsOrResult(objId, intPtrIndexId, numericValueId,
7500 typedArray->type(), forEffect);
7501 writer.returnFromIC();
7502
7503 trackAttached("AtomicsOr");
7504 return AttachDecision::Attach;
7505 }
7506
tryAttachAtomicsXor(HandleFunction callee)7507 AttachDecision CallIRGenerator::tryAttachAtomicsXor(HandleFunction callee) {
7508 if (!canAttachAtomicsReadWriteModify()) {
7509 return AttachDecision::NoAction;
7510 }
7511
7512 auto [objId, intPtrIndexId, numericValueId] =
7513 emitAtomicsReadWriteModifyOperands(callee);
7514
7515 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
7516 bool forEffect = (op_ == JSOp::CallIgnoresRv);
7517
7518 writer.atomicsXorResult(objId, intPtrIndexId, numericValueId,
7519 typedArray->type(), forEffect);
7520 writer.returnFromIC();
7521
7522 trackAttached("AtomicsXor");
7523 return AttachDecision::Attach;
7524 }
7525
tryAttachAtomicsLoad(HandleFunction callee)7526 AttachDecision CallIRGenerator::tryAttachAtomicsLoad(HandleFunction callee) {
7527 if (!JitSupportsAtomics()) {
7528 return AttachDecision::NoAction;
7529 }
7530
7531 // Need two arguments.
7532 if (argc_ != 2) {
7533 return AttachDecision::NoAction;
7534 }
7535
7536 // Arguments: typedArray, index (number).
7537 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
7538 return AttachDecision::NoAction;
7539 }
7540 if (!args_[1].isNumber()) {
7541 return AttachDecision::NoAction;
7542 }
7543
7544 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
7545 if (!AtomicsMeetsPreconditions(typedArray, args_[1])) {
7546 return AttachDecision::NoAction;
7547 }
7548
7549 // Initialize the input operand.
7550 Int32OperandId argcId(writer.setInputOperandId(0));
7551
7552 // Guard callee is the `load` native function.
7553 emitNativeCalleeGuard(callee);
7554
7555 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7556 ObjOperandId objId = writer.guardToObject(arg0Id);
7557 writer.guardShapeForClass(objId, typedArray->shape());
7558
7559 // Convert index to intPtr.
7560 ValOperandId indexId =
7561 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7562 IntPtrOperandId intPtrIndexId =
7563 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false);
7564
7565 writer.atomicsLoadResult(objId, intPtrIndexId, typedArray->type());
7566 writer.returnFromIC();
7567
7568 trackAttached("AtomicsLoad");
7569 return AttachDecision::Attach;
7570 }
7571
tryAttachAtomicsStore(HandleFunction callee)7572 AttachDecision CallIRGenerator::tryAttachAtomicsStore(HandleFunction callee) {
7573 if (!JitSupportsAtomics()) {
7574 return AttachDecision::NoAction;
7575 }
7576
7577 // Need three arguments.
7578 if (argc_ != 3) {
7579 return AttachDecision::NoAction;
7580 }
7581
7582 // Atomics.store() is annoying because it returns the result of converting the
7583 // value by ToInteger(), not the input value, nor the result of converting the
7584 // value by ToInt32(). It is especially annoying because almost nobody uses
7585 // the result value.
7586 //
7587 // As an expedient compromise, therefore, we inline only if the result is
7588 // obviously unused or if the argument is already Int32 and thus requires no
7589 // conversion.
7590
7591 // Arguments: typedArray, index (number), value.
7592 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
7593 return AttachDecision::NoAction;
7594 }
7595 if (!args_[1].isNumber()) {
7596 return AttachDecision::NoAction;
7597 }
7598
7599 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
7600 if (!AtomicsMeetsPreconditions(typedArray, args_[1])) {
7601 return AttachDecision::NoAction;
7602 }
7603
7604 Scalar::Type elementType = typedArray->type();
7605 if (!ValueIsNumeric(elementType, args_[2])) {
7606 return AttachDecision::NoAction;
7607 }
7608
7609 bool guardIsInt32 =
7610 !Scalar::isBigIntType(elementType) && op_ != JSOp::CallIgnoresRv;
7611
7612 if (guardIsInt32 && !args_[2].isInt32()) {
7613 return AttachDecision::NoAction;
7614 }
7615
7616 // Initialize the input operand.
7617 Int32OperandId argcId(writer.setInputOperandId(0));
7618
7619 // Guard callee is the `store` native function.
7620 emitNativeCalleeGuard(callee);
7621
7622 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7623 ObjOperandId objId = writer.guardToObject(arg0Id);
7624 writer.guardShapeForClass(objId, typedArray->shape());
7625
7626 // Convert index to intPtr.
7627 ValOperandId indexId =
7628 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7629 IntPtrOperandId intPtrIndexId =
7630 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false);
7631
7632 // Ensure value is int32 or BigInt.
7633 ValOperandId valueId =
7634 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
7635 OperandId numericValueId;
7636 if (guardIsInt32) {
7637 numericValueId = writer.guardToInt32(valueId);
7638 } else {
7639 numericValueId = emitNumericGuard(valueId, elementType);
7640 }
7641
7642 writer.atomicsStoreResult(objId, intPtrIndexId, numericValueId,
7643 typedArray->type());
7644 writer.returnFromIC();
7645
7646 trackAttached("AtomicsStore");
7647 return AttachDecision::Attach;
7648 }
7649
tryAttachAtomicsIsLockFree(HandleFunction callee)7650 AttachDecision CallIRGenerator::tryAttachAtomicsIsLockFree(
7651 HandleFunction callee) {
7652 // Need one argument.
7653 if (argc_ != 1) {
7654 return AttachDecision::NoAction;
7655 }
7656
7657 if (!args_[0].isInt32()) {
7658 return AttachDecision::NoAction;
7659 }
7660
7661 // Initialize the input operand.
7662 Int32OperandId argcId(writer.setInputOperandId(0));
7663
7664 // Guard callee is the `isLockFree` native function.
7665 emitNativeCalleeGuard(callee);
7666
7667 // Ensure value is int32.
7668 ValOperandId valueId =
7669 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7670 Int32OperandId int32ValueId = writer.guardToInt32(valueId);
7671
7672 writer.atomicsIsLockFreeResult(int32ValueId);
7673 writer.returnFromIC();
7674
7675 trackAttached("AtomicsIsLockFree");
7676 return AttachDecision::Attach;
7677 }
7678
tryAttachBoolean(HandleFunction callee)7679 AttachDecision CallIRGenerator::tryAttachBoolean(HandleFunction callee) {
7680 // Need zero or one argument.
7681 if (argc_ > 1) {
7682 return AttachDecision::NoAction;
7683 }
7684
7685 // Initialize the input operand.
7686 Int32OperandId argcId(writer.setInputOperandId(0));
7687
7688 // Guard callee is the 'Boolean' native function.
7689 emitNativeCalleeGuard(callee);
7690
7691 if (argc_ == 0) {
7692 writer.loadBooleanResult(false);
7693 } else {
7694 ValOperandId valId =
7695 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7696
7697 writer.loadValueTruthyResult(valId);
7698 }
7699
7700 writer.returnFromIC();
7701
7702 trackAttached("Boolean");
7703 return AttachDecision::Attach;
7704 }
7705
tryAttachBailout(HandleFunction callee)7706 AttachDecision CallIRGenerator::tryAttachBailout(HandleFunction callee) {
7707 // Expecting no arguments.
7708 if (argc_ != 0) {
7709 return AttachDecision::NoAction;
7710 }
7711
7712 // Initialize the input operand.
7713 Int32OperandId argcId(writer.setInputOperandId(0));
7714
7715 // Guard callee is the 'bailout' native function.
7716 emitNativeCalleeGuard(callee);
7717
7718 writer.bailout();
7719 writer.loadUndefinedResult();
7720 writer.returnFromIC();
7721
7722 trackAttached("Bailout");
7723 return AttachDecision::Attach;
7724 }
7725
tryAttachAssertFloat32(HandleFunction callee)7726 AttachDecision CallIRGenerator::tryAttachAssertFloat32(HandleFunction callee) {
7727 // Expecting two arguments.
7728 if (argc_ != 2) {
7729 return AttachDecision::NoAction;
7730 }
7731
7732 // Initialize the input operand.
7733 Int32OperandId argcId(writer.setInputOperandId(0));
7734
7735 // Guard callee is the 'assertFloat32' native function.
7736 emitNativeCalleeGuard(callee);
7737
7738 // TODO: Warp doesn't yet optimize Float32 (bug 1655773).
7739
7740 // NOP when not in IonMonkey.
7741 writer.loadUndefinedResult();
7742 writer.returnFromIC();
7743
7744 trackAttached("AssertFloat32");
7745 return AttachDecision::Attach;
7746 }
7747
tryAttachAssertRecoveredOnBailout(HandleFunction callee)7748 AttachDecision CallIRGenerator::tryAttachAssertRecoveredOnBailout(
7749 HandleFunction callee) {
7750 // Expecting two arguments.
7751 if (argc_ != 2) {
7752 return AttachDecision::NoAction;
7753 }
7754
7755 // (Fuzzing unsafe) testing function which must be called with a constant
7756 // boolean as its second argument.
7757 bool mustBeRecovered = args_[1].toBoolean();
7758
7759 // Initialize the input operand.
7760 Int32OperandId argcId(writer.setInputOperandId(0));
7761
7762 // Guard callee is the 'assertRecoveredOnBailout' native function.
7763 emitNativeCalleeGuard(callee);
7764
7765 ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7766
7767 writer.assertRecoveredOnBailoutResult(valId, mustBeRecovered);
7768 writer.returnFromIC();
7769
7770 trackAttached("AssertRecoveredOnBailout");
7771 return AttachDecision::Attach;
7772 }
7773
tryAttachObjectIs(HandleFunction callee)7774 AttachDecision CallIRGenerator::tryAttachObjectIs(HandleFunction callee) {
7775 // Need two arguments.
7776 if (argc_ != 2) {
7777 return AttachDecision::NoAction;
7778 }
7779
7780 // Initialize the input operand.
7781 Int32OperandId argcId(writer.setInputOperandId(0));
7782
7783 // Guard callee is the `is` native function.
7784 emitNativeCalleeGuard(callee);
7785
7786 ValOperandId lhsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7787 ValOperandId rhsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7788
7789 HandleValue lhs = args_[0];
7790 HandleValue rhs = args_[1];
7791
7792 if (!isFirstStub_) {
7793 writer.sameValueResult(lhsId, rhsId);
7794 } else if (lhs.isNumber() && rhs.isNumber() &&
7795 !(lhs.isInt32() && rhs.isInt32())) {
7796 NumberOperandId lhsNumId = writer.guardIsNumber(lhsId);
7797 NumberOperandId rhsNumId = writer.guardIsNumber(rhsId);
7798 writer.compareDoubleSameValueResult(lhsNumId, rhsNumId);
7799 } else if (!SameType(lhs, rhs)) {
7800 // Compare tags for strictly different types.
7801 ValueTagOperandId lhsTypeId = writer.loadValueTag(lhsId);
7802 ValueTagOperandId rhsTypeId = writer.loadValueTag(rhsId);
7803 writer.guardTagNotEqual(lhsTypeId, rhsTypeId);
7804 writer.loadBooleanResult(false);
7805 } else {
7806 MOZ_ASSERT(lhs.type() == rhs.type());
7807 MOZ_ASSERT(lhs.type() != JS::ValueType::Double);
7808
7809 switch (lhs.type()) {
7810 case JS::ValueType::Int32: {
7811 Int32OperandId lhsIntId = writer.guardToInt32(lhsId);
7812 Int32OperandId rhsIntId = writer.guardToInt32(rhsId);
7813 writer.compareInt32Result(JSOp::StrictEq, lhsIntId, rhsIntId);
7814 break;
7815 }
7816 case JS::ValueType::Boolean: {
7817 Int32OperandId lhsIntId = writer.guardBooleanToInt32(lhsId);
7818 Int32OperandId rhsIntId = writer.guardBooleanToInt32(rhsId);
7819 writer.compareInt32Result(JSOp::StrictEq, lhsIntId, rhsIntId);
7820 break;
7821 }
7822 case JS::ValueType::Undefined: {
7823 writer.guardIsUndefined(lhsId);
7824 writer.guardIsUndefined(rhsId);
7825 writer.loadBooleanResult(true);
7826 break;
7827 }
7828 case JS::ValueType::Null: {
7829 writer.guardIsNull(lhsId);
7830 writer.guardIsNull(rhsId);
7831 writer.loadBooleanResult(true);
7832 break;
7833 }
7834 case JS::ValueType::String: {
7835 StringOperandId lhsStrId = writer.guardToString(lhsId);
7836 StringOperandId rhsStrId = writer.guardToString(rhsId);
7837 writer.compareStringResult(JSOp::StrictEq, lhsStrId, rhsStrId);
7838 break;
7839 }
7840 case JS::ValueType::Symbol: {
7841 SymbolOperandId lhsSymId = writer.guardToSymbol(lhsId);
7842 SymbolOperandId rhsSymId = writer.guardToSymbol(rhsId);
7843 writer.compareSymbolResult(JSOp::StrictEq, lhsSymId, rhsSymId);
7844 break;
7845 }
7846 case JS::ValueType::BigInt: {
7847 BigIntOperandId lhsBigIntId = writer.guardToBigInt(lhsId);
7848 BigIntOperandId rhsBigIntId = writer.guardToBigInt(rhsId);
7849 writer.compareBigIntResult(JSOp::StrictEq, lhsBigIntId, rhsBigIntId);
7850 break;
7851 }
7852 case JS::ValueType::Object: {
7853 ObjOperandId lhsObjId = writer.guardToObject(lhsId);
7854 ObjOperandId rhsObjId = writer.guardToObject(rhsId);
7855 writer.compareObjectResult(JSOp::StrictEq, lhsObjId, rhsObjId);
7856 break;
7857 }
7858
7859 #ifdef ENABLE_RECORD_TUPLE
7860 case ValueType::ExtendedPrimitive:
7861 #endif
7862 case JS::ValueType::Double:
7863 case JS::ValueType::Magic:
7864 case JS::ValueType::PrivateGCThing:
7865 MOZ_CRASH("Unexpected type");
7866 }
7867 }
7868
7869 writer.returnFromIC();
7870
7871 trackAttached("ObjectIs");
7872 return AttachDecision::Attach;
7873 }
7874
tryAttachObjectIsPrototypeOf(HandleFunction callee)7875 AttachDecision CallIRGenerator::tryAttachObjectIsPrototypeOf(
7876 HandleFunction callee) {
7877 // Ensure |this| is an object.
7878 if (!thisval_.isObject()) {
7879 return AttachDecision::NoAction;
7880 }
7881
7882 // Need a single argument.
7883 if (argc_ != 1) {
7884 return AttachDecision::NoAction;
7885 }
7886
7887 // Initialize the input operand.
7888 Int32OperandId argcId(writer.setInputOperandId(0));
7889
7890 // Guard callee is the `isPrototypeOf` native function.
7891 emitNativeCalleeGuard(callee);
7892
7893 // Guard that |this| is an object.
7894 ValOperandId thisValId =
7895 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
7896 ObjOperandId thisObjId = writer.guardToObject(thisValId);
7897
7898 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7899
7900 writer.loadInstanceOfObjectResult(argId, thisObjId);
7901 writer.returnFromIC();
7902
7903 trackAttached("ObjectIsPrototypeOf");
7904 return AttachDecision::Attach;
7905 }
7906
tryAttachObjectToString(HandleFunction callee)7907 AttachDecision CallIRGenerator::tryAttachObjectToString(HandleFunction callee) {
7908 // Expecting no arguments.
7909 if (argc_ != 0) {
7910 return AttachDecision::NoAction;
7911 }
7912
7913 // Ensure |this| is an object.
7914 if (!thisval_.isObject()) {
7915 return AttachDecision::NoAction;
7916 }
7917
7918 // Don't attach if the object has @@toStringTag or is a proxy.
7919 if (!ObjectClassToString(cx_, &thisval_.toObject())) {
7920 return AttachDecision::NoAction;
7921 }
7922
7923 // Initialize the input operand.
7924 Int32OperandId argcId(writer.setInputOperandId(0));
7925
7926 // Guard callee is the 'toString' native function.
7927 emitNativeCalleeGuard(callee);
7928
7929 // Guard that |this| is an object.
7930 ValOperandId thisValId =
7931 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
7932 ObjOperandId thisObjId = writer.guardToObject(thisValId);
7933
7934 writer.objectToStringResult(thisObjId);
7935 writer.returnFromIC();
7936
7937 trackAttached("ObjectToString");
7938 return AttachDecision::Attach;
7939 }
7940
tryAttachBigIntAsIntN(HandleFunction callee)7941 AttachDecision CallIRGenerator::tryAttachBigIntAsIntN(HandleFunction callee) {
7942 // Need two arguments (Int32, BigInt).
7943 if (argc_ != 2 || !args_[0].isInt32() || !args_[1].isBigInt()) {
7944 return AttachDecision::NoAction;
7945 }
7946
7947 // Negative bits throws an error.
7948 if (args_[0].toInt32() < 0) {
7949 return AttachDecision::NoAction;
7950 }
7951
7952 // Initialize the input operand.
7953 Int32OperandId argcId(writer.setInputOperandId(0));
7954
7955 // Guard callee is the 'BigInt.asIntN' native function.
7956 emitNativeCalleeGuard(callee);
7957
7958 // Convert bits to int32.
7959 ValOperandId bitsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7960 Int32OperandId int32BitsId = writer.guardToInt32Index(bitsId);
7961
7962 // Number of bits mustn't be negative.
7963 writer.guardInt32IsNonNegative(int32BitsId);
7964
7965 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7966 BigIntOperandId bigIntId = writer.guardToBigInt(arg1Id);
7967
7968 writer.bigIntAsIntNResult(int32BitsId, bigIntId);
7969 writer.returnFromIC();
7970
7971 trackAttached("BigIntAsIntN");
7972 return AttachDecision::Attach;
7973 }
7974
tryAttachBigIntAsUintN(HandleFunction callee)7975 AttachDecision CallIRGenerator::tryAttachBigIntAsUintN(HandleFunction callee) {
7976 // Need two arguments (Int32, BigInt).
7977 if (argc_ != 2 || !args_[0].isInt32() || !args_[1].isBigInt()) {
7978 return AttachDecision::NoAction;
7979 }
7980
7981 // Negative bits throws an error.
7982 if (args_[0].toInt32() < 0) {
7983 return AttachDecision::NoAction;
7984 }
7985
7986 // Initialize the input operand.
7987 Int32OperandId argcId(writer.setInputOperandId(0));
7988
7989 // Guard callee is the 'BigInt.asUintN' native function.
7990 emitNativeCalleeGuard(callee);
7991
7992 // Convert bits to int32.
7993 ValOperandId bitsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7994 Int32OperandId int32BitsId = writer.guardToInt32Index(bitsId);
7995
7996 // Number of bits mustn't be negative.
7997 writer.guardInt32IsNonNegative(int32BitsId);
7998
7999 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8000 BigIntOperandId bigIntId = writer.guardToBigInt(arg1Id);
8001
8002 writer.bigIntAsUintNResult(int32BitsId, bigIntId);
8003 writer.returnFromIC();
8004
8005 trackAttached("BigIntAsUintN");
8006 return AttachDecision::Attach;
8007 }
8008
tryAttachSetHas(HandleFunction callee)8009 AttachDecision CallIRGenerator::tryAttachSetHas(HandleFunction callee) {
8010 // Ensure |this| is a SetObject.
8011 if (!thisval_.isObject() || !thisval_.toObject().is<SetObject>()) {
8012 return AttachDecision::NoAction;
8013 }
8014
8015 // Need a single argument.
8016 if (argc_ != 1) {
8017 return AttachDecision::NoAction;
8018 }
8019
8020 // Initialize the input operand.
8021 Int32OperandId argcId(writer.setInputOperandId(0));
8022
8023 // Guard callee is the 'has' native function.
8024 emitNativeCalleeGuard(callee);
8025
8026 // Guard |this| is a SetObject.
8027 ValOperandId thisValId =
8028 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8029 ObjOperandId objId = writer.guardToObject(thisValId);
8030 writer.guardClass(objId, GuardClassKind::Set);
8031
8032 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8033
8034 #ifndef JS_CODEGEN_X86
8035 // Assume the hash key will likely always have the same type when attaching
8036 // the first stub. If the call is polymorphic on the hash key, attach a stub
8037 // which handles any value.
8038 if (isFirstStub_) {
8039 switch (args_[0].type()) {
8040 case ValueType::Double:
8041 case ValueType::Int32:
8042 case ValueType::Boolean:
8043 case ValueType::Undefined:
8044 case ValueType::Null: {
8045 writer.guardToNonGCThing(argId);
8046 writer.setHasNonGCThingResult(objId, argId);
8047 break;
8048 }
8049 case ValueType::String: {
8050 StringOperandId strId = writer.guardToString(argId);
8051 writer.setHasStringResult(objId, strId);
8052 break;
8053 }
8054 case ValueType::Symbol: {
8055 SymbolOperandId symId = writer.guardToSymbol(argId);
8056 writer.setHasSymbolResult(objId, symId);
8057 break;
8058 }
8059 case ValueType::BigInt: {
8060 BigIntOperandId bigIntId = writer.guardToBigInt(argId);
8061 writer.setHasBigIntResult(objId, bigIntId);
8062 break;
8063 }
8064 case ValueType::Object: {
8065 // Currently only supported on 64-bit platforms.
8066 # ifdef JS_PUNBOX64
8067 ObjOperandId valId = writer.guardToObject(argId);
8068 writer.setHasObjectResult(objId, valId);
8069 # else
8070 writer.setHasResult(objId, argId);
8071 # endif
8072 break;
8073 }
8074
8075 # ifdef ENABLE_RECORD_TUPLE
8076 case ValueType::ExtendedPrimitive:
8077 # endif
8078 case ValueType::Magic:
8079 case ValueType::PrivateGCThing:
8080 MOZ_CRASH("Unexpected type");
8081 }
8082 } else {
8083 writer.setHasResult(objId, argId);
8084 }
8085 #else
8086 // The optimized versions require too many registers on x86.
8087 writer.setHasResult(objId, argId);
8088 #endif
8089
8090 writer.returnFromIC();
8091
8092 trackAttached("SetHas");
8093 return AttachDecision::Attach;
8094 }
8095
tryAttachMapHas(HandleFunction callee)8096 AttachDecision CallIRGenerator::tryAttachMapHas(HandleFunction callee) {
8097 // Ensure |this| is a MapObject.
8098 if (!thisval_.isObject() || !thisval_.toObject().is<MapObject>()) {
8099 return AttachDecision::NoAction;
8100 }
8101
8102 // Need a single argument.
8103 if (argc_ != 1) {
8104 return AttachDecision::NoAction;
8105 }
8106
8107 // Initialize the input operand.
8108 Int32OperandId argcId(writer.setInputOperandId(0));
8109
8110 // Guard callee is the 'has' native function.
8111 emitNativeCalleeGuard(callee);
8112
8113 // Guard |this| is a MapObject.
8114 ValOperandId thisValId =
8115 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8116 ObjOperandId objId = writer.guardToObject(thisValId);
8117 writer.guardClass(objId, GuardClassKind::Map);
8118
8119 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8120
8121 #ifndef JS_CODEGEN_X86
8122 // Assume the hash key will likely always have the same type when attaching
8123 // the first stub. If the call is polymorphic on the hash key, attach a stub
8124 // which handles any value.
8125 if (isFirstStub_) {
8126 switch (args_[0].type()) {
8127 case ValueType::Double:
8128 case ValueType::Int32:
8129 case ValueType::Boolean:
8130 case ValueType::Undefined:
8131 case ValueType::Null: {
8132 writer.guardToNonGCThing(argId);
8133 writer.mapHasNonGCThingResult(objId, argId);
8134 break;
8135 }
8136 case ValueType::String: {
8137 StringOperandId strId = writer.guardToString(argId);
8138 writer.mapHasStringResult(objId, strId);
8139 break;
8140 }
8141 case ValueType::Symbol: {
8142 SymbolOperandId symId = writer.guardToSymbol(argId);
8143 writer.mapHasSymbolResult(objId, symId);
8144 break;
8145 }
8146 case ValueType::BigInt: {
8147 BigIntOperandId bigIntId = writer.guardToBigInt(argId);
8148 writer.mapHasBigIntResult(objId, bigIntId);
8149 break;
8150 }
8151 case ValueType::Object: {
8152 // Currently only supported on 64-bit platforms.
8153 # ifdef JS_PUNBOX64
8154 ObjOperandId valId = writer.guardToObject(argId);
8155 writer.mapHasObjectResult(objId, valId);
8156 # else
8157 writer.mapHasResult(objId, argId);
8158 # endif
8159 break;
8160 }
8161
8162 # ifdef ENABLE_RECORD_TUPLE
8163 case ValueType::ExtendedPrimitive:
8164 # endif
8165 case ValueType::Magic:
8166 case ValueType::PrivateGCThing:
8167 MOZ_CRASH("Unexpected type");
8168 }
8169 } else {
8170 writer.mapHasResult(objId, argId);
8171 }
8172 #else
8173 // The optimized versions require too many registers on x86.
8174 writer.mapHasResult(objId, argId);
8175 #endif
8176
8177 writer.returnFromIC();
8178
8179 trackAttached("MapHas");
8180 return AttachDecision::Attach;
8181 }
8182
tryAttachMapGet(HandleFunction callee)8183 AttachDecision CallIRGenerator::tryAttachMapGet(HandleFunction callee) {
8184 // Ensure |this| is a MapObject.
8185 if (!thisval_.isObject() || !thisval_.toObject().is<MapObject>()) {
8186 return AttachDecision::NoAction;
8187 }
8188
8189 // Need a single argument.
8190 if (argc_ != 1) {
8191 return AttachDecision::NoAction;
8192 }
8193
8194 // Initialize the input operand.
8195 Int32OperandId argcId(writer.setInputOperandId(0));
8196
8197 // Guard callee is the 'get' native function.
8198 emitNativeCalleeGuard(callee);
8199
8200 // Guard |this| is a MapObject.
8201 ValOperandId thisValId =
8202 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8203 ObjOperandId objId = writer.guardToObject(thisValId);
8204 writer.guardClass(objId, GuardClassKind::Map);
8205
8206 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8207
8208 #ifndef JS_CODEGEN_X86
8209 // Assume the hash key will likely always have the same type when attaching
8210 // the first stub. If the call is polymorphic on the hash key, attach a stub
8211 // which handles any value.
8212 if (isFirstStub_) {
8213 switch (args_[0].type()) {
8214 case ValueType::Double:
8215 case ValueType::Int32:
8216 case ValueType::Boolean:
8217 case ValueType::Undefined:
8218 case ValueType::Null: {
8219 writer.guardToNonGCThing(argId);
8220 writer.mapGetNonGCThingResult(objId, argId);
8221 break;
8222 }
8223 case ValueType::String: {
8224 StringOperandId strId = writer.guardToString(argId);
8225 writer.mapGetStringResult(objId, strId);
8226 break;
8227 }
8228 case ValueType::Symbol: {
8229 SymbolOperandId symId = writer.guardToSymbol(argId);
8230 writer.mapGetSymbolResult(objId, symId);
8231 break;
8232 }
8233 case ValueType::BigInt: {
8234 BigIntOperandId bigIntId = writer.guardToBigInt(argId);
8235 writer.mapGetBigIntResult(objId, bigIntId);
8236 break;
8237 }
8238 case ValueType::Object: {
8239 // Currently only supported on 64-bit platforms.
8240 # ifdef JS_PUNBOX64
8241 ObjOperandId valId = writer.guardToObject(argId);
8242 writer.mapGetObjectResult(objId, valId);
8243 # else
8244 writer.mapGetResult(objId, argId);
8245 # endif
8246 break;
8247 }
8248
8249 # ifdef ENABLE_RECORD_TUPLE
8250 case ValueType::ExtendedPrimitive:
8251 # endif
8252 case ValueType::Magic:
8253 case ValueType::PrivateGCThing:
8254 MOZ_CRASH("Unexpected type");
8255 }
8256 } else {
8257 writer.mapGetResult(objId, argId);
8258 }
8259 #else
8260 // The optimized versions require too many registers on x86.
8261 writer.mapGetResult(objId, argId);
8262 #endif
8263
8264 writer.returnFromIC();
8265
8266 trackAttached("MapGet");
8267 return AttachDecision::Attach;
8268 }
8269
tryAttachFunCall(HandleFunction callee)8270 AttachDecision CallIRGenerator::tryAttachFunCall(HandleFunction callee) {
8271 if (!callee->isNativeWithoutJitEntry() || callee->native() != fun_call) {
8272 return AttachDecision::NoAction;
8273 }
8274
8275 if (!thisval_.isObject() || !thisval_.toObject().is<JSFunction>()) {
8276 return AttachDecision::NoAction;
8277 }
8278 auto* target = &thisval_.toObject().as<JSFunction>();
8279
8280 bool isScripted = target->hasJitEntry();
8281 MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry());
8282
8283 if (target->isClassConstructor()) {
8284 return AttachDecision::NoAction;
8285 }
8286 Int32OperandId argcId(writer.setInputOperandId(0));
8287
8288 // Guard that callee is the |fun_call| native function.
8289 ValOperandId calleeValId =
8290 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId);
8291 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
8292 writer.guardSpecificFunction(calleeObjId, callee);
8293
8294 // Guard that |this| is an object.
8295 ValOperandId thisValId =
8296 writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId);
8297 ObjOperandId thisObjId = writer.guardToObject(thisValId);
8298
8299 CallFlags targetFlags(CallFlags::FunCall);
8300 if (mode_ == ICState::Mode::Specialized) {
8301 // Ensure that |this| is the expected target function.
8302 emitCalleeGuard(thisObjId, target);
8303
8304 if (cx_->realm() == target->realm()) {
8305 targetFlags.setIsSameRealm();
8306 }
8307
8308 if (isScripted) {
8309 writer.callScriptedFunction(thisObjId, argcId, targetFlags);
8310 } else {
8311 writer.callNativeFunction(thisObjId, argcId, op_, target, targetFlags);
8312 }
8313 } else {
8314 // Guard that |this| is a function.
8315 writer.guardClass(thisObjId, GuardClassKind::JSFunction);
8316
8317 // Guard that function is not a class constructor.
8318 writer.guardNotClassConstructor(thisObjId);
8319
8320 if (isScripted) {
8321 writer.guardFunctionHasJitEntry(thisObjId, /*isConstructing =*/false);
8322 writer.callScriptedFunction(thisObjId, argcId, targetFlags);
8323 } else {
8324 writer.guardFunctionHasNoJitEntry(thisObjId);
8325 writer.callAnyNativeFunction(thisObjId, argcId, targetFlags);
8326 }
8327 }
8328
8329 writer.returnFromIC();
8330
8331 if (isScripted) {
8332 trackAttached("Scripted fun_call");
8333 } else {
8334 trackAttached("Native fun_call");
8335 }
8336
8337 return AttachDecision::Attach;
8338 }
8339
tryAttachIsTypedArray(HandleFunction callee,bool isPossiblyWrapped)8340 AttachDecision CallIRGenerator::tryAttachIsTypedArray(HandleFunction callee,
8341 bool isPossiblyWrapped) {
8342 // Self-hosted code calls this with a single object argument.
8343 MOZ_ASSERT(argc_ == 1);
8344 MOZ_ASSERT(args_[0].isObject());
8345
8346 // Initialize the input operand.
8347 Int32OperandId argcId(writer.setInputOperandId(0));
8348
8349 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8350
8351 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8352 ObjOperandId objArgId = writer.guardToObject(argId);
8353 writer.isTypedArrayResult(objArgId, isPossiblyWrapped);
8354 writer.returnFromIC();
8355
8356 trackAttached(isPossiblyWrapped ? "IsPossiblyWrappedTypedArray"
8357 : "IsTypedArray");
8358 return AttachDecision::Attach;
8359 }
8360
tryAttachIsTypedArrayConstructor(HandleFunction callee)8361 AttachDecision CallIRGenerator::tryAttachIsTypedArrayConstructor(
8362 HandleFunction callee) {
8363 // Self-hosted code calls this with a single object argument.
8364 MOZ_ASSERT(argc_ == 1);
8365 MOZ_ASSERT(args_[0].isObject());
8366
8367 // Initialize the input operand.
8368 Int32OperandId argcId(writer.setInputOperandId(0));
8369
8370 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8371
8372 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8373 ObjOperandId objArgId = writer.guardToObject(argId);
8374 writer.isTypedArrayConstructorResult(objArgId);
8375 writer.returnFromIC();
8376
8377 trackAttached("IsTypedArrayConstructor");
8378 return AttachDecision::Attach;
8379 }
8380
tryAttachTypedArrayByteOffset(HandleFunction callee)8381 AttachDecision CallIRGenerator::tryAttachTypedArrayByteOffset(
8382 HandleFunction callee) {
8383 // Self-hosted code calls this with a single TypedArrayObject argument.
8384 MOZ_ASSERT(argc_ == 1);
8385 MOZ_ASSERT(args_[0].isObject());
8386 MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>());
8387
8388 auto* tarr = &args_[0].toObject().as<TypedArrayObject>();
8389
8390 // Initialize the input operand.
8391 Int32OperandId argcId(writer.setInputOperandId(0));
8392
8393 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8394
8395 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8396 ObjOperandId objArgId = writer.guardToObject(argId);
8397 if (tarr->byteOffset() <= INT32_MAX) {
8398 writer.arrayBufferViewByteOffsetInt32Result(objArgId);
8399 } else {
8400 writer.arrayBufferViewByteOffsetDoubleResult(objArgId);
8401 }
8402 writer.returnFromIC();
8403
8404 trackAttached("TypedArrayByteOffset");
8405 return AttachDecision::Attach;
8406 }
8407
tryAttachTypedArrayElementSize(HandleFunction callee)8408 AttachDecision CallIRGenerator::tryAttachTypedArrayElementSize(
8409 HandleFunction callee) {
8410 // Self-hosted code calls this with a single TypedArrayObject argument.
8411 MOZ_ASSERT(argc_ == 1);
8412 MOZ_ASSERT(args_[0].isObject());
8413 MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>());
8414
8415 // Initialize the input operand.
8416 Int32OperandId argcId(writer.setInputOperandId(0));
8417
8418 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8419
8420 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8421 ObjOperandId objArgId = writer.guardToObject(argId);
8422 writer.typedArrayElementSizeResult(objArgId);
8423 writer.returnFromIC();
8424
8425 trackAttached("TypedArrayElementSize");
8426 return AttachDecision::Attach;
8427 }
8428
tryAttachTypedArrayLength(HandleFunction callee,bool isPossiblyWrapped)8429 AttachDecision CallIRGenerator::tryAttachTypedArrayLength(
8430 HandleFunction callee, bool isPossiblyWrapped) {
8431 // Self-hosted code calls this with a single, possibly wrapped,
8432 // TypedArrayObject argument.
8433 MOZ_ASSERT(argc_ == 1);
8434 MOZ_ASSERT(args_[0].isObject());
8435
8436 // Only optimize when the object isn't a wrapper.
8437 if (isPossiblyWrapped && IsWrapper(&args_[0].toObject())) {
8438 return AttachDecision::NoAction;
8439 }
8440
8441 MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>());
8442
8443 auto* tarr = &args_[0].toObject().as<TypedArrayObject>();
8444
8445 // Initialize the input operand.
8446 Int32OperandId argcId(writer.setInputOperandId(0));
8447
8448 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8449
8450 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8451 ObjOperandId objArgId = writer.guardToObject(argId);
8452
8453 if (isPossiblyWrapped) {
8454 writer.guardIsNotProxy(objArgId);
8455 }
8456
8457 if (tarr->length() <= INT32_MAX) {
8458 writer.loadArrayBufferViewLengthInt32Result(objArgId);
8459 } else {
8460 writer.loadArrayBufferViewLengthDoubleResult(objArgId);
8461 }
8462 writer.returnFromIC();
8463
8464 trackAttached("TypedArrayLength");
8465 return AttachDecision::Attach;
8466 }
8467
tryAttachArrayBufferByteLength(HandleFunction callee,bool isPossiblyWrapped)8468 AttachDecision CallIRGenerator::tryAttachArrayBufferByteLength(
8469 HandleFunction callee, bool isPossiblyWrapped) {
8470 // Self-hosted code calls this with a single, possibly wrapped,
8471 // ArrayBufferObject argument.
8472 MOZ_ASSERT(argc_ == 1);
8473 MOZ_ASSERT(args_[0].isObject());
8474
8475 // Only optimize when the object isn't a wrapper.
8476 if (isPossiblyWrapped && IsWrapper(&args_[0].toObject())) {
8477 return AttachDecision::NoAction;
8478 }
8479
8480 MOZ_ASSERT(args_[0].toObject().is<ArrayBufferObject>());
8481
8482 auto* buffer = &args_[0].toObject().as<ArrayBufferObject>();
8483
8484 // Initialize the input operand.
8485 Int32OperandId argcId(writer.setInputOperandId(0));
8486
8487 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8488
8489 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8490 ObjOperandId objArgId = writer.guardToObject(argId);
8491
8492 if (isPossiblyWrapped) {
8493 writer.guardIsNotProxy(objArgId);
8494 }
8495
8496 if (buffer->byteLength() <= INT32_MAX) {
8497 writer.loadArrayBufferByteLengthInt32Result(objArgId);
8498 } else {
8499 writer.loadArrayBufferByteLengthDoubleResult(objArgId);
8500 }
8501 writer.returnFromIC();
8502
8503 trackAttached("ArrayBufferByteLength");
8504 return AttachDecision::Attach;
8505 }
8506
tryAttachIsConstructing(HandleFunction callee)8507 AttachDecision CallIRGenerator::tryAttachIsConstructing(HandleFunction callee) {
8508 // Self-hosted code calls this with no arguments in function scripts.
8509 MOZ_ASSERT(argc_ == 0);
8510 MOZ_ASSERT(script_->isFunction());
8511
8512 // Initialize the input operand.
8513 Int32OperandId argcId(writer.setInputOperandId(0));
8514
8515 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8516
8517 writer.frameIsConstructingResult();
8518 writer.returnFromIC();
8519
8520 trackAttached("IsConstructing");
8521 return AttachDecision::Attach;
8522 }
8523
tryAttachGetNextMapSetEntryForIterator(HandleFunction callee,bool isMap)8524 AttachDecision CallIRGenerator::tryAttachGetNextMapSetEntryForIterator(
8525 HandleFunction callee, bool isMap) {
8526 // Self-hosted code calls this with two objects.
8527 MOZ_ASSERT(argc_ == 2);
8528 if (isMap) {
8529 MOZ_ASSERT(args_[0].toObject().is<MapIteratorObject>());
8530 } else {
8531 MOZ_ASSERT(args_[0].toObject().is<SetIteratorObject>());
8532 }
8533 MOZ_ASSERT(args_[1].toObject().is<ArrayObject>());
8534
8535 // Initialize the input operand.
8536 Int32OperandId argcId(writer.setInputOperandId(0));
8537
8538 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8539
8540 ValOperandId iterId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8541 ObjOperandId objIterId = writer.guardToObject(iterId);
8542
8543 ValOperandId resultArrId =
8544 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8545 ObjOperandId objResultArrId = writer.guardToObject(resultArrId);
8546
8547 writer.getNextMapSetEntryForIteratorResult(objIterId, objResultArrId, isMap);
8548 writer.returnFromIC();
8549
8550 trackAttached("GetNextMapSetEntryForIterator");
8551 return AttachDecision::Attach;
8552 }
8553
tryAttachFinishBoundFunctionInit(HandleFunction callee)8554 AttachDecision CallIRGenerator::tryAttachFinishBoundFunctionInit(
8555 HandleFunction callee) {
8556 // Self-hosted code calls this with (boundFunction, targetObj, argCount)
8557 // arguments.
8558 MOZ_ASSERT(argc_ == 3);
8559 MOZ_ASSERT(args_[0].toObject().is<JSFunction>());
8560 MOZ_ASSERT(args_[1].isObject());
8561 MOZ_ASSERT(args_[2].isInt32());
8562
8563 // Initialize the input operand.
8564 Int32OperandId argcId(writer.setInputOperandId(0));
8565
8566 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8567
8568 ValOperandId boundId =
8569 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8570 ObjOperandId objBoundId = writer.guardToObject(boundId);
8571
8572 ValOperandId targetId =
8573 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8574 ObjOperandId objTargetId = writer.guardToObject(targetId);
8575
8576 ValOperandId argCountId =
8577 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
8578 Int32OperandId int32ArgCountId = writer.guardToInt32(argCountId);
8579
8580 writer.finishBoundFunctionInitResult(objBoundId, objTargetId,
8581 int32ArgCountId);
8582 writer.returnFromIC();
8583
8584 trackAttached("FinishBoundFunctionInit");
8585 return AttachDecision::Attach;
8586 }
8587
tryAttachNewArrayIterator(HandleFunction callee)8588 AttachDecision CallIRGenerator::tryAttachNewArrayIterator(
8589 HandleFunction callee) {
8590 // Self-hosted code calls this without any arguments
8591 MOZ_ASSERT(argc_ == 0);
8592
8593 JSObject* templateObj = NewArrayIteratorTemplate(cx_);
8594 if (!templateObj) {
8595 cx_->recoverFromOutOfMemory();
8596 return AttachDecision::NoAction;
8597 }
8598
8599 // Initialize the input operand.
8600 Int32OperandId argcId(writer.setInputOperandId(0));
8601
8602 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8603
8604 writer.newArrayIteratorResult(templateObj);
8605 writer.returnFromIC();
8606
8607 trackAttached("NewArrayIterator");
8608 return AttachDecision::Attach;
8609 }
8610
tryAttachNewStringIterator(HandleFunction callee)8611 AttachDecision CallIRGenerator::tryAttachNewStringIterator(
8612 HandleFunction callee) {
8613 // Self-hosted code calls this without any arguments
8614 MOZ_ASSERT(argc_ == 0);
8615
8616 JSObject* templateObj = NewStringIteratorTemplate(cx_);
8617 if (!templateObj) {
8618 cx_->recoverFromOutOfMemory();
8619 return AttachDecision::NoAction;
8620 }
8621
8622 // Initialize the input operand.
8623 Int32OperandId argcId(writer.setInputOperandId(0));
8624
8625 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8626
8627 writer.newStringIteratorResult(templateObj);
8628 writer.returnFromIC();
8629
8630 trackAttached("NewStringIterator");
8631 return AttachDecision::Attach;
8632 }
8633
tryAttachNewRegExpStringIterator(HandleFunction callee)8634 AttachDecision CallIRGenerator::tryAttachNewRegExpStringIterator(
8635 HandleFunction callee) {
8636 // Self-hosted code calls this without any arguments
8637 MOZ_ASSERT(argc_ == 0);
8638
8639 JSObject* templateObj = NewRegExpStringIteratorTemplate(cx_);
8640 if (!templateObj) {
8641 cx_->recoverFromOutOfMemory();
8642 return AttachDecision::NoAction;
8643 }
8644
8645 // Initialize the input operand.
8646 Int32OperandId argcId(writer.setInputOperandId(0));
8647
8648 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8649
8650 writer.newRegExpStringIteratorResult(templateObj);
8651 writer.returnFromIC();
8652
8653 trackAttached("NewRegExpStringIterator");
8654 return AttachDecision::Attach;
8655 }
8656
tryAttachArrayIteratorPrototypeOptimizable(HandleFunction callee)8657 AttachDecision CallIRGenerator::tryAttachArrayIteratorPrototypeOptimizable(
8658 HandleFunction callee) {
8659 // Self-hosted code calls this without any arguments
8660 MOZ_ASSERT(argc_ == 0);
8661
8662 if (!isFirstStub_) {
8663 // Attach only once to prevent slowdowns for polymorphic calls.
8664 return AttachDecision::NoAction;
8665 }
8666
8667 NativeObject* arrayIteratorProto;
8668 uint32_t slot;
8669 JSFunction* nextFun;
8670 if (!IsArrayIteratorPrototypeOptimizable(cx_, &arrayIteratorProto, &slot,
8671 &nextFun)) {
8672 return AttachDecision::NoAction;
8673 }
8674
8675 // Initialize the input operand.
8676 Int32OperandId argcId(writer.setInputOperandId(0));
8677
8678 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
8679
8680 ObjOperandId protoId = writer.loadObject(arrayIteratorProto);
8681 ObjOperandId nextId = writer.loadObject(nextFun);
8682
8683 writer.guardShape(protoId, arrayIteratorProto->shape());
8684
8685 // Ensure that proto[slot] == nextFun.
8686 writer.guardDynamicSlotIsSpecificObject(protoId, nextId, slot);
8687 writer.loadBooleanResult(true);
8688 writer.returnFromIC();
8689
8690 trackAttached("ArrayIteratorPrototypeOptimizable");
8691 return AttachDecision::Attach;
8692 }
8693
tryAttachObjectCreate(HandleFunction callee)8694 AttachDecision CallIRGenerator::tryAttachObjectCreate(HandleFunction callee) {
8695 // Need a single object-or-null argument.
8696 if (argc_ != 1 || !args_[0].isObjectOrNull()) {
8697 return AttachDecision::NoAction;
8698 }
8699
8700 if (!isFirstStub_) {
8701 // Attach only once to prevent slowdowns for polymorphic calls.
8702 return AttachDecision::NoAction;
8703 }
8704
8705 RootedObject proto(cx_, args_[0].toObjectOrNull());
8706 JSObject* templateObj = ObjectCreateImpl(cx_, proto, TenuredObject);
8707 if (!templateObj) {
8708 cx_->recoverFromOutOfMemory();
8709 return AttachDecision::NoAction;
8710 }
8711
8712 // Initialize the input operand.
8713 Int32OperandId argcId(writer.setInputOperandId(0));
8714
8715 // Guard callee is the 'create' native function.
8716 emitNativeCalleeGuard(callee);
8717
8718 // Guard on the proto argument.
8719 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8720 if (proto) {
8721 ObjOperandId protoId = writer.guardToObject(argId);
8722 writer.guardSpecificObject(protoId, proto);
8723 } else {
8724 writer.guardIsNull(argId);
8725 }
8726
8727 writer.objectCreateResult(templateObj);
8728 writer.returnFromIC();
8729
8730 trackAttached("ObjectCreate");
8731 return AttachDecision::Attach;
8732 }
8733
tryAttachArrayConstructor(HandleFunction callee)8734 AttachDecision CallIRGenerator::tryAttachArrayConstructor(
8735 HandleFunction callee) {
8736 // Only optimize the |Array()| and |Array(n)| cases (with or without |new|)
8737 // for now. Note that self-hosted code calls this without |new| via std_Array.
8738 if (argc_ > 1) {
8739 return AttachDecision::NoAction;
8740 }
8741 if (argc_ == 1 && !args_[0].isInt32()) {
8742 return AttachDecision::NoAction;
8743 }
8744
8745 int32_t length = (argc_ == 1) ? args_[0].toInt32() : 0;
8746 if (length < 0 || uint32_t(length) > ArrayObject::EagerAllocationMaxLength) {
8747 return AttachDecision::NoAction;
8748 }
8749
8750 // We allow inlining this function across realms so make sure the template
8751 // object is allocated in that realm. See CanInlineNativeCrossRealm.
8752 JSObject* templateObj;
8753 {
8754 AutoRealm ar(cx_, callee);
8755 templateObj = NewDenseFullyAllocatedArray(cx_, length, TenuredObject);
8756 if (!templateObj) {
8757 cx_->recoverFromOutOfMemory();
8758 return AttachDecision::NoAction;
8759 }
8760 }
8761
8762 // Initialize the input operand.
8763 Int32OperandId argcId(writer.setInputOperandId(0));
8764
8765 // Guard callee and newTarget (if constructing) are this Array constructor
8766 // function.
8767 emitNativeCalleeGuard(callee);
8768
8769 CallFlags flags(IsConstructPC(pc_), IsSpreadPC(pc_));
8770
8771 Int32OperandId lengthId;
8772 if (argc_ == 1) {
8773 ValOperandId arg0Id =
8774 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags);
8775 lengthId = writer.guardToInt32(arg0Id);
8776 } else {
8777 MOZ_ASSERT(argc_ == 0);
8778 lengthId = writer.loadInt32Constant(0);
8779 }
8780
8781 writer.newArrayFromLengthResult(templateObj, lengthId);
8782 writer.returnFromIC();
8783
8784 trackAttached("ArrayConstructor");
8785 return AttachDecision::Attach;
8786 }
8787
tryAttachTypedArrayConstructor(HandleFunction callee)8788 AttachDecision CallIRGenerator::tryAttachTypedArrayConstructor(
8789 HandleFunction callee) {
8790 MOZ_ASSERT(IsConstructPC(pc_));
8791
8792 if (argc_ == 0 || argc_ > 3) {
8793 return AttachDecision::NoAction;
8794 }
8795
8796 if (!isFirstStub_) {
8797 // Attach only once to prevent slowdowns for polymorphic calls.
8798 return AttachDecision::NoAction;
8799 }
8800
8801 // The first argument must be int32 or a non-proxy object.
8802 if (!args_[0].isInt32() && !args_[0].isObject()) {
8803 return AttachDecision::NoAction;
8804 }
8805 if (args_[0].isObject() && args_[0].toObject().is<ProxyObject>()) {
8806 return AttachDecision::NoAction;
8807 }
8808
8809 #ifdef JS_CODEGEN_X86
8810 // Unfortunately NewTypedArrayFromArrayBufferResult needs more registers than
8811 // we can easily support on 32-bit x86 for now.
8812 if (args_[0].isObject() &&
8813 args_[0].toObject().is<ArrayBufferObjectMaybeShared>()) {
8814 return AttachDecision::NoAction;
8815 }
8816 #endif
8817
8818 RootedObject templateObj(cx_);
8819 if (!TypedArrayObject::GetTemplateObjectForNative(cx_, callee->native(),
8820 args_, &templateObj)) {
8821 cx_->recoverFromOutOfMemory();
8822 return AttachDecision::NoAction;
8823 }
8824
8825 if (!templateObj) {
8826 // This can happen for large length values.
8827 MOZ_ASSERT(args_[0].isInt32());
8828 return AttachDecision::NoAction;
8829 }
8830
8831 // Initialize the input operand.
8832 Int32OperandId argcId(writer.setInputOperandId(0));
8833
8834 // Guard callee and newTarget are this TypedArray constructor function.
8835 emitNativeCalleeGuard(callee);
8836
8837 CallFlags flags(IsConstructPC(pc_), IsSpreadPC(pc_));
8838 ValOperandId arg0Id =
8839 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags);
8840
8841 if (args_[0].isInt32()) {
8842 // From length.
8843 Int32OperandId lengthId = writer.guardToInt32(arg0Id);
8844 writer.newTypedArrayFromLengthResult(templateObj, lengthId);
8845 } else {
8846 JSObject* obj = &args_[0].toObject();
8847 ObjOperandId objId = writer.guardToObject(arg0Id);
8848
8849 if (obj->is<ArrayBufferObjectMaybeShared>()) {
8850 // From ArrayBuffer.
8851 if (obj->is<ArrayBufferObject>()) {
8852 writer.guardClass(objId, GuardClassKind::ArrayBuffer);
8853 } else {
8854 MOZ_ASSERT(obj->is<SharedArrayBufferObject>());
8855 writer.guardClass(objId, GuardClassKind::SharedArrayBuffer);
8856 }
8857 ValOperandId byteOffsetId;
8858 if (argc_ > 1) {
8859 byteOffsetId =
8860 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_, flags);
8861 } else {
8862 byteOffsetId = writer.loadUndefined();
8863 }
8864 ValOperandId lengthId;
8865 if (argc_ > 2) {
8866 lengthId =
8867 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_, flags);
8868 } else {
8869 lengthId = writer.loadUndefined();
8870 }
8871 writer.newTypedArrayFromArrayBufferResult(templateObj, objId,
8872 byteOffsetId, lengthId);
8873 } else {
8874 // From Array-like.
8875 writer.guardIsNotArrayBufferMaybeShared(objId);
8876 writer.guardIsNotProxy(objId);
8877 writer.newTypedArrayFromArrayResult(templateObj, objId);
8878 }
8879 }
8880
8881 writer.returnFromIC();
8882
8883 trackAttached("TypedArrayConstructor");
8884 return AttachDecision::Attach;
8885 }
8886
tryAttachFunApply(HandleFunction calleeFunc)8887 AttachDecision CallIRGenerator::tryAttachFunApply(HandleFunction calleeFunc) {
8888 if (!calleeFunc->isNativeWithoutJitEntry() ||
8889 calleeFunc->native() != fun_apply) {
8890 return AttachDecision::NoAction;
8891 }
8892
8893 if (argc_ != 2) {
8894 return AttachDecision::NoAction;
8895 }
8896
8897 if (!thisval_.isObject() || !thisval_.toObject().is<JSFunction>()) {
8898 return AttachDecision::NoAction;
8899 }
8900 auto* target = &thisval_.toObject().as<JSFunction>();
8901
8902 bool isScripted = target->hasJitEntry();
8903 MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry());
8904
8905 if (target->isClassConstructor()) {
8906 return AttachDecision::NoAction;
8907 }
8908
8909 CallFlags::ArgFormat format = CallFlags::Standard;
8910 if (args_[1].isObject() && args_[1].toObject().is<ArgumentsObject>()) {
8911 auto* argsObj = &args_[1].toObject().as<ArgumentsObject>();
8912 if (argsObj->hasOverriddenElement() || argsObj->anyArgIsForwarded() ||
8913 argsObj->hasOverriddenLength() ||
8914 argsObj->initialLength() > JIT_ARGS_LENGTH_MAX) {
8915 return AttachDecision::NoAction;
8916 }
8917 format = CallFlags::FunApplyArgsObj;
8918 } else if (args_[1].isObject() && args_[1].toObject().is<ArrayObject>() &&
8919 args_[1].toObject().as<ArrayObject>().length() <=
8920 JIT_ARGS_LENGTH_MAX) {
8921 format = CallFlags::FunApplyArray;
8922 } else {
8923 return AttachDecision::NoAction;
8924 }
8925
8926 Int32OperandId argcId(writer.setInputOperandId(0));
8927
8928 // Guard that callee is the |fun_apply| native function.
8929 ValOperandId calleeValId =
8930 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId);
8931 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
8932 writer.guardSpecificFunction(calleeObjId, calleeFunc);
8933
8934 // Guard that |this| is an object.
8935 ValOperandId thisValId =
8936 writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId);
8937 ObjOperandId thisObjId = writer.guardToObject(thisValId);
8938
8939 ValOperandId argValId =
8940 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8941
8942 if (format == CallFlags::FunApplyArgsObj) {
8943 ObjOperandId argObjId = writer.guardToObject(argValId);
8944 if (args_[1].toObject().is<MappedArgumentsObject>()) {
8945 writer.guardClass(argObjId, GuardClassKind::MappedArguments);
8946 } else {
8947 MOZ_ASSERT(args_[1].toObject().is<UnmappedArgumentsObject>());
8948 writer.guardClass(argObjId, GuardClassKind::UnmappedArguments);
8949 }
8950 uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT |
8951 ArgumentsObject::FORWARDED_ARGUMENTS_BIT;
8952 writer.guardArgumentsObjectFlags(argObjId, flags);
8953 } else {
8954 MOZ_ASSERT(format == CallFlags::FunApplyArray);
8955 ObjOperandId argObjId = writer.guardToObject(argValId);
8956 writer.guardClass(argObjId, GuardClassKind::Array);
8957 writer.guardArrayIsPacked(argObjId);
8958 }
8959
8960 CallFlags targetFlags(format);
8961 if (mode_ == ICState::Mode::Specialized) {
8962 // Ensure that |this| is the expected target function.
8963 emitCalleeGuard(thisObjId, target);
8964
8965 if (cx_->realm() == target->realm()) {
8966 targetFlags.setIsSameRealm();
8967 }
8968
8969 if (isScripted) {
8970 writer.callScriptedFunction(thisObjId, argcId, targetFlags);
8971 } else {
8972 writer.callNativeFunction(thisObjId, argcId, op_, target, targetFlags);
8973 }
8974 } else {
8975 // Guard that |this| is a function.
8976 writer.guardClass(thisObjId, GuardClassKind::JSFunction);
8977
8978 // Guard that function is not a class constructor.
8979 writer.guardNotClassConstructor(thisObjId);
8980
8981 if (isScripted) {
8982 // Guard that function is scripted.
8983 writer.guardFunctionHasJitEntry(thisObjId, /*constructing =*/false);
8984 writer.callScriptedFunction(thisObjId, argcId, targetFlags);
8985 } else {
8986 // Guard that function is native.
8987 writer.guardFunctionHasNoJitEntry(thisObjId);
8988 writer.callAnyNativeFunction(thisObjId, argcId, targetFlags);
8989 }
8990 }
8991
8992 writer.returnFromIC();
8993
8994 if (isScripted) {
8995 trackAttached("Scripted fun_apply");
8996 } else {
8997 trackAttached("Native fun_apply");
8998 }
8999
9000 return AttachDecision::Attach;
9001 }
9002
tryAttachWasmCall(HandleFunction calleeFunc)9003 AttachDecision CallIRGenerator::tryAttachWasmCall(HandleFunction calleeFunc) {
9004 // Try to optimize calls into Wasm code by emitting the CallWasmFunction
9005 // CacheIR op. Baseline ICs currently treat this as a CallScriptedFunction op
9006 // (calling Wasm's JitEntry stub) but Warp transpiles it to a more direct call
9007 // into Wasm code.
9008 //
9009 // Note: some code refers to these optimized Wasm calls as "inlined" calls.
9010
9011 MOZ_ASSERT(calleeFunc->isWasmWithJitEntry());
9012
9013 if (!JitOptions.enableWasmIonFastCalls) {
9014 return AttachDecision::NoAction;
9015 }
9016 if (!isFirstStub_) {
9017 return AttachDecision::NoAction;
9018 }
9019 if (JSOp(*pc_) != JSOp::Call && JSOp(*pc_) != JSOp::CallIgnoresRv) {
9020 return AttachDecision::NoAction;
9021 }
9022 if (cx_->realm() != calleeFunc->realm()) {
9023 return AttachDecision::NoAction;
9024 }
9025
9026 wasm::Instance& inst = wasm::ExportedFunctionToInstance(calleeFunc);
9027 uint32_t funcIndex = inst.code().getFuncIndex(calleeFunc);
9028
9029 auto bestTier = inst.code().bestTier();
9030 const wasm::FuncExport& funcExport =
9031 inst.metadata(bestTier).lookupFuncExport(funcIndex);
9032
9033 MOZ_ASSERT(!IsInsideNursery(inst.object()));
9034 MOZ_ASSERT(funcExport.canHaveJitEntry(),
9035 "Function should allow a Wasm JitEntry");
9036
9037 const wasm::FuncType& sig = funcExport.funcType();
9038
9039 // If there are too many arguments, don't optimize (we won't be able to store
9040 // the arguments in the LIR node).
9041 static_assert(wasm::MaxArgsForJitInlineCall <= ArgumentKindArgIndexLimit);
9042 if (sig.args().length() > wasm::MaxArgsForJitInlineCall ||
9043 argc_ > ArgumentKindArgIndexLimit) {
9044 return AttachDecision::NoAction;
9045 }
9046
9047 // If there are too many results, don't optimize as Warp currently doesn't
9048 // have code to handle this.
9049 if (sig.results().length() > wasm::MaxResultsForJitInlineCall) {
9050 return AttachDecision::NoAction;
9051 }
9052
9053 // Bug 1631656 - Don't try to optimize with I64 args on 32-bit platforms
9054 // because it is more difficult (because it requires multiple LIR arguments
9055 // per I64).
9056 //
9057 // Bug 1631650 - On 64-bit platforms, we also give up optimizing for I64 args
9058 // spilled to the stack because it causes problems with register allocation.
9059 #ifdef JS_64BIT
9060 constexpr bool optimizeWithI64 = true;
9061 #else
9062 constexpr bool optimizeWithI64 = false;
9063 #endif
9064 ABIArgGenerator abi;
9065 for (const auto& valType : sig.args()) {
9066 MIRType mirType = ToMIRType(valType);
9067 ABIArg abiArg = abi.next(mirType);
9068 if (mirType != MIRType::Int64) {
9069 continue;
9070 }
9071 if (!optimizeWithI64 || abiArg.kind() == ABIArg::Stack) {
9072 return AttachDecision::NoAction;
9073 }
9074 }
9075
9076 // Check that all arguments can be converted to the Wasm type in Warp code
9077 // without bailing out.
9078 for (size_t i = 0; i < sig.args().length(); i++) {
9079 Value argVal = i < argc_ ? args_[i] : UndefinedValue();
9080 switch (sig.args()[i].kind()) {
9081 case wasm::ValType::I32:
9082 case wasm::ValType::F32:
9083 case wasm::ValType::F64:
9084 if (!argVal.isNumber() && !argVal.isBoolean() &&
9085 !argVal.isUndefined()) {
9086 return AttachDecision::NoAction;
9087 }
9088 break;
9089 case wasm::ValType::I64:
9090 if (!argVal.isBigInt() && !argVal.isBoolean() && !argVal.isString()) {
9091 return AttachDecision::NoAction;
9092 }
9093 break;
9094 case wasm::ValType::Rtt:
9095 case wasm::ValType::V128:
9096 MOZ_CRASH("Function should not have a Wasm JitEntry");
9097 case wasm::ValType::Ref:
9098 // All values can be boxed as AnyRef.
9099 MOZ_ASSERT(sig.args()[i].refTypeKind() == wasm::RefType::Extern,
9100 "Unexpected type for Wasm JitEntry");
9101 break;
9102 }
9103 }
9104
9105 CallFlags flags(/* isConstructing = */ false, /* isSpread = */ false,
9106 /* isSameRealm = */ true);
9107
9108 // Load argc.
9109 Int32OperandId argcId(writer.setInputOperandId(0));
9110
9111 // Load the callee and ensure it is an object
9112 ValOperandId calleeValId =
9113 writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags);
9114 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
9115
9116 // Ensure the callee is this Wasm function.
9117 emitCalleeGuard(calleeObjId, calleeFunc);
9118
9119 // Guard the argument types.
9120 uint32_t guardedArgs = std::min<uint32_t>(sig.args().length(), argc_);
9121 for (uint32_t i = 0; i < guardedArgs; i++) {
9122 ArgumentKind argKind = ArgumentKindForArgIndex(i);
9123 ValOperandId argId = writer.loadArgumentFixedSlot(argKind, argc_, flags);
9124 writer.guardWasmArg(argId, sig.args()[i].kind());
9125 }
9126
9127 writer.callWasmFunction(calleeObjId, argcId, flags, &funcExport,
9128 inst.object());
9129 writer.returnFromIC();
9130
9131 trackAttached("WasmCall");
9132
9133 return AttachDecision::Attach;
9134 }
9135
tryAttachInlinableNative(HandleFunction callee)9136 AttachDecision CallIRGenerator::tryAttachInlinableNative(
9137 HandleFunction callee) {
9138 MOZ_ASSERT(mode_ == ICState::Mode::Specialized);
9139 MOZ_ASSERT(callee->isNativeWithoutJitEntry());
9140
9141 // Special case functions are only optimized for normal calls.
9142 if (op_ != JSOp::Call && op_ != JSOp::New && op_ != JSOp::CallIgnoresRv &&
9143 op_ != JSOp::SpreadCall) {
9144 return AttachDecision::NoAction;
9145 }
9146
9147 if (!callee->hasJitInfo() ||
9148 callee->jitInfo()->type() != JSJitInfo::InlinableNative) {
9149 return AttachDecision::NoAction;
9150 }
9151
9152 InlinableNative native = callee->jitInfo()->inlinableNative;
9153
9154 // Not all natives can be inlined cross-realm.
9155 if (cx_->realm() != callee->realm() && !CanInlineNativeCrossRealm(native)) {
9156 return AttachDecision::NoAction;
9157 }
9158
9159 // Check for special-cased native constructors.
9160 if (op_ == JSOp::New) {
9161 // newTarget must match the callee. CacheIR for this is emitted in
9162 // emitNativeCalleeGuard.
9163 if (callee_ != newTarget_) {
9164 return AttachDecision::NoAction;
9165 }
9166 switch (native) {
9167 case InlinableNative::Array:
9168 return tryAttachArrayConstructor(callee);
9169 case InlinableNative::TypedArrayConstructor:
9170 return tryAttachTypedArrayConstructor(callee);
9171 case InlinableNative::String:
9172 return tryAttachStringConstructor(callee);
9173 default:
9174 break;
9175 }
9176 return AttachDecision::NoAction;
9177 }
9178
9179 // Check for special-cased native spread calls.
9180 if (op_ == JSOp::SpreadCall) {
9181 switch (native) {
9182 case InlinableNative::MathMin:
9183 return tryAttachSpreadMathMinMax(callee, /*isMax = */ false);
9184 case InlinableNative::MathMax:
9185 return tryAttachSpreadMathMinMax(callee, /*isMax = */ true);
9186 default:
9187 break;
9188 }
9189 return AttachDecision::NoAction;
9190 }
9191
9192 // Check for special-cased native functions.
9193 switch (native) {
9194 // Array natives.
9195 case InlinableNative::Array:
9196 return tryAttachArrayConstructor(callee);
9197 case InlinableNative::ArrayPush:
9198 return tryAttachArrayPush(callee);
9199 case InlinableNative::ArrayPop:
9200 case InlinableNative::ArrayShift:
9201 return tryAttachArrayPopShift(callee, native);
9202 case InlinableNative::ArrayJoin:
9203 return tryAttachArrayJoin(callee);
9204 case InlinableNative::ArraySlice:
9205 return tryAttachArraySlice(callee);
9206 case InlinableNative::ArrayIsArray:
9207 return tryAttachArrayIsArray(callee);
9208
9209 // DataView natives.
9210 case InlinableNative::DataViewGetInt8:
9211 return tryAttachDataViewGet(callee, Scalar::Int8);
9212 case InlinableNative::DataViewGetUint8:
9213 return tryAttachDataViewGet(callee, Scalar::Uint8);
9214 case InlinableNative::DataViewGetInt16:
9215 return tryAttachDataViewGet(callee, Scalar::Int16);
9216 case InlinableNative::DataViewGetUint16:
9217 return tryAttachDataViewGet(callee, Scalar::Uint16);
9218 case InlinableNative::DataViewGetInt32:
9219 return tryAttachDataViewGet(callee, Scalar::Int32);
9220 case InlinableNative::DataViewGetUint32:
9221 return tryAttachDataViewGet(callee, Scalar::Uint32);
9222 case InlinableNative::DataViewGetFloat32:
9223 return tryAttachDataViewGet(callee, Scalar::Float32);
9224 case InlinableNative::DataViewGetFloat64:
9225 return tryAttachDataViewGet(callee, Scalar::Float64);
9226 case InlinableNative::DataViewGetBigInt64:
9227 return tryAttachDataViewGet(callee, Scalar::BigInt64);
9228 case InlinableNative::DataViewGetBigUint64:
9229 return tryAttachDataViewGet(callee, Scalar::BigUint64);
9230 case InlinableNative::DataViewSetInt8:
9231 return tryAttachDataViewSet(callee, Scalar::Int8);
9232 case InlinableNative::DataViewSetUint8:
9233 return tryAttachDataViewSet(callee, Scalar::Uint8);
9234 case InlinableNative::DataViewSetInt16:
9235 return tryAttachDataViewSet(callee, Scalar::Int16);
9236 case InlinableNative::DataViewSetUint16:
9237 return tryAttachDataViewSet(callee, Scalar::Uint16);
9238 case InlinableNative::DataViewSetInt32:
9239 return tryAttachDataViewSet(callee, Scalar::Int32);
9240 case InlinableNative::DataViewSetUint32:
9241 return tryAttachDataViewSet(callee, Scalar::Uint32);
9242 case InlinableNative::DataViewSetFloat32:
9243 return tryAttachDataViewSet(callee, Scalar::Float32);
9244 case InlinableNative::DataViewSetFloat64:
9245 return tryAttachDataViewSet(callee, Scalar::Float64);
9246 case InlinableNative::DataViewSetBigInt64:
9247 return tryAttachDataViewSet(callee, Scalar::BigInt64);
9248 case InlinableNative::DataViewSetBigUint64:
9249 return tryAttachDataViewSet(callee, Scalar::BigUint64);
9250
9251 // Intl natives.
9252 case InlinableNative::IntlGuardToCollator:
9253 case InlinableNative::IntlGuardToDateTimeFormat:
9254 case InlinableNative::IntlGuardToDisplayNames:
9255 case InlinableNative::IntlGuardToListFormat:
9256 case InlinableNative::IntlGuardToNumberFormat:
9257 case InlinableNative::IntlGuardToPluralRules:
9258 case InlinableNative::IntlGuardToRelativeTimeFormat:
9259 return tryAttachGuardToClass(callee, native);
9260
9261 // Slot intrinsics.
9262 case InlinableNative::IntrinsicUnsafeGetReservedSlot:
9263 case InlinableNative::IntrinsicUnsafeGetObjectFromReservedSlot:
9264 case InlinableNative::IntrinsicUnsafeGetInt32FromReservedSlot:
9265 case InlinableNative::IntrinsicUnsafeGetStringFromReservedSlot:
9266 case InlinableNative::IntrinsicUnsafeGetBooleanFromReservedSlot:
9267 return tryAttachUnsafeGetReservedSlot(callee, native);
9268 case InlinableNative::IntrinsicUnsafeSetReservedSlot:
9269 return tryAttachUnsafeSetReservedSlot(callee);
9270
9271 // Intrinsics.
9272 case InlinableNative::IntrinsicIsSuspendedGenerator:
9273 return tryAttachIsSuspendedGenerator(callee);
9274 case InlinableNative::IntrinsicToObject:
9275 return tryAttachToObject(callee, native);
9276 case InlinableNative::IntrinsicToInteger:
9277 return tryAttachToInteger(callee);
9278 case InlinableNative::IntrinsicToLength:
9279 return tryAttachToLength(callee);
9280 case InlinableNative::IntrinsicIsObject:
9281 return tryAttachIsObject(callee);
9282 case InlinableNative::IntrinsicIsPackedArray:
9283 return tryAttachIsPackedArray(callee);
9284 case InlinableNative::IntrinsicIsCallable:
9285 return tryAttachIsCallable(callee);
9286 case InlinableNative::IntrinsicIsConstructor:
9287 return tryAttachIsConstructor(callee);
9288 case InlinableNative::IntrinsicIsCrossRealmArrayConstructor:
9289 return tryAttachIsCrossRealmArrayConstructor(callee);
9290 case InlinableNative::IntrinsicGuardToArrayIterator:
9291 case InlinableNative::IntrinsicGuardToMapIterator:
9292 case InlinableNative::IntrinsicGuardToSetIterator:
9293 case InlinableNative::IntrinsicGuardToStringIterator:
9294 case InlinableNative::IntrinsicGuardToRegExpStringIterator:
9295 case InlinableNative::IntrinsicGuardToWrapForValidIterator:
9296 case InlinableNative::IntrinsicGuardToIteratorHelper:
9297 case InlinableNative::IntrinsicGuardToAsyncIteratorHelper:
9298 return tryAttachGuardToClass(callee, native);
9299 case InlinableNative::IntrinsicSubstringKernel:
9300 return tryAttachSubstringKernel(callee);
9301 case InlinableNative::IntrinsicIsConstructing:
9302 return tryAttachIsConstructing(callee);
9303 case InlinableNative::IntrinsicFinishBoundFunctionInit:
9304 return tryAttachFinishBoundFunctionInit(callee);
9305 case InlinableNative::IntrinsicNewArrayIterator:
9306 return tryAttachNewArrayIterator(callee);
9307 case InlinableNative::IntrinsicNewStringIterator:
9308 return tryAttachNewStringIterator(callee);
9309 case InlinableNative::IntrinsicNewRegExpStringIterator:
9310 return tryAttachNewRegExpStringIterator(callee);
9311 case InlinableNative::IntrinsicArrayIteratorPrototypeOptimizable:
9312 return tryAttachArrayIteratorPrototypeOptimizable(callee);
9313 case InlinableNative::IntrinsicObjectHasPrototype:
9314 return tryAttachObjectHasPrototype(callee);
9315
9316 // RegExp natives.
9317 case InlinableNative::IsRegExpObject:
9318 return tryAttachHasClass(callee, &RegExpObject::class_,
9319 /* isPossiblyWrapped = */ false);
9320 case InlinableNative::IsPossiblyWrappedRegExpObject:
9321 return tryAttachHasClass(callee, &RegExpObject::class_,
9322 /* isPossiblyWrapped = */ true);
9323 case InlinableNative::RegExpMatcher:
9324 case InlinableNative::RegExpSearcher:
9325 case InlinableNative::RegExpTester:
9326 return tryAttachRegExpMatcherSearcherTester(callee, native);
9327 case InlinableNative::RegExpPrototypeOptimizable:
9328 return tryAttachRegExpPrototypeOptimizable(callee);
9329 case InlinableNative::RegExpInstanceOptimizable:
9330 return tryAttachRegExpInstanceOptimizable(callee);
9331 case InlinableNative::GetFirstDollarIndex:
9332 return tryAttachGetFirstDollarIndex(callee);
9333
9334 // String natives.
9335 case InlinableNative::String:
9336 return tryAttachString(callee);
9337 case InlinableNative::StringToString:
9338 case InlinableNative::StringValueOf:
9339 return tryAttachStringToStringValueOf(callee);
9340 case InlinableNative::StringCharCodeAt:
9341 return tryAttachStringCharCodeAt(callee);
9342 case InlinableNative::StringCharAt:
9343 return tryAttachStringCharAt(callee);
9344 case InlinableNative::StringFromCharCode:
9345 return tryAttachStringFromCharCode(callee);
9346 case InlinableNative::StringFromCodePoint:
9347 return tryAttachStringFromCodePoint(callee);
9348 case InlinableNative::StringToLowerCase:
9349 return tryAttachStringToLowerCase(callee);
9350 case InlinableNative::StringToUpperCase:
9351 return tryAttachStringToUpperCase(callee);
9352 case InlinableNative::IntrinsicStringReplaceString:
9353 return tryAttachStringReplaceString(callee);
9354 case InlinableNative::IntrinsicStringSplitString:
9355 return tryAttachStringSplitString(callee);
9356
9357 // Math natives.
9358 case InlinableNative::MathRandom:
9359 return tryAttachMathRandom(callee);
9360 case InlinableNative::MathAbs:
9361 return tryAttachMathAbs(callee);
9362 case InlinableNative::MathClz32:
9363 return tryAttachMathClz32(callee);
9364 case InlinableNative::MathSign:
9365 return tryAttachMathSign(callee);
9366 case InlinableNative::MathImul:
9367 return tryAttachMathImul(callee);
9368 case InlinableNative::MathFloor:
9369 return tryAttachMathFloor(callee);
9370 case InlinableNative::MathCeil:
9371 return tryAttachMathCeil(callee);
9372 case InlinableNative::MathTrunc:
9373 return tryAttachMathTrunc(callee);
9374 case InlinableNative::MathRound:
9375 return tryAttachMathRound(callee);
9376 case InlinableNative::MathSqrt:
9377 return tryAttachMathSqrt(callee);
9378 case InlinableNative::MathFRound:
9379 return tryAttachMathFRound(callee);
9380 case InlinableNative::MathHypot:
9381 return tryAttachMathHypot(callee);
9382 case InlinableNative::MathATan2:
9383 return tryAttachMathATan2(callee);
9384 case InlinableNative::MathSin:
9385 return tryAttachMathFunction(callee, UnaryMathFunction::Sin);
9386 case InlinableNative::MathTan:
9387 return tryAttachMathFunction(callee, UnaryMathFunction::Tan);
9388 case InlinableNative::MathCos:
9389 return tryAttachMathFunction(callee, UnaryMathFunction::Cos);
9390 case InlinableNative::MathExp:
9391 return tryAttachMathFunction(callee, UnaryMathFunction::Exp);
9392 case InlinableNative::MathLog:
9393 return tryAttachMathFunction(callee, UnaryMathFunction::Log);
9394 case InlinableNative::MathASin:
9395 return tryAttachMathFunction(callee, UnaryMathFunction::ASin);
9396 case InlinableNative::MathATan:
9397 return tryAttachMathFunction(callee, UnaryMathFunction::ATan);
9398 case InlinableNative::MathACos:
9399 return tryAttachMathFunction(callee, UnaryMathFunction::ACos);
9400 case InlinableNative::MathLog10:
9401 return tryAttachMathFunction(callee, UnaryMathFunction::Log10);
9402 case InlinableNative::MathLog2:
9403 return tryAttachMathFunction(callee, UnaryMathFunction::Log2);
9404 case InlinableNative::MathLog1P:
9405 return tryAttachMathFunction(callee, UnaryMathFunction::Log1P);
9406 case InlinableNative::MathExpM1:
9407 return tryAttachMathFunction(callee, UnaryMathFunction::ExpM1);
9408 case InlinableNative::MathCosH:
9409 return tryAttachMathFunction(callee, UnaryMathFunction::CosH);
9410 case InlinableNative::MathSinH:
9411 return tryAttachMathFunction(callee, UnaryMathFunction::SinH);
9412 case InlinableNative::MathTanH:
9413 return tryAttachMathFunction(callee, UnaryMathFunction::TanH);
9414 case InlinableNative::MathACosH:
9415 return tryAttachMathFunction(callee, UnaryMathFunction::ACosH);
9416 case InlinableNative::MathASinH:
9417 return tryAttachMathFunction(callee, UnaryMathFunction::ASinH);
9418 case InlinableNative::MathATanH:
9419 return tryAttachMathFunction(callee, UnaryMathFunction::ATanH);
9420 case InlinableNative::MathCbrt:
9421 return tryAttachMathFunction(callee, UnaryMathFunction::Cbrt);
9422 case InlinableNative::MathPow:
9423 return tryAttachMathPow(callee);
9424 case InlinableNative::MathMin:
9425 return tryAttachMathMinMax(callee, /* isMax = */ false);
9426 case InlinableNative::MathMax:
9427 return tryAttachMathMinMax(callee, /* isMax = */ true);
9428
9429 // Map intrinsics.
9430 case InlinableNative::IntrinsicGuardToMapObject:
9431 return tryAttachGuardToClass(callee, native);
9432 case InlinableNative::IntrinsicGetNextMapEntryForIterator:
9433 return tryAttachGetNextMapSetEntryForIterator(callee, /* isMap = */ true);
9434
9435 // Number natives.
9436 case InlinableNative::NumberToString:
9437 return tryAttachNumberToString(callee);
9438
9439 // Object natives.
9440 case InlinableNative::Object:
9441 return tryAttachToObject(callee, native);
9442 case InlinableNative::ObjectCreate:
9443 return tryAttachObjectCreate(callee);
9444 case InlinableNative::ObjectIs:
9445 return tryAttachObjectIs(callee);
9446 case InlinableNative::ObjectIsPrototypeOf:
9447 return tryAttachObjectIsPrototypeOf(callee);
9448 case InlinableNative::ObjectToString:
9449 return tryAttachObjectToString(callee);
9450
9451 // Set intrinsics.
9452 case InlinableNative::IntrinsicGuardToSetObject:
9453 return tryAttachGuardToClass(callee, native);
9454 case InlinableNative::IntrinsicGetNextSetEntryForIterator:
9455 return tryAttachGetNextMapSetEntryForIterator(callee,
9456 /* isMap = */ false);
9457
9458 // ArrayBuffer intrinsics.
9459 case InlinableNative::IntrinsicGuardToArrayBuffer:
9460 return tryAttachGuardToClass(callee, native);
9461 case InlinableNative::IntrinsicArrayBufferByteLength:
9462 return tryAttachArrayBufferByteLength(callee,
9463 /* isPossiblyWrapped = */ false);
9464 case InlinableNative::IntrinsicPossiblyWrappedArrayBufferByteLength:
9465 return tryAttachArrayBufferByteLength(callee,
9466 /* isPossiblyWrapped = */ true);
9467
9468 // SharedArrayBuffer intrinsics.
9469 case InlinableNative::IntrinsicGuardToSharedArrayBuffer:
9470 return tryAttachGuardToClass(callee, native);
9471
9472 // TypedArray intrinsics.
9473 case InlinableNative::TypedArrayConstructor:
9474 return AttachDecision::NoAction; // Not callable.
9475 case InlinableNative::IntrinsicIsTypedArray:
9476 return tryAttachIsTypedArray(callee, /* isPossiblyWrapped = */ false);
9477 case InlinableNative::IntrinsicIsPossiblyWrappedTypedArray:
9478 return tryAttachIsTypedArray(callee, /* isPossiblyWrapped = */ true);
9479 case InlinableNative::IntrinsicIsTypedArrayConstructor:
9480 return tryAttachIsTypedArrayConstructor(callee);
9481 case InlinableNative::IntrinsicTypedArrayByteOffset:
9482 return tryAttachTypedArrayByteOffset(callee);
9483 case InlinableNative::IntrinsicTypedArrayElementSize:
9484 return tryAttachTypedArrayElementSize(callee);
9485 case InlinableNative::IntrinsicTypedArrayLength:
9486 return tryAttachTypedArrayLength(callee, /* isPossiblyWrapped = */ false);
9487 case InlinableNative::IntrinsicPossiblyWrappedTypedArrayLength:
9488 return tryAttachTypedArrayLength(callee, /* isPossiblyWrapped = */ true);
9489
9490 // Reflect natives.
9491 case InlinableNative::ReflectGetPrototypeOf:
9492 return tryAttachReflectGetPrototypeOf(callee);
9493
9494 // Atomics intrinsics:
9495 case InlinableNative::AtomicsCompareExchange:
9496 return tryAttachAtomicsCompareExchange(callee);
9497 case InlinableNative::AtomicsExchange:
9498 return tryAttachAtomicsExchange(callee);
9499 case InlinableNative::AtomicsAdd:
9500 return tryAttachAtomicsAdd(callee);
9501 case InlinableNative::AtomicsSub:
9502 return tryAttachAtomicsSub(callee);
9503 case InlinableNative::AtomicsAnd:
9504 return tryAttachAtomicsAnd(callee);
9505 case InlinableNative::AtomicsOr:
9506 return tryAttachAtomicsOr(callee);
9507 case InlinableNative::AtomicsXor:
9508 return tryAttachAtomicsXor(callee);
9509 case InlinableNative::AtomicsLoad:
9510 return tryAttachAtomicsLoad(callee);
9511 case InlinableNative::AtomicsStore:
9512 return tryAttachAtomicsStore(callee);
9513 case InlinableNative::AtomicsIsLockFree:
9514 return tryAttachAtomicsIsLockFree(callee);
9515
9516 // BigInt natives.
9517 case InlinableNative::BigIntAsIntN:
9518 return tryAttachBigIntAsIntN(callee);
9519 case InlinableNative::BigIntAsUintN:
9520 return tryAttachBigIntAsUintN(callee);
9521
9522 // Boolean natives.
9523 case InlinableNative::Boolean:
9524 return tryAttachBoolean(callee);
9525
9526 // Set natives.
9527 case InlinableNative::SetHas:
9528 return tryAttachSetHas(callee);
9529
9530 // Map natives.
9531 case InlinableNative::MapHas:
9532 return tryAttachMapHas(callee);
9533 case InlinableNative::MapGet:
9534 return tryAttachMapGet(callee);
9535
9536 // Testing functions.
9537 case InlinableNative::TestBailout:
9538 return tryAttachBailout(callee);
9539 case InlinableNative::TestAssertFloat32:
9540 return tryAttachAssertFloat32(callee);
9541 case InlinableNative::TestAssertRecoveredOnBailout:
9542 return tryAttachAssertRecoveredOnBailout(callee);
9543
9544 case InlinableNative::Limit:
9545 break;
9546 }
9547
9548 MOZ_CRASH("Shouldn't get here");
9549 }
9550
9551 // Remember the shape of the this object for any script being called as a
9552 // constructor, for later use during Ion compilation.
getThisShapeForScripted(HandleFunction calleeFunc,MutableHandleShape result)9553 ScriptedThisResult CallIRGenerator::getThisShapeForScripted(
9554 HandleFunction calleeFunc, MutableHandleShape result) {
9555 // Some constructors allocate their own |this| object.
9556 if (calleeFunc->constructorNeedsUninitializedThis()) {
9557 return ScriptedThisResult::UninitializedThis;
9558 }
9559
9560 // Only attach a stub if the newTarget is a function with a
9561 // nonconfigurable prototype.
9562 RootedObject newTarget(cx_, &newTarget_.toObject());
9563 if (!newTarget->is<JSFunction>() ||
9564 !newTarget->as<JSFunction>().hasNonConfigurablePrototypeDataProperty()) {
9565 return ScriptedThisResult::NoAction;
9566 }
9567
9568 AutoRealm ar(cx_, calleeFunc);
9569 Shape* thisShape = ThisShapeForFunction(cx_, calleeFunc, newTarget);
9570 if (!thisShape) {
9571 cx_->clearPendingException();
9572 return ScriptedThisResult::NoAction;
9573 }
9574
9575 MOZ_ASSERT(thisShape->realm() == calleeFunc->realm());
9576 result.set(thisShape);
9577 return ScriptedThisResult::PlainObjectShape;
9578 }
9579
tryAttachCallScripted(HandleFunction calleeFunc)9580 AttachDecision CallIRGenerator::tryAttachCallScripted(
9581 HandleFunction calleeFunc) {
9582 MOZ_ASSERT(calleeFunc->hasJitEntry());
9583
9584 if (calleeFunc->isWasmWithJitEntry()) {
9585 TRY_ATTACH(tryAttachWasmCall(calleeFunc));
9586 }
9587
9588 bool isSpecialized = mode_ == ICState::Mode::Specialized;
9589
9590 bool isConstructing = IsConstructPC(pc_);
9591 bool isSpread = IsSpreadPC(pc_);
9592 bool isSameRealm = isSpecialized && cx_->realm() == calleeFunc->realm();
9593 CallFlags flags(isConstructing, isSpread, isSameRealm);
9594
9595 // If callee is not an interpreted constructor, we have to throw.
9596 if (isConstructing && !calleeFunc->isConstructor()) {
9597 return AttachDecision::NoAction;
9598 }
9599
9600 // Likewise, if the callee is a class constructor, we have to throw.
9601 if (!isConstructing && calleeFunc->isClassConstructor()) {
9602 return AttachDecision::NoAction;
9603 }
9604
9605 if (isConstructing && !calleeFunc->hasJitScript()) {
9606 // If we're constructing, require the callee to have a JitScript. This isn't
9607 // required for correctness but avoids allocating a template object below
9608 // for constructors that aren't hot. See bug 1419758.
9609 return AttachDecision::TemporarilyUnoptimizable;
9610 }
9611
9612 // Verify that spread calls have a reasonable number of arguments.
9613 if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) {
9614 return AttachDecision::NoAction;
9615 }
9616
9617 RootedShape thisShape(cx_);
9618 if (isConstructing && isSpecialized) {
9619 switch (getThisShapeForScripted(calleeFunc, &thisShape)) {
9620 case ScriptedThisResult::PlainObjectShape:
9621 break;
9622 case ScriptedThisResult::UninitializedThis:
9623 flags.setNeedsUninitializedThis();
9624 break;
9625 case ScriptedThisResult::NoAction:
9626 return AttachDecision::NoAction;
9627 }
9628 }
9629
9630 // Load argc.
9631 Int32OperandId argcId(writer.setInputOperandId(0));
9632
9633 // Load the callee and ensure it is an object
9634 ValOperandId calleeValId =
9635 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
9636 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
9637
9638 if (isSpecialized) {
9639 MOZ_ASSERT_IF(isConstructing, thisShape || flags.needsUninitializedThis());
9640
9641 // Ensure callee matches this stub's callee
9642 emitCalleeGuard(calleeObjId, calleeFunc);
9643 if (thisShape) {
9644 // Emit guards to ensure the newTarget's .prototype property is what we
9645 // expect. Note that getThisForScripted checked newTarget is a function
9646 // with a non-configurable .prototype data property.
9647 JSFunction* newTarget = &newTarget_.toObject().as<JSFunction>();
9648 Maybe<PropertyInfo> prop = newTarget->lookupPure(cx_->names().prototype);
9649 MOZ_ASSERT(prop.isSome());
9650 uint32_t slot = prop->slot();
9651 MOZ_ASSERT(slot >= newTarget->numFixedSlots(),
9652 "Stub code relies on this");
9653
9654 ValOperandId newTargetValId = writer.loadArgumentDynamicSlot(
9655 ArgumentKind::NewTarget, argcId, flags);
9656 ObjOperandId newTargetObjId = writer.guardToObject(newTargetValId);
9657 writer.guardShape(newTargetObjId, newTarget->shape());
9658
9659 const Value& value = newTarget->getSlot(slot);
9660 if (value.isObject()) {
9661 JSObject* prototypeObject = &value.toObject();
9662
9663 ObjOperandId protoId = writer.loadObject(prototypeObject);
9664 writer.guardDynamicSlotIsSpecificObject(
9665 newTargetObjId, protoId, slot - newTarget->numFixedSlots());
9666 } else {
9667 writer.guardDynamicSlotIsNotObject(newTargetObjId,
9668 slot - newTarget->numFixedSlots());
9669 }
9670
9671 // Call metaScriptedThisShape before emitting the call, so that Warp can
9672 // use the shape to create the |this| object before transpiling the call.
9673 writer.metaScriptedThisShape(thisShape);
9674 }
9675 } else {
9676 // Guard that object is a scripted function
9677 writer.guardClass(calleeObjId, GuardClassKind::JSFunction);
9678 writer.guardFunctionHasJitEntry(calleeObjId, isConstructing);
9679
9680 if (isConstructing) {
9681 // If callee is not a constructor, we have to throw.
9682 writer.guardFunctionIsConstructor(calleeObjId);
9683 } else {
9684 // If callee is a class constructor, we have to throw.
9685 writer.guardNotClassConstructor(calleeObjId);
9686 }
9687 }
9688
9689 writer.callScriptedFunction(calleeObjId, argcId, flags);
9690 writer.returnFromIC();
9691
9692 if (isSpecialized) {
9693 trackAttached("Call scripted func");
9694 } else {
9695 trackAttached("Call any scripted func");
9696 }
9697
9698 return AttachDecision::Attach;
9699 }
9700
tryAttachCallNative(HandleFunction calleeFunc)9701 AttachDecision CallIRGenerator::tryAttachCallNative(HandleFunction calleeFunc) {
9702 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
9703
9704 bool isSpecialized = mode_ == ICState::Mode::Specialized;
9705
9706 bool isSpread = IsSpreadPC(pc_);
9707 bool isSameRealm = isSpecialized && cx_->realm() == calleeFunc->realm();
9708 bool isConstructing = IsConstructPC(pc_);
9709 CallFlags flags(isConstructing, isSpread, isSameRealm);
9710
9711 if (isConstructing && !calleeFunc->isConstructor()) {
9712 return AttachDecision::NoAction;
9713 }
9714
9715 // Verify that spread calls have a reasonable number of arguments.
9716 if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) {
9717 return AttachDecision::NoAction;
9718 }
9719
9720 // Check for specific native-function optimizations.
9721 if (isSpecialized) {
9722 TRY_ATTACH(tryAttachInlinableNative(calleeFunc));
9723 }
9724
9725 // Load argc.
9726 Int32OperandId argcId(writer.setInputOperandId(0));
9727
9728 // Load the callee and ensure it is an object
9729 ValOperandId calleeValId =
9730 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
9731 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
9732
9733 // DOM calls need an additional guard so only try optimizing the first stub.
9734 // Can only optimize normal (non-spread) calls.
9735 if (isFirstStub_ && !isSpread && thisval_.isObject() &&
9736 CanAttachDOMCall(cx_, JSJitInfo::Method, &thisval_.toObject(), calleeFunc,
9737 mode_)) {
9738 MOZ_ASSERT(!isConstructing, "DOM functions are not constructors");
9739
9740 // Guard that |this| is an object.
9741 ValOperandId thisValId =
9742 writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId, flags);
9743 ObjOperandId thisObjId = writer.guardToObject(thisValId);
9744
9745 // Guard on the |this| class to make sure it's the right instance.
9746 writer.guardAnyClass(thisObjId, thisval_.toObject().getClass());
9747
9748 // Ensure callee matches this stub's callee
9749 writer.guardSpecificFunction(calleeObjId, calleeFunc);
9750 writer.callDOMFunction(calleeObjId, argcId, thisObjId, calleeFunc, flags);
9751
9752 trackAttached("CallDOM");
9753 } else if (isSpecialized) {
9754 // Ensure callee matches this stub's callee
9755 writer.guardSpecificFunction(calleeObjId, calleeFunc);
9756 writer.callNativeFunction(calleeObjId, argcId, op_, calleeFunc, flags);
9757
9758 trackAttached("CallNative");
9759 } else {
9760 // Guard that object is a native function
9761 writer.guardClass(calleeObjId, GuardClassKind::JSFunction);
9762 writer.guardFunctionHasNoJitEntry(calleeObjId);
9763
9764 if (isConstructing) {
9765 // If callee is not a constructor, we have to throw.
9766 writer.guardFunctionIsConstructor(calleeObjId);
9767 } else {
9768 // If callee is a class constructor, we have to throw.
9769 writer.guardNotClassConstructor(calleeObjId);
9770 }
9771 writer.callAnyNativeFunction(calleeObjId, argcId, flags);
9772
9773 trackAttached("CallAnyNative");
9774 }
9775
9776 writer.returnFromIC();
9777
9778 return AttachDecision::Attach;
9779 }
9780
tryAttachCallHook(HandleObject calleeObj)9781 AttachDecision CallIRGenerator::tryAttachCallHook(HandleObject calleeObj) {
9782 if (op_ == JSOp::FunCall || op_ == JSOp::FunApply) {
9783 return AttachDecision::NoAction;
9784 }
9785
9786 if (mode_ != ICState::Mode::Specialized) {
9787 // We do not have megamorphic call hook stubs.
9788 // TODO: Should we attach specialized call hook stubs in
9789 // megamorphic mode to avoid going generic?
9790 return AttachDecision::NoAction;
9791 }
9792
9793 bool isSpread = IsSpreadPC(pc_);
9794 bool isConstructing = IsConstructPC(pc_);
9795 CallFlags flags(isConstructing, isSpread);
9796 JSNative hook =
9797 isConstructing ? calleeObj->constructHook() : calleeObj->callHook();
9798 if (!hook) {
9799 return AttachDecision::NoAction;
9800 }
9801
9802 // Verify that spread calls have a reasonable number of arguments.
9803 if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) {
9804 return AttachDecision::NoAction;
9805 }
9806
9807 // Load argc.
9808 Int32OperandId argcId(writer.setInputOperandId(0));
9809
9810 // Load the callee and ensure it is an object
9811 ValOperandId calleeValId =
9812 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
9813 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
9814
9815 // Ensure the callee's class matches the one in this stub.
9816 writer.guardAnyClass(calleeObjId, calleeObj->getClass());
9817
9818 writer.callClassHook(calleeObjId, argcId, hook, flags);
9819 writer.returnFromIC();
9820
9821 trackAttached("Call native func");
9822
9823 return AttachDecision::Attach;
9824 }
9825
tryAttachStub()9826 AttachDecision CallIRGenerator::tryAttachStub() {
9827 AutoAssertNoPendingException aanpe(cx_);
9828
9829 // Some opcodes are not yet supported.
9830 switch (op_) {
9831 case JSOp::Call:
9832 case JSOp::CallIgnoresRv:
9833 case JSOp::CallIter:
9834 case JSOp::SpreadCall:
9835 case JSOp::New:
9836 case JSOp::SpreadNew:
9837 case JSOp::SuperCall:
9838 case JSOp::SpreadSuperCall:
9839 case JSOp::FunCall:
9840 case JSOp::FunApply:
9841 break;
9842 default:
9843 return AttachDecision::NoAction;
9844 }
9845
9846 MOZ_ASSERT(mode_ != ICState::Mode::Generic);
9847
9848 // Ensure callee is a function.
9849 if (!callee_.isObject()) {
9850 return AttachDecision::NoAction;
9851 }
9852
9853 RootedObject calleeObj(cx_, &callee_.toObject());
9854 if (!calleeObj->is<JSFunction>()) {
9855 return tryAttachCallHook(calleeObj);
9856 }
9857
9858 HandleFunction calleeFunc = calleeObj.as<JSFunction>();
9859
9860 if (op_ == JSOp::FunCall) {
9861 return tryAttachFunCall(calleeFunc);
9862 }
9863 if (op_ == JSOp::FunApply) {
9864 return tryAttachFunApply(calleeFunc);
9865 }
9866
9867 // Check for scripted optimizations.
9868 if (calleeFunc->hasJitEntry()) {
9869 return tryAttachCallScripted(calleeFunc);
9870 }
9871
9872 // Check for native-function optimizations.
9873 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
9874
9875 return tryAttachCallNative(calleeFunc);
9876 }
9877
trackAttached(const char * name)9878 void CallIRGenerator::trackAttached(const char* name) {
9879 #ifdef JS_CACHEIR_SPEW
9880 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
9881 sp.valueProperty("callee", callee_);
9882 sp.valueProperty("thisval", thisval_);
9883 sp.valueProperty("argc", Int32Value(argc_));
9884
9885 // Try to log the first two arguments.
9886 if (args_.length() >= 1) {
9887 sp.valueProperty("arg0", args_[0]);
9888 }
9889 if (args_.length() >= 2) {
9890 sp.valueProperty("arg1", args_[1]);
9891 }
9892 }
9893 #endif
9894 }
9895
9896 // Class which holds a shape pointer for use when caches might reference data in
9897 // other zones.
9898 static const JSClass shapeContainerClass = {"ShapeContainer",
9899 JSCLASS_HAS_RESERVED_SLOTS(1)};
9900
9901 static const size_t SHAPE_CONTAINER_SLOT = 0;
9902
NewWrapperWithObjectShape(JSContext * cx,HandleNativeObject obj)9903 static JSObject* NewWrapperWithObjectShape(JSContext* cx,
9904 HandleNativeObject obj) {
9905 MOZ_ASSERT(cx->compartment() != obj->compartment());
9906
9907 RootedObject wrapper(cx);
9908 {
9909 AutoRealm ar(cx, obj);
9910 wrapper = NewBuiltinClassInstance(cx, &shapeContainerClass);
9911 if (!wrapper) {
9912 return nullptr;
9913 }
9914 wrapper->as<NativeObject>().setReservedSlot(
9915 SHAPE_CONTAINER_SLOT, PrivateGCThingValue(obj->shape()));
9916 }
9917 if (!JS_WrapObject(cx, &wrapper)) {
9918 return nullptr;
9919 }
9920 MOZ_ASSERT(IsWrapper(wrapper));
9921 return wrapper;
9922 }
9923
LoadShapeWrapperContents(MacroAssembler & masm,Register obj,Register dst,Label * failure)9924 void jit::LoadShapeWrapperContents(MacroAssembler& masm, Register obj,
9925 Register dst, Label* failure) {
9926 masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), dst);
9927 Address privateAddr(dst,
9928 js::detail::ProxyReservedSlots::offsetOfPrivateSlot());
9929 masm.fallibleUnboxObject(privateAddr, dst, failure);
9930 masm.unboxNonDouble(
9931 Address(dst, NativeObject::getFixedSlotOffset(SHAPE_CONTAINER_SLOT)), dst,
9932 JSVAL_TYPE_PRIVATE_GCTHING);
9933 }
9934
CompareIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,JSOp op,HandleValue lhsVal,HandleValue rhsVal)9935 CompareIRGenerator::CompareIRGenerator(JSContext* cx, HandleScript script,
9936 jsbytecode* pc, ICState state, JSOp op,
9937 HandleValue lhsVal, HandleValue rhsVal)
9938 : IRGenerator(cx, script, pc, CacheKind::Compare, state),
9939 op_(op),
9940 lhsVal_(lhsVal),
9941 rhsVal_(rhsVal) {}
9942
tryAttachString(ValOperandId lhsId,ValOperandId rhsId)9943 AttachDecision CompareIRGenerator::tryAttachString(ValOperandId lhsId,
9944 ValOperandId rhsId) {
9945 if (!lhsVal_.isString() || !rhsVal_.isString()) {
9946 return AttachDecision::NoAction;
9947 }
9948
9949 StringOperandId lhsStrId = writer.guardToString(lhsId);
9950 StringOperandId rhsStrId = writer.guardToString(rhsId);
9951 writer.compareStringResult(op_, lhsStrId, rhsStrId);
9952 writer.returnFromIC();
9953
9954 trackAttached("String");
9955 return AttachDecision::Attach;
9956 }
9957
tryAttachObject(ValOperandId lhsId,ValOperandId rhsId)9958 AttachDecision CompareIRGenerator::tryAttachObject(ValOperandId lhsId,
9959 ValOperandId rhsId) {
9960 MOZ_ASSERT(IsEqualityOp(op_));
9961
9962 if (!lhsVal_.isObject() || !rhsVal_.isObject()) {
9963 return AttachDecision::NoAction;
9964 }
9965
9966 ObjOperandId lhsObjId = writer.guardToObject(lhsId);
9967 ObjOperandId rhsObjId = writer.guardToObject(rhsId);
9968 writer.compareObjectResult(op_, lhsObjId, rhsObjId);
9969 writer.returnFromIC();
9970
9971 trackAttached("Object");
9972 return AttachDecision::Attach;
9973 }
9974
tryAttachSymbol(ValOperandId lhsId,ValOperandId rhsId)9975 AttachDecision CompareIRGenerator::tryAttachSymbol(ValOperandId lhsId,
9976 ValOperandId rhsId) {
9977 MOZ_ASSERT(IsEqualityOp(op_));
9978
9979 if (!lhsVal_.isSymbol() || !rhsVal_.isSymbol()) {
9980 return AttachDecision::NoAction;
9981 }
9982
9983 SymbolOperandId lhsSymId = writer.guardToSymbol(lhsId);
9984 SymbolOperandId rhsSymId = writer.guardToSymbol(rhsId);
9985 writer.compareSymbolResult(op_, lhsSymId, rhsSymId);
9986 writer.returnFromIC();
9987
9988 trackAttached("Symbol");
9989 return AttachDecision::Attach;
9990 }
9991
tryAttachStrictDifferentTypes(ValOperandId lhsId,ValOperandId rhsId)9992 AttachDecision CompareIRGenerator::tryAttachStrictDifferentTypes(
9993 ValOperandId lhsId, ValOperandId rhsId) {
9994 MOZ_ASSERT(IsEqualityOp(op_));
9995
9996 if (op_ != JSOp::StrictEq && op_ != JSOp::StrictNe) {
9997 return AttachDecision::NoAction;
9998 }
9999
10000 // Probably can't hit some of these.
10001 if (SameType(lhsVal_, rhsVal_) ||
10002 (lhsVal_.isNumber() && rhsVal_.isNumber())) {
10003 return AttachDecision::NoAction;
10004 }
10005
10006 // Compare tags
10007 ValueTagOperandId lhsTypeId = writer.loadValueTag(lhsId);
10008 ValueTagOperandId rhsTypeId = writer.loadValueTag(rhsId);
10009 writer.guardTagNotEqual(lhsTypeId, rhsTypeId);
10010
10011 // Now that we've passed the guard, we know differing types, so return the
10012 // bool result.
10013 writer.loadBooleanResult(op_ == JSOp::StrictNe ? true : false);
10014 writer.returnFromIC();
10015
10016 trackAttached("StrictDifferentTypes");
10017 return AttachDecision::Attach;
10018 }
10019
tryAttachInt32(ValOperandId lhsId,ValOperandId rhsId)10020 AttachDecision CompareIRGenerator::tryAttachInt32(ValOperandId lhsId,
10021 ValOperandId rhsId) {
10022 if ((!lhsVal_.isInt32() && !lhsVal_.isBoolean()) ||
10023 (!rhsVal_.isInt32() && !rhsVal_.isBoolean())) {
10024 return AttachDecision::NoAction;
10025 }
10026
10027 Int32OperandId lhsIntId = lhsVal_.isBoolean()
10028 ? writer.guardBooleanToInt32(lhsId)
10029 : writer.guardToInt32(lhsId);
10030 Int32OperandId rhsIntId = rhsVal_.isBoolean()
10031 ? writer.guardBooleanToInt32(rhsId)
10032 : writer.guardToInt32(rhsId);
10033
10034 // Strictly different types should have been handed by
10035 // tryAttachStrictDifferentTypes
10036 MOZ_ASSERT_IF(op_ == JSOp::StrictEq || op_ == JSOp::StrictNe,
10037 lhsVal_.isInt32() == rhsVal_.isInt32());
10038
10039 writer.compareInt32Result(op_, lhsIntId, rhsIntId);
10040 writer.returnFromIC();
10041
10042 trackAttached(lhsVal_.isBoolean() ? "Boolean" : "Int32");
10043 return AttachDecision::Attach;
10044 }
10045
tryAttachNumber(ValOperandId lhsId,ValOperandId rhsId)10046 AttachDecision CompareIRGenerator::tryAttachNumber(ValOperandId lhsId,
10047 ValOperandId rhsId) {
10048 if (!lhsVal_.isNumber() || !rhsVal_.isNumber()) {
10049 return AttachDecision::NoAction;
10050 }
10051
10052 NumberOperandId lhs = writer.guardIsNumber(lhsId);
10053 NumberOperandId rhs = writer.guardIsNumber(rhsId);
10054 writer.compareDoubleResult(op_, lhs, rhs);
10055 writer.returnFromIC();
10056
10057 trackAttached("Number");
10058 return AttachDecision::Attach;
10059 }
10060
tryAttachBigInt(ValOperandId lhsId,ValOperandId rhsId)10061 AttachDecision CompareIRGenerator::tryAttachBigInt(ValOperandId lhsId,
10062 ValOperandId rhsId) {
10063 if (!lhsVal_.isBigInt() || !rhsVal_.isBigInt()) {
10064 return AttachDecision::NoAction;
10065 }
10066
10067 BigIntOperandId lhs = writer.guardToBigInt(lhsId);
10068 BigIntOperandId rhs = writer.guardToBigInt(rhsId);
10069
10070 writer.compareBigIntResult(op_, lhs, rhs);
10071 writer.returnFromIC();
10072
10073 trackAttached("BigInt");
10074 return AttachDecision::Attach;
10075 }
10076
tryAttachNumberUndefined(ValOperandId lhsId,ValOperandId rhsId)10077 AttachDecision CompareIRGenerator::tryAttachNumberUndefined(
10078 ValOperandId lhsId, ValOperandId rhsId) {
10079 if (!(lhsVal_.isUndefined() && rhsVal_.isNumber()) &&
10080 !(rhsVal_.isUndefined() && lhsVal_.isNumber())) {
10081 return AttachDecision::NoAction;
10082 }
10083
10084 // Should have been handled by tryAttachAnyNullUndefined.
10085 MOZ_ASSERT(!IsEqualityOp(op_));
10086
10087 if (lhsVal_.isNumber()) {
10088 writer.guardIsNumber(lhsId);
10089 } else {
10090 writer.guardIsUndefined(lhsId);
10091 }
10092
10093 if (rhsVal_.isNumber()) {
10094 writer.guardIsNumber(rhsId);
10095 } else {
10096 writer.guardIsUndefined(rhsId);
10097 }
10098
10099 // Relational comparing a number with undefined will always be false.
10100 writer.loadBooleanResult(false);
10101 writer.returnFromIC();
10102
10103 trackAttached("NumberUndefined");
10104 return AttachDecision::Attach;
10105 }
10106
tryAttachAnyNullUndefined(ValOperandId lhsId,ValOperandId rhsId)10107 AttachDecision CompareIRGenerator::tryAttachAnyNullUndefined(
10108 ValOperandId lhsId, ValOperandId rhsId) {
10109 MOZ_ASSERT(IsEqualityOp(op_));
10110
10111 // Either RHS or LHS needs to be null/undefined.
10112 if (!lhsVal_.isNullOrUndefined() && !rhsVal_.isNullOrUndefined()) {
10113 return AttachDecision::NoAction;
10114 }
10115
10116 // We assume that the side with null/undefined is usually constant, in
10117 // code like `if (x === undefined) { x = {}; }`.
10118 // That is why we don't attach when both sides are undefined/null,
10119 // because we would basically need to decide by chance which side is
10120 // the likely constant.
10121 // The actual generated code however handles null/undefined of course.
10122 if (lhsVal_.isNullOrUndefined() && rhsVal_.isNullOrUndefined()) {
10123 return AttachDecision::NoAction;
10124 }
10125
10126 if (rhsVal_.isNullOrUndefined()) {
10127 if (rhsVal_.isNull()) {
10128 writer.guardIsNull(rhsId);
10129 writer.compareNullUndefinedResult(op_, /* isUndefined */ false, lhsId);
10130 trackAttached("AnyNull");
10131 } else {
10132 writer.guardIsUndefined(rhsId);
10133 writer.compareNullUndefinedResult(op_, /* isUndefined */ true, lhsId);
10134 trackAttached("AnyUndefined");
10135 }
10136 } else {
10137 if (lhsVal_.isNull()) {
10138 writer.guardIsNull(lhsId);
10139 writer.compareNullUndefinedResult(op_, /* isUndefined */ false, rhsId);
10140 trackAttached("NullAny");
10141 } else {
10142 writer.guardIsUndefined(lhsId);
10143 writer.compareNullUndefinedResult(op_, /* isUndefined */ true, rhsId);
10144 trackAttached("UndefinedAny");
10145 }
10146 }
10147
10148 writer.returnFromIC();
10149 return AttachDecision::Attach;
10150 }
10151
10152 // Handle {null/undefined} x {null,undefined} equality comparisons
tryAttachNullUndefined(ValOperandId lhsId,ValOperandId rhsId)10153 AttachDecision CompareIRGenerator::tryAttachNullUndefined(ValOperandId lhsId,
10154 ValOperandId rhsId) {
10155 if (!lhsVal_.isNullOrUndefined() || !rhsVal_.isNullOrUndefined()) {
10156 return AttachDecision::NoAction;
10157 }
10158
10159 if (op_ == JSOp::Eq || op_ == JSOp::Ne) {
10160 writer.guardIsNullOrUndefined(lhsId);
10161 writer.guardIsNullOrUndefined(rhsId);
10162 // Sloppy equality means we actually only care about the op:
10163 writer.loadBooleanResult(op_ == JSOp::Eq);
10164 trackAttached("SloppyNullUndefined");
10165 } else {
10166 // Strict equality only hits this branch, and only in the
10167 // undef {!,=}== undef and null {!,=}== null cases.
10168 // The other cases should have hit tryAttachStrictDifferentTypes.
10169 MOZ_ASSERT(lhsVal_.isNull() == rhsVal_.isNull());
10170 lhsVal_.isNull() ? writer.guardIsNull(lhsId)
10171 : writer.guardIsUndefined(lhsId);
10172 rhsVal_.isNull() ? writer.guardIsNull(rhsId)
10173 : writer.guardIsUndefined(rhsId);
10174 writer.loadBooleanResult(op_ == JSOp::StrictEq);
10175 trackAttached("StrictNullUndefinedEquality");
10176 }
10177
10178 writer.returnFromIC();
10179 return AttachDecision::Attach;
10180 }
10181
tryAttachStringNumber(ValOperandId lhsId,ValOperandId rhsId)10182 AttachDecision CompareIRGenerator::tryAttachStringNumber(ValOperandId lhsId,
10183 ValOperandId rhsId) {
10184 // Ensure String x Number
10185 if (!(lhsVal_.isString() && rhsVal_.isNumber()) &&
10186 !(rhsVal_.isString() && lhsVal_.isNumber())) {
10187 return AttachDecision::NoAction;
10188 }
10189
10190 // Case should have been handled by tryAttachStrictDifferentTypes
10191 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
10192
10193 auto createGuards = [&](const Value& v, ValOperandId vId) {
10194 if (v.isString()) {
10195 StringOperandId strId = writer.guardToString(vId);
10196 return writer.guardStringToNumber(strId);
10197 }
10198 MOZ_ASSERT(v.isNumber());
10199 NumberOperandId numId = writer.guardIsNumber(vId);
10200 return numId;
10201 };
10202
10203 NumberOperandId lhsGuardedId = createGuards(lhsVal_, lhsId);
10204 NumberOperandId rhsGuardedId = createGuards(rhsVal_, rhsId);
10205 writer.compareDoubleResult(op_, lhsGuardedId, rhsGuardedId);
10206 writer.returnFromIC();
10207
10208 trackAttached("StringNumber");
10209 return AttachDecision::Attach;
10210 }
10211
tryAttachPrimitiveSymbol(ValOperandId lhsId,ValOperandId rhsId)10212 AttachDecision CompareIRGenerator::tryAttachPrimitiveSymbol(
10213 ValOperandId lhsId, ValOperandId rhsId) {
10214 MOZ_ASSERT(IsEqualityOp(op_));
10215
10216 // The set of primitive cases we want to handle here (excluding null,
10217 // undefined, and symbol)
10218 auto isPrimitive = [](const Value& x) {
10219 return x.isString() || x.isBoolean() || x.isNumber() || x.isBigInt();
10220 };
10221
10222 // Ensure Symbol x {String, Bool, Number, BigInt}.
10223 if (!(lhsVal_.isSymbol() && isPrimitive(rhsVal_)) &&
10224 !(rhsVal_.isSymbol() && isPrimitive(lhsVal_))) {
10225 return AttachDecision::NoAction;
10226 }
10227
10228 auto guardPrimitive = [&](const Value& v, ValOperandId id) {
10229 MOZ_ASSERT(isPrimitive(v));
10230 if (v.isNumber()) {
10231 writer.guardIsNumber(id);
10232 return;
10233 }
10234 switch (v.extractNonDoubleType()) {
10235 case JSVAL_TYPE_STRING:
10236 writer.guardToString(id);
10237 return;
10238 case JSVAL_TYPE_BOOLEAN:
10239 writer.guardToBoolean(id);
10240 return;
10241 case JSVAL_TYPE_BIGINT:
10242 writer.guardToBigInt(id);
10243 return;
10244 default:
10245 MOZ_CRASH("unexpected type");
10246 return;
10247 }
10248 };
10249
10250 if (lhsVal_.isSymbol()) {
10251 writer.guardToSymbol(lhsId);
10252 guardPrimitive(rhsVal_, rhsId);
10253 } else {
10254 guardPrimitive(lhsVal_, lhsId);
10255 writer.guardToSymbol(rhsId);
10256 }
10257
10258 // Comparing a primitive with symbol will always be true for Ne/StrictNe, and
10259 // always be false for other compare ops.
10260 writer.loadBooleanResult(op_ == JSOp::Ne || op_ == JSOp::StrictNe);
10261 writer.returnFromIC();
10262
10263 trackAttached("PrimitiveSymbol");
10264 return AttachDecision::Attach;
10265 }
10266
tryAttachBoolStringOrNumber(ValOperandId lhsId,ValOperandId rhsId)10267 AttachDecision CompareIRGenerator::tryAttachBoolStringOrNumber(
10268 ValOperandId lhsId, ValOperandId rhsId) {
10269 // Ensure Boolean x {String, Number}.
10270 if (!(lhsVal_.isBoolean() && (rhsVal_.isString() || rhsVal_.isNumber())) &&
10271 !(rhsVal_.isBoolean() && (lhsVal_.isString() || lhsVal_.isNumber()))) {
10272 return AttachDecision::NoAction;
10273 }
10274
10275 // Case should have been handled by tryAttachStrictDifferentTypes
10276 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
10277
10278 // Case should have been handled by tryAttachInt32
10279 MOZ_ASSERT(!lhsVal_.isInt32() && !rhsVal_.isInt32());
10280
10281 auto createGuards = [&](const Value& v, ValOperandId vId) {
10282 if (v.isBoolean()) {
10283 BooleanOperandId boolId = writer.guardToBoolean(vId);
10284 return writer.booleanToNumber(boolId);
10285 }
10286 if (v.isString()) {
10287 StringOperandId strId = writer.guardToString(vId);
10288 return writer.guardStringToNumber(strId);
10289 }
10290 MOZ_ASSERT(v.isNumber());
10291 return writer.guardIsNumber(vId);
10292 };
10293
10294 NumberOperandId lhsGuardedId = createGuards(lhsVal_, lhsId);
10295 NumberOperandId rhsGuardedId = createGuards(rhsVal_, rhsId);
10296 writer.compareDoubleResult(op_, lhsGuardedId, rhsGuardedId);
10297 writer.returnFromIC();
10298
10299 trackAttached("BoolStringOrNumber");
10300 return AttachDecision::Attach;
10301 }
10302
tryAttachBigIntInt32(ValOperandId lhsId,ValOperandId rhsId)10303 AttachDecision CompareIRGenerator::tryAttachBigIntInt32(ValOperandId lhsId,
10304 ValOperandId rhsId) {
10305 // Ensure BigInt x {Int32, Boolean}.
10306 if (!(lhsVal_.isBigInt() && (rhsVal_.isInt32() || rhsVal_.isBoolean())) &&
10307 !(rhsVal_.isBigInt() && (lhsVal_.isInt32() || lhsVal_.isBoolean()))) {
10308 return AttachDecision::NoAction;
10309 }
10310
10311 // Case should have been handled by tryAttachStrictDifferentTypes
10312 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
10313
10314 auto createGuards = [&](const Value& v, ValOperandId vId) {
10315 if (v.isBoolean()) {
10316 return writer.guardBooleanToInt32(vId);
10317 }
10318 MOZ_ASSERT(v.isInt32());
10319 return writer.guardToInt32(vId);
10320 };
10321
10322 if (lhsVal_.isBigInt()) {
10323 BigIntOperandId bigIntId = writer.guardToBigInt(lhsId);
10324 Int32OperandId intId = createGuards(rhsVal_, rhsId);
10325
10326 writer.compareBigIntInt32Result(op_, bigIntId, intId);
10327 } else {
10328 Int32OperandId intId = createGuards(lhsVal_, lhsId);
10329 BigIntOperandId bigIntId = writer.guardToBigInt(rhsId);
10330
10331 writer.compareBigIntInt32Result(ReverseCompareOp(op_), bigIntId, intId);
10332 }
10333 writer.returnFromIC();
10334
10335 trackAttached("BigIntInt32");
10336 return AttachDecision::Attach;
10337 }
10338
tryAttachBigIntNumber(ValOperandId lhsId,ValOperandId rhsId)10339 AttachDecision CompareIRGenerator::tryAttachBigIntNumber(ValOperandId lhsId,
10340 ValOperandId rhsId) {
10341 // Ensure BigInt x Number.
10342 if (!(lhsVal_.isBigInt() && rhsVal_.isNumber()) &&
10343 !(rhsVal_.isBigInt() && lhsVal_.isNumber())) {
10344 return AttachDecision::NoAction;
10345 }
10346
10347 // Case should have been handled by tryAttachStrictDifferentTypes
10348 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
10349
10350 if (lhsVal_.isBigInt()) {
10351 BigIntOperandId bigIntId = writer.guardToBigInt(lhsId);
10352 NumberOperandId numId = writer.guardIsNumber(rhsId);
10353
10354 writer.compareBigIntNumberResult(op_, bigIntId, numId);
10355 } else {
10356 NumberOperandId numId = writer.guardIsNumber(lhsId);
10357 BigIntOperandId bigIntId = writer.guardToBigInt(rhsId);
10358
10359 writer.compareBigIntNumberResult(ReverseCompareOp(op_), bigIntId, numId);
10360 }
10361 writer.returnFromIC();
10362
10363 trackAttached("BigIntNumber");
10364 return AttachDecision::Attach;
10365 }
10366
tryAttachBigIntString(ValOperandId lhsId,ValOperandId rhsId)10367 AttachDecision CompareIRGenerator::tryAttachBigIntString(ValOperandId lhsId,
10368 ValOperandId rhsId) {
10369 // Ensure BigInt x String.
10370 if (!(lhsVal_.isBigInt() && rhsVal_.isString()) &&
10371 !(rhsVal_.isBigInt() && lhsVal_.isString())) {
10372 return AttachDecision::NoAction;
10373 }
10374
10375 // Case should have been handled by tryAttachStrictDifferentTypes
10376 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
10377
10378 if (lhsVal_.isBigInt()) {
10379 BigIntOperandId bigIntId = writer.guardToBigInt(lhsId);
10380 StringOperandId strId = writer.guardToString(rhsId);
10381
10382 writer.compareBigIntStringResult(op_, bigIntId, strId);
10383 } else {
10384 StringOperandId strId = writer.guardToString(lhsId);
10385 BigIntOperandId bigIntId = writer.guardToBigInt(rhsId);
10386
10387 writer.compareBigIntStringResult(ReverseCompareOp(op_), bigIntId, strId);
10388 }
10389 writer.returnFromIC();
10390
10391 trackAttached("BigIntString");
10392 return AttachDecision::Attach;
10393 }
10394
tryAttachStub()10395 AttachDecision CompareIRGenerator::tryAttachStub() {
10396 MOZ_ASSERT(cacheKind_ == CacheKind::Compare);
10397 MOZ_ASSERT(IsEqualityOp(op_) || IsRelationalOp(op_));
10398
10399 AutoAssertNoPendingException aanpe(cx_);
10400
10401 constexpr uint8_t lhsIndex = 0;
10402 constexpr uint8_t rhsIndex = 1;
10403
10404 static_assert(lhsIndex == 0 && rhsIndex == 1,
10405 "Indexes relied upon by baseline inspector");
10406
10407 ValOperandId lhsId(writer.setInputOperandId(lhsIndex));
10408 ValOperandId rhsId(writer.setInputOperandId(rhsIndex));
10409
10410 // For sloppy equality ops, there are cases this IC does not handle:
10411 // - {Object} x {String, Symbol, Bool, Number, BigInt}.
10412 //
10413 // (The above lists omits the equivalent case {B} x {A} when {A} x {B} is
10414 // already present.)
10415
10416 if (IsEqualityOp(op_)) {
10417 TRY_ATTACH(tryAttachObject(lhsId, rhsId));
10418 TRY_ATTACH(tryAttachSymbol(lhsId, rhsId));
10419
10420 // Handles any (non null or undefined) comparison with null/undefined.
10421 TRY_ATTACH(tryAttachAnyNullUndefined(lhsId, rhsId));
10422
10423 // This covers -strict- equality/inequality using a type tag check, so
10424 // catches all different type pairs outside of Numbers, which cannot be
10425 // checked on tags alone.
10426 TRY_ATTACH(tryAttachStrictDifferentTypes(lhsId, rhsId));
10427
10428 TRY_ATTACH(tryAttachNullUndefined(lhsId, rhsId));
10429
10430 TRY_ATTACH(tryAttachPrimitiveSymbol(lhsId, rhsId));
10431 }
10432
10433 // This should preceed the Int32/Number cases to allow
10434 // them to not concern themselves with handling undefined
10435 // or null.
10436 TRY_ATTACH(tryAttachNumberUndefined(lhsId, rhsId));
10437
10438 // We want these to be last, to allow us to bypass the
10439 // strictly-different-types cases in the below attachment code
10440 TRY_ATTACH(tryAttachInt32(lhsId, rhsId));
10441 TRY_ATTACH(tryAttachNumber(lhsId, rhsId));
10442 TRY_ATTACH(tryAttachBigInt(lhsId, rhsId));
10443 TRY_ATTACH(tryAttachString(lhsId, rhsId));
10444
10445 TRY_ATTACH(tryAttachStringNumber(lhsId, rhsId));
10446 TRY_ATTACH(tryAttachBoolStringOrNumber(lhsId, rhsId));
10447
10448 TRY_ATTACH(tryAttachBigIntInt32(lhsId, rhsId));
10449 TRY_ATTACH(tryAttachBigIntNumber(lhsId, rhsId));
10450 TRY_ATTACH(tryAttachBigIntString(lhsId, rhsId));
10451
10452 trackAttached(IRGenerator::NotAttached);
10453 return AttachDecision::NoAction;
10454 }
10455
trackAttached(const char * name)10456 void CompareIRGenerator::trackAttached(const char* name) {
10457 #ifdef JS_CACHEIR_SPEW
10458 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
10459 sp.valueProperty("lhs", lhsVal_);
10460 sp.valueProperty("rhs", rhsVal_);
10461 sp.opcodeProperty("op", op_);
10462 }
10463 #endif
10464 }
10465
ToBoolIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,HandleValue val)10466 ToBoolIRGenerator::ToBoolIRGenerator(JSContext* cx, HandleScript script,
10467 jsbytecode* pc, ICState state,
10468 HandleValue val)
10469 : IRGenerator(cx, script, pc, CacheKind::ToBool, state), val_(val) {}
10470
trackAttached(const char * name)10471 void ToBoolIRGenerator::trackAttached(const char* name) {
10472 #ifdef JS_CACHEIR_SPEW
10473 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
10474 sp.valueProperty("val", val_);
10475 }
10476 #endif
10477 }
10478
tryAttachStub()10479 AttachDecision ToBoolIRGenerator::tryAttachStub() {
10480 AutoAssertNoPendingException aanpe(cx_);
10481 writer.setTypeData(TypeData(JSValueType(val_.type())));
10482
10483 TRY_ATTACH(tryAttachBool());
10484 TRY_ATTACH(tryAttachInt32());
10485 TRY_ATTACH(tryAttachNumber());
10486 TRY_ATTACH(tryAttachString());
10487 TRY_ATTACH(tryAttachNullOrUndefined());
10488 TRY_ATTACH(tryAttachObject());
10489 TRY_ATTACH(tryAttachSymbol());
10490 TRY_ATTACH(tryAttachBigInt());
10491
10492 trackAttached(IRGenerator::NotAttached);
10493 return AttachDecision::NoAction;
10494 }
10495
tryAttachBool()10496 AttachDecision ToBoolIRGenerator::tryAttachBool() {
10497 if (!val_.isBoolean()) {
10498 return AttachDecision::NoAction;
10499 }
10500
10501 ValOperandId valId(writer.setInputOperandId(0));
10502 writer.guardNonDoubleType(valId, ValueType::Boolean);
10503 writer.loadOperandResult(valId);
10504 writer.returnFromIC();
10505 trackAttached("ToBoolBool");
10506 return AttachDecision::Attach;
10507 }
10508
tryAttachInt32()10509 AttachDecision ToBoolIRGenerator::tryAttachInt32() {
10510 if (!val_.isInt32()) {
10511 return AttachDecision::NoAction;
10512 }
10513
10514 ValOperandId valId(writer.setInputOperandId(0));
10515 writer.guardNonDoubleType(valId, ValueType::Int32);
10516 writer.loadInt32TruthyResult(valId);
10517 writer.returnFromIC();
10518 trackAttached("ToBoolInt32");
10519 return AttachDecision::Attach;
10520 }
10521
tryAttachNumber()10522 AttachDecision ToBoolIRGenerator::tryAttachNumber() {
10523 if (!val_.isNumber()) {
10524 return AttachDecision::NoAction;
10525 }
10526
10527 ValOperandId valId(writer.setInputOperandId(0));
10528 NumberOperandId numId = writer.guardIsNumber(valId);
10529 writer.loadDoubleTruthyResult(numId);
10530 writer.returnFromIC();
10531 trackAttached("ToBoolNumber");
10532 return AttachDecision::Attach;
10533 }
10534
tryAttachSymbol()10535 AttachDecision ToBoolIRGenerator::tryAttachSymbol() {
10536 if (!val_.isSymbol()) {
10537 return AttachDecision::NoAction;
10538 }
10539
10540 ValOperandId valId(writer.setInputOperandId(0));
10541 writer.guardNonDoubleType(valId, ValueType::Symbol);
10542 writer.loadBooleanResult(true);
10543 writer.returnFromIC();
10544 trackAttached("ToBoolSymbol");
10545 return AttachDecision::Attach;
10546 }
10547
tryAttachString()10548 AttachDecision ToBoolIRGenerator::tryAttachString() {
10549 if (!val_.isString()) {
10550 return AttachDecision::NoAction;
10551 }
10552
10553 ValOperandId valId(writer.setInputOperandId(0));
10554 StringOperandId strId = writer.guardToString(valId);
10555 writer.loadStringTruthyResult(strId);
10556 writer.returnFromIC();
10557 trackAttached("ToBoolString");
10558 return AttachDecision::Attach;
10559 }
10560
tryAttachNullOrUndefined()10561 AttachDecision ToBoolIRGenerator::tryAttachNullOrUndefined() {
10562 if (!val_.isNullOrUndefined()) {
10563 return AttachDecision::NoAction;
10564 }
10565
10566 ValOperandId valId(writer.setInputOperandId(0));
10567 writer.guardIsNullOrUndefined(valId);
10568 writer.loadBooleanResult(false);
10569 writer.returnFromIC();
10570 trackAttached("ToBoolNullOrUndefined");
10571 return AttachDecision::Attach;
10572 }
10573
tryAttachObject()10574 AttachDecision ToBoolIRGenerator::tryAttachObject() {
10575 if (!val_.isObject()) {
10576 return AttachDecision::NoAction;
10577 }
10578
10579 ValOperandId valId(writer.setInputOperandId(0));
10580 ObjOperandId objId = writer.guardToObject(valId);
10581 writer.loadObjectTruthyResult(objId);
10582 writer.returnFromIC();
10583 trackAttached("ToBoolObject");
10584 return AttachDecision::Attach;
10585 }
10586
tryAttachBigInt()10587 AttachDecision ToBoolIRGenerator::tryAttachBigInt() {
10588 if (!val_.isBigInt()) {
10589 return AttachDecision::NoAction;
10590 }
10591
10592 ValOperandId valId(writer.setInputOperandId(0));
10593 BigIntOperandId bigIntId = writer.guardToBigInt(valId);
10594 writer.loadBigIntTruthyResult(bigIntId);
10595 writer.returnFromIC();
10596 trackAttached("ToBoolBigInt");
10597 return AttachDecision::Attach;
10598 }
10599
GetIntrinsicIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,HandleValue val)10600 GetIntrinsicIRGenerator::GetIntrinsicIRGenerator(JSContext* cx,
10601 HandleScript script,
10602 jsbytecode* pc, ICState state,
10603 HandleValue val)
10604 : IRGenerator(cx, script, pc, CacheKind::GetIntrinsic, state), val_(val) {}
10605
trackAttached(const char * name)10606 void GetIntrinsicIRGenerator::trackAttached(const char* name) {
10607 #ifdef JS_CACHEIR_SPEW
10608 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
10609 sp.valueProperty("val", val_);
10610 }
10611 #endif
10612 }
10613
tryAttachStub()10614 AttachDecision GetIntrinsicIRGenerator::tryAttachStub() {
10615 AutoAssertNoPendingException aanpe(cx_);
10616 writer.loadValueResult(val_);
10617 writer.returnFromIC();
10618 trackAttached("GetIntrinsic");
10619 return AttachDecision::Attach;
10620 }
10621
UnaryArithIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,JSOp op,HandleValue val,HandleValue res)10622 UnaryArithIRGenerator::UnaryArithIRGenerator(JSContext* cx, HandleScript script,
10623 jsbytecode* pc, ICState state,
10624 JSOp op, HandleValue val,
10625 HandleValue res)
10626 : IRGenerator(cx, script, pc, CacheKind::UnaryArith, state),
10627 op_(op),
10628 val_(val),
10629 res_(res) {}
10630
trackAttached(const char * name)10631 void UnaryArithIRGenerator::trackAttached(const char* name) {
10632 #ifdef JS_CACHEIR_SPEW
10633 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
10634 sp.valueProperty("val", val_);
10635 sp.valueProperty("res", res_);
10636 }
10637 #endif
10638 }
10639
tryAttachStub()10640 AttachDecision UnaryArithIRGenerator::tryAttachStub() {
10641 AutoAssertNoPendingException aanpe(cx_);
10642 TRY_ATTACH(tryAttachInt32());
10643 TRY_ATTACH(tryAttachNumber());
10644 TRY_ATTACH(tryAttachBitwise());
10645 TRY_ATTACH(tryAttachBigInt());
10646 TRY_ATTACH(tryAttachStringInt32());
10647 TRY_ATTACH(tryAttachStringNumber());
10648
10649 trackAttached(IRGenerator::NotAttached);
10650 return AttachDecision::NoAction;
10651 }
10652
CanConvertToInt32ForToNumber(const Value & v)10653 static bool CanConvertToInt32ForToNumber(const Value& v) {
10654 return v.isInt32() || v.isBoolean() || v.isNull();
10655 }
10656
EmitGuardToInt32ForToNumber(CacheIRWriter & writer,ValOperandId id,const Value & v)10657 static Int32OperandId EmitGuardToInt32ForToNumber(CacheIRWriter& writer,
10658 ValOperandId id,
10659 const Value& v) {
10660 if (v.isInt32()) {
10661 return writer.guardToInt32(id);
10662 }
10663 if (v.isNull()) {
10664 writer.guardIsNull(id);
10665 return writer.loadInt32Constant(0);
10666 }
10667 MOZ_ASSERT(v.isBoolean());
10668 return writer.guardBooleanToInt32(id);
10669 }
10670
tryAttachInt32()10671 AttachDecision UnaryArithIRGenerator::tryAttachInt32() {
10672 if (op_ == JSOp::BitNot) {
10673 return AttachDecision::NoAction;
10674 }
10675 if (!CanConvertToInt32ForToNumber(val_) || !res_.isInt32()) {
10676 return AttachDecision::NoAction;
10677 }
10678
10679 ValOperandId valId(writer.setInputOperandId(0));
10680
10681 Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, valId, val_);
10682 switch (op_) {
10683 case JSOp::Pos:
10684 writer.loadInt32Result(intId);
10685 trackAttached("UnaryArith.Int32Pos");
10686 break;
10687 case JSOp::Neg:
10688 writer.int32NegationResult(intId);
10689 trackAttached("UnaryArith.Int32Neg");
10690 break;
10691 case JSOp::Inc:
10692 writer.int32IncResult(intId);
10693 trackAttached("UnaryArith.Int32Inc");
10694 break;
10695 case JSOp::Dec:
10696 writer.int32DecResult(intId);
10697 trackAttached("UnaryArith.Int32Dec");
10698 break;
10699 case JSOp::ToNumeric:
10700 writer.loadInt32Result(intId);
10701 trackAttached("UnaryArith.Int32ToNumeric");
10702 break;
10703 default:
10704 MOZ_CRASH("unexpected OP");
10705 }
10706
10707 writer.returnFromIC();
10708 return AttachDecision::Attach;
10709 }
10710
CanConvertToDoubleForToNumber(const Value & v)10711 static bool CanConvertToDoubleForToNumber(const Value& v) {
10712 return v.isNumber() || v.isBoolean() || v.isNullOrUndefined();
10713 }
10714
EmitGuardToDoubleForToNumber(CacheIRWriter & writer,ValOperandId id,const Value & v)10715 static NumberOperandId EmitGuardToDoubleForToNumber(CacheIRWriter& writer,
10716 ValOperandId id,
10717 const Value& v) {
10718 if (v.isNumber()) {
10719 return writer.guardIsNumber(id);
10720 }
10721 if (v.isBoolean()) {
10722 BooleanOperandId boolId = writer.guardToBoolean(id);
10723 return writer.booleanToNumber(boolId);
10724 }
10725 if (v.isNull()) {
10726 writer.guardIsNull(id);
10727 return writer.loadDoubleConstant(0.0);
10728 }
10729 MOZ_ASSERT(v.isUndefined());
10730 writer.guardIsUndefined(id);
10731 return writer.loadDoubleConstant(JS::GenericNaN());
10732 }
10733
tryAttachNumber()10734 AttachDecision UnaryArithIRGenerator::tryAttachNumber() {
10735 if (op_ == JSOp::BitNot) {
10736 return AttachDecision::NoAction;
10737 }
10738 if (!CanConvertToDoubleForToNumber(val_)) {
10739 return AttachDecision::NoAction;
10740 }
10741 MOZ_ASSERT(res_.isNumber());
10742
10743 ValOperandId valId(writer.setInputOperandId(0));
10744 NumberOperandId numId = EmitGuardToDoubleForToNumber(writer, valId, val_);
10745
10746 switch (op_) {
10747 case JSOp::Pos:
10748 writer.loadDoubleResult(numId);
10749 trackAttached("UnaryArith.DoublePos");
10750 break;
10751 case JSOp::Neg:
10752 writer.doubleNegationResult(numId);
10753 trackAttached("UnaryArith.DoubleNeg");
10754 break;
10755 case JSOp::Inc:
10756 writer.doubleIncResult(numId);
10757 trackAttached("UnaryArith.DoubleInc");
10758 break;
10759 case JSOp::Dec:
10760 writer.doubleDecResult(numId);
10761 trackAttached("UnaryArith.DoubleDec");
10762 break;
10763 case JSOp::ToNumeric:
10764 writer.loadDoubleResult(numId);
10765 trackAttached("UnaryArith.DoubleToNumeric");
10766 break;
10767 default:
10768 MOZ_CRASH("Unexpected OP");
10769 }
10770
10771 writer.returnFromIC();
10772 return AttachDecision::Attach;
10773 }
10774
CanTruncateToInt32(const Value & val)10775 static bool CanTruncateToInt32(const Value& val) {
10776 return val.isNumber() || val.isBoolean() || val.isNullOrUndefined() ||
10777 val.isString();
10778 }
10779
10780 // Convert type into int32 for the bitwise/shift operands.
EmitTruncateToInt32Guard(CacheIRWriter & writer,ValOperandId id,const Value & val)10781 static Int32OperandId EmitTruncateToInt32Guard(CacheIRWriter& writer,
10782 ValOperandId id,
10783 const Value& val) {
10784 MOZ_ASSERT(CanTruncateToInt32(val));
10785 if (val.isInt32()) {
10786 return writer.guardToInt32(id);
10787 }
10788 if (val.isBoolean()) {
10789 return writer.guardBooleanToInt32(id);
10790 }
10791 if (val.isNullOrUndefined()) {
10792 writer.guardIsNullOrUndefined(id);
10793 return writer.loadInt32Constant(0);
10794 }
10795 NumberOperandId numId;
10796 if (val.isString()) {
10797 StringOperandId strId = writer.guardToString(id);
10798 numId = writer.guardStringToNumber(strId);
10799 } else {
10800 MOZ_ASSERT(val.isDouble());
10801 numId = writer.guardIsNumber(id);
10802 }
10803 return writer.truncateDoubleToUInt32(numId);
10804 }
10805
tryAttachBitwise()10806 AttachDecision UnaryArithIRGenerator::tryAttachBitwise() {
10807 // Only bitwise operators.
10808 if (op_ != JSOp::BitNot) {
10809 return AttachDecision::NoAction;
10810 }
10811
10812 // Check guard conditions
10813 if (!CanTruncateToInt32(val_)) {
10814 return AttachDecision::NoAction;
10815 }
10816
10817 // Bitwise operators always produce Int32 values.
10818 MOZ_ASSERT(res_.isInt32());
10819
10820 ValOperandId valId(writer.setInputOperandId(0));
10821 Int32OperandId intId = EmitTruncateToInt32Guard(writer, valId, val_);
10822 writer.int32NotResult(intId);
10823 trackAttached("BinaryArith.Bitwise.BitNot");
10824
10825 writer.returnFromIC();
10826 return AttachDecision::Attach;
10827 }
10828
tryAttachBigInt()10829 AttachDecision UnaryArithIRGenerator::tryAttachBigInt() {
10830 if (!val_.isBigInt()) {
10831 return AttachDecision::NoAction;
10832 }
10833 MOZ_ASSERT(res_.isBigInt());
10834
10835 MOZ_ASSERT(op_ != JSOp::Pos,
10836 "Applying the unary + operator on BigInt values throws an error");
10837
10838 ValOperandId valId(writer.setInputOperandId(0));
10839 BigIntOperandId bigIntId = writer.guardToBigInt(valId);
10840 switch (op_) {
10841 case JSOp::BitNot:
10842 writer.bigIntNotResult(bigIntId);
10843 trackAttached("UnaryArith.BigIntNot");
10844 break;
10845 case JSOp::Neg:
10846 writer.bigIntNegationResult(bigIntId);
10847 trackAttached("UnaryArith.BigIntNeg");
10848 break;
10849 case JSOp::Inc:
10850 writer.bigIntIncResult(bigIntId);
10851 trackAttached("UnaryArith.BigIntInc");
10852 break;
10853 case JSOp::Dec:
10854 writer.bigIntDecResult(bigIntId);
10855 trackAttached("UnaryArith.BigIntDec");
10856 break;
10857 case JSOp::ToNumeric:
10858 writer.loadBigIntResult(bigIntId);
10859 trackAttached("UnaryArith.BigIntToNumeric");
10860 break;
10861 default:
10862 MOZ_CRASH("Unexpected OP");
10863 }
10864
10865 writer.returnFromIC();
10866 return AttachDecision::Attach;
10867 }
10868
tryAttachStringInt32()10869 AttachDecision UnaryArithIRGenerator::tryAttachStringInt32() {
10870 if (!val_.isString()) {
10871 return AttachDecision::NoAction;
10872 }
10873 MOZ_ASSERT(res_.isNumber());
10874
10875 // Case should have been handled by tryAttachBitwise.
10876 MOZ_ASSERT(op_ != JSOp::BitNot);
10877
10878 if (!res_.isInt32()) {
10879 return AttachDecision::NoAction;
10880 }
10881
10882 ValOperandId valId(writer.setInputOperandId(0));
10883 StringOperandId stringId = writer.guardToString(valId);
10884 Int32OperandId intId = writer.guardStringToInt32(stringId);
10885
10886 switch (op_) {
10887 case JSOp::Pos:
10888 writer.loadInt32Result(intId);
10889 trackAttached("UnaryArith.StringInt32Pos");
10890 break;
10891 case JSOp::Neg:
10892 writer.int32NegationResult(intId);
10893 trackAttached("UnaryArith.StringInt32Neg");
10894 break;
10895 case JSOp::Inc:
10896 writer.int32IncResult(intId);
10897 trackAttached("UnaryArith.StringInt32Inc");
10898 break;
10899 case JSOp::Dec:
10900 writer.int32DecResult(intId);
10901 trackAttached("UnaryArith.StringInt32Dec");
10902 break;
10903 case JSOp::ToNumeric:
10904 writer.loadInt32Result(intId);
10905 trackAttached("UnaryArith.StringInt32ToNumeric");
10906 break;
10907 default:
10908 MOZ_CRASH("Unexpected OP");
10909 }
10910
10911 writer.returnFromIC();
10912 return AttachDecision::Attach;
10913 }
10914
tryAttachStringNumber()10915 AttachDecision UnaryArithIRGenerator::tryAttachStringNumber() {
10916 if (!val_.isString()) {
10917 return AttachDecision::NoAction;
10918 }
10919 MOZ_ASSERT(res_.isNumber());
10920
10921 // Case should have been handled by tryAttachBitwise.
10922 MOZ_ASSERT(op_ != JSOp::BitNot);
10923
10924 ValOperandId valId(writer.setInputOperandId(0));
10925 StringOperandId stringId = writer.guardToString(valId);
10926 NumberOperandId numId = writer.guardStringToNumber(stringId);
10927
10928 Int32OperandId truncatedId;
10929 switch (op_) {
10930 case JSOp::Pos:
10931 writer.loadDoubleResult(numId);
10932 trackAttached("UnaryArith.StringNumberPos");
10933 break;
10934 case JSOp::Neg:
10935 writer.doubleNegationResult(numId);
10936 trackAttached("UnaryArith.StringNumberNeg");
10937 break;
10938 case JSOp::Inc:
10939 writer.doubleIncResult(numId);
10940 trackAttached("UnaryArith.StringNumberInc");
10941 break;
10942 case JSOp::Dec:
10943 writer.doubleDecResult(numId);
10944 trackAttached("UnaryArith.StringNumberDec");
10945 break;
10946 case JSOp::ToNumeric:
10947 writer.loadDoubleResult(numId);
10948 trackAttached("UnaryArith.StringNumberToNumeric");
10949 break;
10950 default:
10951 MOZ_CRASH("Unexpected OP");
10952 }
10953
10954 writer.returnFromIC();
10955 return AttachDecision::Attach;
10956 }
10957
ToPropertyKeyIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,HandleValue val)10958 ToPropertyKeyIRGenerator::ToPropertyKeyIRGenerator(JSContext* cx,
10959 HandleScript script,
10960 jsbytecode* pc,
10961 ICState state,
10962 HandleValue val)
10963 : IRGenerator(cx, script, pc, CacheKind::ToPropertyKey, state), val_(val) {}
10964
trackAttached(const char * name)10965 void ToPropertyKeyIRGenerator::trackAttached(const char* name) {
10966 #ifdef JS_CACHEIR_SPEW
10967 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
10968 sp.valueProperty("val", val_);
10969 }
10970 #endif
10971 }
10972
tryAttachStub()10973 AttachDecision ToPropertyKeyIRGenerator::tryAttachStub() {
10974 AutoAssertNoPendingException aanpe(cx_);
10975 TRY_ATTACH(tryAttachInt32());
10976 TRY_ATTACH(tryAttachNumber());
10977 TRY_ATTACH(tryAttachString());
10978 TRY_ATTACH(tryAttachSymbol());
10979
10980 trackAttached(IRGenerator::NotAttached);
10981 return AttachDecision::NoAction;
10982 }
10983
tryAttachInt32()10984 AttachDecision ToPropertyKeyIRGenerator::tryAttachInt32() {
10985 if (!val_.isInt32()) {
10986 return AttachDecision::NoAction;
10987 }
10988
10989 ValOperandId valId(writer.setInputOperandId(0));
10990
10991 Int32OperandId intId = writer.guardToInt32(valId);
10992 writer.loadInt32Result(intId);
10993 writer.returnFromIC();
10994
10995 trackAttached("ToPropertyKey.Int32");
10996 return AttachDecision::Attach;
10997 }
10998
tryAttachNumber()10999 AttachDecision ToPropertyKeyIRGenerator::tryAttachNumber() {
11000 if (!val_.isNumber()) {
11001 return AttachDecision::NoAction;
11002 }
11003
11004 // We allow negative zero here because ToPropertyKey(-0.0) is 0.
11005 int32_t unused;
11006 if (!mozilla::NumberEqualsInt32(val_.toNumber(), &unused)) {
11007 return AttachDecision::NoAction;
11008 }
11009
11010 ValOperandId valId(writer.setInputOperandId(0));
11011
11012 Int32OperandId intId = writer.guardToInt32Index(valId);
11013 writer.loadInt32Result(intId);
11014 writer.returnFromIC();
11015
11016 trackAttached("ToPropertyKey.Number");
11017 return AttachDecision::Attach;
11018 }
11019
tryAttachString()11020 AttachDecision ToPropertyKeyIRGenerator::tryAttachString() {
11021 if (!val_.isString()) {
11022 return AttachDecision::NoAction;
11023 }
11024
11025 ValOperandId valId(writer.setInputOperandId(0));
11026
11027 StringOperandId strId = writer.guardToString(valId);
11028 writer.loadStringResult(strId);
11029 writer.returnFromIC();
11030
11031 trackAttached("ToPropertyKey.String");
11032 return AttachDecision::Attach;
11033 }
11034
tryAttachSymbol()11035 AttachDecision ToPropertyKeyIRGenerator::tryAttachSymbol() {
11036 if (!val_.isSymbol()) {
11037 return AttachDecision::NoAction;
11038 }
11039
11040 ValOperandId valId(writer.setInputOperandId(0));
11041
11042 SymbolOperandId strId = writer.guardToSymbol(valId);
11043 writer.loadSymbolResult(strId);
11044 writer.returnFromIC();
11045
11046 trackAttached("ToPropertyKey.Symbol");
11047 return AttachDecision::Attach;
11048 }
11049
BinaryArithIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,JSOp op,HandleValue lhs,HandleValue rhs,HandleValue res)11050 BinaryArithIRGenerator::BinaryArithIRGenerator(JSContext* cx,
11051 HandleScript script,
11052 jsbytecode* pc, ICState state,
11053 JSOp op, HandleValue lhs,
11054 HandleValue rhs, HandleValue res)
11055 : IRGenerator(cx, script, pc, CacheKind::BinaryArith, state),
11056 op_(op),
11057 lhs_(lhs),
11058 rhs_(rhs),
11059 res_(res) {}
11060
trackAttached(const char * name)11061 void BinaryArithIRGenerator::trackAttached(const char* name) {
11062 #ifdef JS_CACHEIR_SPEW
11063 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
11064 sp.opcodeProperty("op", op_);
11065 sp.valueProperty("rhs", rhs_);
11066 sp.valueProperty("lhs", lhs_);
11067 }
11068 #endif
11069 }
11070
tryAttachStub()11071 AttachDecision BinaryArithIRGenerator::tryAttachStub() {
11072 AutoAssertNoPendingException aanpe(cx_);
11073 // Arithmetic operations with Int32 operands
11074 TRY_ATTACH(tryAttachInt32());
11075
11076 // Bitwise operations with Int32/Double/Boolean/Null/Undefined/String
11077 // operands.
11078 TRY_ATTACH(tryAttachBitwise());
11079
11080 // Arithmetic operations with Double operands. This needs to come after
11081 // tryAttachInt32, as the guards overlap, and we'd prefer to attach the
11082 // more specialized Int32 IC if it is possible.
11083 TRY_ATTACH(tryAttachDouble());
11084
11085 // String x String
11086 TRY_ATTACH(tryAttachStringConcat());
11087
11088 // String x Object
11089 TRY_ATTACH(tryAttachStringObjectConcat());
11090
11091 TRY_ATTACH(tryAttachStringNumberConcat());
11092
11093 // String + Boolean
11094 TRY_ATTACH(tryAttachStringBooleanConcat());
11095
11096 // Arithmetic operations or bitwise operations with BigInt operands
11097 TRY_ATTACH(tryAttachBigInt());
11098
11099 // Arithmetic operations (without addition) with String x Int32.
11100 TRY_ATTACH(tryAttachStringInt32Arith());
11101
11102 trackAttached(IRGenerator::NotAttached);
11103 return AttachDecision::NoAction;
11104 }
11105
tryAttachBitwise()11106 AttachDecision BinaryArithIRGenerator::tryAttachBitwise() {
11107 // Only bit-wise and shifts.
11108 if (op_ != JSOp::BitOr && op_ != JSOp::BitXor && op_ != JSOp::BitAnd &&
11109 op_ != JSOp::Lsh && op_ != JSOp::Rsh && op_ != JSOp::Ursh) {
11110 return AttachDecision::NoAction;
11111 }
11112
11113 // Check guard conditions
11114 if (!CanTruncateToInt32(lhs_) || !CanTruncateToInt32(rhs_)) {
11115 return AttachDecision::NoAction;
11116 }
11117
11118 // All ops, with the exception of Ursh, produce Int32 values.
11119 MOZ_ASSERT_IF(op_ != JSOp::Ursh, res_.isInt32());
11120
11121 ValOperandId lhsId(writer.setInputOperandId(0));
11122 ValOperandId rhsId(writer.setInputOperandId(1));
11123
11124 Int32OperandId lhsIntId = EmitTruncateToInt32Guard(writer, lhsId, lhs_);
11125 Int32OperandId rhsIntId = EmitTruncateToInt32Guard(writer, rhsId, rhs_);
11126
11127 switch (op_) {
11128 case JSOp::BitOr:
11129 writer.int32BitOrResult(lhsIntId, rhsIntId);
11130 trackAttached("BinaryArith.Bitwise.BitOr");
11131 break;
11132 case JSOp::BitXor:
11133 writer.int32BitXorResult(lhsIntId, rhsIntId);
11134 trackAttached("BinaryArith.Bitwise.BitXor");
11135 break;
11136 case JSOp::BitAnd:
11137 writer.int32BitAndResult(lhsIntId, rhsIntId);
11138 trackAttached("BinaryArith.Bitwise.BitAnd");
11139 break;
11140 case JSOp::Lsh:
11141 writer.int32LeftShiftResult(lhsIntId, rhsIntId);
11142 trackAttached("BinaryArith.Bitwise.LeftShift");
11143 break;
11144 case JSOp::Rsh:
11145 writer.int32RightShiftResult(lhsIntId, rhsIntId);
11146 trackAttached("BinaryArith.Bitwise.RightShift");
11147 break;
11148 case JSOp::Ursh:
11149 writer.int32URightShiftResult(lhsIntId, rhsIntId, res_.isDouble());
11150 trackAttached("BinaryArith.Bitwise.UnsignedRightShift");
11151 break;
11152 default:
11153 MOZ_CRASH("Unhandled op in tryAttachBitwise");
11154 }
11155
11156 writer.returnFromIC();
11157 return AttachDecision::Attach;
11158 }
11159
tryAttachDouble()11160 AttachDecision BinaryArithIRGenerator::tryAttachDouble() {
11161 // Check valid opcodes
11162 if (op_ != JSOp::Add && op_ != JSOp::Sub && op_ != JSOp::Mul &&
11163 op_ != JSOp::Div && op_ != JSOp::Mod && op_ != JSOp::Pow) {
11164 return AttachDecision::NoAction;
11165 }
11166
11167 // Check guard conditions.
11168 if (!CanConvertToDoubleForToNumber(lhs_) ||
11169 !CanConvertToDoubleForToNumber(rhs_)) {
11170 return AttachDecision::NoAction;
11171 }
11172
11173 ValOperandId lhsId(writer.setInputOperandId(0));
11174 ValOperandId rhsId(writer.setInputOperandId(1));
11175
11176 NumberOperandId lhs = EmitGuardToDoubleForToNumber(writer, lhsId, lhs_);
11177 NumberOperandId rhs = EmitGuardToDoubleForToNumber(writer, rhsId, rhs_);
11178
11179 switch (op_) {
11180 case JSOp::Add:
11181 writer.doubleAddResult(lhs, rhs);
11182 trackAttached("BinaryArith.Double.Add");
11183 break;
11184 case JSOp::Sub:
11185 writer.doubleSubResult(lhs, rhs);
11186 trackAttached("BinaryArith.Double.Sub");
11187 break;
11188 case JSOp::Mul:
11189 writer.doubleMulResult(lhs, rhs);
11190 trackAttached("BinaryArith.Double.Mul");
11191 break;
11192 case JSOp::Div:
11193 writer.doubleDivResult(lhs, rhs);
11194 trackAttached("BinaryArith.Double.Div");
11195 break;
11196 case JSOp::Mod:
11197 writer.doubleModResult(lhs, rhs);
11198 trackAttached("BinaryArith.Double.Mod");
11199 break;
11200 case JSOp::Pow:
11201 writer.doublePowResult(lhs, rhs);
11202 trackAttached("BinaryArith.Double.Pow");
11203 break;
11204 default:
11205 MOZ_CRASH("Unhandled Op");
11206 }
11207 writer.returnFromIC();
11208 return AttachDecision::Attach;
11209 }
11210
tryAttachInt32()11211 AttachDecision BinaryArithIRGenerator::tryAttachInt32() {
11212 // Check guard conditions.
11213 if (!CanConvertToInt32ForToNumber(lhs_) ||
11214 !CanConvertToInt32ForToNumber(rhs_)) {
11215 return AttachDecision::NoAction;
11216 }
11217
11218 // These ICs will failure() if result can't be encoded in an Int32:
11219 // If sample result is not Int32, we should avoid IC.
11220 if (!res_.isInt32()) {
11221 return AttachDecision::NoAction;
11222 }
11223
11224 if (op_ != JSOp::Add && op_ != JSOp::Sub && op_ != JSOp::Mul &&
11225 op_ != JSOp::Div && op_ != JSOp::Mod && op_ != JSOp::Pow) {
11226 return AttachDecision::NoAction;
11227 }
11228
11229 if (op_ == JSOp::Pow && !CanAttachInt32Pow(lhs_, rhs_)) {
11230 return AttachDecision::NoAction;
11231 }
11232
11233 ValOperandId lhsId(writer.setInputOperandId(0));
11234 ValOperandId rhsId(writer.setInputOperandId(1));
11235
11236 Int32OperandId lhsIntId = EmitGuardToInt32ForToNumber(writer, lhsId, lhs_);
11237 Int32OperandId rhsIntId = EmitGuardToInt32ForToNumber(writer, rhsId, rhs_);
11238
11239 switch (op_) {
11240 case JSOp::Add:
11241 writer.int32AddResult(lhsIntId, rhsIntId);
11242 trackAttached("BinaryArith.Int32.Add");
11243 break;
11244 case JSOp::Sub:
11245 writer.int32SubResult(lhsIntId, rhsIntId);
11246 trackAttached("BinaryArith.Int32.Sub");
11247 break;
11248 case JSOp::Mul:
11249 writer.int32MulResult(lhsIntId, rhsIntId);
11250 trackAttached("BinaryArith.Int32.Mul");
11251 break;
11252 case JSOp::Div:
11253 writer.int32DivResult(lhsIntId, rhsIntId);
11254 trackAttached("BinaryArith.Int32.Div");
11255 break;
11256 case JSOp::Mod:
11257 writer.int32ModResult(lhsIntId, rhsIntId);
11258 trackAttached("BinaryArith.Int32.Mod");
11259 break;
11260 case JSOp::Pow:
11261 writer.int32PowResult(lhsIntId, rhsIntId);
11262 trackAttached("BinaryArith.Int32.Pow");
11263 break;
11264 default:
11265 MOZ_CRASH("Unhandled op in tryAttachInt32");
11266 }
11267
11268 writer.returnFromIC();
11269 return AttachDecision::Attach;
11270 }
11271
tryAttachStringNumberConcat()11272 AttachDecision BinaryArithIRGenerator::tryAttachStringNumberConcat() {
11273 // Only Addition
11274 if (op_ != JSOp::Add) {
11275 return AttachDecision::NoAction;
11276 }
11277
11278 if (!(lhs_.isString() && rhs_.isNumber()) &&
11279 !(lhs_.isNumber() && rhs_.isString())) {
11280 return AttachDecision::NoAction;
11281 }
11282
11283 ValOperandId lhsId(writer.setInputOperandId(0));
11284 ValOperandId rhsId(writer.setInputOperandId(1));
11285
11286 StringOperandId lhsStrId = emitToStringGuard(lhsId, lhs_);
11287 StringOperandId rhsStrId = emitToStringGuard(rhsId, rhs_);
11288
11289 writer.callStringConcatResult(lhsStrId, rhsStrId);
11290
11291 writer.returnFromIC();
11292 trackAttached("BinaryArith.StringNumberConcat");
11293 return AttachDecision::Attach;
11294 }
11295
tryAttachStringBooleanConcat()11296 AttachDecision BinaryArithIRGenerator::tryAttachStringBooleanConcat() {
11297 // Only Addition
11298 if (op_ != JSOp::Add) {
11299 return AttachDecision::NoAction;
11300 }
11301
11302 if ((!lhs_.isString() || !rhs_.isBoolean()) &&
11303 (!lhs_.isBoolean() || !rhs_.isString())) {
11304 return AttachDecision::NoAction;
11305 }
11306
11307 ValOperandId lhsId(writer.setInputOperandId(0));
11308 ValOperandId rhsId(writer.setInputOperandId(1));
11309
11310 auto guardToString = [&](ValOperandId id, const Value& v) {
11311 if (v.isString()) {
11312 return writer.guardToString(id);
11313 }
11314 MOZ_ASSERT(v.isBoolean());
11315 BooleanOperandId boolId = writer.guardToBoolean(id);
11316 return writer.booleanToString(boolId);
11317 };
11318
11319 StringOperandId lhsStrId = guardToString(lhsId, lhs_);
11320 StringOperandId rhsStrId = guardToString(rhsId, rhs_);
11321
11322 writer.callStringConcatResult(lhsStrId, rhsStrId);
11323
11324 writer.returnFromIC();
11325 trackAttached("BinaryArith.StringBooleanConcat");
11326 return AttachDecision::Attach;
11327 }
11328
tryAttachStringConcat()11329 AttachDecision BinaryArithIRGenerator::tryAttachStringConcat() {
11330 // Only Addition
11331 if (op_ != JSOp::Add) {
11332 return AttachDecision::NoAction;
11333 }
11334
11335 // Check guards
11336 if (!lhs_.isString() || !rhs_.isString()) {
11337 return AttachDecision::NoAction;
11338 }
11339
11340 ValOperandId lhsId(writer.setInputOperandId(0));
11341 ValOperandId rhsId(writer.setInputOperandId(1));
11342
11343 StringOperandId lhsStrId = writer.guardToString(lhsId);
11344 StringOperandId rhsStrId = writer.guardToString(rhsId);
11345
11346 writer.callStringConcatResult(lhsStrId, rhsStrId);
11347
11348 writer.returnFromIC();
11349 trackAttached("BinaryArith.StringConcat");
11350 return AttachDecision::Attach;
11351 }
11352
tryAttachStringObjectConcat()11353 AttachDecision BinaryArithIRGenerator::tryAttachStringObjectConcat() {
11354 // Only Addition
11355 if (op_ != JSOp::Add) {
11356 return AttachDecision::NoAction;
11357 }
11358
11359 // Check Guards
11360 if (!(lhs_.isObject() && rhs_.isString()) &&
11361 !(lhs_.isString() && rhs_.isObject()))
11362 return AttachDecision::NoAction;
11363
11364 ValOperandId lhsId(writer.setInputOperandId(0));
11365 ValOperandId rhsId(writer.setInputOperandId(1));
11366
11367 // This guard is actually overly tight, as the runtime
11368 // helper can handle lhs or rhs being a string, so long
11369 // as the other is an object.
11370 if (lhs_.isString()) {
11371 writer.guardToString(lhsId);
11372 writer.guardToObject(rhsId);
11373 } else {
11374 writer.guardToObject(lhsId);
11375 writer.guardToString(rhsId);
11376 }
11377
11378 writer.callStringObjectConcatResult(lhsId, rhsId);
11379
11380 writer.returnFromIC();
11381 trackAttached("BinaryArith.StringObjectConcat");
11382 return AttachDecision::Attach;
11383 }
11384
tryAttachBigInt()11385 AttachDecision BinaryArithIRGenerator::tryAttachBigInt() {
11386 // Check Guards
11387 if (!lhs_.isBigInt() || !rhs_.isBigInt()) {
11388 return AttachDecision::NoAction;
11389 }
11390
11391 switch (op_) {
11392 case JSOp::Add:
11393 case JSOp::Sub:
11394 case JSOp::Mul:
11395 case JSOp::Div:
11396 case JSOp::Mod:
11397 case JSOp::Pow:
11398 // Arithmetic operations.
11399 break;
11400
11401 case JSOp::BitOr:
11402 case JSOp::BitXor:
11403 case JSOp::BitAnd:
11404 case JSOp::Lsh:
11405 case JSOp::Rsh:
11406 // Bitwise operations.
11407 break;
11408
11409 default:
11410 return AttachDecision::NoAction;
11411 }
11412
11413 ValOperandId lhsId(writer.setInputOperandId(0));
11414 ValOperandId rhsId(writer.setInputOperandId(1));
11415
11416 BigIntOperandId lhsBigIntId = writer.guardToBigInt(lhsId);
11417 BigIntOperandId rhsBigIntId = writer.guardToBigInt(rhsId);
11418
11419 switch (op_) {
11420 case JSOp::Add:
11421 writer.bigIntAddResult(lhsBigIntId, rhsBigIntId);
11422 trackAttached("BinaryArith.BigInt.Add");
11423 break;
11424 case JSOp::Sub:
11425 writer.bigIntSubResult(lhsBigIntId, rhsBigIntId);
11426 trackAttached("BinaryArith.BigInt.Sub");
11427 break;
11428 case JSOp::Mul:
11429 writer.bigIntMulResult(lhsBigIntId, rhsBigIntId);
11430 trackAttached("BinaryArith.BigInt.Mul");
11431 break;
11432 case JSOp::Div:
11433 writer.bigIntDivResult(lhsBigIntId, rhsBigIntId);
11434 trackAttached("BinaryArith.BigInt.Div");
11435 break;
11436 case JSOp::Mod:
11437 writer.bigIntModResult(lhsBigIntId, rhsBigIntId);
11438 trackAttached("BinaryArith.BigInt.Mod");
11439 break;
11440 case JSOp::Pow:
11441 writer.bigIntPowResult(lhsBigIntId, rhsBigIntId);
11442 trackAttached("BinaryArith.BigInt.Pow");
11443 break;
11444 case JSOp::BitOr:
11445 writer.bigIntBitOrResult(lhsBigIntId, rhsBigIntId);
11446 trackAttached("BinaryArith.BigInt.BitOr");
11447 break;
11448 case JSOp::BitXor:
11449 writer.bigIntBitXorResult(lhsBigIntId, rhsBigIntId);
11450 trackAttached("BinaryArith.BigInt.BitXor");
11451 break;
11452 case JSOp::BitAnd:
11453 writer.bigIntBitAndResult(lhsBigIntId, rhsBigIntId);
11454 trackAttached("BinaryArith.BigInt.BitAnd");
11455 break;
11456 case JSOp::Lsh:
11457 writer.bigIntLeftShiftResult(lhsBigIntId, rhsBigIntId);
11458 trackAttached("BinaryArith.BigInt.LeftShift");
11459 break;
11460 case JSOp::Rsh:
11461 writer.bigIntRightShiftResult(lhsBigIntId, rhsBigIntId);
11462 trackAttached("BinaryArith.BigInt.RightShift");
11463 break;
11464 default:
11465 MOZ_CRASH("Unhandled op in tryAttachBigInt");
11466 }
11467
11468 writer.returnFromIC();
11469 return AttachDecision::Attach;
11470 }
11471
tryAttachStringInt32Arith()11472 AttachDecision BinaryArithIRGenerator::tryAttachStringInt32Arith() {
11473 // Check for either int32 x string or string x int32.
11474 if (!(lhs_.isInt32() && rhs_.isString()) &&
11475 !(lhs_.isString() && rhs_.isInt32())) {
11476 return AttachDecision::NoAction;
11477 }
11478
11479 // The created ICs will fail if the result can't be encoded as as int32.
11480 // Thus skip this IC, if the sample result is not an int32.
11481 if (!res_.isInt32()) {
11482 return AttachDecision::NoAction;
11483 }
11484
11485 // Must _not_ support Add, because it would be string concatenation instead.
11486 // For Pow we can't easily determine the CanAttachInt32Pow conditions so we
11487 // reject that as well.
11488 if (op_ != JSOp::Sub && op_ != JSOp::Mul && op_ != JSOp::Div &&
11489 op_ != JSOp::Mod) {
11490 return AttachDecision::NoAction;
11491 }
11492
11493 ValOperandId lhsId(writer.setInputOperandId(0));
11494 ValOperandId rhsId(writer.setInputOperandId(1));
11495
11496 auto guardToInt32 = [&](ValOperandId id, const Value& v) {
11497 if (v.isInt32()) {
11498 return writer.guardToInt32(id);
11499 }
11500
11501 MOZ_ASSERT(v.isString());
11502 StringOperandId strId = writer.guardToString(id);
11503 return writer.guardStringToInt32(strId);
11504 };
11505
11506 Int32OperandId lhsIntId = guardToInt32(lhsId, lhs_);
11507 Int32OperandId rhsIntId = guardToInt32(rhsId, rhs_);
11508
11509 switch (op_) {
11510 case JSOp::Sub:
11511 writer.int32SubResult(lhsIntId, rhsIntId);
11512 trackAttached("BinaryArith.StringInt32.Sub");
11513 break;
11514 case JSOp::Mul:
11515 writer.int32MulResult(lhsIntId, rhsIntId);
11516 trackAttached("BinaryArith.StringInt32.Mul");
11517 break;
11518 case JSOp::Div:
11519 writer.int32DivResult(lhsIntId, rhsIntId);
11520 trackAttached("BinaryArith.StringInt32.Div");
11521 break;
11522 case JSOp::Mod:
11523 writer.int32ModResult(lhsIntId, rhsIntId);
11524 trackAttached("BinaryArith.StringInt32.Mod");
11525 break;
11526 default:
11527 MOZ_CRASH("Unhandled op in tryAttachStringInt32Arith");
11528 }
11529
11530 writer.returnFromIC();
11531 return AttachDecision::Attach;
11532 }
11533
NewArrayIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,JSOp op,HandleObject templateObj,BaselineFrame * frame)11534 NewArrayIRGenerator::NewArrayIRGenerator(JSContext* cx, HandleScript script,
11535 jsbytecode* pc, ICState state, JSOp op,
11536 HandleObject templateObj,
11537 BaselineFrame* frame)
11538 : IRGenerator(cx, script, pc, CacheKind::NewArray, state),
11539 #ifdef JS_CACHEIR_SPEW
11540 op_(op),
11541 #endif
11542 templateObject_(templateObj),
11543 frame_(frame) {
11544 MOZ_ASSERT(templateObject_);
11545 }
11546
trackAttached(const char * name)11547 void NewArrayIRGenerator::trackAttached(const char* name) {
11548 #ifdef JS_CACHEIR_SPEW
11549 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
11550 sp.opcodeProperty("op", op_);
11551 }
11552 #endif
11553 }
11554
11555 // Allocation sites are usually created during baseline compilation, but we also
11556 // need to created them when an IC stub is added to a baseline compiled script
11557 // and when trial inlining.
MaybeCreateAllocSite(jsbytecode * pc,BaselineFrame * frame)11558 static gc::AllocSite* MaybeCreateAllocSite(jsbytecode* pc,
11559 BaselineFrame* frame) {
11560 MOZ_ASSERT(BytecodeOpCanHaveAllocSite(JSOp(*pc)));
11561
11562 JSScript* outerScript = frame->outerScript();
11563 bool inInterpreter = frame->runningInInterpreter();
11564 bool isInlined = frame->icScript()->isInlined();
11565
11566 if (inInterpreter && !isInlined) {
11567 return outerScript->zone()->unknownAllocSite();
11568 }
11569
11570 return outerScript->createAllocSite();
11571 }
11572
tryAttachArrayObject()11573 AttachDecision NewArrayIRGenerator::tryAttachArrayObject() {
11574 ArrayObject* arrayObj = &templateObject_->as<ArrayObject>();
11575
11576 MOZ_ASSERT(arrayObj->numUsedFixedSlots() == 0);
11577 MOZ_ASSERT(arrayObj->numDynamicSlots() == 0);
11578 MOZ_ASSERT(!arrayObj->isSharedMemory());
11579
11580 // The macro assembler only supports creating arrays with fixed elements.
11581 if (arrayObj->hasDynamicElements()) {
11582 return AttachDecision::NoAction;
11583 }
11584
11585 // Stub doesn't support metadata builder
11586 if (cx_->realm()->hasAllocationMetadataBuilder()) {
11587 return AttachDecision::NoAction;
11588 }
11589
11590 writer.guardNoAllocationMetadataBuilder(
11591 cx_->realm()->addressOfMetadataBuilder());
11592
11593 gc::AllocSite* site = MaybeCreateAllocSite(pc_, frame_);
11594 if (!site) {
11595 return AttachDecision::NoAction;
11596 }
11597
11598 Shape* shape = arrayObj->shape();
11599 uint32_t length = arrayObj->length();
11600
11601 writer.newArrayObjectResult(length, shape, site);
11602
11603 writer.returnFromIC();
11604
11605 trackAttached("NewArrayObject");
11606 return AttachDecision::Attach;
11607 }
11608
tryAttachStub()11609 AttachDecision NewArrayIRGenerator::tryAttachStub() {
11610 AutoAssertNoPendingException aanpe(cx_);
11611
11612 TRY_ATTACH(tryAttachArrayObject());
11613
11614 trackAttached(IRGenerator::NotAttached);
11615 return AttachDecision::NoAction;
11616 }
11617
NewObjectIRGenerator(JSContext * cx,HandleScript script,jsbytecode * pc,ICState state,JSOp op,HandleObject templateObj,BaselineFrame * frame)11618 NewObjectIRGenerator::NewObjectIRGenerator(JSContext* cx, HandleScript script,
11619 jsbytecode* pc, ICState state,
11620 JSOp op, HandleObject templateObj,
11621 BaselineFrame* frame)
11622 : IRGenerator(cx, script, pc, CacheKind::NewObject, state),
11623 #ifdef JS_CACHEIR_SPEW
11624 op_(op),
11625 #endif
11626 templateObject_(templateObj),
11627 frame_(frame) {
11628 MOZ_ASSERT(templateObject_);
11629 }
11630
trackAttached(const char * name)11631 void NewObjectIRGenerator::trackAttached(const char* name) {
11632 #ifdef JS_CACHEIR_SPEW
11633 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
11634 sp.opcodeProperty("op", op_);
11635 }
11636 #endif
11637 }
11638
tryAttachPlainObject()11639 AttachDecision NewObjectIRGenerator::tryAttachPlainObject() {
11640 // Don't optimize allocations with too many dynamic slots. We use an unrolled
11641 // loop when initializing slots and this avoids generating too much code.
11642 static const uint32_t MaxDynamicSlotsToOptimize = 64;
11643
11644 NativeObject* nativeObj = &templateObject_->as<NativeObject>();
11645 MOZ_ASSERT(nativeObj->is<PlainObject>());
11646
11647 // Stub doesn't support metadata builder
11648 if (cx_->realm()->hasAllocationMetadataBuilder()) {
11649 return AttachDecision::NoAction;
11650 }
11651
11652 if (nativeObj->numDynamicSlots() > MaxDynamicSlotsToOptimize) {
11653 return AttachDecision::NoAction;
11654 }
11655
11656 MOZ_ASSERT(!nativeObj->hasDynamicElements());
11657 MOZ_ASSERT(!nativeObj->isSharedMemory());
11658
11659 gc::AllocSite* site = MaybeCreateAllocSite(pc_, frame_);
11660 if (!site) {
11661 return AttachDecision::NoAction;
11662 }
11663
11664 uint32_t numFixedSlots = nativeObj->numUsedFixedSlots();
11665 uint32_t numDynamicSlots = nativeObj->numDynamicSlots();
11666 gc::AllocKind allocKind = nativeObj->allocKindForTenure();
11667 Shape* shape = nativeObj->shape();
11668
11669 writer.guardNoAllocationMetadataBuilder(
11670 cx_->realm()->addressOfMetadataBuilder());
11671 writer.newPlainObjectResult(numFixedSlots, numDynamicSlots, allocKind, shape,
11672 site);
11673
11674 writer.returnFromIC();
11675
11676 trackAttached("NewPlainObject");
11677 return AttachDecision::Attach;
11678 }
11679
tryAttachStub()11680 AttachDecision NewObjectIRGenerator::tryAttachStub() {
11681 AutoAssertNoPendingException aanpe(cx_);
11682
11683 TRY_ATTACH(tryAttachPlainObject());
11684
11685 trackAttached(IRGenerator::NotAttached);
11686 return AttachDecision::NoAction;
11687 }
11688
11689 #ifdef JS_SIMULATOR
CallAnyNative(JSContext * cx,unsigned argc,Value * vp)11690 bool js::jit::CallAnyNative(JSContext* cx, unsigned argc, Value* vp) {
11691 CallArgs args = CallArgsFromVp(argc, vp);
11692 JSObject* calleeObj = &args.callee();
11693
11694 MOZ_ASSERT(calleeObj->is<JSFunction>());
11695 auto* calleeFunc = &calleeObj->as<JSFunction>();
11696 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
11697
11698 JSNative native = calleeFunc->native();
11699 return native(cx, args.length(), args.base());
11700 }
11701 #endif
11702