Bug 1890513: Directly invoke variadic native functions. r=jandem
[gecko.git] / js / src / jit / CacheIR.cpp
blob03eae141401be54b5d1e7fb39f912534267a8392
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/. */
7 #include "jit/CacheIR.h"
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/FloatingPoint.h"
12 #include "jsapi.h"
13 #include "jsmath.h"
14 #include "jsnum.h"
16 #include "builtin/DataViewObject.h"
17 #include "builtin/MapObject.h"
18 #include "builtin/ModuleObject.h"
19 #include "builtin/Object.h"
20 #include "jit/BaselineIC.h"
21 #include "jit/CacheIRCloner.h"
22 #include "jit/CacheIRCompiler.h"
23 #include "jit/CacheIRGenerator.h"
24 #include "jit/CacheIRSpewer.h"
25 #include "jit/CacheIRWriter.h"
26 #include "jit/InlinableNatives.h"
27 #include "jit/JitContext.h"
28 #include "jit/JitZone.h"
29 #include "js/experimental/JitInfo.h" // JSJitInfo
30 #include "js/friend/DOMProxy.h" // JS::ExpandoAndGeneration
31 #include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowIfWindowProxy
32 #include "js/friend/XrayJitInfo.h" // js::jit::GetXrayJitInfo, JS::XrayJitInfo
33 #include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis
34 #include "js/Prefs.h" // JS::Prefs
35 #include "js/RegExpFlags.h" // JS::RegExpFlags
36 #include "js/ScalarType.h" // js::Scalar::Type
37 #include "js/Utility.h" // JS::AutoEnterOOMUnsafeRegion
38 #include "js/Wrapper.h"
39 #include "proxy/DOMProxy.h" // js::GetDOMProxyHandlerFamily
40 #include "proxy/ScriptedProxyHandler.h"
41 #include "util/DifferentialTesting.h"
42 #include "util/Unicode.h"
43 #include "vm/ArrayBufferObject.h"
44 #include "vm/BoundFunctionObject.h"
45 #include "vm/BytecodeUtil.h"
46 #include "vm/Compartment.h"
47 #include "vm/Iteration.h"
48 #include "vm/PlainObject.h" // js::PlainObject
49 #include "vm/ProxyObject.h"
50 #include "vm/RegExpObject.h"
51 #include "vm/SelfHosting.h"
52 #include "vm/ThrowMsgKind.h" // ThrowCondition
53 #include "vm/Watchtower.h"
54 #include "wasm/WasmInstance.h"
56 #include "jit/BaselineFrame-inl.h"
57 #include "jit/MacroAssembler-inl.h"
58 #include "vm/ArrayBufferObject-inl.h"
59 #include "vm/BytecodeUtil-inl.h"
60 #include "vm/EnvironmentObject-inl.h"
61 #include "vm/JSContext-inl.h"
62 #include "vm/JSFunction-inl.h"
63 #include "vm/JSObject-inl.h"
64 #include "vm/JSScript-inl.h"
65 #include "vm/List-inl.h"
66 #include "vm/NativeObject-inl.h"
67 #include "vm/PlainObject-inl.h"
68 #include "vm/StringObject-inl.h"
69 #include "wasm/WasmInstance-inl.h"
71 using namespace js;
72 using namespace js::jit;
74 using mozilla::DebugOnly;
75 using mozilla::Maybe;
77 using JS::DOMProxyShadowsResult;
78 using JS::ExpandoAndGeneration;
80 const char* const js::jit::CacheKindNames[] = {
81 #define DEFINE_KIND(kind) #kind,
82 CACHE_IR_KINDS(DEFINE_KIND)
83 #undef DEFINE_KIND
86 const char* const js::jit::CacheIROpNames[] = {
87 #define OPNAME(op, ...) #op,
88 CACHE_IR_OPS(OPNAME)
89 #undef OPNAME
92 const CacheIROpInfo js::jit::CacheIROpInfos[] = {
93 #define OPINFO(op, len, transpile, ...) {len, transpile},
94 CACHE_IR_OPS(OPINFO)
95 #undef OPINFO
98 const uint32_t js::jit::CacheIROpHealth[] = {
99 #define OPHEALTH(op, len, transpile, health) health,
100 CACHE_IR_OPS(OPHEALTH)
101 #undef OPHEALTH
104 size_t js::jit::NumInputsForCacheKind(CacheKind kind) {
105 switch (kind) {
106 case CacheKind::NewArray:
107 case CacheKind::NewObject:
108 case CacheKind::GetIntrinsic:
109 return 0;
110 case CacheKind::GetProp:
111 case CacheKind::TypeOf:
112 case CacheKind::ToPropertyKey:
113 case CacheKind::GetIterator:
114 case CacheKind::ToBool:
115 case CacheKind::UnaryArith:
116 case CacheKind::GetName:
117 case CacheKind::BindName:
118 case CacheKind::Call:
119 case CacheKind::OptimizeSpreadCall:
120 case CacheKind::CloseIter:
121 case CacheKind::OptimizeGetIterator:
122 return 1;
123 case CacheKind::Compare:
124 case CacheKind::GetElem:
125 case CacheKind::GetPropSuper:
126 case CacheKind::SetProp:
127 case CacheKind::In:
128 case CacheKind::HasOwn:
129 case CacheKind::CheckPrivateField:
130 case CacheKind::InstanceOf:
131 case CacheKind::BinaryArith:
132 return 2;
133 case CacheKind::GetElemSuper:
134 case CacheKind::SetElem:
135 return 3;
137 MOZ_CRASH("Invalid kind");
140 #ifdef DEBUG
141 void CacheIRWriter::assertSameCompartment(JSObject* obj) {
142 MOZ_ASSERT(cx_->compartment() == obj->compartment());
144 void CacheIRWriter::assertSameZone(Shape* shape) {
145 MOZ_ASSERT(cx_->zone() == shape->zone());
147 #endif
149 StubField CacheIRWriter::readStubField(uint32_t offset,
150 StubField::Type type) const {
151 size_t index = 0;
152 size_t currentOffset = 0;
154 // If we've seen an offset earlier than this before, we know we can start the
155 // search there at least, otherwise, we start the search from the beginning.
156 if (lastOffset_ < offset) {
157 currentOffset = lastOffset_;
158 index = lastIndex_;
161 while (currentOffset != offset) {
162 currentOffset += StubField::sizeInBytes(stubFields_[index].type());
163 index++;
164 MOZ_ASSERT(index < stubFields_.length());
167 MOZ_ASSERT(stubFields_[index].type() == type);
169 lastOffset_ = currentOffset;
170 lastIndex_ = index;
172 return stubFields_[index];
175 CacheIRCloner::CacheIRCloner(ICCacheIRStub* stub)
176 : stubInfo_(stub->stubInfo()), stubData_(stub->stubDataStart()) {}
178 void CacheIRCloner::cloneOp(CacheOp op, CacheIRReader& reader,
179 CacheIRWriter& writer) {
180 switch (op) {
181 #define DEFINE_OP(op, ...) \
182 case CacheOp::op: \
183 clone##op(reader, writer); \
184 break;
185 CACHE_IR_OPS(DEFINE_OP)
186 #undef DEFINE_OP
187 default:
188 MOZ_CRASH("Invalid op");
192 uintptr_t CacheIRCloner::readStubWord(uint32_t offset) {
193 return stubInfo_->getStubRawWord(stubData_, offset);
195 int64_t CacheIRCloner::readStubInt64(uint32_t offset) {
196 return stubInfo_->getStubRawInt64(stubData_, offset);
199 Shape* CacheIRCloner::getShapeField(uint32_t stubOffset) {
200 return reinterpret_cast<Shape*>(readStubWord(stubOffset));
202 Shape* CacheIRCloner::getWeakShapeField(uint32_t stubOffset) {
203 // No barrier is required to clone a weak pointer.
204 return reinterpret_cast<Shape*>(readStubWord(stubOffset));
206 GetterSetter* CacheIRCloner::getWeakGetterSetterField(uint32_t stubOffset) {
207 // No barrier is required to clone a weak pointer.
208 return reinterpret_cast<GetterSetter*>(readStubWord(stubOffset));
210 JSObject* CacheIRCloner::getObjectField(uint32_t stubOffset) {
211 return reinterpret_cast<JSObject*>(readStubWord(stubOffset));
213 JSObject* CacheIRCloner::getWeakObjectField(uint32_t stubOffset) {
214 // No barrier is required to clone a weak pointer.
215 return reinterpret_cast<JSObject*>(readStubWord(stubOffset));
217 JSString* CacheIRCloner::getStringField(uint32_t stubOffset) {
218 return reinterpret_cast<JSString*>(readStubWord(stubOffset));
220 JSAtom* CacheIRCloner::getAtomField(uint32_t stubOffset) {
221 return reinterpret_cast<JSAtom*>(readStubWord(stubOffset));
223 JS::Symbol* CacheIRCloner::getSymbolField(uint32_t stubOffset) {
224 return reinterpret_cast<JS::Symbol*>(readStubWord(stubOffset));
226 BaseScript* CacheIRCloner::getWeakBaseScriptField(uint32_t stubOffset) {
227 // No barrier is required to clone a weak pointer.
228 return reinterpret_cast<BaseScript*>(readStubWord(stubOffset));
230 JitCode* CacheIRCloner::getJitCodeField(uint32_t stubOffset) {
231 return reinterpret_cast<JitCode*>(readStubWord(stubOffset));
233 uint32_t CacheIRCloner::getRawInt32Field(uint32_t stubOffset) {
234 return uint32_t(reinterpret_cast<uintptr_t>(readStubWord(stubOffset)));
236 const void* CacheIRCloner::getRawPointerField(uint32_t stubOffset) {
237 return reinterpret_cast<const void*>(readStubWord(stubOffset));
239 uint64_t CacheIRCloner::getRawInt64Field(uint32_t stubOffset) {
240 return static_cast<uint64_t>(readStubInt64(stubOffset));
242 gc::AllocSite* CacheIRCloner::getAllocSiteField(uint32_t stubOffset) {
243 return reinterpret_cast<gc::AllocSite*>(readStubWord(stubOffset));
246 jsid CacheIRCloner::getIdField(uint32_t stubOffset) {
247 return jsid::fromRawBits(readStubWord(stubOffset));
249 const Value CacheIRCloner::getValueField(uint32_t stubOffset) {
250 return Value::fromRawBits(uint64_t(readStubInt64(stubOffset)));
252 double CacheIRCloner::getDoubleField(uint32_t stubOffset) {
253 uint64_t bits = uint64_t(readStubInt64(stubOffset));
254 return mozilla::BitwiseCast<double>(bits);
257 IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
258 CacheKind cacheKind, ICState state)
259 : writer(cx),
260 cx_(cx),
261 script_(script),
262 pc_(pc),
263 cacheKind_(cacheKind),
264 mode_(state.mode()),
265 isFirstStub_(state.newStubIsFirstStub()),
266 numOptimizedStubs_(state.numOptimizedStubs()) {}
268 GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script,
269 jsbytecode* pc, ICState state,
270 CacheKind cacheKind, HandleValue val,
271 HandleValue idVal)
272 : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {}
274 static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderId,
275 NativeObject* holder, PropertyInfo prop) {
276 if (holder->isFixedSlot(prop.slot())) {
277 writer.loadFixedSlotResult(holderId,
278 NativeObject::getFixedSlotOffset(prop.slot()));
279 } else {
280 size_t dynamicSlotOffset =
281 holder->dynamicSlotIndex(prop.slot()) * sizeof(Value);
282 writer.loadDynamicSlotResult(holderId, dynamicSlotOffset);
286 // DOM proxies
287 // -----------
289 // DOM proxies are proxies that are used to implement various DOM objects like
290 // HTMLDocument and NodeList. DOM proxies may have an expando object - a native
291 // object that stores extra properties added to the object. The following
292 // CacheIR instructions are only used with DOM proxies:
294 // * LoadDOMExpandoValue: returns the Value in the proxy's expando slot. This
295 // returns either an UndefinedValue (no expando), ObjectValue (the expando
296 // object), or PrivateValue(ExpandoAndGeneration*).
298 // * LoadDOMExpandoValueGuardGeneration: guards the Value in the proxy's expando
299 // slot is the same PrivateValue(ExpandoAndGeneration*), then guards on its
300 // generation, then returns expandoAndGeneration->expando. This Value is
301 // either an UndefinedValue or ObjectValue.
303 // * LoadDOMExpandoValueIgnoreGeneration: assumes the Value in the proxy's
304 // expando slot is a PrivateValue(ExpandoAndGeneration*), unboxes it, and
305 // returns the expandoAndGeneration->expando Value.
307 // * GuardDOMExpandoMissingOrGuardShape: takes an expando Value as input, then
308 // guards it's either UndefinedValue or an object with the expected shape.
310 enum class ProxyStubType {
311 None,
312 DOMExpando,
313 DOMShadowed,
314 DOMUnshadowed,
315 Generic
318 static bool IsCacheableDOMProxy(ProxyObject* obj) {
319 const BaseProxyHandler* handler = obj->handler();
320 if (handler->family() != GetDOMProxyHandlerFamily()) {
321 return false;
324 // Some DOM proxies have dynamic prototypes. We can't really cache those very
325 // well.
326 return obj->hasStaticPrototype();
329 static ProxyStubType GetProxyStubType(JSContext* cx, HandleObject obj,
330 HandleId id) {
331 if (!obj->is<ProxyObject>()) {
332 return ProxyStubType::None;
334 auto proxy = obj.as<ProxyObject>();
336 if (!IsCacheableDOMProxy(proxy)) {
337 return ProxyStubType::Generic;
340 // Private fields are defined on a separate expando object.
341 if (id.isPrivateName()) {
342 return ProxyStubType::Generic;
345 DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, proxy, id);
346 if (shadows == DOMProxyShadowsResult::ShadowCheckFailed) {
347 cx->clearPendingException();
348 return ProxyStubType::None;
351 if (DOMProxyIsShadowing(shadows)) {
352 if (shadows == DOMProxyShadowsResult::ShadowsViaDirectExpando ||
353 shadows == DOMProxyShadowsResult::ShadowsViaIndirectExpando) {
354 return ProxyStubType::DOMExpando;
356 return ProxyStubType::DOMShadowed;
359 MOZ_ASSERT(shadows == DOMProxyShadowsResult::DoesntShadow ||
360 shadows == DOMProxyShadowsResult::DoesntShadowUnique);
361 return ProxyStubType::DOMUnshadowed;
364 static bool ValueToNameOrSymbolId(JSContext* cx, HandleValue idVal,
365 MutableHandleId id, bool* nameOrSymbol) {
366 *nameOrSymbol = false;
368 if (!idVal.isString() && !idVal.isSymbol() && !idVal.isUndefined() &&
369 !idVal.isNull()) {
370 return true;
373 if (!PrimitiveValueToId<CanGC>(cx, idVal, id)) {
374 return false;
377 if (!id.isAtom() && !id.isSymbol()) {
378 id.set(JS::PropertyKey::Void());
379 return true;
382 if (id.isAtom() && id.toAtom()->isIndex()) {
383 id.set(JS::PropertyKey::Void());
384 return true;
387 *nameOrSymbol = true;
388 return true;
391 AttachDecision GetPropIRGenerator::tryAttachStub() {
392 AutoAssertNoPendingException aanpe(cx_);
394 ValOperandId valId(writer.setInputOperandId(0));
395 if (cacheKind_ != CacheKind::GetProp) {
396 MOZ_ASSERT_IF(cacheKind_ == CacheKind::GetPropSuper,
397 getSuperReceiverValueId().id() == 1);
398 MOZ_ASSERT_IF(cacheKind_ != CacheKind::GetPropSuper,
399 getElemKeyValueId().id() == 1);
400 writer.setInputOperandId(1);
402 if (cacheKind_ == CacheKind::GetElemSuper) {
403 MOZ_ASSERT(getSuperReceiverValueId().id() == 2);
404 writer.setInputOperandId(2);
407 RootedId id(cx_);
408 bool nameOrSymbol;
409 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
410 cx_->clearPendingException();
411 return AttachDecision::NoAction;
414 // |super.prop| getter calls use a |this| value that differs from lookup
415 // object.
416 ValOperandId receiverId = isSuper() ? getSuperReceiverValueId() : valId;
418 if (val_.isObject()) {
419 RootedObject obj(cx_, &val_.toObject());
420 ObjOperandId objId = writer.guardToObject(valId);
421 if (nameOrSymbol) {
422 TRY_ATTACH(tryAttachObjectLength(obj, objId, id));
423 TRY_ATTACH(tryAttachTypedArray(obj, objId, id));
424 TRY_ATTACH(tryAttachDataView(obj, objId, id));
425 TRY_ATTACH(tryAttachArrayBufferMaybeShared(obj, objId, id));
426 TRY_ATTACH(tryAttachRegExp(obj, objId, id));
427 TRY_ATTACH(tryAttachMap(obj, objId, id));
428 TRY_ATTACH(tryAttachSet(obj, objId, id));
429 TRY_ATTACH(tryAttachNative(obj, objId, id, receiverId));
430 TRY_ATTACH(tryAttachModuleNamespace(obj, objId, id));
431 TRY_ATTACH(tryAttachWindowProxy(obj, objId, id));
432 TRY_ATTACH(tryAttachCrossCompartmentWrapper(obj, objId, id));
433 TRY_ATTACH(
434 tryAttachXrayCrossCompartmentWrapper(obj, objId, id, receiverId));
435 TRY_ATTACH(tryAttachFunction(obj, objId, id));
436 TRY_ATTACH(tryAttachArgumentsObjectIterator(obj, objId, id));
437 TRY_ATTACH(tryAttachArgumentsObjectCallee(obj, objId, id));
438 TRY_ATTACH(tryAttachProxy(obj, objId, id, receiverId));
440 trackAttached(IRGenerator::NotAttached);
441 return AttachDecision::NoAction;
444 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
445 cacheKind_ == CacheKind::GetElemSuper);
447 TRY_ATTACH(tryAttachProxyElement(obj, objId));
448 TRY_ATTACH(tryAttachTypedArrayElement(obj, objId));
450 uint32_t index;
451 Int32OperandId indexId;
452 if (maybeGuardInt32Index(idVal_, getElemKeyValueId(), &index, &indexId)) {
453 TRY_ATTACH(tryAttachDenseElement(obj, objId, index, indexId));
454 TRY_ATTACH(tryAttachDenseElementHole(obj, objId, index, indexId));
455 TRY_ATTACH(tryAttachSparseElement(obj, objId, index, indexId));
456 TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, index, indexId));
457 TRY_ATTACH(tryAttachArgumentsObjectArgHole(obj, objId, index, indexId));
458 TRY_ATTACH(
459 tryAttachGenericElement(obj, objId, index, indexId, receiverId));
461 trackAttached(IRGenerator::NotAttached);
462 return AttachDecision::NoAction;
465 trackAttached(IRGenerator::NotAttached);
466 return AttachDecision::NoAction;
469 if (nameOrSymbol) {
470 TRY_ATTACH(tryAttachPrimitive(valId, id));
471 TRY_ATTACH(tryAttachStringLength(valId, id));
473 trackAttached(IRGenerator::NotAttached);
474 return AttachDecision::NoAction;
477 if (idVal_.isInt32()) {
478 ValOperandId indexId = getElemKeyValueId();
479 TRY_ATTACH(tryAttachStringChar(valId, indexId));
481 trackAttached(IRGenerator::NotAttached);
482 return AttachDecision::NoAction;
485 trackAttached(IRGenerator::NotAttached);
486 return AttachDecision::NoAction;
489 #ifdef DEBUG
490 // Any property lookups performed when trying to attach ICs must be pure, i.e.
491 // must use LookupPropertyPure() or similar functions. Pure lookups are
492 // guaranteed to never modify the prototype chain. This ensures that the holder
493 // object can always be found on the prototype chain.
494 static bool IsCacheableProtoChain(NativeObject* obj, NativeObject* holder) {
495 while (obj != holder) {
496 JSObject* proto = obj->staticPrototype();
497 if (!proto || !proto->is<NativeObject>()) {
498 return false;
500 obj = &proto->as<NativeObject>();
502 return true;
504 #endif
506 static bool IsCacheableGetPropSlot(NativeObject* obj, NativeObject* holder,
507 PropertyInfo prop) {
508 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
510 return prop.isDataProperty();
513 enum class NativeGetPropKind {
514 None,
515 Missing,
516 Slot,
517 NativeGetter,
518 ScriptedGetter,
521 static NativeGetPropKind IsCacheableGetPropCall(NativeObject* obj,
522 NativeObject* holder,
523 PropertyInfo prop,
524 jsbytecode* pc = nullptr) {
525 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
527 if (pc && JSOp(*pc) == JSOp::GetBoundName) {
528 return NativeGetPropKind::None;
531 if (!prop.isAccessorProperty()) {
532 return NativeGetPropKind::None;
535 JSObject* getterObject = holder->getGetter(prop);
536 if (!getterObject || !getterObject->is<JSFunction>()) {
537 return NativeGetPropKind::None;
540 JSFunction& getter = getterObject->as<JSFunction>();
542 if (getter.isClassConstructor()) {
543 return NativeGetPropKind::None;
546 // Scripted functions and natives with JIT entry can use the scripted path.
547 if (getter.hasJitEntry()) {
548 return NativeGetPropKind::ScriptedGetter;
551 MOZ_ASSERT(getter.isNativeWithoutJitEntry());
552 return NativeGetPropKind::NativeGetter;
555 static bool CheckHasNoSuchOwnProperty(JSContext* cx, JSObject* obj, jsid id) {
556 if (!obj->is<NativeObject>()) {
557 return false;
559 // Don't handle objects with resolve hooks.
560 if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj)) {
561 return false;
563 if (obj->as<NativeObject>().contains(cx, id)) {
564 return false;
566 return true;
569 static bool CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, jsid id) {
570 JSObject* curObj = obj;
571 do {
572 if (!CheckHasNoSuchOwnProperty(cx, curObj, id)) {
573 return false;
576 curObj = curObj->staticPrototype();
577 } while (curObj);
579 return true;
582 static bool IsCacheableNoProperty(JSContext* cx, NativeObject* obj,
583 NativeObject* holder, jsid id,
584 jsbytecode* pc) {
585 MOZ_ASSERT(!holder);
587 // If we're doing a name lookup, we have to throw a ReferenceError.
588 if (JSOp(*pc) == JSOp::GetBoundName) {
589 return false;
592 return CheckHasNoSuchProperty(cx, obj, id);
595 static NativeGetPropKind CanAttachNativeGetProp(JSContext* cx, JSObject* obj,
596 PropertyKey id,
597 NativeObject** holder,
598 Maybe<PropertyInfo>* propInfo,
599 jsbytecode* pc) {
600 MOZ_ASSERT(id.isString() || id.isSymbol());
601 MOZ_ASSERT(!*holder);
603 // The lookup needs to be universally pure, otherwise we risk calling hooks
604 // out of turn. We don't mind doing this even when purity isn't required,
605 // because we only miss out on shape hashification, which is only a temporary
606 // perf cost. The limits were arbitrarily set, anyways.
607 NativeObject* baseHolder = nullptr;
608 PropertyResult prop;
609 if (!LookupPropertyPure(cx, obj, id, &baseHolder, &prop)) {
610 return NativeGetPropKind::None;
612 auto* nobj = &obj->as<NativeObject>();
614 if (prop.isNativeProperty()) {
615 MOZ_ASSERT(baseHolder);
616 *holder = baseHolder;
617 *propInfo = mozilla::Some(prop.propertyInfo());
619 if (IsCacheableGetPropSlot(nobj, *holder, propInfo->ref())) {
620 return NativeGetPropKind::Slot;
623 return IsCacheableGetPropCall(nobj, *holder, propInfo->ref(), pc);
626 if (!prop.isFound()) {
627 if (IsCacheableNoProperty(cx, nobj, *holder, id, pc)) {
628 return NativeGetPropKind::Missing;
632 return NativeGetPropKind::None;
635 static void GuardReceiverProto(CacheIRWriter& writer, NativeObject* obj,
636 ObjOperandId objId) {
637 // Note: we guard on the actual prototype and not on the shape because this is
638 // used for sparse elements where we expect shape changes.
640 if (JSObject* proto = obj->staticPrototype()) {
641 writer.guardProto(objId, proto);
642 } else {
643 writer.guardNullProto(objId);
647 // Guard that a given object has same class and same OwnProperties (excluding
648 // dense elements and dynamic properties).
649 static void TestMatchingNativeReceiver(CacheIRWriter& writer, NativeObject* obj,
650 ObjOperandId objId) {
651 writer.guardShapeForOwnProperties(objId, obj->shape());
654 // Similar to |TestMatchingNativeReceiver|, but specialized for ProxyObject.
655 static void TestMatchingProxyReceiver(CacheIRWriter& writer, ProxyObject* obj,
656 ObjOperandId objId) {
657 writer.guardShapeForClass(objId, obj->shape());
660 static void GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj,
661 NativeObject* holder, ObjOperandId objId) {
662 // Assuming target property is on |holder|, generate appropriate guards to
663 // ensure |holder| is still on the prototype chain of |obj| and we haven't
664 // introduced any shadowing definitions.
666 // For each item in the proto chain before holder, we must ensure that
667 // [[GetPrototypeOf]] still has the expected result, and that
668 // [[GetOwnProperty]] has no definition of the target property.
671 // [SMDOC] Shape Teleporting Optimization
672 // --------------------------------------
674 // Starting with the assumption (and guideline to developers) that mutating
675 // prototypes is an uncommon and fair-to-penalize operation we move cost
676 // from the access side to the mutation side.
678 // Consider the following proto chain, with B defining a property 'x':
680 // D -> C -> B{x: 3} -> A -> null
682 // When accessing |D.x| we refer to D as the "receiver", and B as the
683 // "holder". To optimize this access we need to ensure that neither D nor C
684 // has since defined a shadowing property 'x'. Since C is a prototype that
685 // we assume is rarely mutated we would like to avoid checking each time if
686 // new properties are added. To do this we require that whenever C starts
687 // shadowing a property on its proto chain, we invalidate (and opt out of) the
688 // teleporting optimization by setting the InvalidatedTeleporting flag on the
689 // object we're shadowing, triggering a shape change of that object. As a
690 // result, checking the shape of D and B is sufficient. Note that we do not
691 // care if the shape or properties of A change since the lookup of 'x' will
692 // stop at B.
694 // The second condition we must verify is that the prototype chain was not
695 // mutated. The same mechanism as above is used. When the prototype link is
696 // changed, we generate a new shape for the object. If the object whose
697 // link we are mutating is itself a prototype, we regenerate shapes down
698 // the chain by setting the InvalidatedTeleporting flag on them. This means
699 // the same two shape checks as above are sufficient.
701 // Once the InvalidatedTeleporting flag is set, it means the shape will no
702 // longer be changed by ReshapeForProtoMutation and ReshapeForShadowedProp.
703 // In this case we can no longer apply the optimization.
705 // See:
706 // - ReshapeForProtoMutation
707 // - ReshapeForShadowedProp
709 MOZ_ASSERT(holder);
710 MOZ_ASSERT(obj != holder);
712 // Receiver guards (see TestMatchingReceiver) ensure the receiver's proto is
713 // unchanged so peel off the receiver.
714 JSObject* pobj = obj->staticPrototype();
715 MOZ_ASSERT(pobj->isUsedAsPrototype());
717 // If teleporting is supported for this holder, we are done.
718 if (!holder->hasInvalidatedTeleporting()) {
719 return;
722 // If already at the holder, no further proto checks are needed.
723 if (pobj == holder) {
724 return;
727 // Synchronize pobj and protoId.
728 MOZ_ASSERT(pobj == obj->staticPrototype());
729 ObjOperandId protoId = writer.loadProto(objId);
731 // Shape guard each prototype object between receiver and holder. This guards
732 // against both proto changes and shadowing properties.
733 while (pobj != holder) {
734 writer.guardShape(protoId, pobj->shape());
736 pobj = pobj->staticPrototype();
737 protoId = writer.loadProto(protoId);
741 static void GeneratePrototypeHoleGuards(CacheIRWriter& writer,
742 NativeObject* obj, ObjOperandId objId,
743 bool alwaysGuardFirstProto) {
744 if (alwaysGuardFirstProto) {
745 GuardReceiverProto(writer, obj, objId);
748 JSObject* pobj = obj->staticPrototype();
749 while (pobj) {
750 ObjOperandId protoId = writer.loadObject(pobj);
752 // Make sure the shape matches, to ensure the proto is unchanged and to
753 // avoid non-dense elements or anything else that is being checked by
754 // CanAttachDenseElementHole.
755 MOZ_ASSERT(pobj->is<NativeObject>());
756 writer.guardShape(protoId, pobj->shape());
758 // Also make sure there are no dense elements.
759 writer.guardNoDenseElements(protoId);
761 pobj = pobj->staticPrototype();
765 // Similar to |TestMatchingReceiver|, but for the holder object (when it
766 // differs from the receiver). The holder may also be the expando of the
767 // receiver if it exists.
768 static void TestMatchingHolder(CacheIRWriter& writer, NativeObject* obj,
769 ObjOperandId objId) {
770 // The GeneratePrototypeGuards + TestMatchingHolder checks only support
771 // prototype chains composed of NativeObject (excluding the receiver
772 // itself).
773 writer.guardShapeForOwnProperties(objId, obj->shape());
776 enum class IsCrossCompartment { No, Yes };
778 // Emit a shape guard for all objects on the proto chain. This does NOT include
779 // the receiver; callers must ensure the receiver's proto is the first proto by
780 // either emitting a shape guard or a prototype guard for |objId|.
782 // Note: this relies on shape implying proto.
783 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
784 static void ShapeGuardProtoChain(CacheIRWriter& writer, NativeObject* obj,
785 ObjOperandId objId) {
786 uint32_t depth = 0;
787 static const uint32_t MAX_CACHED_LOADS = 4;
788 ObjOperandId receiverObjId = objId;
790 while (true) {
791 JSObject* proto = obj->staticPrototype();
792 if (!proto) {
793 return;
796 obj = &proto->as<NativeObject>();
798 // After guarding the shape of an object, we can safely bake that
799 // object's proto into the stub data. Compared to LoadProto, this
800 // takes one load instead of three (object -> shape -> baseshape
801 // -> proto). We cap the depth to avoid bloating the size of the
802 // stub data. To avoid compartment mismatch, we skip this optimization
803 // in the cross-compartment case.
804 if (depth < MAX_CACHED_LOADS &&
805 MaybeCrossCompartment == IsCrossCompartment::No) {
806 objId = writer.loadProtoObject(obj, receiverObjId);
807 } else {
808 objId = writer.loadProto(objId);
810 depth++;
812 writer.guardShape(objId, obj->shape());
816 // For cross compartment guards we shape-guard the prototype chain to avoid
817 // referencing the holder object.
819 // This peels off the first layer because it's guarded against obj == holder.
821 // Returns the holder's OperandId.
822 static ObjOperandId ShapeGuardProtoChainForCrossCompartmentHolder(
823 CacheIRWriter& writer, NativeObject* obj, ObjOperandId objId,
824 NativeObject* holder) {
825 MOZ_ASSERT(obj != holder);
826 MOZ_ASSERT(holder);
827 while (true) {
828 MOZ_ASSERT(obj->staticPrototype());
829 obj = &obj->staticPrototype()->as<NativeObject>();
831 objId = writer.loadProto(objId);
832 if (obj == holder) {
833 TestMatchingHolder(writer, obj, objId);
834 return objId;
836 writer.guardShapeForOwnProperties(objId, obj->shape());
840 // Emit guards for reading a data property on |holder|. Returns the holder's
841 // OperandId.
842 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
843 static ObjOperandId EmitReadSlotGuard(CacheIRWriter& writer, NativeObject* obj,
844 NativeObject* holder,
845 ObjOperandId objId) {
846 MOZ_ASSERT(holder);
847 TestMatchingNativeReceiver(writer, obj, objId);
849 if (obj == holder) {
850 return objId;
853 if (MaybeCrossCompartment == IsCrossCompartment::Yes) {
854 // Guard proto chain integrity.
855 // We use a variant of guards that avoid baking in any cross-compartment
856 // object pointers.
857 return ShapeGuardProtoChainForCrossCompartmentHolder(writer, obj, objId,
858 holder);
861 // Guard proto chain integrity.
862 GeneratePrototypeGuards(writer, obj, holder, objId);
864 // Guard on the holder's shape.
865 ObjOperandId holderId = writer.loadObject(holder);
866 TestMatchingHolder(writer, holder, holderId);
867 return holderId;
870 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
871 static void EmitMissingPropGuard(CacheIRWriter& writer, NativeObject* obj,
872 ObjOperandId objId) {
873 TestMatchingNativeReceiver(writer, obj, objId);
875 // The property does not exist. Guard on everything in the prototype
876 // chain. This is guaranteed to see only Native objects because of
877 // CanAttachNativeGetProp().
878 ShapeGuardProtoChain<MaybeCrossCompartment>(writer, obj, objId);
881 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
882 static void EmitReadSlotResult(CacheIRWriter& writer, NativeObject* obj,
883 NativeObject* holder, PropertyInfo prop,
884 ObjOperandId objId) {
885 MOZ_ASSERT(holder);
887 ObjOperandId holderId =
888 EmitReadSlotGuard<MaybeCrossCompartment>(writer, obj, holder, objId);
890 MOZ_ASSERT(holderId.valid());
891 EmitLoadSlotResult(writer, holderId, holder, prop);
894 template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
895 static void EmitMissingPropResult(CacheIRWriter& writer, NativeObject* obj,
896 ObjOperandId objId) {
897 EmitMissingPropGuard<MaybeCrossCompartment>(writer, obj, objId);
898 writer.loadUndefinedResult();
901 static void EmitCallGetterResultNoGuards(JSContext* cx, CacheIRWriter& writer,
902 NativeGetPropKind kind,
903 NativeObject* obj,
904 NativeObject* holder,
905 PropertyInfo prop,
906 ValOperandId receiverId) {
907 MOZ_ASSERT(IsCacheableGetPropCall(obj, holder, prop) == kind);
909 JSFunction* target = &holder->getGetter(prop)->as<JSFunction>();
910 bool sameRealm = cx->realm() == target->realm();
912 switch (kind) {
913 case NativeGetPropKind::NativeGetter: {
914 writer.callNativeGetterResult(receiverId, target, sameRealm);
915 writer.returnFromIC();
916 break;
918 case NativeGetPropKind::ScriptedGetter: {
919 writer.callScriptedGetterResult(receiverId, target, sameRealm);
920 writer.returnFromIC();
921 break;
923 default:
924 // CanAttachNativeGetProp guarantees that the getter is either a native or
925 // a scripted function.
926 MOZ_ASSERT_UNREACHABLE("Can't attach getter");
927 break;
931 // See the SMDOC comment in vm/GetterSetter.h for more info on Getter/Setter
932 // properties
933 static void EmitGuardGetterSetterSlot(CacheIRWriter& writer,
934 NativeObject* holder, PropertyInfo prop,
935 ObjOperandId holderId,
936 bool holderIsConstant = false) {
937 // If the holder is guaranteed to be the same object, and it never had a
938 // slot holding a GetterSetter mutated or deleted, its Shape will change when
939 // that does happen so we don't need to guard on the GetterSetter.
940 if (holderIsConstant && !holder->hadGetterSetterChange()) {
941 return;
944 size_t slot = prop.slot();
945 Value slotVal = holder->getSlot(slot);
946 MOZ_ASSERT(slotVal.isPrivateGCThing());
948 if (holder->isFixedSlot(slot)) {
949 size_t offset = NativeObject::getFixedSlotOffset(slot);
950 writer.guardFixedSlotValue(holderId, offset, slotVal);
951 } else {
952 size_t offset = holder->dynamicSlotIndex(slot) * sizeof(Value);
953 writer.guardDynamicSlotValue(holderId, offset, slotVal);
957 static void EmitCallGetterResultGuards(CacheIRWriter& writer, NativeObject* obj,
958 NativeObject* holder, HandleId id,
959 PropertyInfo prop, ObjOperandId objId,
960 ICState::Mode mode) {
961 // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
962 // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
963 // require outerizing).
965 MOZ_ASSERT(holder->containsPure(id, prop));
967 if (mode == ICState::Mode::Specialized || IsWindow(obj)) {
968 TestMatchingNativeReceiver(writer, obj, objId);
970 if (obj != holder) {
971 GeneratePrototypeGuards(writer, obj, holder, objId);
973 // Guard on the holder's shape.
974 ObjOperandId holderId = writer.loadObject(holder);
975 TestMatchingHolder(writer, holder, holderId);
977 EmitGuardGetterSetterSlot(writer, holder, prop, holderId,
978 /* holderIsConstant = */ true);
979 } else {
980 EmitGuardGetterSetterSlot(writer, holder, prop, objId);
982 } else {
983 GetterSetter* gs = holder->getGetterSetter(prop);
984 writer.guardHasGetterSetter(objId, id, gs);
988 static void EmitCallGetterResult(JSContext* cx, CacheIRWriter& writer,
989 NativeGetPropKind kind, NativeObject* obj,
990 NativeObject* holder, HandleId id,
991 PropertyInfo prop, ObjOperandId objId,
992 ValOperandId receiverId, ICState::Mode mode) {
993 EmitCallGetterResultGuards(writer, obj, holder, id, prop, objId, mode);
994 EmitCallGetterResultNoGuards(cx, writer, kind, obj, holder, prop, receiverId);
997 static bool CanAttachDOMCall(JSContext* cx, JSJitInfo::OpType type,
998 JSObject* obj, JSFunction* fun,
999 ICState::Mode mode) {
1000 MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter ||
1001 type == JSJitInfo::Method);
1003 if (mode != ICState::Mode::Specialized) {
1004 return false;
1007 if (!fun->hasJitInfo()) {
1008 return false;
1011 if (cx->realm() != fun->realm()) {
1012 return false;
1015 const JSJitInfo* jitInfo = fun->jitInfo();
1016 if (jitInfo->type() != type) {
1017 return false;
1020 MOZ_ASSERT_IF(IsWindow(obj), !jitInfo->needsOuterizedThisObject());
1022 const JSClass* clasp = obj->getClass();
1023 if (!clasp->isDOMClass()) {
1024 return false;
1027 if (type != JSJitInfo::Method && clasp->isProxyObject()) {
1028 return false;
1031 // Ion codegen expects DOM_OBJECT_SLOT to be a fixed slot in LoadDOMPrivate.
1032 // It can be a dynamic slot if we transplanted this reflector object with a
1033 // proxy.
1034 if (obj->is<NativeObject>() && obj->as<NativeObject>().numFixedSlots() == 0) {
1035 return false;
1038 // Tell the analysis the |DOMInstanceClassHasProtoAtDepth| hook can't GC.
1039 JS::AutoSuppressGCAnalysis nogc;
1041 DOMInstanceClassHasProtoAtDepth instanceChecker =
1042 cx->runtime()->DOMcallbacks->instanceClassMatchesProto;
1043 return instanceChecker(clasp, jitInfo->protoID, jitInfo->depth);
1046 static bool CanAttachDOMGetterSetter(JSContext* cx, JSJitInfo::OpType type,
1047 NativeObject* obj, NativeObject* holder,
1048 PropertyInfo prop, ICState::Mode mode) {
1049 MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter);
1051 JSObject* accessor = type == JSJitInfo::Getter ? holder->getGetter(prop)
1052 : holder->getSetter(prop);
1053 JSFunction* fun = &accessor->as<JSFunction>();
1055 return CanAttachDOMCall(cx, type, obj, fun, mode);
1058 static void EmitCallDOMGetterResultNoGuards(CacheIRWriter& writer,
1059 NativeObject* holder,
1060 PropertyInfo prop,
1061 ObjOperandId objId) {
1062 JSFunction* getter = &holder->getGetter(prop)->as<JSFunction>();
1063 writer.callDOMGetterResult(objId, getter->jitInfo());
1064 writer.returnFromIC();
1067 static void EmitCallDOMGetterResult(JSContext* cx, CacheIRWriter& writer,
1068 NativeObject* obj, NativeObject* holder,
1069 HandleId id, PropertyInfo prop,
1070 ObjOperandId objId) {
1071 // Note: this relies on EmitCallGetterResultGuards emitting a shape guard
1072 // for specialized stubs.
1073 // The shape guard ensures the receiver's Class is valid for this DOM getter.
1074 EmitCallGetterResultGuards(writer, obj, holder, id, prop, objId,
1075 ICState::Mode::Specialized);
1076 EmitCallDOMGetterResultNoGuards(writer, holder, prop, objId);
1079 static ValOperandId EmitLoadSlot(CacheIRWriter& writer, NativeObject* holder,
1080 ObjOperandId holderId, uint32_t slot) {
1081 if (holder->isFixedSlot(slot)) {
1082 return writer.loadFixedSlot(holderId,
1083 NativeObject::getFixedSlotOffset(slot));
1085 size_t dynamicSlotIndex = holder->dynamicSlotIndex(slot);
1086 return writer.loadDynamicSlot(holderId, dynamicSlotIndex);
1089 void GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId,
1090 jsid id) {
1091 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
1093 // We don't support GetBoundName because environment objects have
1094 // lookupProperty hooks and GetBoundName is usually not megamorphic.
1095 MOZ_ASSERT(JSOp(*pc_) != JSOp::GetBoundName);
1097 if (cacheKind_ == CacheKind::GetProp ||
1098 cacheKind_ == CacheKind::GetPropSuper) {
1099 writer.megamorphicLoadSlotResult(objId, id);
1100 } else {
1101 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
1102 cacheKind_ == CacheKind::GetElemSuper);
1103 writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId());
1105 writer.returnFromIC();
1107 trackAttached("GetProp.MegamorphicNativeSlot");
1110 AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj,
1111 ObjOperandId objId,
1112 HandleId id,
1113 ValOperandId receiverId) {
1114 Maybe<PropertyInfo> prop;
1115 NativeObject* holder = nullptr;
1117 NativeGetPropKind kind =
1118 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
1119 switch (kind) {
1120 case NativeGetPropKind::None:
1121 return AttachDecision::NoAction;
1122 case NativeGetPropKind::Missing:
1123 case NativeGetPropKind::Slot: {
1124 auto* nobj = &obj->as<NativeObject>();
1126 if (mode_ == ICState::Mode::Megamorphic &&
1127 JSOp(*pc_) != JSOp::GetBoundName) {
1128 attachMegamorphicNativeSlot(objId, id);
1129 return AttachDecision::Attach;
1132 maybeEmitIdGuard(id);
1133 if (kind == NativeGetPropKind::Slot) {
1134 EmitReadSlotResult(writer, nobj, holder, *prop, objId);
1135 writer.returnFromIC();
1136 trackAttached("GetProp.NativeSlot");
1137 } else {
1138 EmitMissingPropResult(writer, nobj, objId);
1139 writer.returnFromIC();
1140 trackAttached("GetProp.Missing");
1142 return AttachDecision::Attach;
1144 case NativeGetPropKind::ScriptedGetter:
1145 case NativeGetPropKind::NativeGetter: {
1146 auto* nobj = &obj->as<NativeObject>();
1148 maybeEmitIdGuard(id);
1150 if (!isSuper() && CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, nobj,
1151 holder, *prop, mode_)) {
1152 EmitCallDOMGetterResult(cx_, writer, nobj, holder, id, *prop, objId);
1154 trackAttached("GetProp.DOMGetter");
1155 return AttachDecision::Attach;
1158 EmitCallGetterResult(cx_, writer, kind, nobj, holder, id, *prop, objId,
1159 receiverId, mode_);
1161 trackAttached("GetProp.NativeGetter");
1162 return AttachDecision::Attach;
1166 MOZ_CRASH("Bad NativeGetPropKind");
1169 // Returns whether obj is a WindowProxy wrapping the script's global.
1170 static bool IsWindowProxyForScriptGlobal(JSScript* script, JSObject* obj) {
1171 if (!IsWindowProxy(obj)) {
1172 return false;
1175 MOZ_ASSERT(obj->getClass() ==
1176 script->runtimeFromMainThread()->maybeWindowProxyClass());
1178 JSObject* window = ToWindowIfWindowProxy(obj);
1180 // Ion relies on the WindowProxy's group changing (and the group getting
1181 // marked as having unknown properties) on navigation. If we ever stop
1182 // transplanting same-compartment WindowProxies, this assert will fail and we
1183 // need to fix that code.
1184 MOZ_ASSERT(window == &obj->nonCCWGlobal());
1186 // This must be a WindowProxy for a global in this compartment. Else it would
1187 // be a cross-compartment wrapper and IsWindowProxy returns false for
1188 // those.
1189 MOZ_ASSERT(script->compartment() == obj->compartment());
1191 // Only optimize lookups on the WindowProxy for the current global. Other
1192 // WindowProxies in the compartment may require security checks (based on
1193 // mutable document.domain). See bug 1516775.
1194 return window == &script->global();
1197 // Guards objId is a WindowProxy for windowObj. Returns the window's operand id.
1198 static ObjOperandId GuardAndLoadWindowProxyWindow(CacheIRWriter& writer,
1199 ObjOperandId objId,
1200 GlobalObject* windowObj) {
1201 writer.guardClass(objId, GuardClassKind::WindowProxy);
1202 ObjOperandId windowObjId = writer.loadWrapperTarget(objId,
1203 /*fallible = */ false);
1204 writer.guardSpecificObject(windowObjId, windowObj);
1205 return windowObjId;
1208 // Whether a getter/setter on the global should have the WindowProxy as |this|
1209 // value instead of the Window (the global object). This always returns true for
1210 // scripted functions.
1211 static bool GetterNeedsWindowProxyThis(NativeObject* holder,
1212 PropertyInfo prop) {
1213 JSFunction* callee = &holder->getGetter(prop)->as<JSFunction>();
1214 return !callee->hasJitInfo() || callee->jitInfo()->needsOuterizedThisObject();
1216 static bool SetterNeedsWindowProxyThis(NativeObject* holder,
1217 PropertyInfo prop) {
1218 JSFunction* callee = &holder->getSetter(prop)->as<JSFunction>();
1219 return !callee->hasJitInfo() || callee->jitInfo()->needsOuterizedThisObject();
1222 AttachDecision GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
1223 ObjOperandId objId,
1224 HandleId id) {
1225 // Attach a stub when the receiver is a WindowProxy and we can do the lookup
1226 // on the Window (the global object).
1228 if (!IsWindowProxyForScriptGlobal(script_, obj)) {
1229 return AttachDecision::NoAction;
1232 // If we're megamorphic prefer a generic proxy stub that handles a lot more
1233 // cases.
1234 if (mode_ == ICState::Mode::Megamorphic) {
1235 return AttachDecision::NoAction;
1238 // Now try to do the lookup on the Window (the current global).
1239 GlobalObject* windowObj = cx_->global();
1240 NativeObject* holder = nullptr;
1241 Maybe<PropertyInfo> prop;
1242 NativeGetPropKind kind =
1243 CanAttachNativeGetProp(cx_, windowObj, id, &holder, &prop, pc_);
1244 switch (kind) {
1245 case NativeGetPropKind::None:
1246 return AttachDecision::NoAction;
1248 case NativeGetPropKind::Slot: {
1249 maybeEmitIdGuard(id);
1250 ObjOperandId windowObjId =
1251 GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
1252 EmitReadSlotResult(writer, windowObj, holder, *prop, windowObjId);
1253 writer.returnFromIC();
1255 trackAttached("GetProp.WindowProxySlot");
1256 return AttachDecision::Attach;
1259 case NativeGetPropKind::Missing: {
1260 maybeEmitIdGuard(id);
1261 ObjOperandId windowObjId =
1262 GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
1263 EmitMissingPropResult(writer, windowObj, windowObjId);
1264 writer.returnFromIC();
1266 trackAttached("GetProp.WindowProxyMissing");
1267 return AttachDecision::Attach;
1270 case NativeGetPropKind::NativeGetter:
1271 case NativeGetPropKind::ScriptedGetter: {
1272 // If a |super| access, it is not worth the complexity to attach an IC.
1273 if (isSuper()) {
1274 return AttachDecision::NoAction;
1277 bool needsWindowProxy = GetterNeedsWindowProxyThis(holder, *prop);
1279 // Guard the incoming object is a WindowProxy and inline a getter call
1280 // based on the Window object.
1281 maybeEmitIdGuard(id);
1282 ObjOperandId windowObjId =
1283 GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
1285 if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, windowObj, holder,
1286 *prop, mode_)) {
1287 MOZ_ASSERT(!needsWindowProxy);
1288 EmitCallDOMGetterResult(cx_, writer, windowObj, holder, id, *prop,
1289 windowObjId);
1290 trackAttached("GetProp.WindowProxyDOMGetter");
1291 } else {
1292 ValOperandId receiverId =
1293 writer.boxObject(needsWindowProxy ? objId : windowObjId);
1294 EmitCallGetterResult(cx_, writer, kind, windowObj, holder, id, *prop,
1295 windowObjId, receiverId, mode_);
1296 trackAttached("GetProp.WindowProxyGetter");
1299 return AttachDecision::Attach;
1303 MOZ_CRASH("Unreachable");
1306 AttachDecision GetPropIRGenerator::tryAttachCrossCompartmentWrapper(
1307 HandleObject obj, ObjOperandId objId, HandleId id) {
1308 // We can only optimize this very wrapper-handler, because others might
1309 // have a security policy.
1310 if (!IsWrapper(obj) ||
1311 Wrapper::wrapperHandler(obj) != &CrossCompartmentWrapper::singleton) {
1312 return AttachDecision::NoAction;
1315 // If we're megamorphic prefer a generic proxy stub that handles a lot more
1316 // cases.
1317 if (mode_ == ICState::Mode::Megamorphic) {
1318 return AttachDecision::NoAction;
1321 RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj));
1322 MOZ_ASSERT(unwrapped == UnwrapOneCheckedStatic(obj));
1323 MOZ_ASSERT(!IsCrossCompartmentWrapper(unwrapped),
1324 "CCWs must not wrap other CCWs");
1326 // If we allowed different zones we would have to wrap strings.
1327 if (unwrapped->compartment()->zone() != cx_->compartment()->zone()) {
1328 return AttachDecision::NoAction;
1331 // Take the unwrapped object's global, and wrap in a
1332 // this-compartment wrapper. This is what will be stored in the IC
1333 // keep the compartment alive.
1334 RootedObject wrappedTargetGlobal(cx_, &unwrapped->nonCCWGlobal());
1335 if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal)) {
1336 cx_->clearPendingException();
1337 return AttachDecision::NoAction;
1340 NativeObject* holder = nullptr;
1341 Maybe<PropertyInfo> prop;
1343 // Enter realm of target to prevent failing compartment assertions when doing
1344 // the lookup.
1346 AutoRealm ar(cx_, unwrapped);
1348 NativeGetPropKind kind =
1349 CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &prop, pc_);
1350 if (kind != NativeGetPropKind::Slot && kind != NativeGetPropKind::Missing) {
1351 return AttachDecision::NoAction;
1354 auto* unwrappedNative = &unwrapped->as<NativeObject>();
1356 maybeEmitIdGuard(id);
1357 writer.guardIsProxy(objId);
1358 writer.guardHasProxyHandler(objId, Wrapper::wrapperHandler(obj));
1360 // Load the object wrapped by the CCW
1361 ObjOperandId wrapperTargetId =
1362 writer.loadWrapperTarget(objId, /*fallible = */ false);
1364 // If the compartment of the wrapped object is different we should fail.
1365 writer.guardCompartment(wrapperTargetId, wrappedTargetGlobal,
1366 unwrappedNative->compartment());
1368 ObjOperandId unwrappedId = wrapperTargetId;
1369 if (holder) {
1370 EmitReadSlotResult<IsCrossCompartment::Yes>(writer, unwrappedNative, holder,
1371 *prop, unwrappedId);
1372 writer.wrapResult();
1373 writer.returnFromIC();
1374 trackAttached("GetProp.CCWSlot");
1375 } else {
1376 EmitMissingPropResult<IsCrossCompartment::Yes>(writer, unwrappedNative,
1377 unwrappedId);
1378 writer.returnFromIC();
1379 trackAttached("GetProp.CCWMissing");
1381 return AttachDecision::Attach;
1384 static JSObject* NewWrapperWithObjectShape(JSContext* cx,
1385 Handle<NativeObject*> obj);
1387 static bool GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray,
1388 MutableHandleObject wrapper) {
1389 Value v = GetProxyReservedSlot(xray, GetXrayJitInfo()->xrayHolderSlot);
1390 if (v.isObject()) {
1391 NativeObject* holder = &v.toObject().as<NativeObject>();
1392 v = holder->getFixedSlot(GetXrayJitInfo()->holderExpandoSlot);
1393 if (v.isObject()) {
1394 Rooted<NativeObject*> expando(
1395 cx, &UncheckedUnwrap(&v.toObject())->as<NativeObject>());
1396 wrapper.set(NewWrapperWithObjectShape(cx, expando));
1397 return wrapper != nullptr;
1400 wrapper.set(nullptr);
1401 return true;
1404 AttachDecision GetPropIRGenerator::tryAttachXrayCrossCompartmentWrapper(
1405 HandleObject obj, ObjOperandId objId, HandleId id,
1406 ValOperandId receiverId) {
1407 if (!obj->is<ProxyObject>()) {
1408 return AttachDecision::NoAction;
1411 JS::XrayJitInfo* info = GetXrayJitInfo();
1412 if (!info || !info->isCrossCompartmentXray(GetProxyHandler(obj))) {
1413 return AttachDecision::NoAction;
1416 if (!info->compartmentHasExclusiveExpandos(obj)) {
1417 return AttachDecision::NoAction;
1420 RootedObject target(cx_, UncheckedUnwrap(obj));
1422 RootedObject expandoShapeWrapper(cx_);
1423 if (!GetXrayExpandoShapeWrapper(cx_, obj, &expandoShapeWrapper)) {
1424 cx_->recoverFromOutOfMemory();
1425 return AttachDecision::NoAction;
1428 // Look for a getter we can call on the xray or its prototype chain.
1429 Rooted<Maybe<PropertyDescriptor>> desc(cx_);
1430 RootedObject holder(cx_, obj);
1431 RootedObjectVector prototypes(cx_);
1432 RootedObjectVector prototypeExpandoShapeWrappers(cx_);
1433 while (true) {
1434 if (!GetOwnPropertyDescriptor(cx_, holder, id, &desc)) {
1435 cx_->clearPendingException();
1436 return AttachDecision::NoAction;
1438 if (desc.isSome()) {
1439 break;
1441 if (!GetPrototype(cx_, holder, &holder)) {
1442 cx_->clearPendingException();
1443 return AttachDecision::NoAction;
1445 if (!holder || !holder->is<ProxyObject>() ||
1446 !info->isCrossCompartmentXray(GetProxyHandler(holder))) {
1447 return AttachDecision::NoAction;
1449 RootedObject prototypeExpandoShapeWrapper(cx_);
1450 if (!GetXrayExpandoShapeWrapper(cx_, holder,
1451 &prototypeExpandoShapeWrapper) ||
1452 !prototypes.append(holder) ||
1453 !prototypeExpandoShapeWrappers.append(prototypeExpandoShapeWrapper)) {
1454 cx_->recoverFromOutOfMemory();
1455 return AttachDecision::NoAction;
1458 if (!desc->isAccessorDescriptor()) {
1459 return AttachDecision::NoAction;
1462 RootedObject getter(cx_, desc->getter());
1463 if (!getter || !getter->is<JSFunction>() ||
1464 !getter->as<JSFunction>().isNativeWithoutJitEntry()) {
1465 return AttachDecision::NoAction;
1468 maybeEmitIdGuard(id);
1469 writer.guardIsProxy(objId);
1470 writer.guardHasProxyHandler(objId, GetProxyHandler(obj));
1472 // Load the object wrapped by the CCW
1473 ObjOperandId wrapperTargetId =
1474 writer.loadWrapperTarget(objId, /*fallible = */ false);
1476 // Test the wrapped object's class. The properties held by xrays or their
1477 // prototypes will be invariant for objects of a given class, except for
1478 // changes due to xray expandos or xray prototype mutations.
1479 writer.guardAnyClass(wrapperTargetId, target->getClass());
1481 // Make sure the expandos on the xray and its prototype chain match up with
1482 // what we expect. The expando shape needs to be consistent, to ensure it
1483 // has not had any shadowing properties added, and the expando cannot have
1484 // any custom prototype (xray prototypes are stable otherwise).
1486 // We can only do this for xrays with exclusive access to their expandos
1487 // (as we checked earlier), which store a pointer to their expando
1488 // directly. Xrays in other compartments may share their expandos with each
1489 // other and a VM call is needed just to find the expando.
1490 if (expandoShapeWrapper) {
1491 writer.guardXrayExpandoShapeAndDefaultProto(objId, expandoShapeWrapper);
1492 } else {
1493 writer.guardXrayNoExpando(objId);
1495 for (size_t i = 0; i < prototypes.length(); i++) {
1496 JSObject* proto = prototypes[i];
1497 ObjOperandId protoId = writer.loadObject(proto);
1498 if (JSObject* protoShapeWrapper = prototypeExpandoShapeWrappers[i]) {
1499 writer.guardXrayExpandoShapeAndDefaultProto(protoId, protoShapeWrapper);
1500 } else {
1501 writer.guardXrayNoExpando(protoId);
1505 bool sameRealm = cx_->realm() == getter->as<JSFunction>().realm();
1506 writer.callNativeGetterResult(receiverId, &getter->as<JSFunction>(),
1507 sameRealm);
1508 writer.returnFromIC();
1510 trackAttached("GetProp.XrayCCW");
1511 return AttachDecision::Attach;
1514 #ifdef JS_PUNBOX64
1515 AttachDecision GetPropIRGenerator::tryAttachScriptedProxy(
1516 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id) {
1517 if (cacheKind_ != CacheKind::GetProp && cacheKind_ != CacheKind::GetElem) {
1518 return AttachDecision::NoAction;
1520 if (cacheKind_ == CacheKind::GetElem) {
1521 if (!idVal_.isString() && !idVal_.isInt32() && !idVal_.isSymbol()) {
1522 return AttachDecision::NoAction;
1526 JSObject* handlerObj = ScriptedProxyHandler::handlerObject(obj);
1527 if (!handlerObj) {
1528 return AttachDecision::NoAction;
1531 NativeObject* trapHolder = nullptr;
1532 Maybe<PropertyInfo> trapProp;
1533 // We call with pc_ even though that's not the actual corresponding pc. It
1534 // should, however, be fine, because it's just used to check if this is a
1535 // GetBoundName, which it's not.
1536 NativeGetPropKind trapKind = CanAttachNativeGetProp(
1537 cx_, handlerObj, NameToId(cx_->names().get), &trapHolder, &trapProp, pc_);
1539 if (trapKind != NativeGetPropKind::Missing &&
1540 trapKind != NativeGetPropKind::Slot) {
1541 return AttachDecision::NoAction;
1544 if (trapKind != NativeGetPropKind::Missing) {
1545 uint32_t trapSlot = trapProp->slot();
1546 const Value& trapVal = trapHolder->getSlot(trapSlot);
1547 if (!trapVal.isObject()) {
1548 return AttachDecision::NoAction;
1551 JSObject* trapObj = &trapVal.toObject();
1552 if (!trapObj->is<JSFunction>()) {
1553 return AttachDecision::NoAction;
1556 JSFunction* trapFn = &trapObj->as<JSFunction>();
1557 if (trapFn->isClassConstructor()) {
1558 return AttachDecision::NoAction;
1561 if (!trapFn->hasJitEntry()) {
1562 return AttachDecision::NoAction;
1565 if (cx_->realm() != trapFn->realm()) {
1566 return AttachDecision::NoAction;
1570 NativeObject* nHandlerObj = &handlerObj->as<NativeObject>();
1571 JSObject* targetObj = obj->target();
1572 MOZ_ASSERT(targetObj, "Guaranteed by the scripted Proxy constructor");
1574 // We just require that the target is a NativeObject to make our lives
1575 // easier. There's too much nonsense we might have to handle otherwise and
1576 // we're not set up to recursively call GetPropIRGenerator::tryAttachStub
1577 // for the target object.
1578 if (!targetObj->is<NativeObject>()) {
1579 return AttachDecision::NoAction;
1582 writer.guardIsProxy(objId);
1583 writer.guardHasProxyHandler(objId, &ScriptedProxyHandler::singleton);
1584 ObjOperandId handlerObjId = writer.loadScriptedProxyHandler(objId);
1585 ObjOperandId targetObjId =
1586 writer.loadWrapperTarget(objId, /*fallible =*/true);
1588 writer.guardIsNativeObject(targetObjId);
1590 if (trapKind == NativeGetPropKind::Missing) {
1591 EmitMissingPropGuard(writer, nHandlerObj, handlerObjId);
1592 if (cacheKind_ == CacheKind::GetProp) {
1593 writer.megamorphicLoadSlotResult(targetObjId, id);
1594 } else {
1595 writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId());
1597 } else {
1598 uint32_t trapSlot = trapProp->slot();
1599 const Value& trapVal = trapHolder->getSlot(trapSlot);
1600 JSObject* trapObj = &trapVal.toObject();
1601 JSFunction* trapFn = &trapObj->as<JSFunction>();
1602 ObjOperandId trapHolderId =
1603 EmitReadSlotGuard(writer, nHandlerObj, trapHolder, handlerObjId);
1605 ValOperandId fnValId =
1606 EmitLoadSlot(writer, trapHolder, trapHolderId, trapSlot);
1607 ObjOperandId fnObjId = writer.guardToObject(fnValId);
1608 writer.guardSpecificFunction(fnObjId, trapFn);
1609 ValOperandId targetValId = writer.boxObject(targetObjId);
1610 if (cacheKind_ == CacheKind::GetProp) {
1611 writer.callScriptedProxyGetResult(targetValId, objId, handlerObjId,
1612 trapFn, id);
1613 } else {
1614 ValOperandId idId = getElemKeyValueId();
1615 ValOperandId stringIdId = writer.idToStringOrSymbol(idId);
1616 writer.callScriptedProxyGetByValueResult(targetValId, objId, handlerObjId,
1617 stringIdId, trapFn);
1620 writer.returnFromIC();
1622 trackAttached("GetScriptedProxy");
1623 return AttachDecision::Attach;
1625 #endif
1627 AttachDecision GetPropIRGenerator::tryAttachGenericProxy(
1628 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
1629 bool handleDOMProxies) {
1630 writer.guardIsProxy(objId);
1632 if (!handleDOMProxies) {
1633 // Ensure that the incoming object is not a DOM proxy, so that we can get to
1634 // the specialized stubs
1635 writer.guardIsNotDOMProxy(objId);
1638 if (cacheKind_ == CacheKind::GetProp || mode_ == ICState::Mode::Specialized) {
1639 MOZ_ASSERT(!isSuper());
1640 maybeEmitIdGuard(id);
1641 writer.proxyGetResult(objId, id);
1642 } else {
1643 // Attach a stub that handles every id.
1644 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
1645 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
1646 MOZ_ASSERT(!isSuper());
1647 writer.proxyGetByValueResult(objId, getElemKeyValueId());
1650 writer.returnFromIC();
1652 trackAttached("GetProp.GenericProxy");
1653 return AttachDecision::Attach;
1656 static bool ValueIsInt64Index(const Value& val, int64_t* index) {
1657 // Try to convert the Value to a TypedArray index or DataView offset.
1659 if (val.isInt32()) {
1660 *index = val.toInt32();
1661 return true;
1664 if (val.isDouble()) {
1665 // Use NumberEqualsInt64 because ToPropertyKey(-0) is 0.
1666 return mozilla::NumberEqualsInt64(val.toDouble(), index);
1669 return false;
1672 IntPtrOperandId IRGenerator::guardToIntPtrIndex(const Value& index,
1673 ValOperandId indexId,
1674 bool supportOOB) {
1675 #ifdef DEBUG
1676 int64_t indexInt64;
1677 MOZ_ASSERT_IF(!supportOOB, ValueIsInt64Index(index, &indexInt64));
1678 #endif
1680 if (index.isInt32()) {
1681 Int32OperandId int32IndexId = writer.guardToInt32(indexId);
1682 return writer.int32ToIntPtr(int32IndexId);
1685 MOZ_ASSERT(index.isNumber());
1686 NumberOperandId numberIndexId = writer.guardIsNumber(indexId);
1687 return writer.guardNumberToIntPtrIndex(numberIndexId, supportOOB);
1690 ObjOperandId IRGenerator::guardDOMProxyExpandoObjectAndShape(
1691 ProxyObject* obj, ObjOperandId objId, const Value& expandoVal,
1692 NativeObject* expandoObj) {
1693 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1695 TestMatchingProxyReceiver(writer, obj, objId);
1697 // Shape determines Class, so now it must be a DOM proxy.
1698 ValOperandId expandoValId;
1699 if (expandoVal.isObject()) {
1700 expandoValId = writer.loadDOMExpandoValue(objId);
1701 } else {
1702 expandoValId = writer.loadDOMExpandoValueIgnoreGeneration(objId);
1705 // Guard the expando is an object and shape guard.
1706 ObjOperandId expandoObjId = writer.guardToObject(expandoValId);
1707 TestMatchingHolder(writer, expandoObj, expandoObjId);
1708 return expandoObjId;
1711 AttachDecision GetPropIRGenerator::tryAttachDOMProxyExpando(
1712 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
1713 ValOperandId receiverId) {
1714 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1716 Value expandoVal = GetProxyPrivate(obj);
1717 JSObject* expandoObj;
1718 if (expandoVal.isObject()) {
1719 expandoObj = &expandoVal.toObject();
1720 } else {
1721 MOZ_ASSERT(!expandoVal.isUndefined(),
1722 "How did a missing expando manage to shadow things?");
1723 auto expandoAndGeneration =
1724 static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
1725 MOZ_ASSERT(expandoAndGeneration);
1726 expandoObj = &expandoAndGeneration->expando.toObject();
1729 // Try to do the lookup on the expando object.
1730 NativeObject* holder = nullptr;
1731 Maybe<PropertyInfo> prop;
1732 NativeGetPropKind kind =
1733 CanAttachNativeGetProp(cx_, expandoObj, id, &holder, &prop, pc_);
1734 if (kind == NativeGetPropKind::None) {
1735 return AttachDecision::NoAction;
1737 if (!holder) {
1738 return AttachDecision::NoAction;
1740 auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
1742 MOZ_ASSERT(holder == nativeExpandoObj);
1744 maybeEmitIdGuard(id);
1745 ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
1746 obj, objId, expandoVal, nativeExpandoObj);
1748 if (kind == NativeGetPropKind::Slot) {
1749 // Load from the expando's slots.
1750 EmitLoadSlotResult(writer, expandoObjId, nativeExpandoObj, *prop);
1751 writer.returnFromIC();
1752 } else {
1753 // Call the getter. Note that we pass objId, the DOM proxy, as |this|
1754 // and not the expando object.
1755 MOZ_ASSERT(kind == NativeGetPropKind::NativeGetter ||
1756 kind == NativeGetPropKind::ScriptedGetter);
1757 EmitGuardGetterSetterSlot(writer, nativeExpandoObj, *prop, expandoObjId);
1758 EmitCallGetterResultNoGuards(cx_, writer, kind, nativeExpandoObj,
1759 nativeExpandoObj, *prop, receiverId);
1762 trackAttached("GetProp.DOMProxyExpando");
1763 return AttachDecision::Attach;
1766 AttachDecision GetPropIRGenerator::tryAttachDOMProxyShadowed(
1767 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id) {
1768 MOZ_ASSERT(!isSuper());
1769 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1771 maybeEmitIdGuard(id);
1772 TestMatchingProxyReceiver(writer, obj, objId);
1773 writer.proxyGetResult(objId, id);
1774 writer.returnFromIC();
1776 trackAttached("GetProp.DOMProxyShadowed");
1777 return AttachDecision::Attach;
1780 // Emit CacheIR to guard the DOM proxy doesn't shadow |id|. There are two types
1781 // of DOM proxies:
1783 // (a) DOM proxies marked LegacyOverrideBuiltIns in WebIDL, for example
1784 // HTMLDocument or HTMLFormElement. These proxies look up properties in this
1785 // order:
1787 // (1) The expando object.
1788 // (2) The proxy's named-property handler.
1789 // (3) The prototype chain.
1791 // To optimize properties on the prototype chain, we have to guard that (1)
1792 // and (2) don't shadow (3). We handle (1) by either emitting a shape guard
1793 // for the expando object or by guarding the proxy has no expando object. To
1794 // efficiently handle (2), the proxy must have an ExpandoAndGeneration*
1795 // stored as PrivateValue. We guard on its generation field to ensure the
1796 // set of names hasn't changed.
1798 // Missing properties can be optimized in a similar way by emitting shape
1799 // guards for the prototype chain.
1801 // (b) Other DOM proxies. These proxies look up properties in this
1802 // order:
1804 // (1) The expando object.
1805 // (2) The prototype chain.
1806 // (3) The proxy's named-property handler.
1808 // To optimize properties on the prototype chain, we only have to guard the
1809 // expando object doesn't shadow it.
1811 // Missing properties can't be optimized in this case because we don't have
1812 // an efficient way to guard against the proxy handler shadowing the
1813 // property (there's no ExpandoAndGeneration*).
1815 // See also:
1816 // * DOMProxyShadows in DOMJSProxyHandler.cpp
1817 // * https://webidl.spec.whatwg.org/#dfn-named-property-visibility (the Note at
1818 // the end)
1820 // Callers are expected to have already guarded on the shape of the
1821 // object, which guarantees the object is a DOM proxy.
1822 static void CheckDOMProxyDoesNotShadow(CacheIRWriter& writer, ProxyObject* obj,
1823 jsid id, ObjOperandId objId,
1824 bool* canOptimizeMissing) {
1825 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1827 Value expandoVal = GetProxyPrivate(obj);
1829 ValOperandId expandoId;
1830 if (!expandoVal.isObject() && !expandoVal.isUndefined()) {
1831 // Case (a).
1832 auto expandoAndGeneration =
1833 static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
1834 uint64_t generation = expandoAndGeneration->generation;
1835 expandoId = writer.loadDOMExpandoValueGuardGeneration(
1836 objId, expandoAndGeneration, generation);
1837 expandoVal = expandoAndGeneration->expando;
1838 *canOptimizeMissing = true;
1839 } else {
1840 // Case (b).
1841 expandoId = writer.loadDOMExpandoValue(objId);
1842 *canOptimizeMissing = false;
1845 if (expandoVal.isUndefined()) {
1846 // Guard there's no expando object.
1847 writer.guardNonDoubleType(expandoId, ValueType::Undefined);
1848 } else if (expandoVal.isObject()) {
1849 // Guard the proxy either has no expando object or, if it has one, that
1850 // the shape matches the current expando object.
1851 NativeObject& expandoObj = expandoVal.toObject().as<NativeObject>();
1852 MOZ_ASSERT(!expandoObj.containsPure(id));
1853 writer.guardDOMExpandoMissingOrGuardShape(expandoId, expandoObj.shape());
1854 } else {
1855 MOZ_CRASH("Invalid expando value");
1859 AttachDecision GetPropIRGenerator::tryAttachDOMProxyUnshadowed(
1860 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
1861 ValOperandId receiverId) {
1862 MOZ_ASSERT(IsCacheableDOMProxy(obj));
1864 JSObject* protoObj = obj->staticPrototype();
1865 if (!protoObj) {
1866 return AttachDecision::NoAction;
1869 NativeObject* holder = nullptr;
1870 Maybe<PropertyInfo> prop;
1871 NativeGetPropKind kind =
1872 CanAttachNativeGetProp(cx_, protoObj, id, &holder, &prop, pc_);
1873 if (kind == NativeGetPropKind::None) {
1874 return AttachDecision::NoAction;
1876 auto* nativeProtoObj = &protoObj->as<NativeObject>();
1878 maybeEmitIdGuard(id);
1880 // Guard that our proxy (expando) object hasn't started shadowing this
1881 // property.
1882 TestMatchingProxyReceiver(writer, obj, objId);
1883 bool canOptimizeMissing = false;
1884 CheckDOMProxyDoesNotShadow(writer, obj, id, objId, &canOptimizeMissing);
1886 if (holder) {
1887 // Found the property on the prototype chain. Treat it like a native
1888 // getprop.
1889 GeneratePrototypeGuards(writer, obj, holder, objId);
1891 // Guard on the holder of the property.
1892 ObjOperandId holderId = writer.loadObject(holder);
1893 TestMatchingHolder(writer, holder, holderId);
1895 if (kind == NativeGetPropKind::Slot) {
1896 EmitLoadSlotResult(writer, holderId, holder, *prop);
1897 writer.returnFromIC();
1898 } else {
1899 // EmitCallGetterResultNoGuards expects |obj| to be the object the
1900 // property is on to do some checks. Since we actually looked at
1901 // checkObj, and no extra guards will be generated, we can just
1902 // pass that instead.
1903 MOZ_ASSERT(kind == NativeGetPropKind::NativeGetter ||
1904 kind == NativeGetPropKind::ScriptedGetter);
1905 MOZ_ASSERT(!isSuper());
1906 EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
1907 /* holderIsConstant = */ true);
1908 EmitCallGetterResultNoGuards(cx_, writer, kind, nativeProtoObj, holder,
1909 *prop, receiverId);
1911 } else {
1912 // Property was not found on the prototype chain.
1913 MOZ_ASSERT(kind == NativeGetPropKind::Missing);
1914 if (canOptimizeMissing) {
1915 // We already guarded on the proxy's shape, so now shape guard the proto
1916 // chain.
1917 ObjOperandId protoId = writer.loadObject(nativeProtoObj);
1918 EmitMissingPropResult(writer, nativeProtoObj, protoId);
1919 } else {
1920 MOZ_ASSERT(!isSuper());
1921 writer.proxyGetResult(objId, id);
1923 writer.returnFromIC();
1926 trackAttached("GetProp.DOMProxyUnshadowed");
1927 return AttachDecision::Attach;
1930 AttachDecision GetPropIRGenerator::tryAttachProxy(HandleObject obj,
1931 ObjOperandId objId,
1932 HandleId id,
1933 ValOperandId receiverId) {
1934 // The proxy stubs don't currently support |super| access.
1935 if (isSuper()) {
1936 return AttachDecision::NoAction;
1939 // Always try to attach scripted proxy get even if we're megamorphic.
1940 // In Speedometer 3 we'll often run into cases where we're megamorphic
1941 // overall, but monomorphic for the proxy case. This is because there
1942 // are functions which lazily turn various differently-shaped objects
1943 // into proxies. So the un-proxified objects are megamorphic, but the
1944 // proxy handlers are actually monomorphic. There is room for a bit
1945 // more sophistication here, but this should do for now.
1946 if (!obj->is<ProxyObject>()) {
1947 return AttachDecision::NoAction;
1949 auto proxy = obj.as<ProxyObject>();
1950 #ifdef JS_PUNBOX64
1951 if (proxy->handler()->isScripted()) {
1952 TRY_ATTACH(tryAttachScriptedProxy(proxy, objId, id));
1954 #endif
1956 ProxyStubType type = GetProxyStubType(cx_, obj, id);
1957 if (type == ProxyStubType::None) {
1958 return AttachDecision::NoAction;
1961 if (mode_ == ICState::Mode::Megamorphic) {
1962 return tryAttachGenericProxy(proxy, objId, id,
1963 /* handleDOMProxies = */ true);
1966 switch (type) {
1967 case ProxyStubType::None:
1968 break;
1969 case ProxyStubType::DOMExpando:
1970 TRY_ATTACH(tryAttachDOMProxyExpando(proxy, objId, id, receiverId));
1971 [[fallthrough]]; // Fall through to the generic shadowed case.
1972 case ProxyStubType::DOMShadowed:
1973 return tryAttachDOMProxyShadowed(proxy, objId, id);
1974 case ProxyStubType::DOMUnshadowed:
1975 TRY_ATTACH(tryAttachDOMProxyUnshadowed(proxy, objId, id, receiverId));
1976 return tryAttachGenericProxy(proxy, objId, id,
1977 /* handleDOMProxies = */ true);
1978 case ProxyStubType::Generic:
1979 return tryAttachGenericProxy(proxy, objId, id,
1980 /* handleDOMProxies = */ false);
1983 MOZ_CRASH("Unexpected ProxyStubType");
1986 const JSClass* js::jit::ClassFor(GuardClassKind kind) {
1987 switch (kind) {
1988 case GuardClassKind::Array:
1989 return &ArrayObject::class_;
1990 case GuardClassKind::PlainObject:
1991 return &PlainObject::class_;
1992 case GuardClassKind::FixedLengthArrayBuffer:
1993 return &FixedLengthArrayBufferObject::class_;
1994 case GuardClassKind::ResizableArrayBuffer:
1995 return &ResizableArrayBufferObject::class_;
1996 case GuardClassKind::FixedLengthSharedArrayBuffer:
1997 return &FixedLengthSharedArrayBufferObject::class_;
1998 case GuardClassKind::GrowableSharedArrayBuffer:
1999 return &GrowableSharedArrayBufferObject::class_;
2000 case GuardClassKind::FixedLengthDataView:
2001 return &FixedLengthDataViewObject::class_;
2002 case GuardClassKind::ResizableDataView:
2003 return &ResizableDataViewObject::class_;
2004 case GuardClassKind::MappedArguments:
2005 return &MappedArgumentsObject::class_;
2006 case GuardClassKind::UnmappedArguments:
2007 return &UnmappedArgumentsObject::class_;
2008 case GuardClassKind::WindowProxy:
2009 // Caller needs to handle this case, see
2010 // JSRuntime::maybeWindowProxyClass().
2011 break;
2012 case GuardClassKind::JSFunction:
2013 // Caller needs to handle this case. Can be either |js::FunctionClass| or
2014 // |js::ExtendedFunctionClass|.
2015 break;
2016 case GuardClassKind::BoundFunction:
2017 return &BoundFunctionObject::class_;
2018 case GuardClassKind::Set:
2019 return &SetObject::class_;
2020 case GuardClassKind::Map:
2021 return &MapObject::class_;
2023 MOZ_CRASH("unexpected kind");
2026 // Guards the class of an object. Because shape implies class, and a shape guard
2027 // is faster than a class guard, if this is our first time attaching a stub, we
2028 // instead generate a shape guard.
2029 void IRGenerator::emitOptimisticClassGuard(ObjOperandId objId, JSObject* obj,
2030 GuardClassKind kind) {
2031 #ifdef DEBUG
2032 switch (kind) {
2033 case GuardClassKind::Array:
2034 case GuardClassKind::PlainObject:
2035 case GuardClassKind::FixedLengthArrayBuffer:
2036 case GuardClassKind::ResizableArrayBuffer:
2037 case GuardClassKind::FixedLengthSharedArrayBuffer:
2038 case GuardClassKind::GrowableSharedArrayBuffer:
2039 case GuardClassKind::FixedLengthDataView:
2040 case GuardClassKind::ResizableDataView:
2041 case GuardClassKind::Set:
2042 case GuardClassKind::Map:
2043 MOZ_ASSERT(obj->hasClass(ClassFor(kind)));
2044 break;
2046 case GuardClassKind::MappedArguments:
2047 case GuardClassKind::UnmappedArguments:
2048 case GuardClassKind::JSFunction:
2049 case GuardClassKind::BoundFunction:
2050 case GuardClassKind::WindowProxy:
2051 // Arguments, functions, and the global object have
2052 // less consistent shapes.
2053 MOZ_CRASH("GuardClassKind not supported");
2055 #endif
2057 if (isFirstStub_) {
2058 writer.guardShapeForClass(objId, obj->shape());
2059 } else {
2060 writer.guardClass(objId, kind);
2064 static void AssertArgumentsCustomDataProp(ArgumentsObject* obj,
2065 PropertyKey key) {
2066 #ifdef DEBUG
2067 // The property must still be a custom data property if it has been resolved.
2068 // If this assertion fails, we're probably missing a call to mark this
2069 // property overridden.
2070 Maybe<PropertyInfo> prop = obj->lookupPure(key);
2071 MOZ_ASSERT_IF(prop, prop->isCustomDataProperty());
2072 #endif
2075 AttachDecision GetPropIRGenerator::tryAttachObjectLength(HandleObject obj,
2076 ObjOperandId objId,
2077 HandleId id) {
2078 if (!id.isAtom(cx_->names().length)) {
2079 return AttachDecision::NoAction;
2082 if (obj->is<ArrayObject>()) {
2083 if (obj->as<ArrayObject>().length() > INT32_MAX) {
2084 return AttachDecision::NoAction;
2087 maybeEmitIdGuard(id);
2088 emitOptimisticClassGuard(objId, obj, GuardClassKind::Array);
2089 writer.loadInt32ArrayLengthResult(objId);
2090 writer.returnFromIC();
2092 trackAttached("GetProp.ArrayLength");
2093 return AttachDecision::Attach;
2096 if (obj->is<ArgumentsObject>() &&
2097 !obj->as<ArgumentsObject>().hasOverriddenLength()) {
2098 AssertArgumentsCustomDataProp(&obj->as<ArgumentsObject>(), id);
2099 maybeEmitIdGuard(id);
2100 if (obj->is<MappedArgumentsObject>()) {
2101 writer.guardClass(objId, GuardClassKind::MappedArguments);
2102 } else {
2103 MOZ_ASSERT(obj->is<UnmappedArgumentsObject>());
2104 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
2106 writer.loadArgumentsObjectLengthResult(objId);
2107 writer.returnFromIC();
2109 trackAttached("GetProp.ArgumentsObjectLength");
2110 return AttachDecision::Attach;
2113 return AttachDecision::NoAction;
2116 AttachDecision GetPropIRGenerator::tryAttachTypedArray(HandleObject obj,
2117 ObjOperandId objId,
2118 HandleId id) {
2119 if (!obj->is<TypedArrayObject>()) {
2120 return AttachDecision::NoAction;
2123 if (mode_ != ICState::Mode::Specialized) {
2124 return AttachDecision::NoAction;
2127 // Receiver should be the object.
2128 if (isSuper()) {
2129 return AttachDecision::NoAction;
2132 bool isLength = id.isAtom(cx_->names().length);
2133 bool isByteOffset = id.isAtom(cx_->names().byteOffset);
2134 if (!isLength && !isByteOffset && !id.isAtom(cx_->names().byteLength)) {
2135 return AttachDecision::NoAction;
2138 NativeObject* holder = nullptr;
2139 Maybe<PropertyInfo> prop;
2140 NativeGetPropKind kind =
2141 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
2142 if (kind != NativeGetPropKind::NativeGetter) {
2143 return AttachDecision::NoAction;
2146 JSFunction& fun = holder->getGetter(*prop)->as<JSFunction>();
2147 if (isLength) {
2148 if (!TypedArrayObject::isOriginalLengthGetter(fun.native())) {
2149 return AttachDecision::NoAction;
2151 } else if (isByteOffset) {
2152 if (!TypedArrayObject::isOriginalByteOffsetGetter(fun.native())) {
2153 return AttachDecision::NoAction;
2155 } else {
2156 if (!TypedArrayObject::isOriginalByteLengthGetter(fun.native())) {
2157 return AttachDecision::NoAction;
2161 auto* tarr = &obj->as<TypedArrayObject>();
2163 maybeEmitIdGuard(id);
2164 // Emit all the normal guards for calling this native, but specialize
2165 // callNativeGetterResult.
2166 EmitCallGetterResultGuards(writer, tarr, holder, id, *prop, objId, mode_);
2167 if (isLength) {
2168 size_t length = tarr->length().valueOr(0);
2169 if (tarr->is<FixedLengthTypedArrayObject>()) {
2170 if (length <= INT32_MAX) {
2171 writer.loadArrayBufferViewLengthInt32Result(objId);
2172 } else {
2173 writer.loadArrayBufferViewLengthDoubleResult(objId);
2175 } else {
2176 if (length <= INT32_MAX) {
2177 writer.resizableTypedArrayLengthInt32Result(objId);
2178 } else {
2179 writer.resizableTypedArrayLengthDoubleResult(objId);
2182 trackAttached("GetProp.TypedArrayLength");
2183 } else if (isByteOffset) {
2184 // byteOffset doesn't need to use different code paths for fixed-length and
2185 // resizable TypedArrays.
2186 size_t byteOffset = tarr->byteOffset().valueOr(0);
2187 if (byteOffset <= INT32_MAX) {
2188 writer.arrayBufferViewByteOffsetInt32Result(objId);
2189 } else {
2190 writer.arrayBufferViewByteOffsetDoubleResult(objId);
2192 trackAttached("GetProp.TypedArrayByteOffset");
2193 } else {
2194 size_t byteLength = tarr->byteLength().valueOr(0);
2195 if (tarr->is<FixedLengthTypedArrayObject>()) {
2196 if (byteLength <= INT32_MAX) {
2197 writer.typedArrayByteLengthInt32Result(objId);
2198 } else {
2199 writer.typedArrayByteLengthDoubleResult(objId);
2201 } else {
2202 if (byteLength <= INT32_MAX) {
2203 writer.resizableTypedArrayByteLengthInt32Result(objId);
2204 } else {
2205 writer.resizableTypedArrayByteLengthDoubleResult(objId);
2208 trackAttached("GetProp.TypedArrayByteLength");
2210 writer.returnFromIC();
2212 return AttachDecision::Attach;
2215 AttachDecision GetPropIRGenerator::tryAttachDataView(HandleObject obj,
2216 ObjOperandId objId,
2217 HandleId id) {
2218 if (!obj->is<DataViewObject>()) {
2219 return AttachDecision::NoAction;
2221 auto* dv = &obj->as<DataViewObject>();
2223 if (mode_ != ICState::Mode::Specialized) {
2224 return AttachDecision::NoAction;
2227 // Receiver should be the object.
2228 if (isSuper()) {
2229 return AttachDecision::NoAction;
2232 bool isByteOffset = id.isAtom(cx_->names().byteOffset);
2233 if (!isByteOffset && !id.isAtom(cx_->names().byteLength)) {
2234 return AttachDecision::NoAction;
2237 // byteOffset and byteLength both throw when the ArrayBuffer is detached.
2238 if (dv->hasDetachedBuffer()) {
2239 return AttachDecision::NoAction;
2242 // byteOffset and byteLength both throw when the ArrayBuffer is out-of-bounds.
2243 if (dv->is<ResizableDataViewObject>() &&
2244 dv->as<ResizableDataViewObject>().isOutOfBounds()) {
2245 return AttachDecision::NoAction;
2248 NativeObject* holder = nullptr;
2249 Maybe<PropertyInfo> prop;
2250 NativeGetPropKind kind =
2251 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
2252 if (kind != NativeGetPropKind::NativeGetter) {
2253 return AttachDecision::NoAction;
2256 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
2257 if (isByteOffset) {
2258 if (!DataViewObject::isOriginalByteOffsetGetter(fun.native())) {
2259 return AttachDecision::NoAction;
2261 } else {
2262 if (!DataViewObject::isOriginalByteLengthGetter(fun.native())) {
2263 return AttachDecision::NoAction;
2267 maybeEmitIdGuard(id);
2268 // Emit all the normal guards for calling this native, but specialize
2269 // callNativeGetterResult.
2270 EmitCallGetterResultGuards(writer, dv, holder, id, *prop, objId, mode_);
2271 writer.guardHasAttachedArrayBuffer(objId);
2272 if (dv->is<ResizableDataViewObject>()) {
2273 writer.guardResizableArrayBufferViewInBounds(objId);
2275 if (isByteOffset) {
2276 // byteOffset doesn't need to use different code paths for fixed-length and
2277 // resizable DataViews.
2278 size_t byteOffset = dv->byteOffset().valueOr(0);
2279 if (byteOffset <= INT32_MAX) {
2280 writer.arrayBufferViewByteOffsetInt32Result(objId);
2281 } else {
2282 writer.arrayBufferViewByteOffsetDoubleResult(objId);
2284 trackAttached("GetProp.DataViewByteOffset");
2285 } else {
2286 size_t byteLength = dv->byteLength().valueOr(0);
2287 if (dv->is<FixedLengthDataViewObject>()) {
2288 if (byteLength <= INT32_MAX) {
2289 writer.loadArrayBufferViewLengthInt32Result(objId);
2290 } else {
2291 writer.loadArrayBufferViewLengthDoubleResult(objId);
2293 } else {
2294 if (byteLength <= INT32_MAX) {
2295 writer.resizableDataViewByteLengthInt32Result(objId);
2296 } else {
2297 writer.resizableDataViewByteLengthDoubleResult(objId);
2300 trackAttached("GetProp.DataViewByteLength");
2302 writer.returnFromIC();
2304 return AttachDecision::Attach;
2307 AttachDecision GetPropIRGenerator::tryAttachArrayBufferMaybeShared(
2308 HandleObject obj, ObjOperandId objId, HandleId id) {
2309 if (!obj->is<ArrayBufferObjectMaybeShared>()) {
2310 return AttachDecision::NoAction;
2312 auto* buf = &obj->as<ArrayBufferObjectMaybeShared>();
2314 if (mode_ != ICState::Mode::Specialized) {
2315 return AttachDecision::NoAction;
2318 // Receiver should be the object.
2319 if (isSuper()) {
2320 return AttachDecision::NoAction;
2323 if (!id.isAtom(cx_->names().byteLength)) {
2324 return AttachDecision::NoAction;
2327 NativeObject* holder = nullptr;
2328 Maybe<PropertyInfo> prop;
2329 NativeGetPropKind kind =
2330 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
2331 if (kind != NativeGetPropKind::NativeGetter) {
2332 return AttachDecision::NoAction;
2335 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
2336 if (buf->is<ArrayBufferObject>()) {
2337 if (!ArrayBufferObject::isOriginalByteLengthGetter(fun.native())) {
2338 return AttachDecision::NoAction;
2340 } else {
2341 if (!SharedArrayBufferObject::isOriginalByteLengthGetter(fun.native())) {
2342 return AttachDecision::NoAction;
2346 maybeEmitIdGuard(id);
2347 // Emit all the normal guards for calling this native, but specialize
2348 // callNativeGetterResult.
2349 EmitCallGetterResultGuards(writer, buf, holder, id, *prop, objId, mode_);
2350 if (!buf->is<GrowableSharedArrayBufferObject>()) {
2351 if (buf->byteLength() <= INT32_MAX) {
2352 writer.loadArrayBufferByteLengthInt32Result(objId);
2353 } else {
2354 writer.loadArrayBufferByteLengthDoubleResult(objId);
2356 } else {
2357 if (buf->byteLength() <= INT32_MAX) {
2358 writer.growableSharedArrayBufferByteLengthInt32Result(objId);
2359 } else {
2360 writer.growableSharedArrayBufferByteLengthDoubleResult(objId);
2363 writer.returnFromIC();
2365 trackAttached("GetProp.ArrayBufferMaybeSharedByteLength");
2366 return AttachDecision::Attach;
2369 AttachDecision GetPropIRGenerator::tryAttachRegExp(HandleObject obj,
2370 ObjOperandId objId,
2371 HandleId id) {
2372 if (!obj->is<RegExpObject>()) {
2373 return AttachDecision::NoAction;
2375 auto* regExp = &obj->as<RegExpObject>();
2377 if (mode_ != ICState::Mode::Specialized) {
2378 return AttachDecision::NoAction;
2381 // Receiver should be the object.
2382 if (isSuper()) {
2383 return AttachDecision::NoAction;
2386 NativeObject* holder = nullptr;
2387 Maybe<PropertyInfo> prop;
2388 NativeGetPropKind kind =
2389 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
2390 if (kind != NativeGetPropKind::NativeGetter) {
2391 return AttachDecision::NoAction;
2394 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
2395 JS::RegExpFlags flags = JS::RegExpFlag::NoFlags;
2396 if (!RegExpObject::isOriginalFlagGetter(fun.native(), &flags)) {
2397 return AttachDecision::NoAction;
2400 maybeEmitIdGuard(id);
2401 // Emit all the normal guards for calling this native, but specialize
2402 // callNativeGetterResult.
2403 EmitCallGetterResultGuards(writer, regExp, holder, id, *prop, objId, mode_);
2405 writer.regExpFlagResult(objId, flags.value());
2406 writer.returnFromIC();
2408 trackAttached("GetProp.RegExpFlag");
2409 return AttachDecision::Attach;
2412 AttachDecision GetPropIRGenerator::tryAttachMap(HandleObject obj,
2413 ObjOperandId objId,
2414 HandleId id) {
2415 if (!obj->is<MapObject>()) {
2416 return AttachDecision::NoAction;
2418 auto* mapObj = &obj->as<MapObject>();
2420 if (mode_ != ICState::Mode::Specialized) {
2421 return AttachDecision::NoAction;
2424 // Receiver should be the object.
2425 if (isSuper()) {
2426 return AttachDecision::NoAction;
2429 if (!id.isAtom(cx_->names().size)) {
2430 return AttachDecision::NoAction;
2433 NativeObject* holder = nullptr;
2434 Maybe<PropertyInfo> prop;
2435 NativeGetPropKind kind =
2436 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
2437 if (kind != NativeGetPropKind::NativeGetter) {
2438 return AttachDecision::NoAction;
2441 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
2442 if (!MapObject::isOriginalSizeGetter(fun.native())) {
2443 return AttachDecision::NoAction;
2446 maybeEmitIdGuard(id);
2448 // Emit all the normal guards for calling this native, but specialize
2449 // callNativeGetterResult.
2450 EmitCallGetterResultGuards(writer, mapObj, holder, id, *prop, objId, mode_);
2452 writer.mapSizeResult(objId);
2453 writer.returnFromIC();
2455 trackAttached("GetProp.MapSize");
2456 return AttachDecision::Attach;
2459 AttachDecision GetPropIRGenerator::tryAttachSet(HandleObject obj,
2460 ObjOperandId objId,
2461 HandleId id) {
2462 if (!obj->is<SetObject>()) {
2463 return AttachDecision::NoAction;
2465 auto* setObj = &obj->as<SetObject>();
2467 if (mode_ != ICState::Mode::Specialized) {
2468 return AttachDecision::NoAction;
2471 // Receiver should be the object.
2472 if (isSuper()) {
2473 return AttachDecision::NoAction;
2476 if (!id.isAtom(cx_->names().size)) {
2477 return AttachDecision::NoAction;
2480 NativeObject* holder = nullptr;
2481 Maybe<PropertyInfo> prop;
2482 NativeGetPropKind kind =
2483 CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
2484 if (kind != NativeGetPropKind::NativeGetter) {
2485 return AttachDecision::NoAction;
2488 auto& fun = holder->getGetter(*prop)->as<JSFunction>();
2489 if (!SetObject::isOriginalSizeGetter(fun.native())) {
2490 return AttachDecision::NoAction;
2493 maybeEmitIdGuard(id);
2495 // Emit all the normal guards for calling this native, but specialize
2496 // callNativeGetterResult.
2497 EmitCallGetterResultGuards(writer, setObj, holder, id, *prop, objId, mode_);
2499 writer.setSizeResult(objId);
2500 writer.returnFromIC();
2502 trackAttached("GetProp.SetSize");
2503 return AttachDecision::Attach;
2506 AttachDecision GetPropIRGenerator::tryAttachFunction(HandleObject obj,
2507 ObjOperandId objId,
2508 HandleId id) {
2509 // Function properties are lazily resolved so they might not be defined yet.
2510 // And we might end up in a situation where we always have a fresh function
2511 // object during the IC generation.
2512 if (!obj->is<JSFunction>()) {
2513 return AttachDecision::NoAction;
2516 bool isLength = id.isAtom(cx_->names().length);
2517 if (!isLength && !id.isAtom(cx_->names().name)) {
2518 return AttachDecision::NoAction;
2521 NativeObject* holder = nullptr;
2522 PropertyResult prop;
2523 // If this property exists already, don't attach the stub.
2524 if (LookupPropertyPure(cx_, obj, id, &holder, &prop)) {
2525 return AttachDecision::NoAction;
2528 JSFunction* fun = &obj->as<JSFunction>();
2530 if (isLength) {
2531 // length was probably deleted from the function.
2532 if (fun->hasResolvedLength()) {
2533 return AttachDecision::NoAction;
2536 // Lazy functions don't store the length.
2537 if (!fun->hasBytecode()) {
2538 return AttachDecision::NoAction;
2540 } else {
2541 // name was probably deleted from the function.
2542 if (fun->hasResolvedName()) {
2543 return AttachDecision::NoAction;
2547 maybeEmitIdGuard(id);
2548 writer.guardClass(objId, GuardClassKind::JSFunction);
2549 if (isLength) {
2550 writer.loadFunctionLengthResult(objId);
2551 writer.returnFromIC();
2552 trackAttached("GetProp.FunctionLength");
2553 } else {
2554 writer.loadFunctionNameResult(objId);
2555 writer.returnFromIC();
2556 trackAttached("GetProp.FunctionName");
2558 return AttachDecision::Attach;
2561 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectIterator(
2562 HandleObject obj, ObjOperandId objId, HandleId id) {
2563 if (!obj->is<ArgumentsObject>()) {
2564 return AttachDecision::NoAction;
2567 if (!id.isWellKnownSymbol(JS::SymbolCode::iterator)) {
2568 return AttachDecision::NoAction;
2571 Handle<ArgumentsObject*> args = obj.as<ArgumentsObject>();
2572 if (args->hasOverriddenIterator()) {
2573 return AttachDecision::NoAction;
2576 AssertArgumentsCustomDataProp(args, id);
2578 RootedValue iterator(cx_);
2579 if (!ArgumentsObject::getArgumentsIterator(cx_, &iterator)) {
2580 cx_->recoverFromOutOfMemory();
2581 return AttachDecision::NoAction;
2583 MOZ_ASSERT(iterator.isObject());
2585 maybeEmitIdGuard(id);
2586 if (args->is<MappedArgumentsObject>()) {
2587 writer.guardClass(objId, GuardClassKind::MappedArguments);
2588 } else {
2589 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
2590 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
2592 uint32_t flags = ArgumentsObject::ITERATOR_OVERRIDDEN_BIT;
2593 writer.guardArgumentsObjectFlags(objId, flags);
2595 ObjOperandId iterId = writer.loadObject(&iterator.toObject());
2596 writer.loadObjectResult(iterId);
2597 writer.returnFromIC();
2599 trackAttached("GetProp.ArgumentsObjectIterator");
2600 return AttachDecision::Attach;
2603 AttachDecision GetPropIRGenerator::tryAttachModuleNamespace(HandleObject obj,
2604 ObjOperandId objId,
2605 HandleId id) {
2606 if (!obj->is<ModuleNamespaceObject>()) {
2607 return AttachDecision::NoAction;
2610 auto* ns = &obj->as<ModuleNamespaceObject>();
2611 ModuleEnvironmentObject* env = nullptr;
2612 Maybe<PropertyInfo> prop;
2613 if (!ns->bindings().lookup(id, &env, &prop)) {
2614 return AttachDecision::NoAction;
2617 // Don't emit a stub until the target binding has been initialized.
2618 if (env->getSlot(prop->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) {
2619 return AttachDecision::NoAction;
2622 // Check for the specific namespace object.
2623 maybeEmitIdGuard(id);
2624 writer.guardSpecificObject(objId, ns);
2626 ObjOperandId envId = writer.loadObject(env);
2627 EmitLoadSlotResult(writer, envId, env, *prop);
2628 writer.returnFromIC();
2630 trackAttached("GetProp.ModuleNamespace");
2631 return AttachDecision::Attach;
2634 AttachDecision GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId,
2635 HandleId id) {
2636 MOZ_ASSERT(!isSuper(), "SuperBase is guaranteed to be an object");
2638 JSProtoKey protoKey;
2639 switch (val_.type()) {
2640 case ValueType::String:
2641 if (id.isAtom(cx_->names().length)) {
2642 // String length is special-cased, see js::GetProperty.
2643 return AttachDecision::NoAction;
2645 protoKey = JSProto_String;
2646 break;
2647 case ValueType::Int32:
2648 case ValueType::Double:
2649 protoKey = JSProto_Number;
2650 break;
2651 case ValueType::Boolean:
2652 protoKey = JSProto_Boolean;
2653 break;
2654 case ValueType::Symbol:
2655 protoKey = JSProto_Symbol;
2656 break;
2657 case ValueType::BigInt:
2658 protoKey = JSProto_BigInt;
2659 break;
2660 case ValueType::Null:
2661 case ValueType::Undefined:
2662 case ValueType::Magic:
2663 return AttachDecision::NoAction;
2664 #ifdef ENABLE_RECORD_TUPLE
2665 case ValueType::ExtendedPrimitive:
2666 #endif
2667 case ValueType::Object:
2668 case ValueType::PrivateGCThing:
2669 MOZ_CRASH("unexpected type");
2672 JSObject* proto = GlobalObject::getOrCreatePrototype(cx_, protoKey);
2673 if (!proto) {
2674 cx_->recoverFromOutOfMemory();
2675 return AttachDecision::NoAction;
2678 NativeObject* holder = nullptr;
2679 Maybe<PropertyInfo> prop;
2680 NativeGetPropKind kind =
2681 CanAttachNativeGetProp(cx_, proto, id, &holder, &prop, pc_);
2682 switch (kind) {
2683 case NativeGetPropKind::None:
2684 return AttachDecision::NoAction;
2685 case NativeGetPropKind::Missing:
2686 case NativeGetPropKind::Slot: {
2687 auto* nproto = &proto->as<NativeObject>();
2689 if (val_.isNumber()) {
2690 writer.guardIsNumber(valId);
2691 } else {
2692 writer.guardNonDoubleType(valId, val_.type());
2694 maybeEmitIdGuard(id);
2696 ObjOperandId protoId = writer.loadObject(nproto);
2697 if (kind == NativeGetPropKind::Slot) {
2698 EmitReadSlotResult(writer, nproto, holder, *prop, protoId);
2699 writer.returnFromIC();
2700 trackAttached("GetProp.PrimitiveSlot");
2701 } else {
2702 EmitMissingPropResult(writer, nproto, protoId);
2703 writer.returnFromIC();
2704 trackAttached("GetProp.PrimitiveMissing");
2706 return AttachDecision::Attach;
2708 case NativeGetPropKind::ScriptedGetter:
2709 case NativeGetPropKind::NativeGetter: {
2710 auto* nproto = &proto->as<NativeObject>();
2712 if (val_.isNumber()) {
2713 writer.guardIsNumber(valId);
2714 } else {
2715 writer.guardNonDoubleType(valId, val_.type());
2717 maybeEmitIdGuard(id);
2719 ObjOperandId protoId = writer.loadObject(nproto);
2720 EmitCallGetterResult(cx_, writer, kind, nproto, holder, id, *prop,
2721 protoId, valId, mode_);
2723 trackAttached("GetProp.PrimitiveGetter");
2724 return AttachDecision::Attach;
2728 MOZ_CRASH("Bad NativeGetPropKind");
2731 AttachDecision GetPropIRGenerator::tryAttachStringLength(ValOperandId valId,
2732 HandleId id) {
2733 if (!val_.isString() || !id.isAtom(cx_->names().length)) {
2734 return AttachDecision::NoAction;
2737 StringOperandId strId = writer.guardToString(valId);
2738 maybeEmitIdGuard(id);
2739 writer.loadStringLengthResult(strId);
2740 writer.returnFromIC();
2742 trackAttached("GetProp.StringLength");
2743 return AttachDecision::Attach;
2746 enum class AttachStringChar { No, Yes, Linearize, OutOfBounds };
2748 static AttachStringChar CanAttachStringChar(const Value& val,
2749 const Value& idVal,
2750 StringChar kind) {
2751 if (!val.isString() || !idVal.isInt32()) {
2752 return AttachStringChar::No;
2755 JSString* str = val.toString();
2756 int32_t index = idVal.toInt32();
2758 if (index < 0 && kind == StringChar::At) {
2759 static_assert(JSString::MAX_LENGTH <= INT32_MAX,
2760 "string length fits in int32");
2761 index += int32_t(str->length());
2764 if (index < 0 || size_t(index) >= str->length()) {
2765 return AttachStringChar::OutOfBounds;
2768 // This follows JSString::getChar and MacroAssembler::loadStringChar.
2769 if (str->isRope()) {
2770 JSRope* rope = &str->asRope();
2771 if (size_t(index) < rope->leftChild()->length()) {
2772 str = rope->leftChild();
2774 // MacroAssembler::loadStringChar doesn't support surrogate pairs which
2775 // are split between the left and right child of a rope.
2776 if (kind == StringChar::CodePointAt &&
2777 size_t(index) + 1 == str->length() && str->isLinear()) {
2778 // Linearize the string when the last character of the left child is a
2779 // a lead surrogate.
2780 char16_t ch = str->asLinear().latin1OrTwoByteChar(index);
2781 if (unicode::IsLeadSurrogate(ch)) {
2782 return AttachStringChar::Linearize;
2785 } else {
2786 str = rope->rightChild();
2790 if (!str->isLinear()) {
2791 return AttachStringChar::Linearize;
2794 return AttachStringChar::Yes;
2797 AttachDecision GetPropIRGenerator::tryAttachStringChar(ValOperandId valId,
2798 ValOperandId indexId) {
2799 MOZ_ASSERT(idVal_.isInt32());
2801 auto attach = CanAttachStringChar(val_, idVal_, StringChar::CharAt);
2802 if (attach == AttachStringChar::No) {
2803 return AttachDecision::NoAction;
2806 // Can't attach for out-of-bounds access without guarding that indexed
2807 // properties aren't present along the prototype chain of |String.prototype|.
2808 if (attach == AttachStringChar::OutOfBounds) {
2809 return AttachDecision::NoAction;
2812 StringOperandId strId = writer.guardToString(valId);
2813 Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
2814 if (attach == AttachStringChar::Linearize) {
2815 strId = writer.linearizeForCharAccess(strId, int32IndexId);
2817 writer.loadStringCharResult(strId, int32IndexId, /* handleOOB = */ false);
2818 writer.returnFromIC();
2820 trackAttached("GetProp.StringChar");
2821 return AttachDecision::Attach;
2824 static bool ClassCanHaveExtraProperties(const JSClass* clasp) {
2825 return clasp->getResolve() || clasp->getOpsLookupProperty() ||
2826 clasp->getOpsGetProperty() || IsTypedArrayClass(clasp);
2829 enum class OwnProperty : bool { No, Yes };
2830 enum class AllowIndexedReceiver : bool { No, Yes };
2831 enum class AllowExtraReceiverProperties : bool { No, Yes };
2833 static bool CanAttachDenseElementHole(
2834 NativeObject* obj, OwnProperty ownProp,
2835 AllowIndexedReceiver allowIndexedReceiver = AllowIndexedReceiver::No,
2836 AllowExtraReceiverProperties allowExtraReceiverProperties =
2837 AllowExtraReceiverProperties::No) {
2838 // Make sure the objects on the prototype don't have any indexed properties
2839 // or that such properties can't appear without a shape change.
2840 // Otherwise returning undefined for holes would obviously be incorrect,
2841 // because we would have to lookup a property on the prototype instead.
2842 do {
2843 // The first two checks are also relevant to the receiver object.
2844 if (allowIndexedReceiver == AllowIndexedReceiver::No && obj->isIndexed()) {
2845 return false;
2847 allowIndexedReceiver = AllowIndexedReceiver::No;
2849 if (allowExtraReceiverProperties == AllowExtraReceiverProperties::No &&
2850 ClassCanHaveExtraProperties(obj->getClass())) {
2851 return false;
2853 allowExtraReceiverProperties = AllowExtraReceiverProperties::No;
2855 // Don't need to check prototype for OwnProperty checks
2856 if (ownProp == OwnProperty::Yes) {
2857 return true;
2860 JSObject* proto = obj->staticPrototype();
2861 if (!proto) {
2862 break;
2865 if (!proto->is<NativeObject>()) {
2866 return false;
2869 // Make sure objects on the prototype don't have dense elements.
2870 if (proto->as<NativeObject>().getDenseInitializedLength() != 0) {
2871 return false;
2874 obj = &proto->as<NativeObject>();
2875 } while (true);
2877 return true;
2880 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectArg(
2881 HandleObject obj, ObjOperandId objId, uint32_t index,
2882 Int32OperandId indexId) {
2883 if (!obj->is<ArgumentsObject>()) {
2884 return AttachDecision::NoAction;
2886 auto* args = &obj->as<ArgumentsObject>();
2888 // No elements must have been overridden or deleted.
2889 if (args->hasOverriddenElement()) {
2890 return AttachDecision::NoAction;
2893 // Check bounds.
2894 if (index >= args->initialLength()) {
2895 return AttachDecision::NoAction;
2898 AssertArgumentsCustomDataProp(args, PropertyKey::Int(index));
2900 // And finally also check that the argument isn't forwarded.
2901 if (args->argIsForwarded(index)) {
2902 return AttachDecision::NoAction;
2905 if (args->is<MappedArgumentsObject>()) {
2906 writer.guardClass(objId, GuardClassKind::MappedArguments);
2907 } else {
2908 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
2909 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
2912 writer.loadArgumentsObjectArgResult(objId, indexId);
2913 writer.returnFromIC();
2915 trackAttached("GetProp.ArgumentsObjectArg");
2916 return AttachDecision::Attach;
2919 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectArgHole(
2920 HandleObject obj, ObjOperandId objId, uint32_t index,
2921 Int32OperandId indexId) {
2922 if (!obj->is<ArgumentsObject>()) {
2923 return AttachDecision::NoAction;
2925 auto* args = &obj->as<ArgumentsObject>();
2927 // No elements must have been overridden or deleted.
2928 if (args->hasOverriddenElement()) {
2929 return AttachDecision::NoAction;
2932 // And also check that the argument isn't forwarded.
2933 if (index < args->initialLength() && args->argIsForwarded(index)) {
2934 return AttachDecision::NoAction;
2937 if (!CanAttachDenseElementHole(args, OwnProperty::No,
2938 AllowIndexedReceiver::Yes,
2939 AllowExtraReceiverProperties::Yes)) {
2940 return AttachDecision::NoAction;
2943 // We don't need to guard on the shape, because we check if any element is
2944 // overridden. Elements are marked as overridden iff any element is defined,
2945 // irrespective of whether the element is in-bounds or out-of-bounds. So when
2946 // that flag isn't set, we can guarantee that the arguments object doesn't
2947 // have any additional own elements.
2949 if (args->is<MappedArgumentsObject>()) {
2950 writer.guardClass(objId, GuardClassKind::MappedArguments);
2951 } else {
2952 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
2953 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
2956 GeneratePrototypeHoleGuards(writer, args, objId,
2957 /* alwaysGuardFirstProto = */ true);
2959 writer.loadArgumentsObjectArgHoleResult(objId, indexId);
2960 writer.returnFromIC();
2962 trackAttached("GetProp.ArgumentsObjectArgHole");
2963 return AttachDecision::Attach;
2966 AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectCallee(
2967 HandleObject obj, ObjOperandId objId, HandleId id) {
2968 // Only mapped arguments objects have a `callee` property.
2969 if (!obj->is<MappedArgumentsObject>()) {
2970 return AttachDecision::NoAction;
2973 if (!id.isAtom(cx_->names().callee)) {
2974 return AttachDecision::NoAction;
2977 // The callee must not have been overridden or deleted.
2978 MappedArgumentsObject* args = &obj->as<MappedArgumentsObject>();
2979 if (args->hasOverriddenCallee()) {
2980 return AttachDecision::NoAction;
2983 AssertArgumentsCustomDataProp(args, id);
2985 maybeEmitIdGuard(id);
2986 writer.guardClass(objId, GuardClassKind::MappedArguments);
2988 uint32_t flags = ArgumentsObject::CALLEE_OVERRIDDEN_BIT;
2989 writer.guardArgumentsObjectFlags(objId, flags);
2991 writer.loadFixedSlotResult(objId,
2992 MappedArgumentsObject::getCalleeSlotOffset());
2993 writer.returnFromIC();
2995 trackAttached("GetProp.ArgumentsObjectCallee");
2996 return AttachDecision::Attach;
2999 AttachDecision GetPropIRGenerator::tryAttachDenseElement(
3000 HandleObject obj, ObjOperandId objId, uint32_t index,
3001 Int32OperandId indexId) {
3002 if (!obj->is<NativeObject>()) {
3003 return AttachDecision::NoAction;
3006 NativeObject* nobj = &obj->as<NativeObject>();
3007 if (!nobj->containsDenseElement(index)) {
3008 return AttachDecision::NoAction;
3011 if (mode_ == ICState::Mode::Megamorphic) {
3012 writer.guardIsNativeObject(objId);
3013 } else {
3014 TestMatchingNativeReceiver(writer, nobj, objId);
3016 writer.loadDenseElementResult(objId, indexId);
3017 writer.returnFromIC();
3019 trackAttached("GetProp.DenseElement");
3020 return AttachDecision::Attach;
3023 AttachDecision GetPropIRGenerator::tryAttachDenseElementHole(
3024 HandleObject obj, ObjOperandId objId, uint32_t index,
3025 Int32OperandId indexId) {
3026 if (!obj->is<NativeObject>()) {
3027 return AttachDecision::NoAction;
3030 NativeObject* nobj = &obj->as<NativeObject>();
3031 if (nobj->containsDenseElement(index)) {
3032 return AttachDecision::NoAction;
3034 if (!CanAttachDenseElementHole(nobj, OwnProperty::No)) {
3035 return AttachDecision::NoAction;
3038 // Guard on the shape, to prevent non-dense elements from appearing.
3039 TestMatchingNativeReceiver(writer, nobj, objId);
3040 GeneratePrototypeHoleGuards(writer, nobj, objId,
3041 /* alwaysGuardFirstProto = */ false);
3042 writer.loadDenseElementHoleResult(objId, indexId);
3043 writer.returnFromIC();
3045 trackAttached("GetProp.DenseElementHole");
3046 return AttachDecision::Attach;
3049 AttachDecision GetPropIRGenerator::tryAttachSparseElement(
3050 HandleObject obj, ObjOperandId objId, uint32_t index,
3051 Int32OperandId indexId) {
3052 if (!obj->is<NativeObject>()) {
3053 return AttachDecision::NoAction;
3055 NativeObject* nobj = &obj->as<NativeObject>();
3057 // Stub doesn't handle negative indices.
3058 if (index > INT32_MAX) {
3059 return AttachDecision::NoAction;
3062 // The object must have sparse elements.
3063 if (!nobj->isIndexed()) {
3064 return AttachDecision::NoAction;
3067 // The index must not be for a dense element.
3068 if (nobj->containsDenseElement(index)) {
3069 return AttachDecision::NoAction;
3072 // Only handle ArrayObject and PlainObject in this stub.
3073 if (!nobj->is<ArrayObject>() && !nobj->is<PlainObject>()) {
3074 return AttachDecision::NoAction;
3077 // GetSparseElementHelper assumes that the target and the receiver
3078 // are the same.
3079 if (isSuper()) {
3080 return AttachDecision::NoAction;
3083 // Here, we ensure that the prototype chain does not define any sparse
3084 // indexed properties on the shape lineage. This allows us to guard on
3085 // the shapes up the prototype chain to ensure that no indexed properties
3086 // exist outside of the dense elements.
3088 // The `GeneratePrototypeHoleGuards` call below will guard on the shapes,
3089 // as well as ensure that no prototypes contain dense elements, allowing
3090 // us to perform a pure shape-search for out-of-bounds integer-indexed
3091 // properties on the receiver object.
3092 if (PrototypeMayHaveIndexedProperties(nobj)) {
3093 return AttachDecision::NoAction;
3096 // Ensure that obj is an ArrayObject or PlainObject.
3097 if (nobj->is<ArrayObject>()) {
3098 writer.guardClass(objId, GuardClassKind::Array);
3099 } else {
3100 MOZ_ASSERT(nobj->is<PlainObject>());
3101 writer.guardClass(objId, GuardClassKind::PlainObject);
3104 // The helper we are going to call only applies to non-dense elements.
3105 writer.guardIndexIsNotDenseElement(objId, indexId);
3107 // Ensures we are able to efficiently able to map to an integral jsid.
3108 writer.guardInt32IsNonNegative(indexId);
3110 // Shape guard the prototype chain to avoid shadowing indexes from appearing.
3111 // The helper function also ensures that the index does not appear within the
3112 // dense element set of the prototypes.
3113 GeneratePrototypeHoleGuards(writer, nobj, objId,
3114 /* alwaysGuardFirstProto = */ true);
3116 // At this point, we are guaranteed that the indexed property will not
3117 // be found on one of the prototypes. We are assured that we only have
3118 // to check that the receiving object has the property.
3120 writer.callGetSparseElementResult(objId, indexId);
3121 writer.returnFromIC();
3123 trackAttached("GetProp.SparseElement");
3124 return AttachDecision::Attach;
3127 // For Uint32Array we let the stub return an Int32 if we have not seen a
3128 // double, to allow better codegen in Warp while avoiding bailout loops.
3129 static bool ForceDoubleForUint32Array(TypedArrayObject* tarr, uint64_t index) {
3130 MOZ_ASSERT(index < tarr->length().valueOr(0));
3132 if (tarr->type() != Scalar::Type::Uint32) {
3133 // Return value is only relevant for Uint32Array.
3134 return false;
3137 Value res;
3138 MOZ_ALWAYS_TRUE(tarr->getElementPure(index, &res));
3139 MOZ_ASSERT(res.isNumber());
3140 return res.isDouble();
3143 static ArrayBufferViewKind ToArrayBufferViewKind(const TypedArrayObject* obj) {
3144 if (obj->is<FixedLengthTypedArrayObject>()) {
3145 return ArrayBufferViewKind::FixedLength;
3148 MOZ_ASSERT(obj->is<ResizableTypedArrayObject>());
3149 return ArrayBufferViewKind::Resizable;
3152 static ArrayBufferViewKind ToArrayBufferViewKind(const DataViewObject* obj) {
3153 if (obj->is<FixedLengthDataViewObject>()) {
3154 return ArrayBufferViewKind::FixedLength;
3157 MOZ_ASSERT(obj->is<ResizableDataViewObject>());
3158 return ArrayBufferViewKind::Resizable;
3161 AttachDecision GetPropIRGenerator::tryAttachTypedArrayElement(
3162 HandleObject obj, ObjOperandId objId) {
3163 if (!obj->is<TypedArrayObject>()) {
3164 return AttachDecision::NoAction;
3167 if (!idVal_.isNumber()) {
3168 return AttachDecision::NoAction;
3171 auto* tarr = &obj->as<TypedArrayObject>();
3173 bool handleOOB = false;
3174 int64_t indexInt64;
3175 if (!ValueIsInt64Index(idVal_, &indexInt64) || indexInt64 < 0 ||
3176 uint64_t(indexInt64) >= tarr->length().valueOr(0)) {
3177 handleOOB = true;
3180 // If the number is not representable as an integer the result will be
3181 // |undefined| so we leave |forceDoubleForUint32| as false.
3182 bool forceDoubleForUint32 = false;
3183 if (!handleOOB) {
3184 uint64_t index = uint64_t(indexInt64);
3185 forceDoubleForUint32 = ForceDoubleForUint32Array(tarr, index);
3188 writer.guardShapeForClass(objId, tarr->shape());
3190 ValOperandId keyId = getElemKeyValueId();
3191 IntPtrOperandId intPtrIndexId = guardToIntPtrIndex(idVal_, keyId, handleOOB);
3193 auto viewKind = ToArrayBufferViewKind(tarr);
3194 writer.loadTypedArrayElementResult(objId, intPtrIndexId, tarr->type(),
3195 handleOOB, forceDoubleForUint32, viewKind);
3196 writer.returnFromIC();
3198 trackAttached("GetProp.TypedElement");
3199 return AttachDecision::Attach;
3202 AttachDecision GetPropIRGenerator::tryAttachGenericElement(
3203 HandleObject obj, ObjOperandId objId, uint32_t index,
3204 Int32OperandId indexId, ValOperandId receiverId) {
3205 if (!obj->is<NativeObject>()) {
3206 return AttachDecision::NoAction;
3209 #ifdef JS_CODEGEN_X86
3210 if (isSuper()) {
3211 // There aren't enough registers available on x86.
3212 return AttachDecision::NoAction;
3214 #endif
3216 // To allow other types to attach in the non-megamorphic case we test the
3217 // specific matching native receiver; however, once megamorphic we can attach
3218 // for any native
3219 if (mode_ == ICState::Mode::Megamorphic) {
3220 writer.guardIsNativeObject(objId);
3221 } else {
3222 NativeObject* nobj = &obj->as<NativeObject>();
3223 TestMatchingNativeReceiver(writer, nobj, objId);
3225 writer.guardIndexIsNotDenseElement(objId, indexId);
3226 if (isSuper()) {
3227 writer.callNativeGetElementSuperResult(objId, indexId, receiverId);
3228 } else {
3229 writer.callNativeGetElementResult(objId, indexId);
3231 writer.returnFromIC();
3233 trackAttached(mode_ == ICState::Mode::Megamorphic
3234 ? "GenericElementMegamorphic"
3235 : "GenericElement");
3236 return AttachDecision::Attach;
3239 AttachDecision GetPropIRGenerator::tryAttachProxyElement(HandleObject obj,
3240 ObjOperandId objId) {
3241 if (!obj->is<ProxyObject>()) {
3242 return AttachDecision::NoAction;
3245 // The proxy stubs don't currently support |super| access.
3246 if (isSuper()) {
3247 return AttachDecision::NoAction;
3250 #ifdef JS_PUNBOX64
3251 auto proxy = obj.as<ProxyObject>();
3252 if (proxy->handler()->isScripted()) {
3253 TRY_ATTACH(tryAttachScriptedProxy(proxy, objId, JS::VoidHandlePropertyKey));
3255 #endif
3257 writer.guardIsProxy(objId);
3259 // We are not guarding against DOM proxies here, because there is no other
3260 // specialized DOM IC we could attach.
3261 // We could call maybeEmitIdGuard here and then emit ProxyGetResult,
3262 // but for GetElem we prefer to attach a stub that can handle any Value
3263 // so we don't attach a new stub for every id.
3264 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
3265 MOZ_ASSERT(!isSuper());
3266 writer.proxyGetByValueResult(objId, getElemKeyValueId());
3267 writer.returnFromIC();
3269 trackAttached("GetProp.ProxyElement");
3270 return AttachDecision::Attach;
3273 void GetPropIRGenerator::trackAttached(const char* name) {
3274 stubName_ = name ? name : "NotAttached";
3275 #ifdef JS_CACHEIR_SPEW
3276 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
3277 sp.valueProperty("base", val_);
3278 sp.valueProperty("property", idVal_);
3280 #endif
3283 void IRGenerator::emitIdGuard(ValOperandId valId, const Value& idVal, jsid id) {
3284 if (id.isSymbol()) {
3285 MOZ_ASSERT(idVal.toSymbol() == id.toSymbol());
3286 SymbolOperandId symId = writer.guardToSymbol(valId);
3287 writer.guardSpecificSymbol(symId, id.toSymbol());
3288 } else {
3289 MOZ_ASSERT(id.isAtom());
3290 if (idVal.isUndefined()) {
3291 MOZ_ASSERT(id.isAtom(cx_->names().undefined));
3292 writer.guardIsUndefined(valId);
3293 } else if (idVal.isNull()) {
3294 MOZ_ASSERT(id.isAtom(cx_->names().null));
3295 writer.guardIsNull(valId);
3296 } else {
3297 MOZ_ASSERT(idVal.isString());
3298 StringOperandId strId = writer.guardToString(valId);
3299 writer.guardSpecificAtom(strId, id.toAtom());
3304 void GetPropIRGenerator::maybeEmitIdGuard(jsid id) {
3305 if (cacheKind_ == CacheKind::GetProp ||
3306 cacheKind_ == CacheKind::GetPropSuper) {
3307 // Constant PropertyName, no guards necessary.
3308 MOZ_ASSERT(&idVal_.toString()->asAtom() == id.toAtom());
3309 return;
3312 MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
3313 cacheKind_ == CacheKind::GetElemSuper);
3314 emitIdGuard(getElemKeyValueId(), idVal_, id);
3317 void SetPropIRGenerator::maybeEmitIdGuard(jsid id) {
3318 if (cacheKind_ == CacheKind::SetProp) {
3319 // Constant PropertyName, no guards necessary.
3320 MOZ_ASSERT(&idVal_.toString()->asAtom() == id.toAtom());
3321 return;
3324 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
3325 emitIdGuard(setElemKeyValueId(), idVal_, id);
3328 GetNameIRGenerator::GetNameIRGenerator(JSContext* cx, HandleScript script,
3329 jsbytecode* pc, ICState state,
3330 HandleObject env,
3331 Handle<PropertyName*> name)
3332 : IRGenerator(cx, script, pc, CacheKind::GetName, state),
3333 env_(env),
3334 name_(name) {}
3336 AttachDecision GetNameIRGenerator::tryAttachStub() {
3337 MOZ_ASSERT(cacheKind_ == CacheKind::GetName);
3339 AutoAssertNoPendingException aanpe(cx_);
3341 ObjOperandId envId(writer.setInputOperandId(0));
3342 RootedId id(cx_, NameToId(name_));
3344 TRY_ATTACH(tryAttachGlobalNameValue(envId, id));
3345 TRY_ATTACH(tryAttachGlobalNameGetter(envId, id));
3346 TRY_ATTACH(tryAttachEnvironmentName(envId, id));
3348 trackAttached(IRGenerator::NotAttached);
3349 return AttachDecision::NoAction;
3352 static bool CanAttachGlobalName(JSContext* cx,
3353 GlobalLexicalEnvironmentObject* globalLexical,
3354 PropertyKey id, NativeObject** holder,
3355 Maybe<PropertyInfo>* prop) {
3356 // The property must be found, and it must be found as a normal data property.
3357 NativeObject* current = globalLexical;
3358 while (true) {
3359 *prop = current->lookup(cx, id);
3360 if (prop->isSome()) {
3361 break;
3364 if (current == globalLexical) {
3365 current = &globalLexical->global();
3366 } else {
3367 // In the browser the global prototype chain should be immutable.
3368 if (!current->staticPrototypeIsImmutable()) {
3369 return false;
3372 JSObject* proto = current->staticPrototype();
3373 if (!proto || !proto->is<NativeObject>()) {
3374 return false;
3377 current = &proto->as<NativeObject>();
3381 *holder = current;
3382 return true;
3385 AttachDecision GetNameIRGenerator::tryAttachGlobalNameValue(ObjOperandId objId,
3386 HandleId id) {
3387 if (!IsGlobalOp(JSOp(*pc_))) {
3388 return AttachDecision::NoAction;
3390 MOZ_ASSERT(!script_->hasNonSyntacticScope());
3392 auto* globalLexical = &env_->as<GlobalLexicalEnvironmentObject>();
3394 NativeObject* holder = nullptr;
3395 Maybe<PropertyInfo> prop;
3396 if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &prop)) {
3397 return AttachDecision::NoAction;
3400 // The property must be found, and it must be found as a normal data property.
3401 if (!prop->isDataProperty()) {
3402 return AttachDecision::NoAction;
3405 // This might still be an uninitialized lexical.
3406 if (holder->getSlot(prop->slot()).isMagic()) {
3407 return AttachDecision::NoAction;
3410 if (holder == globalLexical) {
3411 // There is no need to guard on the shape. Lexical bindings are
3412 // non-configurable, and this stub cannot be shared across globals.
3413 size_t dynamicSlotOffset =
3414 holder->dynamicSlotIndex(prop->slot()) * sizeof(Value);
3415 writer.loadDynamicSlotResult(objId, dynamicSlotOffset);
3416 } else if (holder == &globalLexical->global()) {
3417 MOZ_ASSERT(globalLexical->global().isGenerationCountedGlobal());
3418 writer.guardGlobalGeneration(
3419 globalLexical->global().generationCount(),
3420 globalLexical->global().addressOfGenerationCount());
3421 ObjOperandId holderId = writer.loadObject(holder);
3422 #ifdef DEBUG
3423 writer.assertPropertyLookup(holderId, id, prop->slot());
3424 #endif
3425 EmitLoadSlotResult(writer, holderId, holder, *prop);
3426 } else {
3427 // Check the prototype chain from the global to the holder
3428 // prototype. Ignore the global lexical scope as it doesn't figure
3429 // into the prototype chain. We guard on the global lexical
3430 // scope's shape independently.
3431 if (!IsCacheableGetPropSlot(&globalLexical->global(), holder, *prop)) {
3432 return AttachDecision::NoAction;
3435 // Shape guard for global lexical.
3436 writer.guardShape(objId, globalLexical->shape());
3438 // Guard on the shape of the GlobalObject.
3439 ObjOperandId globalId = writer.loadObject(&globalLexical->global());
3440 writer.guardShape(globalId, globalLexical->global().shape());
3442 // Shape guard holder.
3443 ObjOperandId holderId = writer.loadObject(holder);
3444 writer.guardShape(holderId, holder->shape());
3446 EmitLoadSlotResult(writer, holderId, holder, *prop);
3449 writer.returnFromIC();
3451 trackAttached("GetName.GlobalNameValue");
3452 return AttachDecision::Attach;
3455 AttachDecision GetNameIRGenerator::tryAttachGlobalNameGetter(ObjOperandId objId,
3456 HandleId id) {
3457 if (!IsGlobalOp(JSOp(*pc_))) {
3458 return AttachDecision::NoAction;
3460 MOZ_ASSERT(!script_->hasNonSyntacticScope());
3462 Handle<GlobalLexicalEnvironmentObject*> globalLexical =
3463 env_.as<GlobalLexicalEnvironmentObject>();
3464 MOZ_ASSERT(globalLexical->isGlobal());
3466 NativeObject* holder = nullptr;
3467 Maybe<PropertyInfo> prop;
3468 if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &prop)) {
3469 return AttachDecision::NoAction;
3472 if (holder == globalLexical) {
3473 return AttachDecision::NoAction;
3476 GlobalObject* global = &globalLexical->global();
3478 NativeGetPropKind kind = IsCacheableGetPropCall(global, holder, *prop, pc_);
3479 if (kind != NativeGetPropKind::NativeGetter &&
3480 kind != NativeGetPropKind::ScriptedGetter) {
3481 return AttachDecision::NoAction;
3484 bool needsWindowProxy =
3485 IsWindow(global) && GetterNeedsWindowProxyThis(holder, *prop);
3487 // Shape guard for global lexical.
3488 writer.guardShape(objId, globalLexical->shape());
3490 // Guard on the shape of the GlobalObject.
3491 ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
3492 writer.guardShape(globalId, global->shape());
3494 if (holder != global) {
3495 // Shape guard holder.
3496 ObjOperandId holderId = writer.loadObject(holder);
3497 writer.guardShape(holderId, holder->shape());
3498 EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
3499 /* holderIsConstant = */ true);
3500 } else {
3501 // Note: pass true for |holderIsConstant| because the holder must be the
3502 // current global object.
3503 EmitGuardGetterSetterSlot(writer, holder, *prop, globalId,
3504 /* holderIsConstant = */ true);
3507 if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, global, holder, *prop,
3508 mode_)) {
3509 // The global shape guard above ensures the instance JSClass is correct.
3510 MOZ_ASSERT(!needsWindowProxy);
3511 EmitCallDOMGetterResultNoGuards(writer, holder, *prop, globalId);
3512 trackAttached("GetName.GlobalNameDOMGetter");
3513 } else {
3514 ObjOperandId receiverObjId;
3515 if (needsWindowProxy) {
3516 MOZ_ASSERT(cx_->global()->maybeWindowProxy());
3517 receiverObjId = writer.loadObject(cx_->global()->maybeWindowProxy());
3518 } else {
3519 receiverObjId = globalId;
3521 ValOperandId receiverId = writer.boxObject(receiverObjId);
3522 EmitCallGetterResultNoGuards(cx_, writer, kind, global, holder, *prop,
3523 receiverId);
3524 trackAttached("GetName.GlobalNameGetter");
3527 return AttachDecision::Attach;
3530 static bool NeedEnvironmentShapeGuard(JSContext* cx, JSObject* envObj) {
3531 if (!envObj->is<CallObject>()) {
3532 return true;
3535 // We can skip a guard on the call object if the script's bindings are
3536 // guaranteed to be immutable (and thus cannot introduce shadowing variables).
3537 // If the function is a relazified self-hosted function it has no BaseScript
3538 // and we pessimistically create the guard.
3539 CallObject* callObj = &envObj->as<CallObject>();
3540 JSFunction* fun = &callObj->callee();
3541 if (!fun->hasBaseScript() || fun->baseScript()->funHasExtensibleScope() ||
3542 DebugEnvironments::hasDebugEnvironment(cx, *callObj)) {
3543 return true;
3546 return false;
3549 AttachDecision GetNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
3550 HandleId id) {
3551 if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
3552 return AttachDecision::NoAction;
3555 JSObject* env = env_;
3556 Maybe<PropertyInfo> prop;
3557 NativeObject* holder = nullptr;
3559 while (env) {
3560 if (env->is<GlobalObject>()) {
3561 prop = env->as<GlobalObject>().lookup(cx_, id);
3562 if (prop.isSome()) {
3563 break;
3565 return AttachDecision::NoAction;
3568 if (!env->is<EnvironmentObject>() || env->is<WithEnvironmentObject>()) {
3569 return AttachDecision::NoAction;
3572 // Check for an 'own' property on the env. There is no need to
3573 // check the prototype as non-with scopes do not inherit properties
3574 // from any prototype.
3575 prop = env->as<NativeObject>().lookup(cx_, id);
3576 if (prop.isSome()) {
3577 break;
3580 env = env->enclosingEnvironment();
3583 holder = &env->as<NativeObject>();
3584 if (!IsCacheableGetPropSlot(holder, holder, *prop)) {
3585 return AttachDecision::NoAction;
3587 if (holder->getSlot(prop->slot()).isMagic()) {
3588 MOZ_ASSERT(holder->is<EnvironmentObject>());
3589 return AttachDecision::NoAction;
3592 ObjOperandId lastObjId = objId;
3593 env = env_;
3594 while (env) {
3595 if (NeedEnvironmentShapeGuard(cx_, env)) {
3596 writer.guardShape(lastObjId, env->shape());
3599 if (env == holder) {
3600 break;
3603 lastObjId = writer.loadEnclosingEnvironment(lastObjId);
3604 env = env->enclosingEnvironment();
3607 ValOperandId resId = EmitLoadSlot(writer, holder, lastObjId, prop->slot());
3608 if (holder->is<EnvironmentObject>()) {
3609 writer.guardIsNotUninitializedLexical(resId);
3611 writer.loadOperandResult(resId);
3612 writer.returnFromIC();
3614 trackAttached("GetName.EnvironmentName");
3615 return AttachDecision::Attach;
3618 void GetNameIRGenerator::trackAttached(const char* name) {
3619 stubName_ = name ? name : "NotAttached";
3620 #ifdef JS_CACHEIR_SPEW
3621 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
3622 sp.valueProperty("base", ObjectValue(*env_));
3623 sp.valueProperty("property", StringValue(name_));
3625 #endif
3628 BindNameIRGenerator::BindNameIRGenerator(JSContext* cx, HandleScript script,
3629 jsbytecode* pc, ICState state,
3630 HandleObject env,
3631 Handle<PropertyName*> name)
3632 : IRGenerator(cx, script, pc, CacheKind::BindName, state),
3633 env_(env),
3634 name_(name) {}
3636 AttachDecision BindNameIRGenerator::tryAttachStub() {
3637 MOZ_ASSERT(cacheKind_ == CacheKind::BindName);
3639 AutoAssertNoPendingException aanpe(cx_);
3641 ObjOperandId envId(writer.setInputOperandId(0));
3642 RootedId id(cx_, NameToId(name_));
3644 TRY_ATTACH(tryAttachGlobalName(envId, id));
3645 TRY_ATTACH(tryAttachEnvironmentName(envId, id));
3647 trackAttached(IRGenerator::NotAttached);
3648 return AttachDecision::NoAction;
3651 AttachDecision BindNameIRGenerator::tryAttachGlobalName(ObjOperandId objId,
3652 HandleId id) {
3653 if (!IsGlobalOp(JSOp(*pc_))) {
3654 return AttachDecision::NoAction;
3656 MOZ_ASSERT(!script_->hasNonSyntacticScope());
3658 Handle<GlobalLexicalEnvironmentObject*> globalLexical =
3659 env_.as<GlobalLexicalEnvironmentObject>();
3660 MOZ_ASSERT(globalLexical->isGlobal());
3662 JSObject* result = nullptr;
3663 if (Maybe<PropertyInfo> prop = globalLexical->lookup(cx_, id)) {
3664 // If this is an uninitialized lexical or a const, we need to return a
3665 // RuntimeLexicalErrorObject.
3666 if (globalLexical->getSlot(prop->slot()).isMagic() || !prop->writable()) {
3667 return AttachDecision::NoAction;
3669 result = globalLexical;
3670 } else {
3671 result = &globalLexical->global();
3674 if (result == globalLexical) {
3675 // Lexical bindings are non-configurable so we can just return the
3676 // global lexical.
3677 writer.loadObjectResult(objId);
3678 } else {
3679 // If the property exists on the global and is non-configurable, it cannot
3680 // be shadowed by the lexical scope so we can just return the global without
3681 // a shape guard.
3682 Maybe<PropertyInfo> prop = result->as<GlobalObject>().lookup(cx_, id);
3683 if (prop.isNothing() || prop->configurable()) {
3684 writer.guardShape(objId, globalLexical->shape());
3686 ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
3687 writer.loadObjectResult(globalId);
3689 writer.returnFromIC();
3691 trackAttached("BindName.GlobalName");
3692 return AttachDecision::Attach;
3695 AttachDecision BindNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
3696 HandleId id) {
3697 if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
3698 return AttachDecision::NoAction;
3701 JSObject* env = env_;
3702 Maybe<PropertyInfo> prop;
3703 while (true) {
3704 if (!env->is<GlobalObject>() && !env->is<EnvironmentObject>()) {
3705 return AttachDecision::NoAction;
3707 if (env->is<WithEnvironmentObject>()) {
3708 return AttachDecision::NoAction;
3711 // When we reach an unqualified variables object (like the global) we
3712 // have to stop looking and return that object.
3713 if (env->isUnqualifiedVarObj()) {
3714 break;
3717 // Check for an 'own' property on the env. There is no need to
3718 // check the prototype as non-with scopes do not inherit properties
3719 // from any prototype.
3720 prop = env->as<NativeObject>().lookup(cx_, id);
3721 if (prop.isSome()) {
3722 break;
3725 env = env->enclosingEnvironment();
3728 // If this is an uninitialized lexical or a const, we need to return a
3729 // RuntimeLexicalErrorObject.
3730 auto* holder = &env->as<NativeObject>();
3731 if (prop.isSome() && holder->is<EnvironmentObject>() &&
3732 (holder->getSlot(prop->slot()).isMagic() || !prop->writable())) {
3733 return AttachDecision::NoAction;
3736 ObjOperandId lastObjId = objId;
3737 env = env_;
3738 while (env) {
3739 if (NeedEnvironmentShapeGuard(cx_, env) && !env->is<GlobalObject>()) {
3740 writer.guardShape(lastObjId, env->shape());
3743 if (env == holder) {
3744 break;
3747 lastObjId = writer.loadEnclosingEnvironment(lastObjId);
3748 env = env->enclosingEnvironment();
3751 if (prop.isSome() && holder->is<EnvironmentObject>()) {
3752 ValOperandId valId = EmitLoadSlot(writer, holder, lastObjId, prop->slot());
3753 writer.guardIsNotUninitializedLexical(valId);
3756 writer.loadObjectResult(lastObjId);
3757 writer.returnFromIC();
3759 trackAttached("BindName.EnvironmentName");
3760 return AttachDecision::Attach;
3763 void BindNameIRGenerator::trackAttached(const char* name) {
3764 stubName_ = name ? name : "NotAttached";
3765 #ifdef JS_CACHEIR_SPEW
3766 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
3767 sp.valueProperty("base", ObjectValue(*env_));
3768 sp.valueProperty("property", StringValue(name_));
3770 #endif
3773 HasPropIRGenerator::HasPropIRGenerator(JSContext* cx, HandleScript script,
3774 jsbytecode* pc, ICState state,
3775 CacheKind cacheKind, HandleValue idVal,
3776 HandleValue val)
3777 : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {}
3779 AttachDecision HasPropIRGenerator::tryAttachDense(HandleObject obj,
3780 ObjOperandId objId,
3781 uint32_t index,
3782 Int32OperandId indexId) {
3783 if (!obj->is<NativeObject>()) {
3784 return AttachDecision::NoAction;
3787 NativeObject* nobj = &obj->as<NativeObject>();
3788 if (!nobj->containsDenseElement(index)) {
3789 return AttachDecision::NoAction;
3792 if (mode_ == ICState::Mode::Megamorphic) {
3793 writer.guardIsNativeObject(objId);
3794 } else {
3795 // Guard shape to ensure object class is NativeObject.
3796 TestMatchingNativeReceiver(writer, nobj, objId);
3798 writer.loadDenseElementExistsResult(objId, indexId);
3799 writer.returnFromIC();
3801 trackAttached("HasProp.Dense");
3802 return AttachDecision::Attach;
3805 AttachDecision HasPropIRGenerator::tryAttachDenseHole(HandleObject obj,
3806 ObjOperandId objId,
3807 uint32_t index,
3808 Int32OperandId indexId) {
3809 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3810 OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No;
3812 if (!obj->is<NativeObject>()) {
3813 return AttachDecision::NoAction;
3816 NativeObject* nobj = &obj->as<NativeObject>();
3817 if (nobj->containsDenseElement(index)) {
3818 return AttachDecision::NoAction;
3820 if (!CanAttachDenseElementHole(nobj, ownProp)) {
3821 return AttachDecision::NoAction;
3824 // Guard shape to ensure class is NativeObject and to prevent non-dense
3825 // elements being added. Also ensures prototype doesn't change if dynamic
3826 // checks aren't emitted.
3827 TestMatchingNativeReceiver(writer, nobj, objId);
3829 // Generate prototype guards if needed. This includes monitoring that
3830 // properties were not added in the chain.
3831 if (!hasOwn) {
3832 GeneratePrototypeHoleGuards(writer, nobj, objId,
3833 /* alwaysGuardFirstProto = */ false);
3836 writer.loadDenseElementHoleExistsResult(objId, indexId);
3837 writer.returnFromIC();
3839 trackAttached("HasProp.DenseHole");
3840 return AttachDecision::Attach;
3843 AttachDecision HasPropIRGenerator::tryAttachSparse(HandleObject obj,
3844 ObjOperandId objId,
3845 Int32OperandId indexId) {
3846 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3847 OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No;
3849 if (!obj->is<NativeObject>()) {
3850 return AttachDecision::NoAction;
3852 auto* nobj = &obj->as<NativeObject>();
3854 if (!nobj->isIndexed()) {
3855 return AttachDecision::NoAction;
3857 if (!CanAttachDenseElementHole(nobj, ownProp, AllowIndexedReceiver::Yes)) {
3858 return AttachDecision::NoAction;
3861 // Guard that this is a native object.
3862 writer.guardIsNativeObject(objId);
3864 // Generate prototype guards if needed. This includes monitoring that
3865 // properties were not added in the chain.
3866 if (!hasOwn) {
3867 GeneratePrototypeHoleGuards(writer, nobj, objId,
3868 /* alwaysGuardFirstProto = */ true);
3871 // Because of the prototype guard we know that the prototype chain
3872 // does not include any dense or sparse (i.e indexed) properties.
3873 writer.callObjectHasSparseElementResult(objId, indexId);
3874 writer.returnFromIC();
3876 trackAttached("HasProp.Sparse");
3877 return AttachDecision::Attach;
3880 AttachDecision HasPropIRGenerator::tryAttachArgumentsObjectArg(
3881 HandleObject obj, ObjOperandId objId, Int32OperandId indexId) {
3882 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3883 OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No;
3885 if (!obj->is<ArgumentsObject>()) {
3886 return AttachDecision::NoAction;
3888 auto* args = &obj->as<ArgumentsObject>();
3890 // No elements must have been overridden or deleted.
3891 if (args->hasOverriddenElement()) {
3892 return AttachDecision::NoAction;
3895 if (!CanAttachDenseElementHole(args, ownProp, AllowIndexedReceiver::Yes,
3896 AllowExtraReceiverProperties::Yes)) {
3897 return AttachDecision::NoAction;
3900 if (args->is<MappedArgumentsObject>()) {
3901 writer.guardClass(objId, GuardClassKind::MappedArguments);
3902 } else {
3903 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
3904 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
3907 if (!hasOwn) {
3908 GeneratePrototypeHoleGuards(writer, args, objId,
3909 /* alwaysGuardFirstProto = */ true);
3912 writer.loadArgumentsObjectArgExistsResult(objId, indexId);
3913 writer.returnFromIC();
3915 trackAttached("HasProp.ArgumentsObjectArg");
3916 return AttachDecision::Attach;
3919 AttachDecision HasPropIRGenerator::tryAttachNamedProp(HandleObject obj,
3920 ObjOperandId objId,
3921 HandleId key,
3922 ValOperandId keyId) {
3923 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
3925 Rooted<NativeObject*> holder(cx_);
3926 PropertyResult prop;
3928 if (hasOwn) {
3929 if (!LookupOwnPropertyPure(cx_, obj, key, &prop)) {
3930 return AttachDecision::NoAction;
3933 holder.set(&obj->as<NativeObject>());
3934 } else {
3935 NativeObject* nHolder = nullptr;
3936 if (!LookupPropertyPure(cx_, obj, key, &nHolder, &prop)) {
3937 return AttachDecision::NoAction;
3939 holder.set(nHolder);
3941 if (prop.isNotFound()) {
3942 return AttachDecision::NoAction;
3945 TRY_ATTACH(tryAttachSmallObjectVariableKey(obj, objId, key, keyId));
3946 TRY_ATTACH(tryAttachMegamorphic(objId, keyId));
3947 TRY_ATTACH(tryAttachNative(&obj->as<NativeObject>(), objId, key, keyId, prop,
3948 holder.get()));
3950 return AttachDecision::NoAction;
3953 AttachDecision HasPropIRGenerator::tryAttachSmallObjectVariableKey(
3954 HandleObject obj, ObjOperandId objId, jsid key, ValOperandId keyId) {
3955 MOZ_ASSERT(obj->is<NativeObject>());
3957 if (cacheKind_ != CacheKind::HasOwn) {
3958 return AttachDecision::NoAction;
3961 if (mode_ != ICState::Mode::Megamorphic) {
3962 return AttachDecision::NoAction;
3965 if (numOptimizedStubs_ != 0) {
3966 return AttachDecision::NoAction;
3969 if (!key.isString()) {
3970 return AttachDecision::NoAction;
3973 if (!obj->as<NativeObject>().hasEmptyElements()) {
3974 return AttachDecision::NoAction;
3977 if (obj->getClass()->getResolve()) {
3978 return AttachDecision::NoAction;
3981 if (!obj->shape()->isShared()) {
3982 return AttachDecision::NoAction;
3985 static constexpr size_t SMALL_OBJECT_SIZE = 5;
3987 if (obj->shape()->asShared().slotSpan() > SMALL_OBJECT_SIZE) {
3988 return AttachDecision::NoAction;
3991 Rooted<ListObject*> keyListObj(cx_, ListObject::create(cx_));
3992 if (!keyListObj) {
3993 cx_->recoverFromOutOfMemory();
3994 return AttachDecision::NoAction;
3997 for (SharedShapePropertyIter<CanGC> iter(cx_, &obj->shape()->asShared());
3998 !iter.done(); iter++) {
3999 if (!iter->key().isAtom()) {
4000 return AttachDecision::NoAction;
4003 if (keyListObj->length() == SMALL_OBJECT_SIZE) {
4004 return AttachDecision::NoAction;
4007 RootedValue key(cx_, StringValue(iter->key().toAtom()));
4008 if (!keyListObj->append(cx_, key)) {
4009 cx_->recoverFromOutOfMemory();
4010 return AttachDecision::NoAction;
4014 writer.guardShape(objId, obj->shape());
4015 writer.guardNoDenseElements(objId);
4016 StringOperandId keyStrId = writer.guardToString(keyId);
4017 StringOperandId keyAtomId = writer.stringToAtom(keyStrId);
4018 writer.smallObjectVariableKeyHasOwnResult(keyAtomId, keyListObj,
4019 obj->shape());
4020 writer.returnFromIC();
4021 trackAttached("HasProp.SmallObjectVariableKey");
4022 return AttachDecision::Attach;
4025 AttachDecision HasPropIRGenerator::tryAttachMegamorphic(ObjOperandId objId,
4026 ValOperandId keyId) {
4027 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
4029 if (mode_ != ICState::Mode::Megamorphic) {
4030 return AttachDecision::NoAction;
4033 writer.megamorphicHasPropResult(objId, keyId, hasOwn);
4034 writer.returnFromIC();
4035 trackAttached("HasProp.Megamorphic");
4036 return AttachDecision::Attach;
4039 AttachDecision HasPropIRGenerator::tryAttachNative(NativeObject* obj,
4040 ObjOperandId objId, jsid key,
4041 ValOperandId keyId,
4042 PropertyResult prop,
4043 NativeObject* holder) {
4044 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
4046 if (!prop.isNativeProperty()) {
4047 return AttachDecision::NoAction;
4050 emitIdGuard(keyId, idVal_, key);
4051 EmitReadSlotGuard(writer, obj, holder, objId);
4052 writer.loadBooleanResult(true);
4053 writer.returnFromIC();
4055 trackAttached("HasProp.Native");
4056 return AttachDecision::Attach;
4059 static void EmitGuardTypedArray(CacheIRWriter& writer, TypedArrayObject* obj,
4060 ObjOperandId objId) {
4061 if (obj->is<FixedLengthTypedArrayObject>()) {
4062 writer.guardIsFixedLengthTypedArray(objId);
4063 } else {
4064 writer.guardIsResizableTypedArray(objId);
4068 AttachDecision HasPropIRGenerator::tryAttachTypedArray(HandleObject obj,
4069 ObjOperandId objId,
4070 ValOperandId keyId) {
4071 if (!obj->is<TypedArrayObject>()) {
4072 return AttachDecision::NoAction;
4075 int64_t index;
4076 if (!ValueIsInt64Index(idVal_, &index)) {
4077 return AttachDecision::NoAction;
4080 auto* tarr = &obj->as<TypedArrayObject>();
4081 EmitGuardTypedArray(writer, tarr, objId);
4083 IntPtrOperandId intPtrIndexId =
4084 guardToIntPtrIndex(idVal_, keyId, /* supportOOB = */ true);
4086 auto viewKind = ToArrayBufferViewKind(tarr);
4087 writer.loadTypedArrayElementExistsResult(objId, intPtrIndexId, viewKind);
4088 writer.returnFromIC();
4090 trackAttached("HasProp.TypedArrayObject");
4091 return AttachDecision::Attach;
4094 AttachDecision HasPropIRGenerator::tryAttachSlotDoesNotExist(
4095 NativeObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId) {
4096 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
4098 emitIdGuard(keyId, idVal_, key);
4099 if (hasOwn) {
4100 TestMatchingNativeReceiver(writer, obj, objId);
4101 } else {
4102 EmitMissingPropGuard(writer, obj, objId);
4104 writer.loadBooleanResult(false);
4105 writer.returnFromIC();
4107 trackAttached("HasProp.DoesNotExist");
4108 return AttachDecision::Attach;
4111 AttachDecision HasPropIRGenerator::tryAttachDoesNotExist(HandleObject obj,
4112 ObjOperandId objId,
4113 HandleId key,
4114 ValOperandId keyId) {
4115 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
4117 // Check that property doesn't exist on |obj| or it's prototype chain. These
4118 // checks allow NativeObjects with a NativeObject prototype chain. They return
4119 // NoAction if unknown such as resolve hooks or proxies.
4120 if (hasOwn) {
4121 if (!CheckHasNoSuchOwnProperty(cx_, obj, key)) {
4122 return AttachDecision::NoAction;
4124 } else {
4125 if (!CheckHasNoSuchProperty(cx_, obj, key)) {
4126 return AttachDecision::NoAction;
4130 TRY_ATTACH(tryAttachSmallObjectVariableKey(obj, objId, key, keyId));
4131 TRY_ATTACH(tryAttachMegamorphic(objId, keyId));
4132 TRY_ATTACH(
4133 tryAttachSlotDoesNotExist(&obj->as<NativeObject>(), objId, key, keyId));
4135 return AttachDecision::NoAction;
4138 AttachDecision HasPropIRGenerator::tryAttachProxyElement(HandleObject obj,
4139 ObjOperandId objId,
4140 ValOperandId keyId) {
4141 bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
4143 if (!obj->is<ProxyObject>()) {
4144 return AttachDecision::NoAction;
4147 writer.guardIsProxy(objId);
4148 writer.proxyHasPropResult(objId, keyId, hasOwn);
4149 writer.returnFromIC();
4151 trackAttached("HasProp.ProxyElement");
4152 return AttachDecision::Attach;
4155 AttachDecision HasPropIRGenerator::tryAttachStub() {
4156 MOZ_ASSERT(cacheKind_ == CacheKind::In || cacheKind_ == CacheKind::HasOwn);
4158 AutoAssertNoPendingException aanpe(cx_);
4160 // NOTE: Argument order is PROPERTY, OBJECT
4161 ValOperandId keyId(writer.setInputOperandId(0));
4162 ValOperandId valId(writer.setInputOperandId(1));
4164 if (!val_.isObject()) {
4165 trackAttached(IRGenerator::NotAttached);
4166 return AttachDecision::NoAction;
4168 RootedObject obj(cx_, &val_.toObject());
4169 ObjOperandId objId = writer.guardToObject(valId);
4171 // Optimize Proxies
4172 TRY_ATTACH(tryAttachProxyElement(obj, objId, keyId));
4174 RootedId id(cx_);
4175 bool nameOrSymbol;
4176 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
4177 cx_->clearPendingException();
4178 return AttachDecision::NoAction;
4181 if (nameOrSymbol) {
4182 TRY_ATTACH(tryAttachNamedProp(obj, objId, id, keyId));
4183 TRY_ATTACH(tryAttachDoesNotExist(obj, objId, id, keyId));
4185 trackAttached(IRGenerator::NotAttached);
4186 return AttachDecision::NoAction;
4189 TRY_ATTACH(tryAttachTypedArray(obj, objId, keyId));
4191 uint32_t index;
4192 Int32OperandId indexId;
4193 if (maybeGuardInt32Index(idVal_, keyId, &index, &indexId)) {
4194 TRY_ATTACH(tryAttachDense(obj, objId, index, indexId));
4195 TRY_ATTACH(tryAttachDenseHole(obj, objId, index, indexId));
4196 TRY_ATTACH(tryAttachSparse(obj, objId, indexId));
4197 TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, indexId));
4199 trackAttached(IRGenerator::NotAttached);
4200 return AttachDecision::NoAction;
4203 trackAttached(IRGenerator::NotAttached);
4204 return AttachDecision::NoAction;
4207 void HasPropIRGenerator::trackAttached(const char* name) {
4208 stubName_ = name ? name : "NotAttached";
4209 #ifdef JS_CACHEIR_SPEW
4210 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
4211 sp.valueProperty("base", val_);
4212 sp.valueProperty("property", idVal_);
4214 #endif
4217 CheckPrivateFieldIRGenerator::CheckPrivateFieldIRGenerator(
4218 JSContext* cx, HandleScript script, jsbytecode* pc, ICState state,
4219 CacheKind cacheKind, HandleValue idVal, HandleValue val)
4220 : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {
4221 MOZ_ASSERT(idVal.isSymbol() && idVal.toSymbol()->isPrivateName());
4224 AttachDecision CheckPrivateFieldIRGenerator::tryAttachStub() {
4225 AutoAssertNoPendingException aanpe(cx_);
4227 ValOperandId valId(writer.setInputOperandId(0));
4228 ValOperandId keyId(writer.setInputOperandId(1));
4230 if (!val_.isObject()) {
4231 trackAttached(IRGenerator::NotAttached);
4232 return AttachDecision::NoAction;
4234 JSObject* obj = &val_.toObject();
4235 ObjOperandId objId = writer.guardToObject(valId);
4236 PropertyKey key = PropertyKey::Symbol(idVal_.toSymbol());
4238 ThrowCondition condition;
4239 ThrowMsgKind msgKind;
4240 GetCheckPrivateFieldOperands(pc_, &condition, &msgKind);
4242 PropertyResult prop;
4243 if (!LookupOwnPropertyPure(cx_, obj, key, &prop)) {
4244 return AttachDecision::NoAction;
4247 if (CheckPrivateFieldWillThrow(condition, prop.isFound())) {
4248 // Don't attach a stub if the operation will throw.
4249 return AttachDecision::NoAction;
4252 auto* nobj = &obj->as<NativeObject>();
4254 TRY_ATTACH(tryAttachNative(nobj, objId, key, keyId, prop));
4256 return AttachDecision::NoAction;
4259 AttachDecision CheckPrivateFieldIRGenerator::tryAttachNative(
4260 NativeObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId,
4261 PropertyResult prop) {
4262 MOZ_ASSERT(prop.isNativeProperty() || prop.isNotFound());
4264 emitIdGuard(keyId, idVal_, key);
4265 TestMatchingNativeReceiver(writer, obj, objId);
4266 writer.loadBooleanResult(prop.isFound());
4267 writer.returnFromIC();
4269 trackAttached("CheckPrivateField.Native");
4270 return AttachDecision::Attach;
4273 void CheckPrivateFieldIRGenerator::trackAttached(const char* name) {
4274 stubName_ = name ? name : "NotAttached";
4275 #ifdef JS_CACHEIR_SPEW
4276 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
4277 sp.valueProperty("base", val_);
4278 sp.valueProperty("property", idVal_);
4280 #endif
4283 bool IRGenerator::maybeGuardInt32Index(const Value& index, ValOperandId indexId,
4284 uint32_t* int32Index,
4285 Int32OperandId* int32IndexId) {
4286 if (index.isNumber()) {
4287 int32_t indexSigned;
4288 if (index.isInt32()) {
4289 indexSigned = index.toInt32();
4290 } else {
4291 // We allow negative zero here.
4292 if (!mozilla::NumberEqualsInt32(index.toDouble(), &indexSigned)) {
4293 return false;
4297 if (indexSigned < 0) {
4298 return false;
4301 *int32Index = uint32_t(indexSigned);
4302 *int32IndexId = writer.guardToInt32Index(indexId);
4303 return true;
4306 if (index.isString()) {
4307 int32_t indexSigned = GetIndexFromString(index.toString());
4308 if (indexSigned < 0) {
4309 return false;
4312 StringOperandId strId = writer.guardToString(indexId);
4313 *int32Index = uint32_t(indexSigned);
4314 *int32IndexId = writer.guardStringToIndex(strId);
4315 return true;
4318 return false;
4321 SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, HandleScript script,
4322 jsbytecode* pc, CacheKind cacheKind,
4323 ICState state, HandleValue lhsVal,
4324 HandleValue idVal, HandleValue rhsVal)
4325 : IRGenerator(cx, script, pc, cacheKind, state),
4326 lhsVal_(lhsVal),
4327 idVal_(idVal),
4328 rhsVal_(rhsVal) {}
4330 AttachDecision SetPropIRGenerator::tryAttachStub() {
4331 AutoAssertNoPendingException aanpe(cx_);
4333 ValOperandId objValId(writer.setInputOperandId(0));
4334 ValOperandId rhsValId;
4335 if (cacheKind_ == CacheKind::SetProp) {
4336 rhsValId = ValOperandId(writer.setInputOperandId(1));
4337 } else {
4338 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
4339 MOZ_ASSERT(setElemKeyValueId().id() == 1);
4340 writer.setInputOperandId(1);
4341 rhsValId = ValOperandId(writer.setInputOperandId(2));
4344 RootedId id(cx_);
4345 bool nameOrSymbol;
4346 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
4347 cx_->clearPendingException();
4348 return AttachDecision::NoAction;
4351 if (lhsVal_.isObject()) {
4352 RootedObject obj(cx_, &lhsVal_.toObject());
4354 ObjOperandId objId = writer.guardToObject(objValId);
4355 if (IsPropertySetOp(JSOp(*pc_))) {
4356 TRY_ATTACH(tryAttachMegamorphicSetElement(obj, objId, rhsValId));
4358 if (nameOrSymbol) {
4359 TRY_ATTACH(tryAttachNativeSetSlot(obj, objId, id, rhsValId));
4360 if (IsPropertySetOp(JSOp(*pc_))) {
4361 TRY_ATTACH(tryAttachSetArrayLength(obj, objId, id, rhsValId));
4362 TRY_ATTACH(tryAttachSetter(obj, objId, id, rhsValId));
4363 TRY_ATTACH(tryAttachWindowProxy(obj, objId, id, rhsValId));
4364 TRY_ATTACH(tryAttachProxy(obj, objId, id, rhsValId));
4365 TRY_ATTACH(tryAttachMegamorphicSetSlot(obj, objId, id, rhsValId));
4367 if (canAttachAddSlotStub(obj, id)) {
4368 deferType_ = DeferType::AddSlot;
4369 return AttachDecision::Deferred;
4371 return AttachDecision::NoAction;
4374 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
4376 if (IsPropertySetOp(JSOp(*pc_))) {
4377 TRY_ATTACH(tryAttachProxyElement(obj, objId, rhsValId));
4380 TRY_ATTACH(tryAttachSetTypedArrayElement(obj, objId, rhsValId));
4382 uint32_t index;
4383 Int32OperandId indexId;
4384 if (maybeGuardInt32Index(idVal_, setElemKeyValueId(), &index, &indexId)) {
4385 TRY_ATTACH(
4386 tryAttachSetDenseElement(obj, objId, index, indexId, rhsValId));
4387 TRY_ATTACH(
4388 tryAttachSetDenseElementHole(obj, objId, index, indexId, rhsValId));
4389 TRY_ATTACH(tryAttachAddOrUpdateSparseElement(obj, objId, index, indexId,
4390 rhsValId));
4391 return AttachDecision::NoAction;
4394 return AttachDecision::NoAction;
4397 static void EmitStoreSlotAndReturn(CacheIRWriter& writer, ObjOperandId objId,
4398 NativeObject* nobj, PropertyInfo prop,
4399 ValOperandId rhsId) {
4400 if (nobj->isFixedSlot(prop.slot())) {
4401 size_t offset = NativeObject::getFixedSlotOffset(prop.slot());
4402 writer.storeFixedSlot(objId, offset, rhsId);
4403 } else {
4404 size_t offset = nobj->dynamicSlotIndex(prop.slot()) * sizeof(Value);
4405 writer.storeDynamicSlot(objId, offset, rhsId);
4407 writer.returnFromIC();
4410 static Maybe<PropertyInfo> LookupShapeForSetSlot(JSOp op, NativeObject* obj,
4411 jsid id) {
4412 Maybe<PropertyInfo> prop = obj->lookupPure(id);
4413 if (prop.isNothing() || !prop->isDataProperty() || !prop->writable()) {
4414 return mozilla::Nothing();
4417 // If this is a property init operation, the property's attributes may have to
4418 // be changed too, so make sure the current flags match.
4419 if (IsPropertyInitOp(op)) {
4420 // Don't support locked init operations.
4421 if (IsLockedInitOp(op)) {
4422 return mozilla::Nothing();
4425 // Can't redefine a non-configurable property.
4426 if (!prop->configurable()) {
4427 return mozilla::Nothing();
4430 // Make sure the enumerable flag matches the init operation.
4431 if (IsHiddenInitOp(op) == prop->enumerable()) {
4432 return mozilla::Nothing();
4436 return prop;
4439 static bool CanAttachNativeSetSlot(JSOp op, JSObject* obj, PropertyKey id,
4440 Maybe<PropertyInfo>* prop) {
4441 if (!obj->is<NativeObject>()) {
4442 return false;
4445 if (Watchtower::watchesPropertyModification(&obj->as<NativeObject>())) {
4446 return false;
4449 *prop = LookupShapeForSetSlot(op, &obj->as<NativeObject>(), id);
4450 return prop->isSome();
4453 // There is no need to guard on the shape. Global lexical bindings are
4454 // non-configurable and can not be shadowed.
4455 static bool IsGlobalLexicalSetGName(JSOp op, NativeObject* obj,
4456 PropertyInfo prop) {
4457 // Ensure that the env can't change.
4458 if (op != JSOp::SetGName && op != JSOp::StrictSetGName) {
4459 return false;
4462 if (!obj->is<GlobalLexicalEnvironmentObject>()) {
4463 return false;
4466 // Uninitialized let bindings use a RuntimeLexicalErrorObject.
4467 MOZ_ASSERT(!obj->getSlot(prop.slot()).isMagic());
4468 MOZ_ASSERT(prop.writable());
4469 MOZ_ASSERT(!prop.configurable());
4470 return true;
4473 AttachDecision SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj,
4474 ObjOperandId objId,
4475 HandleId id,
4476 ValOperandId rhsId) {
4477 Maybe<PropertyInfo> prop;
4478 if (!CanAttachNativeSetSlot(JSOp(*pc_), obj, id, &prop)) {
4479 return AttachDecision::NoAction;
4482 if (mode_ == ICState::Mode::Megamorphic && cacheKind_ == CacheKind::SetProp &&
4483 IsPropertySetOp(JSOp(*pc_))) {
4484 return AttachDecision::NoAction;
4487 maybeEmitIdGuard(id);
4489 NativeObject* nobj = &obj->as<NativeObject>();
4490 if (!IsGlobalLexicalSetGName(JSOp(*pc_), nobj, *prop)) {
4491 TestMatchingNativeReceiver(writer, nobj, objId);
4493 EmitStoreSlotAndReturn(writer, objId, nobj, *prop, rhsId);
4495 trackAttached("SetProp.NativeSlot");
4496 return AttachDecision::Attach;
4499 static bool ValueCanConvertToNumeric(Scalar::Type type, const Value& val) {
4500 if (Scalar::isBigIntType(type)) {
4501 return val.isBigInt();
4503 return val.isNumber() || val.isNullOrUndefined() || val.isBoolean() ||
4504 val.isString();
4507 OperandId IRGenerator::emitNumericGuard(ValOperandId valId, const Value& v,
4508 Scalar::Type type) {
4509 MOZ_ASSERT(ValueCanConvertToNumeric(type, v));
4510 switch (type) {
4511 case Scalar::Int8:
4512 case Scalar::Uint8:
4513 case Scalar::Int16:
4514 case Scalar::Uint16:
4515 case Scalar::Int32:
4516 case Scalar::Uint32: {
4517 if (v.isNumber()) {
4518 return writer.guardToInt32ModUint32(valId);
4520 if (v.isNullOrUndefined()) {
4521 writer.guardIsNullOrUndefined(valId);
4522 return writer.loadInt32Constant(0);
4524 if (v.isBoolean()) {
4525 return writer.guardBooleanToInt32(valId);
4527 MOZ_ASSERT(v.isString());
4528 StringOperandId strId = writer.guardToString(valId);
4529 NumberOperandId numId = writer.guardStringToNumber(strId);
4530 return writer.truncateDoubleToUInt32(numId);
4533 case Scalar::Float32:
4534 case Scalar::Float64: {
4535 if (v.isNumber()) {
4536 return writer.guardIsNumber(valId);
4538 if (v.isNull()) {
4539 writer.guardIsNull(valId);
4540 return writer.loadDoubleConstant(0.0);
4542 if (v.isUndefined()) {
4543 writer.guardIsUndefined(valId);
4544 return writer.loadDoubleConstant(JS::GenericNaN());
4546 if (v.isBoolean()) {
4547 BooleanOperandId boolId = writer.guardToBoolean(valId);
4548 return writer.booleanToNumber(boolId);
4550 MOZ_ASSERT(v.isString());
4551 StringOperandId strId = writer.guardToString(valId);
4552 return writer.guardStringToNumber(strId);
4555 case Scalar::Uint8Clamped: {
4556 if (v.isNumber()) {
4557 return writer.guardToUint8Clamped(valId);
4559 if (v.isNullOrUndefined()) {
4560 writer.guardIsNullOrUndefined(valId);
4561 return writer.loadInt32Constant(0);
4563 if (v.isBoolean()) {
4564 return writer.guardBooleanToInt32(valId);
4566 MOZ_ASSERT(v.isString());
4567 StringOperandId strId = writer.guardToString(valId);
4568 NumberOperandId numId = writer.guardStringToNumber(strId);
4569 return writer.doubleToUint8Clamped(numId);
4572 case Scalar::BigInt64:
4573 case Scalar::BigUint64:
4574 MOZ_ASSERT(v.isBigInt());
4575 return writer.guardToBigInt(valId);
4577 case Scalar::MaxTypedArrayViewType:
4578 case Scalar::Int64:
4579 case Scalar::Simd128:
4580 break;
4582 MOZ_CRASH("Unsupported TypedArray type");
4585 void SetPropIRGenerator::trackAttached(const char* name) {
4586 stubName_ = name ? name : "NotAttached";
4587 #ifdef JS_CACHEIR_SPEW
4588 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
4589 sp.opcodeProperty("op", JSOp(*pc_));
4590 sp.valueProperty("base", lhsVal_);
4591 sp.valueProperty("property", idVal_);
4592 sp.valueProperty("value", rhsVal_);
4594 #endif
4597 static bool IsCacheableSetPropCallNative(NativeObject* obj,
4598 NativeObject* holder,
4599 PropertyInfo prop) {
4600 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
4602 if (!prop.isAccessorProperty()) {
4603 return false;
4606 JSObject* setterObject = holder->getSetter(prop);
4607 if (!setterObject || !setterObject->is<JSFunction>()) {
4608 return false;
4611 JSFunction& setter = setterObject->as<JSFunction>();
4612 if (!setter.isNativeWithoutJitEntry()) {
4613 return false;
4616 if (setter.isClassConstructor()) {
4617 return false;
4620 return true;
4623 static bool IsCacheableSetPropCallScripted(NativeObject* obj,
4624 NativeObject* holder,
4625 PropertyInfo prop) {
4626 MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
4628 if (!prop.isAccessorProperty()) {
4629 return false;
4632 JSObject* setterObject = holder->getSetter(prop);
4633 if (!setterObject || !setterObject->is<JSFunction>()) {
4634 return false;
4637 JSFunction& setter = setterObject->as<JSFunction>();
4638 if (setter.isClassConstructor()) {
4639 return false;
4642 // Scripted functions and natives with JIT entry can use the scripted path.
4643 return setter.hasJitEntry();
4646 static bool CanAttachSetter(JSContext* cx, jsbytecode* pc, JSObject* obj,
4647 PropertyKey id, NativeObject** holder,
4648 Maybe<PropertyInfo>* propInfo) {
4649 // Don't attach a setter stub for ops like JSOp::InitElem.
4650 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc)));
4652 PropertyResult prop;
4653 if (!LookupPropertyPure(cx, obj, id, holder, &prop)) {
4654 return false;
4656 auto* nobj = &obj->as<NativeObject>();
4658 if (!prop.isNativeProperty()) {
4659 return false;
4662 if (!IsCacheableSetPropCallScripted(nobj, *holder, prop.propertyInfo()) &&
4663 !IsCacheableSetPropCallNative(nobj, *holder, prop.propertyInfo())) {
4664 return false;
4667 *propInfo = mozilla::Some(prop.propertyInfo());
4668 return true;
4671 static void EmitCallSetterNoGuards(JSContext* cx, CacheIRWriter& writer,
4672 NativeObject* obj, NativeObject* holder,
4673 PropertyInfo prop, ObjOperandId receiverId,
4674 ValOperandId rhsId) {
4675 JSFunction* target = &holder->getSetter(prop)->as<JSFunction>();
4676 bool sameRealm = cx->realm() == target->realm();
4678 if (target->isNativeWithoutJitEntry()) {
4679 MOZ_ASSERT(IsCacheableSetPropCallNative(obj, holder, prop));
4680 writer.callNativeSetter(receiverId, target, rhsId, sameRealm);
4681 writer.returnFromIC();
4682 return;
4685 MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, prop));
4686 writer.callScriptedSetter(receiverId, target, rhsId, sameRealm);
4687 writer.returnFromIC();
4690 static void EmitCallDOMSetterNoGuards(JSContext* cx, CacheIRWriter& writer,
4691 NativeObject* holder, PropertyInfo prop,
4692 ObjOperandId objId, ValOperandId rhsId) {
4693 JSFunction* setter = &holder->getSetter(prop)->as<JSFunction>();
4694 MOZ_ASSERT(cx->realm() == setter->realm());
4696 writer.callDOMSetter(objId, setter->jitInfo(), rhsId);
4697 writer.returnFromIC();
4700 AttachDecision SetPropIRGenerator::tryAttachSetter(HandleObject obj,
4701 ObjOperandId objId,
4702 HandleId id,
4703 ValOperandId rhsId) {
4704 // Don't attach a setter stub for ops like JSOp::InitElem.
4705 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
4707 NativeObject* holder = nullptr;
4708 Maybe<PropertyInfo> prop;
4709 if (!CanAttachSetter(cx_, pc_, obj, id, &holder, &prop)) {
4710 return AttachDecision::NoAction;
4712 auto* nobj = &obj->as<NativeObject>();
4714 bool needsWindowProxy =
4715 IsWindow(nobj) && SetterNeedsWindowProxyThis(holder, *prop);
4717 maybeEmitIdGuard(id);
4719 // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
4720 // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
4721 // require outerizing).
4722 if (mode_ == ICState::Mode::Specialized || IsWindow(nobj)) {
4723 TestMatchingNativeReceiver(writer, nobj, objId);
4725 if (nobj != holder) {
4726 GeneratePrototypeGuards(writer, nobj, holder, objId);
4728 // Guard on the holder's shape.
4729 ObjOperandId holderId = writer.loadObject(holder);
4730 TestMatchingHolder(writer, holder, holderId);
4732 EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
4733 /* holderIsConstant = */ true);
4734 } else {
4735 EmitGuardGetterSetterSlot(writer, holder, *prop, objId);
4737 } else {
4738 GetterSetter* gs = holder->getGetterSetter(*prop);
4739 writer.guardHasGetterSetter(objId, id, gs);
4742 if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Setter, nobj, holder, *prop,
4743 mode_)) {
4744 MOZ_ASSERT(!needsWindowProxy);
4745 EmitCallDOMSetterNoGuards(cx_, writer, holder, *prop, objId, rhsId);
4747 trackAttached("SetProp.DOMSetter");
4748 return AttachDecision::Attach;
4751 ObjOperandId receiverId;
4752 if (needsWindowProxy) {
4753 MOZ_ASSERT(cx_->global()->maybeWindowProxy());
4754 receiverId = writer.loadObject(cx_->global()->maybeWindowProxy());
4755 } else {
4756 receiverId = objId;
4758 EmitCallSetterNoGuards(cx_, writer, nobj, holder, *prop, receiverId, rhsId);
4760 trackAttached("SetProp.Setter");
4761 return AttachDecision::Attach;
4764 AttachDecision SetPropIRGenerator::tryAttachSetArrayLength(HandleObject obj,
4765 ObjOperandId objId,
4766 HandleId id,
4767 ValOperandId rhsId) {
4768 // Don't attach an array length stub for ops like JSOp::InitElem.
4769 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
4771 if (!obj->is<ArrayObject>() || !id.isAtom(cx_->names().length) ||
4772 !obj->as<ArrayObject>().lengthIsWritable()) {
4773 return AttachDecision::NoAction;
4776 maybeEmitIdGuard(id);
4777 emitOptimisticClassGuard(objId, obj, GuardClassKind::Array);
4778 writer.callSetArrayLength(objId, IsStrictSetPC(pc_), rhsId);
4779 writer.returnFromIC();
4781 trackAttached("SetProp.ArrayLength");
4782 return AttachDecision::Attach;
4785 AttachDecision SetPropIRGenerator::tryAttachSetDenseElement(
4786 HandleObject obj, ObjOperandId objId, uint32_t index,
4787 Int32OperandId indexId, ValOperandId rhsId) {
4788 if (!obj->is<NativeObject>()) {
4789 return AttachDecision::NoAction;
4792 NativeObject* nobj = &obj->as<NativeObject>();
4793 if (!nobj->containsDenseElement(index) || nobj->denseElementsAreFrozen()) {
4794 return AttachDecision::NoAction;
4797 // Setting holes requires extra code for marking the elements non-packed.
4798 MOZ_ASSERT(!rhsVal_.isMagic(JS_ELEMENTS_HOLE));
4800 JSOp op = JSOp(*pc_);
4802 // We don't currently emit locked init for any indexed properties.
4803 MOZ_ASSERT(!IsLockedInitOp(op));
4805 // We don't currently emit hidden init for any existing indexed properties.
4806 MOZ_ASSERT(!IsHiddenInitOp(op));
4808 // Don't optimize InitElem (DefineProperty) on non-extensible objects: when
4809 // the elements are sealed, we have to throw an exception. Note that we have
4810 // to check !isExtensible instead of denseElementsAreSealed because sealing
4811 // a (non-extensible) object does not necessarily trigger a Shape change.
4812 if (IsPropertyInitOp(op) && !nobj->isExtensible()) {
4813 return AttachDecision::NoAction;
4816 TestMatchingNativeReceiver(writer, nobj, objId);
4818 writer.storeDenseElement(objId, indexId, rhsId);
4819 writer.returnFromIC();
4821 trackAttached("SetProp.DenseElement");
4822 return AttachDecision::Attach;
4825 static bool CanAttachAddElement(NativeObject* obj, bool isInit,
4826 AllowIndexedReceiver allowIndexedReceiver) {
4827 // Make sure the receiver doesn't have any indexed properties and that such
4828 // properties can't appear without a shape change.
4829 if (allowIndexedReceiver == AllowIndexedReceiver::No && obj->isIndexed()) {
4830 return false;
4833 do {
4834 // This check is also relevant for the receiver object.
4835 const JSClass* clasp = obj->getClass();
4836 if (clasp != &ArrayObject::class_ &&
4837 (clasp->getAddProperty() || clasp->getResolve() ||
4838 clasp->getOpsLookupProperty() || clasp->getOpsSetProperty())) {
4839 return false;
4842 // If we're initializing a property instead of setting one, the objects
4843 // on the prototype are not relevant.
4844 if (isInit) {
4845 break;
4848 JSObject* proto = obj->staticPrototype();
4849 if (!proto) {
4850 break;
4853 if (!proto->is<NativeObject>()) {
4854 return false;
4857 NativeObject* nproto = &proto->as<NativeObject>();
4858 if (nproto->isIndexed()) {
4859 return false;
4862 // We have to make sure the proto has no non-writable (frozen) elements
4863 // because we're not allowed to shadow them.
4864 if (nproto->denseElementsAreFrozen() &&
4865 nproto->getDenseInitializedLength() > 0) {
4866 return false;
4869 obj = nproto;
4870 } while (true);
4872 return true;
4875 AttachDecision SetPropIRGenerator::tryAttachSetDenseElementHole(
4876 HandleObject obj, ObjOperandId objId, uint32_t index,
4877 Int32OperandId indexId, ValOperandId rhsId) {
4878 if (!obj->is<NativeObject>()) {
4879 return AttachDecision::NoAction;
4882 // Setting holes requires extra code for marking the elements non-packed.
4883 if (rhsVal_.isMagic(JS_ELEMENTS_HOLE)) {
4884 return AttachDecision::NoAction;
4887 JSOp op = JSOp(*pc_);
4888 MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
4890 // We don't currently emit locked init for any indexed properties.
4891 MOZ_ASSERT(!IsLockedInitOp(op));
4893 // Hidden init can be emitted for absent indexed properties.
4894 if (IsHiddenInitOp(op)) {
4895 MOZ_ASSERT(op == JSOp::InitHiddenElem);
4896 return AttachDecision::NoAction;
4899 NativeObject* nobj = &obj->as<NativeObject>();
4900 if (!nobj->isExtensible()) {
4901 return AttachDecision::NoAction;
4904 MOZ_ASSERT(!nobj->denseElementsAreFrozen(),
4905 "Extensible objects should not have frozen elements");
4907 uint32_t initLength = nobj->getDenseInitializedLength();
4909 // Optimize if we're adding an element at initLength or writing to a hole.
4911 // In the case where index > initLength, we need noteHasDenseAdd to be called
4912 // to ensure Ion is aware that writes have occurred to-out-of-bound indexes
4913 // before.
4915 // TODO(post-Warp): noteHasDenseAdd (nee: noteArrayWriteHole) no longer exists
4916 bool isAdd = index == initLength;
4917 bool isHoleInBounds =
4918 index < initLength && !nobj->containsDenseElement(index);
4919 if (!isAdd && !isHoleInBounds) {
4920 return AttachDecision::NoAction;
4923 // Can't add new elements to arrays with non-writable length.
4924 if (isAdd && nobj->is<ArrayObject>() &&
4925 !nobj->as<ArrayObject>().lengthIsWritable()) {
4926 return AttachDecision::NoAction;
4929 // Typed arrays don't have dense elements.
4930 if (nobj->is<TypedArrayObject>()) {
4931 return AttachDecision::NoAction;
4934 // Check for other indexed properties or class hooks.
4935 if (!CanAttachAddElement(nobj, IsPropertyInitOp(op),
4936 AllowIndexedReceiver::No)) {
4937 return AttachDecision::NoAction;
4940 TestMatchingNativeReceiver(writer, nobj, objId);
4942 // Also shape guard the proto chain, unless this is an InitElem.
4943 if (IsPropertySetOp(op)) {
4944 ShapeGuardProtoChain(writer, nobj, objId);
4947 writer.storeDenseElementHole(objId, indexId, rhsId, isAdd);
4948 writer.returnFromIC();
4950 trackAttached(isAdd ? "AddDenseElement" : "StoreDenseElementHole");
4951 return AttachDecision::Attach;
4954 // Add an IC for adding or updating a sparse element.
4955 AttachDecision SetPropIRGenerator::tryAttachAddOrUpdateSparseElement(
4956 HandleObject obj, ObjOperandId objId, uint32_t index,
4957 Int32OperandId indexId, ValOperandId rhsId) {
4958 JSOp op = JSOp(*pc_);
4959 MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
4961 if (op != JSOp::SetElem && op != JSOp::StrictSetElem) {
4962 return AttachDecision::NoAction;
4965 if (!obj->is<NativeObject>()) {
4966 return AttachDecision::NoAction;
4968 NativeObject* nobj = &obj->as<NativeObject>();
4970 // We cannot attach a stub to a non-extensible object
4971 if (!nobj->isExtensible()) {
4972 return AttachDecision::NoAction;
4975 // Stub doesn't handle negative indices.
4976 if (index > INT32_MAX) {
4977 return AttachDecision::NoAction;
4980 // The index must not be for a dense element.
4981 if (nobj->containsDenseElement(index)) {
4982 return AttachDecision::NoAction;
4985 // Only handle ArrayObject and PlainObject in this stub.
4986 if (!nobj->is<ArrayObject>() && !nobj->is<PlainObject>()) {
4987 return AttachDecision::NoAction;
4990 // Don't attach if we're adding to an array with non-writable length.
4991 if (nobj->is<ArrayObject>()) {
4992 ArrayObject* aobj = &nobj->as<ArrayObject>();
4993 bool isAdd = (index >= aobj->length());
4994 if (isAdd && !aobj->lengthIsWritable()) {
4995 return AttachDecision::NoAction;
4999 // Check for class hooks or indexed properties on the prototype chain that
5000 // we're not allowed to shadow.
5001 if (!CanAttachAddElement(nobj, /* isInit = */ false,
5002 AllowIndexedReceiver::Yes)) {
5003 return AttachDecision::NoAction;
5006 // Ensure that obj is an ArrayObject or PlainObject.
5007 if (nobj->is<ArrayObject>()) {
5008 writer.guardClass(objId, GuardClassKind::Array);
5009 } else {
5010 MOZ_ASSERT(nobj->is<PlainObject>());
5011 writer.guardClass(objId, GuardClassKind::PlainObject);
5014 // The helper we are going to call only applies to non-dense elements.
5015 writer.guardIndexIsNotDenseElement(objId, indexId);
5017 // Guard extensible: We may be trying to add a new element, and so we'd best
5018 // be able to do so safely.
5019 writer.guardIsExtensible(objId);
5021 // Ensures we are able to efficiently able to map to an integral jsid.
5022 writer.guardInt32IsNonNegative(indexId);
5024 // Shape guard the prototype chain to avoid shadowing indexes from appearing.
5025 // Guard the prototype of the receiver explicitly, because the receiver's
5026 // shape is not being guarded as a proxy for that.
5027 GuardReceiverProto(writer, nobj, objId);
5029 // Dense elements may appear on the prototype chain (and prototypes may
5030 // have a different notion of which elements are dense), but they can
5031 // only be data properties, so our specialized Set handler is ok to bind
5032 // to them.
5033 if (IsPropertySetOp(op)) {
5034 ShapeGuardProtoChain(writer, nobj, objId);
5037 // Ensure that if we're adding an element to the object, the object's
5038 // length is writable.
5039 if (nobj->is<ArrayObject>()) {
5040 writer.guardIndexIsValidUpdateOrAdd(objId, indexId);
5043 writer.callAddOrUpdateSparseElementHelper(
5044 objId, indexId, rhsId,
5045 /* strict = */ op == JSOp::StrictSetElem);
5046 writer.returnFromIC();
5048 trackAttached("SetProp.AddOrUpdateSparseElement");
5049 return AttachDecision::Attach;
5052 AttachDecision SetPropIRGenerator::tryAttachSetTypedArrayElement(
5053 HandleObject obj, ObjOperandId objId, ValOperandId rhsId) {
5054 if (!obj->is<TypedArrayObject>()) {
5055 return AttachDecision::NoAction;
5057 if (!idVal_.isNumber()) {
5058 return AttachDecision::NoAction;
5061 auto* tarr = &obj->as<TypedArrayObject>();
5062 Scalar::Type elementType = tarr->type();
5064 // Don't attach if the input type doesn't match the guard added below.
5065 if (!ValueCanConvertToNumeric(elementType, rhsVal_)) {
5066 return AttachDecision::NoAction;
5069 bool handleOOB = false;
5070 int64_t indexInt64;
5071 if (!ValueIsInt64Index(idVal_, &indexInt64) || indexInt64 < 0 ||
5072 uint64_t(indexInt64) >= tarr->length().valueOr(0)) {
5073 handleOOB = true;
5076 JSOp op = JSOp(*pc_);
5078 // The only expected property init operation is InitElem.
5079 MOZ_ASSERT_IF(IsPropertyInitOp(op), op == JSOp::InitElem);
5081 // InitElem (DefineProperty) has to throw an exception on out-of-bounds.
5082 if (handleOOB && IsPropertyInitOp(op)) {
5083 return AttachDecision::NoAction;
5086 writer.guardShapeForClass(objId, tarr->shape());
5088 OperandId rhsValId = emitNumericGuard(rhsId, rhsVal_, elementType);
5090 ValOperandId keyId = setElemKeyValueId();
5091 IntPtrOperandId indexId = guardToIntPtrIndex(idVal_, keyId, handleOOB);
5093 auto viewKind = ToArrayBufferViewKind(tarr);
5094 writer.storeTypedArrayElement(objId, elementType, indexId, rhsValId,
5095 handleOOB, viewKind);
5096 writer.returnFromIC();
5098 trackAttached(handleOOB ? "SetTypedElementOOB" : "SetTypedElement");
5099 return AttachDecision::Attach;
5102 AttachDecision SetPropIRGenerator::tryAttachGenericProxy(
5103 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
5104 ValOperandId rhsId, bool handleDOMProxies) {
5105 // Don't attach a proxy stub for ops like JSOp::InitElem.
5106 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5108 writer.guardIsProxy(objId);
5110 if (!handleDOMProxies) {
5111 // Ensure that the incoming object is not a DOM proxy, so that we can
5112 // get to the specialized stubs. If handleDOMProxies is true, we were
5113 // unable to attach a specialized DOM stub, so we just handle all
5114 // proxies here.
5115 writer.guardIsNotDOMProxy(objId);
5118 if (cacheKind_ == CacheKind::SetProp || mode_ == ICState::Mode::Specialized) {
5119 maybeEmitIdGuard(id);
5120 writer.proxySet(objId, id, rhsId, IsStrictSetPC(pc_));
5121 } else {
5122 // Attach a stub that handles every id.
5123 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
5124 MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
5125 writer.proxySetByValue(objId, setElemKeyValueId(), rhsId,
5126 IsStrictSetPC(pc_));
5129 writer.returnFromIC();
5131 trackAttached("SetProp.GenericProxy");
5132 return AttachDecision::Attach;
5135 AttachDecision SetPropIRGenerator::tryAttachDOMProxyShadowed(
5136 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
5137 ValOperandId rhsId) {
5138 // Don't attach a proxy stub for ops like JSOp::InitElem.
5139 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5141 MOZ_ASSERT(IsCacheableDOMProxy(obj));
5143 maybeEmitIdGuard(id);
5144 TestMatchingProxyReceiver(writer, obj, objId);
5145 writer.proxySet(objId, id, rhsId, IsStrictSetPC(pc_));
5146 writer.returnFromIC();
5148 trackAttached("SetProp.DOMProxyShadowed");
5149 return AttachDecision::Attach;
5152 AttachDecision SetPropIRGenerator::tryAttachDOMProxyUnshadowed(
5153 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
5154 ValOperandId rhsId) {
5155 // Don't attach a proxy stub for ops like JSOp::InitElem.
5156 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5158 MOZ_ASSERT(IsCacheableDOMProxy(obj));
5160 JSObject* proto = obj->staticPrototype();
5161 if (!proto) {
5162 return AttachDecision::NoAction;
5165 NativeObject* holder = nullptr;
5166 Maybe<PropertyInfo> prop;
5167 if (!CanAttachSetter(cx_, pc_, proto, id, &holder, &prop)) {
5168 return AttachDecision::NoAction;
5170 auto* nproto = &proto->as<NativeObject>();
5172 maybeEmitIdGuard(id);
5174 // Guard that our proxy (expando) object hasn't started shadowing this
5175 // property.
5176 TestMatchingProxyReceiver(writer, obj, objId);
5177 bool canOptimizeMissing = false;
5178 CheckDOMProxyDoesNotShadow(writer, obj, id, objId, &canOptimizeMissing);
5180 GeneratePrototypeGuards(writer, obj, holder, objId);
5182 // Guard on the holder of the property.
5183 ObjOperandId holderId = writer.loadObject(holder);
5184 TestMatchingHolder(writer, holder, holderId);
5186 EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
5187 /* holderIsConstant = */ true);
5189 // EmitCallSetterNoGuards expects |obj| to be the object the property is
5190 // on to do some checks. Since we actually looked at proto, and no extra
5191 // guards will be generated, we can just pass that instead.
5192 EmitCallSetterNoGuards(cx_, writer, nproto, holder, *prop, objId, rhsId);
5194 trackAttached("SetProp.DOMProxyUnshadowed");
5195 return AttachDecision::Attach;
5198 AttachDecision SetPropIRGenerator::tryAttachDOMProxyExpando(
5199 Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
5200 ValOperandId rhsId) {
5201 // Don't attach a proxy stub for ops like JSOp::InitElem.
5202 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5204 MOZ_ASSERT(IsCacheableDOMProxy(obj));
5206 Value expandoVal = GetProxyPrivate(obj);
5207 JSObject* expandoObj;
5208 if (expandoVal.isObject()) {
5209 expandoObj = &expandoVal.toObject();
5210 } else {
5211 MOZ_ASSERT(!expandoVal.isUndefined(),
5212 "How did a missing expando manage to shadow things?");
5213 auto expandoAndGeneration =
5214 static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
5215 MOZ_ASSERT(expandoAndGeneration);
5216 expandoObj = &expandoAndGeneration->expando.toObject();
5219 Maybe<PropertyInfo> prop;
5220 if (CanAttachNativeSetSlot(JSOp(*pc_), expandoObj, id, &prop)) {
5221 auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
5223 maybeEmitIdGuard(id);
5224 ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
5225 obj, objId, expandoVal, nativeExpandoObj);
5227 EmitStoreSlotAndReturn(writer, expandoObjId, nativeExpandoObj, *prop,
5228 rhsId);
5229 trackAttached("SetProp.DOMProxyExpandoSlot");
5230 return AttachDecision::Attach;
5233 NativeObject* holder = nullptr;
5234 if (CanAttachSetter(cx_, pc_, expandoObj, id, &holder, &prop)) {
5235 auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
5237 // Call the setter. Note that we pass objId, the DOM proxy, as |this|
5238 // and not the expando object.
5239 maybeEmitIdGuard(id);
5240 ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
5241 obj, objId, expandoVal, nativeExpandoObj);
5243 MOZ_ASSERT(holder == nativeExpandoObj);
5244 EmitGuardGetterSetterSlot(writer, nativeExpandoObj, *prop, expandoObjId);
5245 EmitCallSetterNoGuards(cx_, writer, nativeExpandoObj, nativeExpandoObj,
5246 *prop, objId, rhsId);
5247 trackAttached("SetProp.DOMProxyExpandoSetter");
5248 return AttachDecision::Attach;
5251 return AttachDecision::NoAction;
5254 AttachDecision SetPropIRGenerator::tryAttachProxy(HandleObject obj,
5255 ObjOperandId objId,
5256 HandleId id,
5257 ValOperandId rhsId) {
5258 // Don't attach a proxy stub for ops like JSOp::InitElem.
5259 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5261 ProxyStubType type = GetProxyStubType(cx_, obj, id);
5262 if (type == ProxyStubType::None) {
5263 return AttachDecision::NoAction;
5265 auto proxy = obj.as<ProxyObject>();
5267 if (mode_ == ICState::Mode::Megamorphic) {
5268 return tryAttachGenericProxy(proxy, objId, id, rhsId,
5269 /* handleDOMProxies = */ true);
5272 switch (type) {
5273 case ProxyStubType::None:
5274 break;
5275 case ProxyStubType::DOMExpando:
5276 TRY_ATTACH(tryAttachDOMProxyExpando(proxy, objId, id, rhsId));
5277 [[fallthrough]]; // Fall through to the generic shadowed case.
5278 case ProxyStubType::DOMShadowed:
5279 return tryAttachDOMProxyShadowed(proxy, objId, id, rhsId);
5280 case ProxyStubType::DOMUnshadowed:
5281 TRY_ATTACH(tryAttachDOMProxyUnshadowed(proxy, objId, id, rhsId));
5282 return tryAttachGenericProxy(proxy, objId, id, rhsId,
5283 /* handleDOMProxies = */ true);
5284 case ProxyStubType::Generic:
5285 return tryAttachGenericProxy(proxy, objId, id, rhsId,
5286 /* handleDOMProxies = */ false);
5289 MOZ_CRASH("Unexpected ProxyStubType");
5292 AttachDecision SetPropIRGenerator::tryAttachProxyElement(HandleObject obj,
5293 ObjOperandId objId,
5294 ValOperandId rhsId) {
5295 // Don't attach a proxy stub for ops like JSOp::InitElem.
5296 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5298 if (!obj->is<ProxyObject>()) {
5299 return AttachDecision::NoAction;
5302 writer.guardIsProxy(objId);
5304 // Like GetPropIRGenerator::tryAttachProxyElement, don't check for DOM
5305 // proxies here as we don't have specialized DOM stubs for this.
5306 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
5307 writer.proxySetByValue(objId, setElemKeyValueId(), rhsId, IsStrictSetPC(pc_));
5308 writer.returnFromIC();
5310 trackAttached("SetProp.ProxyElement");
5311 return AttachDecision::Attach;
5314 AttachDecision SetPropIRGenerator::tryAttachMegamorphicSetElement(
5315 HandleObject obj, ObjOperandId objId, ValOperandId rhsId) {
5316 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5318 if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetElem) {
5319 return AttachDecision::NoAction;
5322 // The generic proxy stubs are faster.
5323 if (obj->is<ProxyObject>()) {
5324 return AttachDecision::NoAction;
5327 writer.megamorphicSetElement(objId, setElemKeyValueId(), rhsId,
5328 IsStrictSetPC(pc_));
5329 writer.returnFromIC();
5331 trackAttached("SetProp.MegamorphicSetElement");
5332 return AttachDecision::Attach;
5335 AttachDecision SetPropIRGenerator::tryAttachMegamorphicSetSlot(
5336 HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) {
5337 if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetProp) {
5338 return AttachDecision::NoAction;
5341 writer.megamorphicStoreSlot(objId, id, rhsId, IsStrictSetPC(pc_));
5342 writer.returnFromIC();
5343 trackAttached("SetProp.MegamorphicNativeSlot");
5344 return AttachDecision::Attach;
5347 AttachDecision SetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
5348 ObjOperandId objId,
5349 HandleId id,
5350 ValOperandId rhsId) {
5351 // Don't attach a window proxy stub for ops like JSOp::InitElem.
5352 MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
5354 // Attach a stub when the receiver is a WindowProxy and we can do the set
5355 // on the Window (the global object).
5357 if (!IsWindowProxyForScriptGlobal(script_, obj)) {
5358 return AttachDecision::NoAction;
5361 // If we're megamorphic prefer a generic proxy stub that handles a lot more
5362 // cases.
5363 if (mode_ == ICState::Mode::Megamorphic) {
5364 return AttachDecision::NoAction;
5367 // Now try to do the set on the Window (the current global).
5368 GlobalObject* windowObj = cx_->global();
5370 Maybe<PropertyInfo> prop;
5371 if (!CanAttachNativeSetSlot(JSOp(*pc_), windowObj, id, &prop)) {
5372 return AttachDecision::NoAction;
5375 maybeEmitIdGuard(id);
5377 ObjOperandId windowObjId =
5378 GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
5379 writer.guardShape(windowObjId, windowObj->shape());
5381 EmitStoreSlotAndReturn(writer, windowObjId, windowObj, *prop, rhsId);
5383 trackAttached("SetProp.WindowProxySlot");
5384 return AttachDecision::Attach;
5387 // Detect if |id| refers to the 'prototype' property of a function object. This
5388 // property is special-cased in canAttachAddSlotStub().
5389 static bool IsFunctionPrototype(const JSAtomState& names, JSObject* obj,
5390 PropertyKey id) {
5391 return obj->is<JSFunction>() && id.isAtom(names.prototype);
5394 bool SetPropIRGenerator::canAttachAddSlotStub(HandleObject obj, HandleId id) {
5395 if (!obj->is<NativeObject>()) {
5396 return false;
5398 auto* nobj = &obj->as<NativeObject>();
5400 // Special-case JSFunction resolve hook to allow redefining the 'prototype'
5401 // property without triggering lazy expansion of property and object
5402 // allocation.
5403 if (IsFunctionPrototype(cx_->names(), nobj, id)) {
5404 MOZ_ASSERT(ClassMayResolveId(cx_->names(), nobj->getClass(), id, nobj));
5406 // We're only interested in functions that have a builtin .prototype
5407 // property (needsPrototypeProperty). The stub will guard on this because
5408 // the builtin .prototype property is non-configurable/non-enumerable and it
5409 // would be wrong to add a property with those attributes to a function that
5410 // doesn't have a builtin .prototype.
5412 // Inlining needsPrototypeProperty in JIT code is complicated so we use
5413 // isNonBuiltinConstructor as a stronger condition that's easier to check
5414 // from JIT code.
5415 JSFunction* fun = &nobj->as<JSFunction>();
5416 if (!fun->isNonBuiltinConstructor()) {
5417 return false;
5419 MOZ_ASSERT(fun->needsPrototypeProperty());
5421 // If property exists this isn't an "add".
5422 if (fun->lookupPure(id)) {
5423 return false;
5425 } else {
5426 // Normal Case: If property exists this isn't an "add"
5427 PropertyResult prop;
5428 if (!LookupOwnPropertyPure(cx_, nobj, id, &prop)) {
5429 return false;
5431 if (prop.isFound()) {
5432 return false;
5436 // For now we don't optimize Watchtower-monitored objects.
5437 if (Watchtower::watchesPropertyAdd(nobj)) {
5438 return false;
5441 // Object must be extensible, or we must be initializing a private
5442 // elem.
5443 bool canAddNewProperty = nobj->isExtensible() || id.isPrivateName();
5444 if (!canAddNewProperty) {
5445 return false;
5448 JSOp op = JSOp(*pc_);
5449 if (IsPropertyInitOp(op)) {
5450 return true;
5453 MOZ_ASSERT(IsPropertySetOp(op));
5455 // Walk up the object prototype chain and ensure that all prototypes are
5456 // native, and that all prototypes have no setter defined on the property.
5457 for (JSObject* proto = nobj->staticPrototype(); proto;
5458 proto = proto->staticPrototype()) {
5459 if (!proto->is<NativeObject>()) {
5460 return false;
5463 // If prototype defines this property in a non-plain way, don't optimize.
5464 Maybe<PropertyInfo> protoProp = proto->as<NativeObject>().lookup(cx_, id);
5465 if (protoProp.isSome() && !protoProp->isDataProperty()) {
5466 return false;
5469 // Otherwise, if there's no such property, watch out for a resolve hook
5470 // that would need to be invoked and thus prevent inlining of property
5471 // addition. Allow the JSFunction resolve hook as it only defines plain
5472 // data properties and we don't need to invoke it for objects on the
5473 // proto chain.
5474 if (ClassMayResolveId(cx_->names(), proto->getClass(), id, proto) &&
5475 !proto->is<JSFunction>()) {
5476 return false;
5480 return true;
5483 static PropertyFlags SetPropertyFlags(JSOp op, bool isFunctionPrototype) {
5484 // Locked properties are non-writable, non-enumerable, and non-configurable.
5485 if (IsLockedInitOp(op)) {
5486 return {};
5489 // Hidden properties are writable, non-enumerable, and configurable.
5490 if (IsHiddenInitOp(op)) {
5491 return {
5492 PropertyFlag::Writable,
5493 PropertyFlag::Configurable,
5497 // This is a special case to overwrite an unresolved function.prototype
5498 // property. The initial property flags of this property are writable,
5499 // non-enumerable, and non-configurable. See canAttachAddSlotStub.
5500 if (isFunctionPrototype) {
5501 return {
5502 PropertyFlag::Writable,
5506 // Other properties are writable, enumerable, and configurable.
5507 return PropertyFlags::defaultDataPropFlags;
5510 AttachDecision SetPropIRGenerator::tryAttachAddSlotStub(
5511 Handle<Shape*> oldShape) {
5512 ValOperandId objValId(writer.setInputOperandId(0));
5513 ValOperandId rhsValId;
5514 if (cacheKind_ == CacheKind::SetProp) {
5515 rhsValId = ValOperandId(writer.setInputOperandId(1));
5516 } else {
5517 MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
5518 MOZ_ASSERT(setElemKeyValueId().id() == 1);
5519 writer.setInputOperandId(1);
5520 rhsValId = ValOperandId(writer.setInputOperandId(2));
5523 RootedId id(cx_);
5524 bool nameOrSymbol;
5525 if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
5526 cx_->clearPendingException();
5527 return AttachDecision::NoAction;
5530 if (!lhsVal_.isObject() || !nameOrSymbol) {
5531 return AttachDecision::NoAction;
5534 JSObject* obj = &lhsVal_.toObject();
5536 PropertyResult prop;
5537 if (!LookupOwnPropertyPure(cx_, obj, id, &prop)) {
5538 return AttachDecision::NoAction;
5540 if (prop.isNotFound()) {
5541 return AttachDecision::NoAction;
5544 if (!obj->is<NativeObject>()) {
5545 return AttachDecision::NoAction;
5547 auto* nobj = &obj->as<NativeObject>();
5549 PropertyInfo propInfo = prop.propertyInfo();
5550 NativeObject* holder = nobj;
5552 if (holder->inDictionaryMode()) {
5553 return AttachDecision::NoAction;
5556 SharedShape* oldSharedShape = &oldShape->asShared();
5558 // The property must be the last added property of the object.
5559 SharedShape* newShape = holder->sharedShape();
5560 MOZ_RELEASE_ASSERT(newShape->lastProperty() == propInfo);
5562 #ifdef DEBUG
5563 // Verify exactly one property was added by comparing the property map
5564 // lengths.
5565 if (oldSharedShape->propMapLength() == PropMap::Capacity) {
5566 MOZ_ASSERT(newShape->propMapLength() == 1);
5567 } else {
5568 MOZ_ASSERT(newShape->propMapLength() ==
5569 oldSharedShape->propMapLength() + 1);
5571 #endif
5573 bool isFunctionPrototype = IsFunctionPrototype(cx_->names(), nobj, id);
5575 JSOp op = JSOp(*pc_);
5576 PropertyFlags flags = SetPropertyFlags(op, isFunctionPrototype);
5578 // Basic property checks.
5579 if (!propInfo.isDataProperty() || propInfo.flags() != flags) {
5580 return AttachDecision::NoAction;
5583 ObjOperandId objId = writer.guardToObject(objValId);
5584 maybeEmitIdGuard(id);
5586 // Shape guard the object.
5587 writer.guardShape(objId, oldShape);
5589 // If this is the special function.prototype case, we need to guard the
5590 // function is a non-builtin constructor. See canAttachAddSlotStub.
5591 if (isFunctionPrototype) {
5592 MOZ_ASSERT(nobj->as<JSFunction>().isNonBuiltinConstructor());
5593 writer.guardFunctionIsNonBuiltinCtor(objId);
5596 // Also shape guard the proto chain, unless this is an InitElem.
5597 if (IsPropertySetOp(op)) {
5598 ShapeGuardProtoChain(writer, nobj, objId);
5601 // If the JSClass has an addProperty hook, we need to call a VM function to
5602 // invoke this hook. Ignore the Array addProperty hook, because it doesn't do
5603 // anything for non-index properties.
5604 DebugOnly<uint32_t> index;
5605 MOZ_ASSERT_IF(obj->is<ArrayObject>(), !IdIsIndex(id, &index));
5606 bool mustCallAddPropertyHook =
5607 obj->getClass()->getAddProperty() && !obj->is<ArrayObject>();
5609 if (mustCallAddPropertyHook) {
5610 writer.addSlotAndCallAddPropHook(objId, rhsValId, newShape);
5611 trackAttached("SetProp.AddSlotWithAddPropertyHook");
5612 } else if (holder->isFixedSlot(propInfo.slot())) {
5613 size_t offset = NativeObject::getFixedSlotOffset(propInfo.slot());
5614 writer.addAndStoreFixedSlot(objId, offset, rhsValId, newShape);
5615 trackAttached("SetProp.AddSlotFixed");
5616 } else {
5617 size_t offset = holder->dynamicSlotIndex(propInfo.slot()) * sizeof(Value);
5618 uint32_t numOldSlots = NativeObject::calculateDynamicSlots(oldSharedShape);
5619 uint32_t numNewSlots = holder->numDynamicSlots();
5620 if (numOldSlots == numNewSlots) {
5621 writer.addAndStoreDynamicSlot(objId, offset, rhsValId, newShape);
5622 trackAttached("SetProp.AddSlotDynamic");
5623 } else {
5624 MOZ_ASSERT(numNewSlots > numOldSlots);
5625 writer.allocateAndStoreDynamicSlot(objId, offset, rhsValId, newShape,
5626 numNewSlots);
5627 trackAttached("SetProp.AllocateSlot");
5630 writer.returnFromIC();
5632 return AttachDecision::Attach;
5635 InstanceOfIRGenerator::InstanceOfIRGenerator(JSContext* cx, HandleScript script,
5636 jsbytecode* pc, ICState state,
5637 HandleValue lhs, HandleObject rhs)
5638 : IRGenerator(cx, script, pc, CacheKind::InstanceOf, state),
5639 lhsVal_(lhs),
5640 rhsObj_(rhs) {}
5642 AttachDecision InstanceOfIRGenerator::tryAttachStub() {
5643 MOZ_ASSERT(cacheKind_ == CacheKind::InstanceOf);
5644 AutoAssertNoPendingException aanpe(cx_);
5646 // Ensure RHS is a function -- could be a Proxy, which the IC isn't prepared
5647 // to handle.
5648 if (!rhsObj_->is<JSFunction>()) {
5649 trackAttached(IRGenerator::NotAttached);
5650 return AttachDecision::NoAction;
5653 HandleFunction fun = rhsObj_.as<JSFunction>();
5655 // Look up the @@hasInstance property, and check that Function.__proto__ is
5656 // the property holder, and that no object further down the prototype chain
5657 // (including this function) has shadowed it; together with the fact that
5658 // Function.__proto__[@@hasInstance] is immutable, this ensures that the
5659 // hasInstance hook will not change without the need to guard on the actual
5660 // property value.
5661 PropertyResult hasInstanceProp;
5662 NativeObject* hasInstanceHolder = nullptr;
5663 jsid hasInstanceID = PropertyKey::Symbol(cx_->wellKnownSymbols().hasInstance);
5664 if (!LookupPropertyPure(cx_, fun, hasInstanceID, &hasInstanceHolder,
5665 &hasInstanceProp) ||
5666 !hasInstanceProp.isNativeProperty()) {
5667 trackAttached(IRGenerator::NotAttached);
5668 return AttachDecision::NoAction;
5671 JSObject& funProto = cx_->global()->getPrototype(JSProto_Function);
5672 if (hasInstanceHolder != &funProto) {
5673 trackAttached(IRGenerator::NotAttached);
5674 return AttachDecision::NoAction;
5677 // If the above succeeded, then these should be true about @@hasInstance,
5678 // because the property on Function.__proto__ is an immutable data property:
5679 MOZ_ASSERT(hasInstanceProp.propertyInfo().isDataProperty());
5680 MOZ_ASSERT(!hasInstanceProp.propertyInfo().configurable());
5681 MOZ_ASSERT(!hasInstanceProp.propertyInfo().writable());
5683 MOZ_ASSERT(IsCacheableProtoChain(fun, hasInstanceHolder));
5685 // Ensure that the function's prototype slot is the same.
5686 Maybe<PropertyInfo> prop = fun->lookupPure(cx_->names().prototype);
5687 if (prop.isNothing() || !prop->isDataProperty()) {
5688 trackAttached(IRGenerator::NotAttached);
5689 return AttachDecision::NoAction;
5692 uint32_t slot = prop->slot();
5693 MOZ_ASSERT(slot >= fun->numFixedSlots(), "Stub code relies on this");
5694 if (!fun->getSlot(slot).isObject()) {
5695 trackAttached(IRGenerator::NotAttached);
5696 return AttachDecision::NoAction;
5699 // Abstract Objects
5700 ValOperandId lhs(writer.setInputOperandId(0));
5701 ValOperandId rhs(writer.setInputOperandId(1));
5703 ObjOperandId rhsId = writer.guardToObject(rhs);
5704 writer.guardShape(rhsId, fun->shape());
5706 // Ensure that the shapes up the prototype chain for the RHS remain the same
5707 // so that @@hasInstance is not shadowed by some intermediate prototype
5708 // object.
5709 if (hasInstanceHolder != fun) {
5710 GeneratePrototypeGuards(writer, fun, hasInstanceHolder, rhsId);
5711 ObjOperandId holderId = writer.loadObject(hasInstanceHolder);
5712 TestMatchingHolder(writer, hasInstanceHolder, holderId);
5715 // Load the .prototype value and ensure it's an object.
5716 ValOperandId protoValId =
5717 writer.loadDynamicSlot(rhsId, slot - fun->numFixedSlots());
5718 ObjOperandId protoId = writer.guardToObject(protoValId);
5720 // Needn't guard LHS is object, because the actual stub can handle that
5721 // and correctly return false.
5722 writer.loadInstanceOfObjectResult(lhs, protoId);
5723 writer.returnFromIC();
5724 trackAttached("InstanceOf");
5725 return AttachDecision::Attach;
5728 void InstanceOfIRGenerator::trackAttached(const char* name) {
5729 stubName_ = name ? name : "NotAttached";
5730 #ifdef JS_CACHEIR_SPEW
5731 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
5732 sp.valueProperty("lhs", lhsVal_);
5733 sp.valueProperty("rhs", ObjectValue(*rhsObj_));
5735 #else
5736 // Silence Clang -Wunused-private-field warning.
5737 (void)lhsVal_;
5738 #endif
5741 TypeOfIRGenerator::TypeOfIRGenerator(JSContext* cx, HandleScript script,
5742 jsbytecode* pc, ICState state,
5743 HandleValue value)
5744 : IRGenerator(cx, script, pc, CacheKind::TypeOf, state), val_(value) {}
5746 void TypeOfIRGenerator::trackAttached(const char* name) {
5747 stubName_ = name ? name : "NotAttached";
5748 #ifdef JS_CACHEIR_SPEW
5749 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
5750 sp.valueProperty("val", val_);
5752 #endif
5755 AttachDecision TypeOfIRGenerator::tryAttachStub() {
5756 MOZ_ASSERT(cacheKind_ == CacheKind::TypeOf);
5758 AutoAssertNoPendingException aanpe(cx_);
5760 ValOperandId valId(writer.setInputOperandId(0));
5762 TRY_ATTACH(tryAttachPrimitive(valId));
5763 TRY_ATTACH(tryAttachObject(valId));
5765 MOZ_ASSERT_UNREACHABLE("Failed to attach TypeOf");
5766 return AttachDecision::NoAction;
5769 AttachDecision TypeOfIRGenerator::tryAttachPrimitive(ValOperandId valId) {
5770 if (!val_.isPrimitive()) {
5771 return AttachDecision::NoAction;
5774 // Note: we don't use GuardIsNumber for int32 values because it's less
5775 // efficient in Warp (unboxing to double instead of int32).
5776 if (val_.isDouble()) {
5777 writer.guardIsNumber(valId);
5778 } else {
5779 writer.guardNonDoubleType(valId, val_.type());
5782 writer.loadConstantStringResult(
5783 TypeName(js::TypeOfValue(val_), cx_->names()));
5784 writer.returnFromIC();
5785 writer.setTypeData(TypeData(JSValueType(val_.type())));
5786 trackAttached("TypeOf.Primitive");
5787 return AttachDecision::Attach;
5790 AttachDecision TypeOfIRGenerator::tryAttachObject(ValOperandId valId) {
5791 if (!val_.isObject()) {
5792 return AttachDecision::NoAction;
5795 ObjOperandId objId = writer.guardToObject(valId);
5796 writer.loadTypeOfObjectResult(objId);
5797 writer.returnFromIC();
5798 writer.setTypeData(TypeData(JSValueType(val_.type())));
5799 trackAttached("TypeOf.Object");
5800 return AttachDecision::Attach;
5803 GetIteratorIRGenerator::GetIteratorIRGenerator(JSContext* cx,
5804 HandleScript script,
5805 jsbytecode* pc, ICState state,
5806 HandleValue value)
5807 : IRGenerator(cx, script, pc, CacheKind::GetIterator, state), val_(value) {}
5809 AttachDecision GetIteratorIRGenerator::tryAttachStub() {
5810 MOZ_ASSERT(cacheKind_ == CacheKind::GetIterator);
5812 AutoAssertNoPendingException aanpe(cx_);
5814 ValOperandId valId(writer.setInputOperandId(0));
5816 TRY_ATTACH(tryAttachObject(valId));
5817 TRY_ATTACH(tryAttachNullOrUndefined(valId));
5818 TRY_ATTACH(tryAttachGeneric(valId));
5820 trackAttached(IRGenerator::NotAttached);
5821 return AttachDecision::NoAction;
5824 AttachDecision GetIteratorIRGenerator::tryAttachObject(ValOperandId valId) {
5825 if (!val_.isObject()) {
5826 return AttachDecision::NoAction;
5829 MOZ_ASSERT(val_.toObject().compartment() == cx_->compartment());
5831 ObjOperandId objId = writer.guardToObject(valId);
5832 writer.objectToIteratorResult(objId, cx_->compartment()->enumeratorsAddr());
5833 writer.returnFromIC();
5835 trackAttached("GetIterator.Object");
5836 return AttachDecision::Attach;
5839 AttachDecision GetIteratorIRGenerator::tryAttachNullOrUndefined(
5840 ValOperandId valId) {
5841 MOZ_ASSERT(JSOp(*pc_) == JSOp::Iter);
5843 // For null/undefined we can simply return the empty iterator singleton. This
5844 // works because this iterator is unlinked and immutable.
5846 if (!val_.isNullOrUndefined()) {
5847 return AttachDecision::NoAction;
5850 PropertyIteratorObject* emptyIter =
5851 GlobalObject::getOrCreateEmptyIterator(cx_);
5852 if (!emptyIter) {
5853 cx_->recoverFromOutOfMemory();
5854 return AttachDecision::NoAction;
5857 writer.guardIsNullOrUndefined(valId);
5859 ObjOperandId iterId = writer.loadObject(emptyIter);
5860 writer.loadObjectResult(iterId);
5861 writer.returnFromIC();
5863 trackAttached("GetIterator.NullOrUndefined");
5864 return AttachDecision::Attach;
5867 AttachDecision GetIteratorIRGenerator::tryAttachGeneric(ValOperandId valId) {
5868 writer.valueToIteratorResult(valId);
5869 writer.returnFromIC();
5871 trackAttached("GetIterator.Generic");
5872 return AttachDecision::Attach;
5875 void GetIteratorIRGenerator::trackAttached(const char* name) {
5876 stubName_ = name ? name : "NotAttached";
5877 #ifdef JS_CACHEIR_SPEW
5878 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
5879 sp.valueProperty("val", val_);
5881 #endif
5884 OptimizeSpreadCallIRGenerator::OptimizeSpreadCallIRGenerator(
5885 JSContext* cx, HandleScript script, jsbytecode* pc, ICState state,
5886 HandleValue value)
5887 : IRGenerator(cx, script, pc, CacheKind::OptimizeSpreadCall, state),
5888 val_(value) {}
5890 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachStub() {
5891 MOZ_ASSERT(cacheKind_ == CacheKind::OptimizeSpreadCall);
5893 AutoAssertNoPendingException aanpe(cx_);
5895 TRY_ATTACH(tryAttachArray());
5896 TRY_ATTACH(tryAttachArguments());
5897 TRY_ATTACH(tryAttachNotOptimizable());
5899 trackAttached(IRGenerator::NotAttached);
5900 return AttachDecision::NoAction;
5903 static bool IsArrayInstanceOptimizable(JSContext* cx, Handle<ArrayObject*> arr,
5904 MutableHandle<NativeObject*> arrProto) {
5905 // Prototype must be Array.prototype.
5906 auto* proto = cx->global()->maybeGetArrayPrototype();
5907 if (!proto || arr->staticPrototype() != proto) {
5908 return false;
5910 arrProto.set(proto);
5912 // The object must not have an own @@iterator property.
5913 PropertyKey iteratorKey =
5914 PropertyKey::Symbol(cx->wellKnownSymbols().iterator);
5915 return !arr->lookupPure(iteratorKey);
5918 static bool IsArrayPrototypeOptimizable(JSContext* cx, Handle<ArrayObject*> arr,
5919 Handle<NativeObject*> arrProto,
5920 uint32_t* slot,
5921 MutableHandle<JSFunction*> iterFun) {
5922 PropertyKey iteratorKey =
5923 PropertyKey::Symbol(cx->wellKnownSymbols().iterator);
5924 // Ensure that Array.prototype's @@iterator slot is unchanged.
5925 Maybe<PropertyInfo> prop = arrProto->lookupPure(iteratorKey);
5926 if (prop.isNothing() || !prop->isDataProperty()) {
5927 return false;
5930 *slot = prop->slot();
5931 MOZ_ASSERT(arrProto->numFixedSlots() == 0, "Stub code relies on this");
5933 const Value& iterVal = arrProto->getSlot(*slot);
5934 if (!iterVal.isObject() || !iterVal.toObject().is<JSFunction>()) {
5935 return false;
5938 iterFun.set(&iterVal.toObject().as<JSFunction>());
5939 return IsSelfHostedFunctionWithName(iterFun, cx->names().dollar_ArrayValues_);
5942 enum class AllowIteratorReturn : bool {
5944 Yes,
5946 static bool IsArrayIteratorPrototypeOptimizable(
5947 JSContext* cx, AllowIteratorReturn allowReturn,
5948 MutableHandle<NativeObject*> arrIterProto, uint32_t* slot,
5949 MutableHandle<JSFunction*> nextFun) {
5950 NativeObject* proto = nullptr;
5952 AutoEnterOOMUnsafeRegion oom;
5953 proto = GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global());
5954 if (!proto) {
5955 oom.crash("failed to allocate Array iterator prototype");
5958 arrIterProto.set(proto);
5960 // Ensure that %ArrayIteratorPrototype%'s "next" slot is unchanged.
5961 Maybe<PropertyInfo> prop = proto->lookupPure(cx->names().next);
5962 if (prop.isNothing() || !prop->isDataProperty()) {
5963 return false;
5966 *slot = prop->slot();
5967 MOZ_ASSERT(proto->numFixedSlots() == 0, "Stub code relies on this");
5969 const Value& nextVal = proto->getSlot(*slot);
5970 if (!nextVal.isObject() || !nextVal.toObject().is<JSFunction>()) {
5971 return false;
5974 nextFun.set(&nextVal.toObject().as<JSFunction>());
5975 if (!IsSelfHostedFunctionWithName(nextFun, cx->names().ArrayIteratorNext)) {
5976 return false;
5979 if (allowReturn == AllowIteratorReturn::No) {
5980 // Ensure that %ArrayIteratorPrototype% doesn't define "return".
5981 if (!CheckHasNoSuchProperty(cx, proto, NameToId(cx->names().return_))) {
5982 return false;
5986 return true;
5989 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArray() {
5990 if (!isFirstStub_) {
5991 return AttachDecision::NoAction;
5994 // The value must be a packed array.
5995 if (!val_.isObject()) {
5996 return AttachDecision::NoAction;
5998 Rooted<JSObject*> obj(cx_, &val_.toObject());
5999 if (!IsPackedArray(obj)) {
6000 return AttachDecision::NoAction;
6003 // Prototype must be Array.prototype and Array.prototype[@@iterator] must not
6004 // be modified.
6005 Rooted<NativeObject*> arrProto(cx_);
6006 uint32_t arrProtoIterSlot;
6007 Rooted<JSFunction*> iterFun(cx_);
6008 if (!IsArrayInstanceOptimizable(cx_, obj.as<ArrayObject>(), &arrProto)) {
6009 return AttachDecision::NoAction;
6012 if (!IsArrayPrototypeOptimizable(cx_, obj.as<ArrayObject>(), arrProto,
6013 &arrProtoIterSlot, &iterFun)) {
6014 return AttachDecision::NoAction;
6017 // %ArrayIteratorPrototype%.next must not be modified.
6018 Rooted<NativeObject*> arrayIteratorProto(cx_);
6019 uint32_t iterNextSlot;
6020 Rooted<JSFunction*> nextFun(cx_);
6021 if (!IsArrayIteratorPrototypeOptimizable(cx_, AllowIteratorReturn::Yes,
6022 &arrayIteratorProto, &iterNextSlot,
6023 &nextFun)) {
6024 return AttachDecision::NoAction;
6027 ValOperandId valId(writer.setInputOperandId(0));
6028 ObjOperandId objId = writer.guardToObject(valId);
6030 // Guard the object is a packed array with Array.prototype as proto.
6031 MOZ_ASSERT(obj->is<ArrayObject>());
6032 writer.guardShape(objId, obj->shape());
6033 writer.guardArrayIsPacked(objId);
6035 // Guard on Array.prototype[@@iterator].
6036 ObjOperandId arrProtoId = writer.loadObject(arrProto);
6037 ObjOperandId iterId = writer.loadObject(iterFun);
6038 writer.guardShape(arrProtoId, arrProto->shape());
6039 writer.guardDynamicSlotIsSpecificObject(arrProtoId, iterId, arrProtoIterSlot);
6041 // Guard on %ArrayIteratorPrototype%.next.
6042 ObjOperandId iterProtoId = writer.loadObject(arrayIteratorProto);
6043 ObjOperandId nextId = writer.loadObject(nextFun);
6044 writer.guardShape(iterProtoId, arrayIteratorProto->shape());
6045 writer.guardDynamicSlotIsSpecificObject(iterProtoId, nextId, iterNextSlot);
6047 writer.loadObjectResult(objId);
6048 writer.returnFromIC();
6050 trackAttached("OptimizeSpreadCall.Array");
6051 return AttachDecision::Attach;
6054 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArguments() {
6055 // The value must be an arguments object.
6056 if (!val_.isObject()) {
6057 return AttachDecision::NoAction;
6059 RootedObject obj(cx_, &val_.toObject());
6060 if (!obj->is<ArgumentsObject>()) {
6061 return AttachDecision::NoAction;
6063 auto args = obj.as<ArgumentsObject>();
6065 // Ensure neither elements, nor the length, nor the iterator has been
6066 // overridden. Also ensure no args are forwarded to allow reading them
6067 // directly from the frame.
6068 if (args->hasOverriddenElement() || args->hasOverriddenLength() ||
6069 args->hasOverriddenIterator() || args->anyArgIsForwarded()) {
6070 return AttachDecision::NoAction;
6073 Rooted<Shape*> shape(cx_, GlobalObject::getArrayShapeWithDefaultProto(cx_));
6074 if (!shape) {
6075 cx_->clearPendingException();
6076 return AttachDecision::NoAction;
6079 Rooted<NativeObject*> arrayIteratorProto(cx_);
6080 uint32_t slot;
6081 Rooted<JSFunction*> nextFun(cx_);
6082 if (!IsArrayIteratorPrototypeOptimizable(cx_, AllowIteratorReturn::Yes,
6083 &arrayIteratorProto, &slot,
6084 &nextFun)) {
6085 return AttachDecision::NoAction;
6088 ValOperandId valId(writer.setInputOperandId(0));
6089 ObjOperandId objId = writer.guardToObject(valId);
6091 if (args->is<MappedArgumentsObject>()) {
6092 writer.guardClass(objId, GuardClassKind::MappedArguments);
6093 } else {
6094 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
6095 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
6097 uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT |
6098 ArgumentsObject::LENGTH_OVERRIDDEN_BIT |
6099 ArgumentsObject::ITERATOR_OVERRIDDEN_BIT |
6100 ArgumentsObject::FORWARDED_ARGUMENTS_BIT;
6101 writer.guardArgumentsObjectFlags(objId, flags);
6103 ObjOperandId protoId = writer.loadObject(arrayIteratorProto);
6104 ObjOperandId nextId = writer.loadObject(nextFun);
6106 writer.guardShape(protoId, arrayIteratorProto->shape());
6108 // Ensure that proto[slot] == nextFun.
6109 writer.guardDynamicSlotIsSpecificObject(protoId, nextId, slot);
6111 writer.arrayFromArgumentsObjectResult(objId, shape);
6112 writer.returnFromIC();
6114 trackAttached("OptimizeSpreadCall.Arguments");
6115 return AttachDecision::Attach;
6118 AttachDecision OptimizeSpreadCallIRGenerator::tryAttachNotOptimizable() {
6119 ValOperandId valId(writer.setInputOperandId(0));
6121 writer.loadUndefinedResult();
6122 writer.returnFromIC();
6124 trackAttached("OptimizeSpreadCall.NotOptimizable");
6125 return AttachDecision::Attach;
6128 void OptimizeSpreadCallIRGenerator::trackAttached(const char* name) {
6129 stubName_ = name ? name : "NotAttached";
6130 #ifdef JS_CACHEIR_SPEW
6131 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
6132 sp.valueProperty("val", val_);
6134 #endif
6137 CallIRGenerator::CallIRGenerator(JSContext* cx, HandleScript script,
6138 jsbytecode* pc, JSOp op, ICState state,
6139 uint32_t argc, HandleValue callee,
6140 HandleValue thisval, HandleValue newTarget,
6141 HandleValueArray args)
6142 : IRGenerator(cx, script, pc, CacheKind::Call, state),
6143 op_(op),
6144 argc_(argc),
6145 callee_(callee),
6146 thisval_(thisval),
6147 newTarget_(newTarget),
6148 args_(args) {}
6150 void InlinableNativeIRGenerator::emitNativeCalleeGuard() {
6151 // Note: we rely on GuardSpecificFunction to also guard against the same
6152 // native from a different realm.
6153 MOZ_ASSERT(callee_->isNativeWithoutJitEntry());
6155 ObjOperandId calleeObjId;
6156 if (flags_.getArgFormat() == CallFlags::Standard) {
6157 ValOperandId calleeValId =
6158 writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags_);
6159 calleeObjId = writer.guardToObject(calleeValId);
6160 } else if (flags_.getArgFormat() == CallFlags::Spread) {
6161 ValOperandId calleeValId =
6162 writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags_);
6163 calleeObjId = writer.guardToObject(calleeValId);
6164 } else if (flags_.getArgFormat() == CallFlags::FunCall) {
6165 MOZ_ASSERT(generator_.writer.numOperandIds() > 0, "argcId is initialized");
6167 Int32OperandId argcId(0);
6168 calleeObjId = generator_.emitFunCallOrApplyGuard(argcId);
6169 } else {
6170 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::FunApplyArray);
6171 MOZ_ASSERT(generator_.writer.numOperandIds() > 0, "argcId is initialized");
6173 Int32OperandId argcId(0);
6174 calleeObjId = generator_.emitFunApplyGuard(argcId);
6177 writer.guardSpecificFunction(calleeObjId, callee_);
6179 // If we're constructing we also need to guard newTarget == callee.
6180 if (flags_.isConstructing()) {
6181 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard);
6182 MOZ_ASSERT(&newTarget_.toObject() == callee_);
6184 ValOperandId newTargetValId =
6185 writer.loadArgumentFixedSlot(ArgumentKind::NewTarget, argc_, flags_);
6186 ObjOperandId newTargetObjId = writer.guardToObject(newTargetValId);
6187 writer.guardSpecificFunction(newTargetObjId, callee_);
6191 ObjOperandId InlinableNativeIRGenerator::emitLoadArgsArray() {
6192 if (flags_.getArgFormat() == CallFlags::Spread) {
6193 return writer.loadSpreadArgs();
6196 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::FunApplyArray);
6197 return generator_.emitFunApplyArgsGuard(flags_.getArgFormat()).ref();
6200 void IRGenerator::emitCalleeGuard(ObjOperandId calleeId, JSFunction* callee) {
6201 // Guarding on the callee JSFunction* is most efficient, but doesn't work well
6202 // for lambda clones (multiple functions with the same BaseScript). We guard
6203 // on the function's BaseScript if the callee is scripted and this isn't the
6204 // first IC stub.
6206 // Self-hosted functions are more complicated: top-level functions can be
6207 // relazified using SelfHostedLazyScript and this means they don't have a
6208 // stable BaseScript pointer. These functions are never lambda clones, though,
6209 // so we can just always guard on the JSFunction*. Self-hosted lambdas are
6210 // never relazified so there we use the normal heuristics.
6211 if (isFirstStub_ || !callee->hasBaseScript() ||
6212 (callee->isSelfHostedBuiltin() && !callee->isLambda())) {
6213 writer.guardSpecificFunction(calleeId, callee);
6214 } else {
6215 MOZ_ASSERT_IF(callee->isSelfHostedBuiltin(),
6216 !callee->baseScript()->allowRelazify());
6217 writer.guardClass(calleeId, GuardClassKind::JSFunction);
6218 writer.guardFunctionScript(calleeId, callee->baseScript());
6222 ObjOperandId CallIRGenerator::emitFunCallOrApplyGuard(Int32OperandId argcId) {
6223 JSFunction* callee = &callee_.toObject().as<JSFunction>();
6224 MOZ_ASSERT(callee->native() == fun_call || callee->native() == fun_apply);
6226 // Guard that callee is the |fun_call| or |fun_apply| native function.
6227 ValOperandId calleeValId =
6228 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId);
6229 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
6230 writer.guardSpecificFunction(calleeObjId, callee);
6232 // Guard that |this| is an object.
6233 ValOperandId thisValId =
6234 writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId);
6235 return writer.guardToObject(thisValId);
6238 ObjOperandId CallIRGenerator::emitFunCallGuard(Int32OperandId argcId) {
6239 MOZ_ASSERT(callee_.toObject().as<JSFunction>().native() == fun_call);
6241 return emitFunCallOrApplyGuard(argcId);
6244 ObjOperandId CallIRGenerator::emitFunApplyGuard(Int32OperandId argcId) {
6245 MOZ_ASSERT(callee_.toObject().as<JSFunction>().native() == fun_apply);
6247 return emitFunCallOrApplyGuard(argcId);
6250 Maybe<ObjOperandId> CallIRGenerator::emitFunApplyArgsGuard(
6251 CallFlags::ArgFormat format) {
6252 MOZ_ASSERT(argc_ == 2);
6254 ValOperandId argValId =
6255 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6257 if (format == CallFlags::FunApplyArgsObj) {
6258 ObjOperandId argObjId = writer.guardToObject(argValId);
6259 if (args_[1].toObject().is<MappedArgumentsObject>()) {
6260 writer.guardClass(argObjId, GuardClassKind::MappedArguments);
6261 } else {
6262 MOZ_ASSERT(args_[1].toObject().is<UnmappedArgumentsObject>());
6263 writer.guardClass(argObjId, GuardClassKind::UnmappedArguments);
6265 uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT |
6266 ArgumentsObject::FORWARDED_ARGUMENTS_BIT;
6267 writer.guardArgumentsObjectFlags(argObjId, flags);
6268 return mozilla::Some(argObjId);
6271 if (format == CallFlags::FunApplyArray) {
6272 ObjOperandId argObjId = writer.guardToObject(argValId);
6273 emitOptimisticClassGuard(argObjId, &args_[1].toObject(),
6274 GuardClassKind::Array);
6275 writer.guardArrayIsPacked(argObjId);
6276 return mozilla::Some(argObjId);
6279 MOZ_ASSERT(format == CallFlags::FunApplyNullUndefined);
6280 writer.guardIsNullOrUndefined(argValId);
6281 return mozilla::Nothing();
6284 AttachDecision InlinableNativeIRGenerator::tryAttachArrayPush() {
6285 // Only optimize on obj.push(val);
6286 if (argc_ != 1 || !thisval_.isObject()) {
6287 return AttachDecision::NoAction;
6290 // Where |obj| is a native array.
6291 JSObject* thisobj = &thisval_.toObject();
6292 if (!thisobj->is<ArrayObject>()) {
6293 return AttachDecision::NoAction;
6296 auto* thisarray = &thisobj->as<ArrayObject>();
6298 // Check for other indexed properties or class hooks.
6299 if (!CanAttachAddElement(thisarray, /* isInit = */ false,
6300 AllowIndexedReceiver::No)) {
6301 return AttachDecision::NoAction;
6304 // Can't add new elements to arrays with non-writable length.
6305 if (!thisarray->lengthIsWritable()) {
6306 return AttachDecision::NoAction;
6309 // Check that array is extensible.
6310 if (!thisarray->isExtensible()) {
6311 return AttachDecision::NoAction;
6314 // Check that the array is completely initialized (no holes).
6315 if (thisarray->getDenseInitializedLength() != thisarray->length()) {
6316 return AttachDecision::NoAction;
6319 MOZ_ASSERT(!thisarray->denseElementsAreFrozen(),
6320 "Extensible arrays should not have frozen elements");
6322 // After this point, we can generate code fine.
6324 // Initialize the input operand.
6325 initializeInputOperand();
6327 // Guard callee is the 'push' native function.
6328 emitNativeCalleeGuard();
6330 // Guard this is an array object.
6331 ValOperandId thisValId =
6332 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6333 ObjOperandId thisObjId = writer.guardToObject(thisValId);
6335 // Guard that the shape matches.
6336 TestMatchingNativeReceiver(writer, thisarray, thisObjId);
6338 // Guard proto chain shapes.
6339 ShapeGuardProtoChain(writer, thisarray, thisObjId);
6341 // arr.push(x) is equivalent to arr[arr.length] = x for regular arrays.
6342 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6343 writer.arrayPush(thisObjId, argId);
6345 writer.returnFromIC();
6347 trackAttached("ArrayPush");
6348 return AttachDecision::Attach;
6351 AttachDecision InlinableNativeIRGenerator::tryAttachArrayPopShift(
6352 InlinableNative native) {
6353 // Expecting no arguments.
6354 if (argc_ != 0) {
6355 return AttachDecision::NoAction;
6358 // Only optimize if |this| is a packed array.
6359 if (!thisval_.isObject() || !IsPackedArray(&thisval_.toObject())) {
6360 return AttachDecision::NoAction;
6363 // Other conditions:
6365 // * The array length needs to be writable because we're changing it.
6366 // * The array must be extensible. Non-extensible arrays require preserving
6367 // the |initializedLength == capacity| invariant on ObjectElements.
6368 // See NativeObject::shrinkCapacityToInitializedLength.
6369 // This also ensures the elements aren't sealed/frozen.
6370 // * There must not be a for-in iterator for the elements because the IC stub
6371 // does not suppress deleted properties.
6372 ArrayObject* arr = &thisval_.toObject().as<ArrayObject>();
6373 if (!arr->lengthIsWritable() || !arr->isExtensible() ||
6374 arr->denseElementsHaveMaybeInIterationFlag()) {
6375 return AttachDecision::NoAction;
6378 // Initialize the input operand.
6379 initializeInputOperand();
6381 // Guard callee is the 'pop' or 'shift' native function.
6382 emitNativeCalleeGuard();
6384 ValOperandId thisValId =
6385 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6386 ObjOperandId objId = writer.guardToObject(thisValId);
6387 emitOptimisticClassGuard(objId, arr, GuardClassKind::Array);
6389 if (native == InlinableNative::ArrayPop) {
6390 writer.packedArrayPopResult(objId);
6391 } else {
6392 MOZ_ASSERT(native == InlinableNative::ArrayShift);
6393 writer.packedArrayShiftResult(objId);
6396 writer.returnFromIC();
6398 trackAttached("ArrayPopShift");
6399 return AttachDecision::Attach;
6402 AttachDecision InlinableNativeIRGenerator::tryAttachArrayJoin() {
6403 // Only handle argc <= 1.
6404 if (argc_ > 1) {
6405 return AttachDecision::NoAction;
6408 // Only optimize if |this| is an array.
6409 if (!thisval_.isObject() || !thisval_.toObject().is<ArrayObject>()) {
6410 return AttachDecision::NoAction;
6413 // The separator argument must be a string, if present.
6414 if (argc_ > 0 && !args_[0].isString()) {
6415 return AttachDecision::NoAction;
6418 // IC stub code can handle non-packed array.
6420 // Initialize the input operand.
6421 initializeInputOperand();
6423 // Guard callee is the 'join' native function.
6424 emitNativeCalleeGuard();
6426 // Guard this is an array object.
6427 ValOperandId thisValId =
6428 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6429 ObjOperandId thisObjId = writer.guardToObject(thisValId);
6430 emitOptimisticClassGuard(thisObjId, &thisval_.toObject(),
6431 GuardClassKind::Array);
6433 StringOperandId sepId;
6434 if (argc_ == 1) {
6435 // If argcount is 1, guard that the argument is a string.
6436 ValOperandId argValId =
6437 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6438 sepId = writer.guardToString(argValId);
6439 } else {
6440 sepId = writer.loadConstantString(cx_->names().comma_);
6443 // Do the join.
6444 writer.arrayJoinResult(thisObjId, sepId);
6446 writer.returnFromIC();
6448 trackAttached("ArrayJoin");
6449 return AttachDecision::Attach;
6452 AttachDecision InlinableNativeIRGenerator::tryAttachArraySlice() {
6453 // Only handle argc <= 2.
6454 if (argc_ > 2) {
6455 return AttachDecision::NoAction;
6458 // Only optimize if |this| is a packed array or an arguments object.
6459 if (!thisval_.isObject()) {
6460 return AttachDecision::NoAction;
6463 bool isPackedArray = IsPackedArray(&thisval_.toObject());
6464 if (!isPackedArray) {
6465 if (!thisval_.toObject().is<ArgumentsObject>()) {
6466 return AttachDecision::NoAction;
6468 auto* args = &thisval_.toObject().as<ArgumentsObject>();
6470 // No elements must have been overridden or deleted.
6471 if (args->hasOverriddenElement()) {
6472 return AttachDecision::NoAction;
6475 // The length property mustn't be overridden.
6476 if (args->hasOverriddenLength()) {
6477 return AttachDecision::NoAction;
6480 // And finally also check that no argument is forwarded.
6481 if (args->anyArgIsForwarded()) {
6482 return AttachDecision::NoAction;
6486 // Arguments for the sliced region must be integers.
6487 if (argc_ > 0 && !args_[0].isInt32()) {
6488 return AttachDecision::NoAction;
6490 if (argc_ > 1 && !args_[1].isInt32()) {
6491 return AttachDecision::NoAction;
6494 JSObject* templateObj = NewDenseFullyAllocatedArray(cx_, 0, TenuredObject);
6495 if (!templateObj) {
6496 cx_->recoverFromOutOfMemory();
6497 return AttachDecision::NoAction;
6500 // Initialize the input operand.
6501 initializeInputOperand();
6503 // Guard callee is the 'slice' native function.
6504 emitNativeCalleeGuard();
6506 ValOperandId thisValId =
6507 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6508 ObjOperandId objId = writer.guardToObject(thisValId);
6510 if (isPackedArray) {
6511 emitOptimisticClassGuard(objId, &thisval_.toObject(),
6512 GuardClassKind::Array);
6513 } else {
6514 auto* args = &thisval_.toObject().as<ArgumentsObject>();
6516 if (args->is<MappedArgumentsObject>()) {
6517 writer.guardClass(objId, GuardClassKind::MappedArguments);
6518 } else {
6519 MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
6520 writer.guardClass(objId, GuardClassKind::UnmappedArguments);
6523 uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT |
6524 ArgumentsObject::LENGTH_OVERRIDDEN_BIT |
6525 ArgumentsObject::FORWARDED_ARGUMENTS_BIT;
6526 writer.guardArgumentsObjectFlags(objId, flags);
6529 Int32OperandId int32BeginId;
6530 if (argc_ > 0) {
6531 ValOperandId beginId =
6532 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6533 int32BeginId = writer.guardToInt32(beginId);
6534 } else {
6535 int32BeginId = writer.loadInt32Constant(0);
6538 Int32OperandId int32EndId;
6539 if (argc_ > 1) {
6540 ValOperandId endId =
6541 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6542 int32EndId = writer.guardToInt32(endId);
6543 } else if (isPackedArray) {
6544 int32EndId = writer.loadInt32ArrayLength(objId);
6545 } else {
6546 int32EndId = writer.loadArgumentsObjectLength(objId);
6549 if (isPackedArray) {
6550 writer.packedArraySliceResult(templateObj, objId, int32BeginId, int32EndId);
6551 } else {
6552 writer.argumentsSliceResult(templateObj, objId, int32BeginId, int32EndId);
6554 writer.returnFromIC();
6556 trackAttached(isPackedArray ? "ArraySlice" : "ArgumentsSlice");
6557 return AttachDecision::Attach;
6560 AttachDecision InlinableNativeIRGenerator::tryAttachArrayIsArray() {
6561 // Need a single argument.
6562 if (argc_ != 1) {
6563 return AttachDecision::NoAction;
6566 // Initialize the input operand.
6567 initializeInputOperand();
6569 // Guard callee is the 'isArray' native function.
6570 emitNativeCalleeGuard();
6572 // Check if the argument is an Array and return result.
6573 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6574 writer.isArrayResult(argId);
6575 writer.returnFromIC();
6577 trackAttached("ArrayIsArray");
6578 return AttachDecision::Attach;
6581 AttachDecision InlinableNativeIRGenerator::tryAttachDataViewGet(
6582 Scalar::Type type) {
6583 // Ensure |this| is a DataViewObject.
6584 if (!thisval_.isObject() || !thisval_.toObject().is<DataViewObject>()) {
6585 return AttachDecision::NoAction;
6588 // Expected arguments: offset (number), optional littleEndian (boolean).
6589 if (argc_ < 1 || argc_ > 2) {
6590 return AttachDecision::NoAction;
6592 int64_t offsetInt64;
6593 if (!ValueIsInt64Index(args_[0], &offsetInt64)) {
6594 return AttachDecision::NoAction;
6596 if (argc_ > 1 && !args_[1].isBoolean()) {
6597 return AttachDecision::NoAction;
6600 auto* dv = &thisval_.toObject().as<DataViewObject>();
6602 // Bounds check the offset.
6603 size_t byteLength = dv->byteLength().valueOr(0);
6604 if (offsetInt64 < 0 || !DataViewObject::offsetIsInBounds(
6605 Scalar::byteSize(type), offsetInt64, byteLength)) {
6606 return AttachDecision::NoAction;
6609 // For getUint32 we let the stub return an Int32 if we have not seen a
6610 // double, to allow better codegen in Warp while avoiding bailout loops.
6611 bool forceDoubleForUint32 = false;
6612 if (type == Scalar::Uint32) {
6613 bool isLittleEndian = argc_ > 1 && args_[1].toBoolean();
6614 uint32_t res = dv->read<uint32_t>(offsetInt64, byteLength, isLittleEndian);
6615 forceDoubleForUint32 = res >= INT32_MAX;
6618 // Initialize the input operand.
6619 initializeInputOperand();
6621 // Guard callee is this DataView native function.
6622 emitNativeCalleeGuard();
6624 // Guard |this| is a DataViewObject.
6625 ValOperandId thisValId =
6626 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6627 ObjOperandId objId = writer.guardToObject(thisValId);
6629 if (dv->is<FixedLengthDataViewObject>()) {
6630 emitOptimisticClassGuard(objId, &thisval_.toObject(),
6631 GuardClassKind::FixedLengthDataView);
6632 } else {
6633 emitOptimisticClassGuard(objId, &thisval_.toObject(),
6634 GuardClassKind::ResizableDataView);
6637 // Convert offset to intPtr.
6638 ValOperandId offsetId =
6639 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6640 IntPtrOperandId intPtrOffsetId =
6641 guardToIntPtrIndex(args_[0], offsetId, /* supportOOB = */ false);
6643 BooleanOperandId boolLittleEndianId;
6644 if (argc_ > 1) {
6645 ValOperandId littleEndianId =
6646 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6647 boolLittleEndianId = writer.guardToBoolean(littleEndianId);
6648 } else {
6649 boolLittleEndianId = writer.loadBooleanConstant(false);
6652 auto viewKind = ToArrayBufferViewKind(dv);
6653 writer.loadDataViewValueResult(objId, intPtrOffsetId, boolLittleEndianId,
6654 type, forceDoubleForUint32, viewKind);
6656 writer.returnFromIC();
6658 trackAttached("DataViewGet");
6659 return AttachDecision::Attach;
6662 AttachDecision InlinableNativeIRGenerator::tryAttachDataViewSet(
6663 Scalar::Type type) {
6664 // Ensure |this| is a DataViewObject.
6665 if (!thisval_.isObject() || !thisval_.toObject().is<DataViewObject>()) {
6666 return AttachDecision::NoAction;
6669 // Expected arguments: offset (number), value, optional littleEndian (boolean)
6670 if (argc_ < 2 || argc_ > 3) {
6671 return AttachDecision::NoAction;
6673 int64_t offsetInt64;
6674 if (!ValueIsInt64Index(args_[0], &offsetInt64)) {
6675 return AttachDecision::NoAction;
6677 if (!ValueCanConvertToNumeric(type, args_[1])) {
6678 return AttachDecision::NoAction;
6680 if (argc_ > 2 && !args_[2].isBoolean()) {
6681 return AttachDecision::NoAction;
6684 auto* dv = &thisval_.toObject().as<DataViewObject>();
6686 // Bounds check the offset.
6687 size_t byteLength = dv->byteLength().valueOr(0);
6688 if (offsetInt64 < 0 || !DataViewObject::offsetIsInBounds(
6689 Scalar::byteSize(type), offsetInt64, byteLength)) {
6690 return AttachDecision::NoAction;
6693 // Initialize the input operand.
6694 initializeInputOperand();
6696 // Guard callee is this DataView native function.
6697 emitNativeCalleeGuard();
6699 // Guard |this| is a DataViewObject.
6700 ValOperandId thisValId =
6701 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
6702 ObjOperandId objId = writer.guardToObject(thisValId);
6704 if (dv->is<FixedLengthDataViewObject>()) {
6705 emitOptimisticClassGuard(objId, &thisval_.toObject(),
6706 GuardClassKind::FixedLengthDataView);
6707 } else {
6708 emitOptimisticClassGuard(objId, &thisval_.toObject(),
6709 GuardClassKind::ResizableDataView);
6712 // Convert offset to intPtr.
6713 ValOperandId offsetId =
6714 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6715 IntPtrOperandId intPtrOffsetId =
6716 guardToIntPtrIndex(args_[0], offsetId, /* supportOOB = */ false);
6718 // Convert value to number or BigInt.
6719 ValOperandId valueId =
6720 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
6721 OperandId numericValueId = emitNumericGuard(valueId, args_[1], type);
6723 BooleanOperandId boolLittleEndianId;
6724 if (argc_ > 2) {
6725 ValOperandId littleEndianId =
6726 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
6727 boolLittleEndianId = writer.guardToBoolean(littleEndianId);
6728 } else {
6729 boolLittleEndianId = writer.loadBooleanConstant(false);
6732 auto viewKind = ToArrayBufferViewKind(dv);
6733 writer.storeDataViewValueResult(objId, intPtrOffsetId, numericValueId,
6734 boolLittleEndianId, type, viewKind);
6736 writer.returnFromIC();
6738 trackAttached("DataViewSet");
6739 return AttachDecision::Attach;
6742 AttachDecision InlinableNativeIRGenerator::tryAttachUnsafeGetReservedSlot(
6743 InlinableNative native) {
6744 // Self-hosted code calls this with (object, int32) arguments.
6745 MOZ_ASSERT(argc_ == 2);
6746 MOZ_ASSERT(args_[0].isObject());
6747 MOZ_ASSERT(args_[1].isInt32());
6748 MOZ_ASSERT(args_[1].toInt32() >= 0);
6750 uint32_t slot = uint32_t(args_[1].toInt32());
6751 if (slot >= NativeObject::MAX_FIXED_SLOTS) {
6752 return AttachDecision::NoAction;
6754 size_t offset = NativeObject::getFixedSlotOffset(slot);
6756 // Initialize the input operand.
6757 initializeInputOperand();
6759 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6761 // Guard that the first argument is an object.
6762 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6763 ObjOperandId objId = writer.guardToObject(arg0Id);
6765 // BytecodeEmitter::assertSelfHostedUnsafeGetReservedSlot ensures that the
6766 // slot argument is constant. (At least for direct calls)
6768 switch (native) {
6769 case InlinableNative::IntrinsicUnsafeGetReservedSlot:
6770 writer.loadFixedSlotResult(objId, offset);
6771 break;
6772 case InlinableNative::IntrinsicUnsafeGetObjectFromReservedSlot:
6773 writer.loadFixedSlotTypedResult(objId, offset, ValueType::Object);
6774 break;
6775 case InlinableNative::IntrinsicUnsafeGetInt32FromReservedSlot:
6776 writer.loadFixedSlotTypedResult(objId, offset, ValueType::Int32);
6777 break;
6778 case InlinableNative::IntrinsicUnsafeGetStringFromReservedSlot:
6779 writer.loadFixedSlotTypedResult(objId, offset, ValueType::String);
6780 break;
6781 default:
6782 MOZ_CRASH("unexpected native");
6785 writer.returnFromIC();
6787 trackAttached("UnsafeGetReservedSlot");
6788 return AttachDecision::Attach;
6791 AttachDecision InlinableNativeIRGenerator::tryAttachUnsafeSetReservedSlot() {
6792 // Self-hosted code calls this with (object, int32, value) arguments.
6793 MOZ_ASSERT(argc_ == 3);
6794 MOZ_ASSERT(args_[0].isObject());
6795 MOZ_ASSERT(args_[1].isInt32());
6796 MOZ_ASSERT(args_[1].toInt32() >= 0);
6798 uint32_t slot = uint32_t(args_[1].toInt32());
6799 if (slot >= NativeObject::MAX_FIXED_SLOTS) {
6800 return AttachDecision::NoAction;
6802 size_t offset = NativeObject::getFixedSlotOffset(slot);
6804 // Initialize the input operand.
6805 initializeInputOperand();
6807 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6809 // Guard that the first argument is an object.
6810 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6811 ObjOperandId objId = writer.guardToObject(arg0Id);
6813 // BytecodeEmitter::assertSelfHostedUnsafeSetReservedSlot ensures that the
6814 // slot argument is constant. (At least for direct calls)
6816 // Get the value to set.
6817 ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
6819 // Set the fixed slot and return undefined.
6820 writer.storeFixedSlotUndefinedResult(objId, offset, valId);
6822 // This stub always returns undefined.
6823 writer.returnFromIC();
6825 trackAttached("UnsafeSetReservedSlot");
6826 return AttachDecision::Attach;
6829 AttachDecision InlinableNativeIRGenerator::tryAttachIsSuspendedGenerator() {
6830 // The IsSuspendedGenerator intrinsic is only called in
6831 // self-hosted code, so it's safe to assume we have a single
6832 // argument and the callee is our intrinsic.
6834 MOZ_ASSERT(argc_ == 1);
6836 initializeInputOperand();
6838 // Stack layout here is (bottom to top):
6839 // 2: Callee
6840 // 1: ThisValue
6841 // 0: Arg <-- Top of stack.
6842 // We only care about the argument.
6843 ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6845 // Check whether the argument is a suspended generator.
6846 // We don't need guards, because IsSuspendedGenerator returns
6847 // false for values that are not generator objects.
6848 writer.callIsSuspendedGeneratorResult(valId);
6849 writer.returnFromIC();
6851 trackAttached("IsSuspendedGenerator");
6852 return AttachDecision::Attach;
6855 AttachDecision InlinableNativeIRGenerator::tryAttachToObject() {
6856 // Self-hosted code calls this with a single argument.
6857 MOZ_ASSERT(argc_ == 1);
6859 // Need a single object argument.
6860 // TODO(Warp): Support all or more conversions to object.
6861 if (!args_[0].isObject()) {
6862 return AttachDecision::NoAction;
6865 // Initialize the input operand.
6866 initializeInputOperand();
6868 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6870 // Guard that the argument is an object.
6871 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6872 ObjOperandId objId = writer.guardToObject(argId);
6874 // Return the object.
6875 writer.loadObjectResult(objId);
6876 writer.returnFromIC();
6878 trackAttached("ToObject");
6879 return AttachDecision::Attach;
6882 AttachDecision InlinableNativeIRGenerator::tryAttachToInteger() {
6883 // Self-hosted code calls this with a single argument.
6884 MOZ_ASSERT(argc_ == 1);
6886 // Need a single int32 argument.
6887 // TODO(Warp): Support all or more conversions to integer.
6888 // Make sure to update this code correctly if we ever start
6889 // returning non-int32 integers.
6890 if (!args_[0].isInt32()) {
6891 return AttachDecision::NoAction;
6894 // Initialize the input operand.
6895 initializeInputOperand();
6897 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6899 // Guard that the argument is an int32.
6900 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6901 Int32OperandId int32Id = writer.guardToInt32(argId);
6903 // Return the int32.
6904 writer.loadInt32Result(int32Id);
6905 writer.returnFromIC();
6907 trackAttached("ToInteger");
6908 return AttachDecision::Attach;
6911 AttachDecision InlinableNativeIRGenerator::tryAttachToLength() {
6912 // Self-hosted code calls this with a single argument.
6913 MOZ_ASSERT(argc_ == 1);
6915 // Need a single int32 argument.
6916 if (!args_[0].isInt32()) {
6917 return AttachDecision::NoAction;
6920 // Initialize the input operand.
6921 initializeInputOperand();
6923 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6925 // ToLength(int32) is equivalent to max(int32, 0).
6926 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6927 Int32OperandId int32ArgId = writer.guardToInt32(argId);
6928 Int32OperandId zeroId = writer.loadInt32Constant(0);
6929 bool isMax = true;
6930 Int32OperandId maxId = writer.int32MinMax(isMax, int32ArgId, zeroId);
6931 writer.loadInt32Result(maxId);
6932 writer.returnFromIC();
6934 trackAttached("ToLength");
6935 return AttachDecision::Attach;
6938 AttachDecision InlinableNativeIRGenerator::tryAttachIsObject() {
6939 // Self-hosted code calls this with a single argument.
6940 MOZ_ASSERT(argc_ == 1);
6942 // Initialize the input operand.
6943 initializeInputOperand();
6945 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6947 // Type check the argument and return result.
6948 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6949 writer.isObjectResult(argId);
6950 writer.returnFromIC();
6952 trackAttached("IsObject");
6953 return AttachDecision::Attach;
6956 AttachDecision InlinableNativeIRGenerator::tryAttachIsPackedArray() {
6957 // Self-hosted code calls this with a single object argument.
6958 MOZ_ASSERT(argc_ == 1);
6959 MOZ_ASSERT(args_[0].isObject());
6961 // Initialize the input operand.
6962 initializeInputOperand();
6964 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6966 // Check if the argument is packed and return result.
6967 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6968 ObjOperandId objArgId = writer.guardToObject(argId);
6969 writer.isPackedArrayResult(objArgId);
6970 writer.returnFromIC();
6972 trackAttached("IsPackedArray");
6973 return AttachDecision::Attach;
6976 AttachDecision InlinableNativeIRGenerator::tryAttachIsCallable() {
6977 // Self-hosted code calls this with a single argument.
6978 MOZ_ASSERT(argc_ == 1);
6980 // Initialize the input operand.
6981 initializeInputOperand();
6983 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
6985 // Check if the argument is callable and return result.
6986 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
6987 writer.isCallableResult(argId);
6988 writer.returnFromIC();
6990 trackAttached("IsCallable");
6991 return AttachDecision::Attach;
6994 AttachDecision InlinableNativeIRGenerator::tryAttachIsConstructor() {
6995 // Self-hosted code calls this with a single argument.
6996 MOZ_ASSERT(argc_ == 1);
6998 // Need a single object argument.
6999 if (!args_[0].isObject()) {
7000 return AttachDecision::NoAction;
7003 // Initialize the input operand.
7004 initializeInputOperand();
7006 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7008 // Guard that the argument is an object.
7009 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7010 ObjOperandId objId = writer.guardToObject(argId);
7012 // Check if the argument is a constructor and return result.
7013 writer.isConstructorResult(objId);
7014 writer.returnFromIC();
7016 trackAttached("IsConstructor");
7017 return AttachDecision::Attach;
7020 AttachDecision
7021 InlinableNativeIRGenerator::tryAttachIsCrossRealmArrayConstructor() {
7022 // Self-hosted code calls this with an object argument.
7023 MOZ_ASSERT(argc_ == 1);
7024 MOZ_ASSERT(args_[0].isObject());
7026 if (args_[0].toObject().is<ProxyObject>()) {
7027 return AttachDecision::NoAction;
7030 // Initialize the input operand.
7031 initializeInputOperand();
7033 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7035 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7036 ObjOperandId objId = writer.guardToObject(argId);
7037 writer.guardIsNotProxy(objId);
7038 writer.isCrossRealmArrayConstructorResult(objId);
7039 writer.returnFromIC();
7041 trackAttached("IsCrossRealmArrayConstructor");
7042 return AttachDecision::Attach;
7045 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToClass(
7046 InlinableNative native) {
7047 // Self-hosted code calls this with an object argument.
7048 MOZ_ASSERT(argc_ == 1);
7049 MOZ_ASSERT(args_[0].isObject());
7051 // Class must match.
7052 const JSClass* clasp = InlinableNativeGuardToClass(native);
7053 if (args_[0].toObject().getClass() != clasp) {
7054 return AttachDecision::NoAction;
7057 // Initialize the input operand.
7058 initializeInputOperand();
7060 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7062 // Guard that the argument is an object.
7063 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7064 ObjOperandId objId = writer.guardToObject(argId);
7066 // Guard that the object has the correct class.
7067 writer.guardAnyClass(objId, clasp);
7069 // Return the object.
7070 writer.loadObjectResult(objId);
7071 writer.returnFromIC();
7073 trackAttached("GuardToClass");
7074 return AttachDecision::Attach;
7077 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToClass(
7078 GuardClassKind kind) {
7079 // Self-hosted code calls this with an object argument.
7080 MOZ_ASSERT(argc_ == 1);
7081 MOZ_ASSERT(args_[0].isObject());
7083 // Class must match.
7084 const JSClass* clasp = ClassFor(kind);
7085 if (args_[0].toObject().getClass() != clasp) {
7086 return AttachDecision::NoAction;
7089 // Initialize the input operand.
7090 initializeInputOperand();
7092 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7094 // Guard that the argument is an object.
7095 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7096 ObjOperandId objId = writer.guardToObject(argId);
7098 // Guard that the object has the correct class.
7099 writer.guardClass(objId, kind);
7101 // Return the object.
7102 writer.loadObjectResult(objId);
7103 writer.returnFromIC();
7105 trackAttached("GuardToClass");
7106 return AttachDecision::Attach;
7109 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToEitherClass(
7110 GuardClassKind kind1, GuardClassKind kind2) {
7111 MOZ_ASSERT(kind1 != kind2,
7112 "prefer tryAttachGuardToClass for the same class case");
7114 // Self-hosted code calls this with an object argument.
7115 MOZ_ASSERT(argc_ == 1);
7116 MOZ_ASSERT(args_[0].isObject());
7118 // Class must match.
7119 const JSClass* clasp1 = ClassFor(kind1);
7120 const JSClass* clasp2 = ClassFor(kind2);
7121 const JSClass* objClass = args_[0].toObject().getClass();
7122 if (objClass != clasp1 && objClass != clasp2) {
7123 return AttachDecision::NoAction;
7126 // Initialize the input operand.
7127 initializeInputOperand();
7129 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7131 // Guard that the argument is an object.
7132 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7133 ObjOperandId objId = writer.guardToObject(argId);
7135 // Guard that the object has the correct class.
7136 writer.guardEitherClass(objId, kind1, kind2);
7138 // Return the object.
7139 writer.loadObjectResult(objId);
7140 writer.returnFromIC();
7142 trackAttached("GuardToEitherClass");
7143 return AttachDecision::Attach;
7146 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToArrayBuffer() {
7147 return tryAttachGuardToEitherClass(GuardClassKind::FixedLengthArrayBuffer,
7148 GuardClassKind::ResizableArrayBuffer);
7151 AttachDecision InlinableNativeIRGenerator::tryAttachGuardToSharedArrayBuffer() {
7152 return tryAttachGuardToEitherClass(
7153 GuardClassKind::FixedLengthSharedArrayBuffer,
7154 GuardClassKind::GrowableSharedArrayBuffer);
7157 AttachDecision InlinableNativeIRGenerator::tryAttachHasClass(
7158 const JSClass* clasp, bool isPossiblyWrapped) {
7159 // Self-hosted code calls this with an object argument.
7160 MOZ_ASSERT(argc_ == 1);
7161 MOZ_ASSERT(args_[0].isObject());
7163 // Only optimize when the object isn't a proxy.
7164 if (isPossiblyWrapped && args_[0].toObject().is<ProxyObject>()) {
7165 return AttachDecision::NoAction;
7168 // Initialize the input operand.
7169 initializeInputOperand();
7171 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7173 // Perform the Class check.
7174 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7175 ObjOperandId objId = writer.guardToObject(argId);
7177 if (isPossiblyWrapped) {
7178 writer.guardIsNotProxy(objId);
7181 writer.hasClassResult(objId, clasp);
7182 writer.returnFromIC();
7184 trackAttached("HasClass");
7185 return AttachDecision::Attach;
7188 // Returns whether the .lastIndex property is a non-negative int32 value and is
7189 // still writable.
7190 static bool HasOptimizableLastIndexSlot(RegExpObject* regexp, JSContext* cx) {
7191 auto lastIndexProp = regexp->lookupPure(cx->names().lastIndex);
7192 MOZ_ASSERT(lastIndexProp->isDataProperty());
7193 if (!lastIndexProp->writable()) {
7194 return false;
7196 Value lastIndex = regexp->getLastIndex();
7197 if (!lastIndex.isInt32() || lastIndex.toInt32() < 0) {
7198 return false;
7200 return true;
7203 // Returns the RegExp stub used by the optimized code path for this intrinsic.
7204 // We store a pointer to this in the IC stub to ensure GC doesn't discard it.
7205 static JitCode* GetOrCreateRegExpStub(JSContext* cx, InlinableNative native) {
7206 #ifdef ENABLE_PORTABLE_BASELINE_INTERP
7207 return nullptr;
7208 #else
7209 // The stubs assume the global has non-null RegExpStatics and match result
7210 // shape.
7211 if (!GlobalObject::getRegExpStatics(cx, cx->global()) ||
7212 !cx->global()->regExpRealm().getOrCreateMatchResultShape(cx)) {
7213 MOZ_ASSERT(cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed());
7214 cx->clearPendingException();
7215 return nullptr;
7217 JitCode* code;
7218 switch (native) {
7219 case InlinableNative::IntrinsicRegExpBuiltinExecForTest:
7220 case InlinableNative::IntrinsicRegExpExecForTest:
7221 code = cx->zone()->jitZone()->ensureRegExpExecTestStubExists(cx);
7222 break;
7223 case InlinableNative::IntrinsicRegExpBuiltinExec:
7224 case InlinableNative::IntrinsicRegExpExec:
7225 code = cx->zone()->jitZone()->ensureRegExpExecMatchStubExists(cx);
7226 break;
7227 case InlinableNative::RegExpMatcher:
7228 code = cx->zone()->jitZone()->ensureRegExpMatcherStubExists(cx);
7229 break;
7230 case InlinableNative::RegExpSearcher:
7231 code = cx->zone()->jitZone()->ensureRegExpSearcherStubExists(cx);
7232 break;
7233 default:
7234 MOZ_CRASH("Unexpected native");
7236 if (!code) {
7237 MOZ_ASSERT(cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed());
7238 cx->clearPendingException();
7239 return nullptr;
7241 return code;
7242 #endif
7245 static void EmitGuardLastIndexIsNonNegativeInt32(CacheIRWriter& writer,
7246 ObjOperandId regExpId) {
7247 size_t offset =
7248 NativeObject::getFixedSlotOffset(RegExpObject::lastIndexSlot());
7249 ValOperandId lastIndexValId = writer.loadFixedSlot(regExpId, offset);
7250 Int32OperandId lastIndexId = writer.guardToInt32(lastIndexValId);
7251 writer.guardInt32IsNonNegative(lastIndexId);
7254 AttachDecision InlinableNativeIRGenerator::tryAttachIntrinsicRegExpBuiltinExec(
7255 InlinableNative native) {
7256 // Self-hosted code calls this with (regexp, string) arguments.
7257 MOZ_ASSERT(argc_ == 2);
7258 MOZ_ASSERT(args_[0].isObject());
7259 MOZ_ASSERT(args_[1].isString());
7261 JitCode* stub = GetOrCreateRegExpStub(cx_, native);
7262 if (!stub) {
7263 return AttachDecision::NoAction;
7266 RegExpObject* re = &args_[0].toObject().as<RegExpObject>();
7267 if (!HasOptimizableLastIndexSlot(re, cx_)) {
7268 return AttachDecision::NoAction;
7271 // Initialize the input operand.
7272 initializeInputOperand();
7274 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7276 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7277 ObjOperandId regExpId = writer.guardToObject(arg0Id);
7278 writer.guardShape(regExpId, re->shape());
7279 EmitGuardLastIndexIsNonNegativeInt32(writer, regExpId);
7281 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7282 StringOperandId inputId = writer.guardToString(arg1Id);
7284 if (native == InlinableNative::IntrinsicRegExpBuiltinExecForTest) {
7285 writer.regExpBuiltinExecTestResult(regExpId, inputId, stub);
7286 } else {
7287 writer.regExpBuiltinExecMatchResult(regExpId, inputId, stub);
7289 writer.returnFromIC();
7291 trackAttached("IntrinsicRegExpBuiltinExec");
7292 return AttachDecision::Attach;
7295 AttachDecision InlinableNativeIRGenerator::tryAttachIntrinsicRegExpExec(
7296 InlinableNative native) {
7297 // Self-hosted code calls this with (object, string) arguments.
7298 MOZ_ASSERT(argc_ == 2);
7299 MOZ_ASSERT(args_[0].isObject());
7300 MOZ_ASSERT(args_[1].isString());
7302 if (!args_[0].toObject().is<RegExpObject>()) {
7303 return AttachDecision::NoAction;
7306 JitCode* stub = GetOrCreateRegExpStub(cx_, native);
7307 if (!stub) {
7308 return AttachDecision::NoAction;
7311 RegExpObject* re = &args_[0].toObject().as<RegExpObject>();
7312 if (!HasOptimizableLastIndexSlot(re, cx_)) {
7313 return AttachDecision::NoAction;
7316 // Ensure regexp.exec is the original RegExp.prototype.exec function on the
7317 // prototype.
7318 if (re->containsPure(cx_->names().exec)) {
7319 return AttachDecision::NoAction;
7321 MOZ_ASSERT(cx_->global()->maybeGetRegExpPrototype());
7322 auto* regExpProto =
7323 &cx_->global()->maybeGetRegExpPrototype()->as<NativeObject>();
7324 if (re->staticPrototype() != regExpProto) {
7325 return AttachDecision::NoAction;
7327 auto execProp = regExpProto->as<NativeObject>().lookupPure(cx_->names().exec);
7328 if (!execProp || !execProp->isDataProperty()) {
7329 return AttachDecision::NoAction;
7331 // It should be stored in a dynamic slot. We assert this in
7332 // FinishRegExpClassInit.
7333 if (regExpProto->isFixedSlot(execProp->slot())) {
7334 return AttachDecision::NoAction;
7336 Value execVal = regExpProto->getSlot(execProp->slot());
7337 PropertyName* execName = cx_->names().RegExp_prototype_Exec;
7338 if (!IsSelfHostedFunctionWithName(execVal, execName)) {
7339 return AttachDecision::NoAction;
7341 JSFunction* execFunction = &execVal.toObject().as<JSFunction>();
7343 // Initialize the input operand.
7344 initializeInputOperand();
7346 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7348 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7349 ObjOperandId regExpId = writer.guardToObject(arg0Id);
7350 writer.guardShape(regExpId, re->shape());
7351 EmitGuardLastIndexIsNonNegativeInt32(writer, regExpId);
7353 // Emit guards for the RegExp.prototype.exec property.
7354 ObjOperandId regExpProtoId = writer.loadObject(regExpProto);
7355 writer.guardShape(regExpProtoId, regExpProto->shape());
7356 size_t offset =
7357 regExpProto->dynamicSlotIndex(execProp->slot()) * sizeof(Value);
7358 writer.guardDynamicSlotValue(regExpProtoId, offset,
7359 ObjectValue(*execFunction));
7361 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7362 StringOperandId inputId = writer.guardToString(arg1Id);
7364 if (native == InlinableNative::IntrinsicRegExpExecForTest) {
7365 writer.regExpBuiltinExecTestResult(regExpId, inputId, stub);
7366 } else {
7367 writer.regExpBuiltinExecMatchResult(regExpId, inputId, stub);
7369 writer.returnFromIC();
7371 trackAttached("IntrinsicRegExpExec");
7372 return AttachDecision::Attach;
7375 AttachDecision InlinableNativeIRGenerator::tryAttachRegExpMatcherSearcher(
7376 InlinableNative native) {
7377 // Self-hosted code calls this with (object, string, number) arguments.
7378 MOZ_ASSERT(argc_ == 3);
7379 MOZ_ASSERT(args_[0].isObject());
7380 MOZ_ASSERT(args_[1].isString());
7381 MOZ_ASSERT(args_[2].isNumber());
7383 // It's not guaranteed that the JITs have typed |lastIndex| as an Int32.
7384 if (!args_[2].isInt32()) {
7385 return AttachDecision::NoAction;
7388 JitCode* stub = GetOrCreateRegExpStub(cx_, native);
7389 if (!stub) {
7390 return AttachDecision::NoAction;
7393 // Initialize the input operand.
7394 initializeInputOperand();
7396 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7398 // Guard argument types.
7399 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7400 ObjOperandId reId = writer.guardToObject(arg0Id);
7402 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7403 StringOperandId inputId = writer.guardToString(arg1Id);
7405 ValOperandId arg2Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
7406 Int32OperandId lastIndexId = writer.guardToInt32(arg2Id);
7408 switch (native) {
7409 case InlinableNative::RegExpMatcher:
7410 writer.callRegExpMatcherResult(reId, inputId, lastIndexId, stub);
7411 writer.returnFromIC();
7412 trackAttached("RegExpMatcher");
7413 break;
7415 case InlinableNative::RegExpSearcher:
7416 writer.callRegExpSearcherResult(reId, inputId, lastIndexId, stub);
7417 writer.returnFromIC();
7418 trackAttached("RegExpSearcher");
7419 break;
7421 default:
7422 MOZ_CRASH("Unexpected native");
7425 return AttachDecision::Attach;
7428 AttachDecision InlinableNativeIRGenerator::tryAttachRegExpSearcherLastLimit() {
7429 // Self-hosted code calls this with a string argument that's only used for an
7430 // assertion.
7431 MOZ_ASSERT(argc_ == 1);
7432 MOZ_ASSERT(args_[0].isString());
7434 // Initialize the input operand.
7435 initializeInputOperand();
7437 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7439 writer.regExpSearcherLastLimitResult();
7440 writer.returnFromIC();
7442 trackAttached("RegExpSearcherLastLimit");
7443 return AttachDecision::Attach;
7446 AttachDecision InlinableNativeIRGenerator::tryAttachRegExpHasCaptureGroups() {
7447 // Self-hosted code calls this with object and string arguments.
7448 MOZ_ASSERT(argc_ == 2);
7449 MOZ_ASSERT(args_[0].toObject().is<RegExpObject>());
7450 MOZ_ASSERT(args_[1].isString());
7452 // Initialize the input operand.
7453 initializeInputOperand();
7455 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7457 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7458 ObjOperandId objId = writer.guardToObject(arg0Id);
7460 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7461 StringOperandId inputId = writer.guardToString(arg1Id);
7463 writer.regExpHasCaptureGroupsResult(objId, inputId);
7464 writer.returnFromIC();
7466 trackAttached("RegExpHasCaptureGroups");
7467 return AttachDecision::Attach;
7470 AttachDecision
7471 InlinableNativeIRGenerator::tryAttachRegExpPrototypeOptimizable() {
7472 // Self-hosted code calls this with a single object argument.
7473 MOZ_ASSERT(argc_ == 1);
7474 MOZ_ASSERT(args_[0].isObject());
7476 // Initialize the input operand.
7477 initializeInputOperand();
7479 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7481 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7482 ObjOperandId protoId = writer.guardToObject(arg0Id);
7484 writer.regExpPrototypeOptimizableResult(protoId);
7485 writer.returnFromIC();
7487 trackAttached("RegExpPrototypeOptimizable");
7488 return AttachDecision::Attach;
7491 AttachDecision
7492 InlinableNativeIRGenerator::tryAttachRegExpInstanceOptimizable() {
7493 // Self-hosted code calls this with two object arguments.
7494 MOZ_ASSERT(argc_ == 2);
7495 MOZ_ASSERT(args_[0].isObject());
7496 MOZ_ASSERT(args_[1].isObject());
7498 // Initialize the input operand.
7499 initializeInputOperand();
7501 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7503 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7504 ObjOperandId regexpId = writer.guardToObject(arg0Id);
7506 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7507 ObjOperandId protoId = writer.guardToObject(arg1Id);
7509 writer.regExpInstanceOptimizableResult(regexpId, protoId);
7510 writer.returnFromIC();
7512 trackAttached("RegExpInstanceOptimizable");
7513 return AttachDecision::Attach;
7516 AttachDecision InlinableNativeIRGenerator::tryAttachGetFirstDollarIndex() {
7517 // Self-hosted code calls this with a single string argument.
7518 MOZ_ASSERT(argc_ == 1);
7519 MOZ_ASSERT(args_[0].isString());
7521 // Initialize the input operand.
7522 initializeInputOperand();
7524 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7526 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7527 StringOperandId strId = writer.guardToString(arg0Id);
7529 writer.getFirstDollarIndexResult(strId);
7530 writer.returnFromIC();
7532 trackAttached("GetFirstDollarIndex");
7533 return AttachDecision::Attach;
7536 AttachDecision InlinableNativeIRGenerator::tryAttachSubstringKernel() {
7537 // Self-hosted code calls this with (string, int32, int32) arguments.
7538 MOZ_ASSERT(argc_ == 3);
7539 MOZ_ASSERT(args_[0].isString());
7540 MOZ_ASSERT(args_[1].isInt32());
7541 MOZ_ASSERT(args_[2].isInt32());
7543 // Initialize the input operand.
7544 initializeInputOperand();
7546 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7548 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7549 StringOperandId strId = writer.guardToString(arg0Id);
7551 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7552 Int32OperandId beginId = writer.guardToInt32(arg1Id);
7554 ValOperandId arg2Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
7555 Int32OperandId lengthId = writer.guardToInt32(arg2Id);
7557 writer.callSubstringKernelResult(strId, beginId, lengthId);
7558 writer.returnFromIC();
7560 trackAttached("SubstringKernel");
7561 return AttachDecision::Attach;
7564 AttachDecision InlinableNativeIRGenerator::tryAttachObjectHasPrototype() {
7565 // Self-hosted code calls this with (object, object) arguments.
7566 MOZ_ASSERT(argc_ == 2);
7567 MOZ_ASSERT(args_[0].isObject());
7568 MOZ_ASSERT(args_[1].isObject());
7570 auto* obj = &args_[0].toObject().as<NativeObject>();
7571 auto* proto = &args_[1].toObject().as<NativeObject>();
7573 // Only attach when obj.__proto__ is proto.
7574 if (obj->staticPrototype() != proto) {
7575 return AttachDecision::NoAction;
7578 // Initialize the input operand.
7579 initializeInputOperand();
7581 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7583 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7584 ObjOperandId objId = writer.guardToObject(arg0Id);
7586 writer.guardProto(objId, proto);
7587 writer.loadBooleanResult(true);
7588 writer.returnFromIC();
7590 trackAttached("ObjectHasPrototype");
7591 return AttachDecision::Attach;
7594 static bool CanConvertToString(const Value& v) {
7595 return v.isString() || v.isNumber() || v.isBoolean() || v.isNullOrUndefined();
7598 AttachDecision InlinableNativeIRGenerator::tryAttachString() {
7599 // Need a single argument that is or can be converted to a string.
7600 if (argc_ != 1 || !CanConvertToString(args_[0])) {
7601 return AttachDecision::NoAction;
7604 // Initialize the input operand.
7605 initializeInputOperand();
7607 // Guard callee is the 'String' function.
7608 emitNativeCalleeGuard();
7610 // Guard that the argument is a string or can be converted to one.
7611 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7612 StringOperandId strId = emitToStringGuard(argId, args_[0]);
7614 // Return the string.
7615 writer.loadStringResult(strId);
7616 writer.returnFromIC();
7618 trackAttached("String");
7619 return AttachDecision::Attach;
7622 AttachDecision InlinableNativeIRGenerator::tryAttachStringConstructor() {
7623 // Need a single argument that is or can be converted to a string.
7624 if (argc_ != 1 || !CanConvertToString(args_[0])) {
7625 return AttachDecision::NoAction;
7628 RootedString emptyString(cx_, cx_->runtime()->emptyString);
7629 JSObject* templateObj = StringObject::create(
7630 cx_, emptyString, /* proto = */ nullptr, TenuredObject);
7631 if (!templateObj) {
7632 cx_->recoverFromOutOfMemory();
7633 return AttachDecision::NoAction;
7636 // Initialize the input operand.
7637 initializeInputOperand();
7639 // Guard callee is the 'String' function.
7640 emitNativeCalleeGuard();
7642 // Guard on number and convert to string.
7643 ValOperandId argId =
7644 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags_);
7645 StringOperandId strId = emitToStringGuard(argId, args_[0]);
7647 writer.newStringObjectResult(templateObj, strId);
7648 writer.returnFromIC();
7650 trackAttached("StringConstructor");
7651 return AttachDecision::Attach;
7654 AttachDecision InlinableNativeIRGenerator::tryAttachStringToStringValueOf() {
7655 // Expecting no arguments.
7656 if (argc_ != 0) {
7657 return AttachDecision::NoAction;
7660 // Ensure |this| is a primitive string value.
7661 if (!thisval_.isString()) {
7662 return AttachDecision::NoAction;
7665 // Initialize the input operand.
7666 initializeInputOperand();
7668 // Guard callee is the 'toString' OR 'valueOf' native function.
7669 emitNativeCalleeGuard();
7671 // Guard |this| is a string.
7672 ValOperandId thisValId =
7673 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
7674 StringOperandId strId = writer.guardToString(thisValId);
7676 // Return the string
7677 writer.loadStringResult(strId);
7678 writer.returnFromIC();
7680 trackAttached("StringToStringValueOf");
7681 return AttachDecision::Attach;
7684 AttachDecision InlinableNativeIRGenerator::tryAttachStringReplaceString() {
7685 // Self-hosted code calls this with (string, string, string) arguments.
7686 MOZ_ASSERT(argc_ == 3);
7687 MOZ_ASSERT(args_[0].isString());
7688 MOZ_ASSERT(args_[1].isString());
7689 MOZ_ASSERT(args_[2].isString());
7691 // Initialize the input operand.
7692 initializeInputOperand();
7694 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7696 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7697 StringOperandId strId = writer.guardToString(arg0Id);
7699 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7700 StringOperandId patternId = writer.guardToString(arg1Id);
7702 ValOperandId arg2Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
7703 StringOperandId replacementId = writer.guardToString(arg2Id);
7705 writer.stringReplaceStringResult(strId, patternId, replacementId);
7706 writer.returnFromIC();
7708 trackAttached("StringReplaceString");
7709 return AttachDecision::Attach;
7712 AttachDecision InlinableNativeIRGenerator::tryAttachStringSplitString() {
7713 // Self-hosted code calls this with (string, string) arguments.
7714 MOZ_ASSERT(argc_ == 2);
7715 MOZ_ASSERT(args_[0].isString());
7716 MOZ_ASSERT(args_[1].isString());
7718 // Initialize the input operand.
7719 initializeInputOperand();
7721 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
7723 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7724 StringOperandId strId = writer.guardToString(arg0Id);
7726 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
7727 StringOperandId separatorId = writer.guardToString(arg1Id);
7729 writer.stringSplitStringResult(strId, separatorId);
7730 writer.returnFromIC();
7732 trackAttached("StringSplitString");
7733 return AttachDecision::Attach;
7736 AttachDecision InlinableNativeIRGenerator::tryAttachStringChar(
7737 StringChar kind) {
7738 // Need one argument.
7739 if (argc_ != 1) {
7740 return AttachDecision::NoAction;
7743 auto attach = CanAttachStringChar(thisval_, args_[0], kind);
7744 if (attach == AttachStringChar::No) {
7745 return AttachDecision::NoAction;
7748 bool handleOOB = attach == AttachStringChar::OutOfBounds;
7750 // Initialize the input operand.
7751 initializeInputOperand();
7753 // Guard callee is the 'charCodeAt', 'codePointAt', 'charAt', or 'at' native
7754 // function.
7755 emitNativeCalleeGuard();
7757 // Guard this is a string.
7758 ValOperandId thisValId =
7759 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
7760 StringOperandId strId = writer.guardToString(thisValId);
7762 // Guard int32 index.
7763 ValOperandId indexId =
7764 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7765 Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
7767 // Handle relative string indices, if necessary.
7768 if (kind == StringChar::At) {
7769 int32IndexId = writer.toRelativeStringIndex(int32IndexId, strId);
7772 // Linearize the string.
7774 // AttachStringChar doesn't have a separate state when OOB access happens on
7775 // a string which needs to be linearized, so just linearize unconditionally
7776 // for out-of-bounds accesses.
7777 if (attach == AttachStringChar::Linearize ||
7778 attach == AttachStringChar::OutOfBounds) {
7779 switch (kind) {
7780 case StringChar::CharCodeAt:
7781 case StringChar::CharAt:
7782 case StringChar::At:
7783 strId = writer.linearizeForCharAccess(strId, int32IndexId);
7784 break;
7785 case StringChar::CodePointAt:
7786 strId = writer.linearizeForCodePointAccess(strId, int32IndexId);
7787 break;
7791 // Load string char or code.
7792 switch (kind) {
7793 case StringChar::CharCodeAt:
7794 writer.loadStringCharCodeResult(strId, int32IndexId, handleOOB);
7795 break;
7796 case StringChar::CodePointAt:
7797 writer.loadStringCodePointResult(strId, int32IndexId, handleOOB);
7798 break;
7799 case StringChar::CharAt:
7800 writer.loadStringCharResult(strId, int32IndexId, handleOOB);
7801 break;
7802 case StringChar::At:
7803 writer.loadStringAtResult(strId, int32IndexId, handleOOB);
7804 break;
7807 writer.returnFromIC();
7809 switch (kind) {
7810 case StringChar::CharCodeAt:
7811 trackAttached("StringCharCodeAt");
7812 break;
7813 case StringChar::CodePointAt:
7814 trackAttached("StringCodePointAt");
7815 break;
7816 case StringChar::CharAt:
7817 trackAttached("StringCharAt");
7818 break;
7819 case StringChar::At:
7820 trackAttached("StringAt");
7821 break;
7824 return AttachDecision::Attach;
7827 AttachDecision InlinableNativeIRGenerator::tryAttachStringCharCodeAt() {
7828 return tryAttachStringChar(StringChar::CharCodeAt);
7831 AttachDecision InlinableNativeIRGenerator::tryAttachStringCodePointAt() {
7832 return tryAttachStringChar(StringChar::CodePointAt);
7835 AttachDecision InlinableNativeIRGenerator::tryAttachStringCharAt() {
7836 return tryAttachStringChar(StringChar::CharAt);
7839 AttachDecision InlinableNativeIRGenerator::tryAttachStringAt() {
7840 return tryAttachStringChar(StringChar::At);
7843 AttachDecision InlinableNativeIRGenerator::tryAttachStringFromCharCode() {
7844 // Need one number argument.
7845 if (argc_ != 1 || !args_[0].isNumber()) {
7846 return AttachDecision::NoAction;
7849 // Initialize the input operand.
7850 initializeInputOperand();
7852 // Guard callee is the 'fromCharCode' native function.
7853 emitNativeCalleeGuard();
7855 // Guard int32 argument.
7856 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7857 Int32OperandId codeId;
7858 if (args_[0].isInt32()) {
7859 codeId = writer.guardToInt32(argId);
7860 } else {
7861 // 'fromCharCode' performs ToUint16 on its input. We can use Uint32
7862 // semantics, because ToUint16(ToUint32(v)) == ToUint16(v).
7863 codeId = writer.guardToInt32ModUint32(argId);
7866 // Return string created from code.
7867 writer.stringFromCharCodeResult(codeId);
7868 writer.returnFromIC();
7870 trackAttached("StringFromCharCode");
7871 return AttachDecision::Attach;
7874 AttachDecision InlinableNativeIRGenerator::tryAttachStringFromCodePoint() {
7875 // Need one int32 argument.
7876 if (argc_ != 1 || !args_[0].isInt32()) {
7877 return AttachDecision::NoAction;
7880 // String.fromCodePoint throws for invalid code points.
7881 int32_t codePoint = args_[0].toInt32();
7882 if (codePoint < 0 || codePoint > int32_t(unicode::NonBMPMax)) {
7883 return AttachDecision::NoAction;
7886 // Initialize the input operand.
7887 initializeInputOperand();
7889 // Guard callee is the 'fromCodePoint' native function.
7890 emitNativeCalleeGuard();
7892 // Guard int32 argument.
7893 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7894 Int32OperandId codeId = writer.guardToInt32(argId);
7896 // Return string created from code point.
7897 writer.stringFromCodePointResult(codeId);
7898 writer.returnFromIC();
7900 trackAttached("StringFromCodePoint");
7901 return AttachDecision::Attach;
7904 AttachDecision InlinableNativeIRGenerator::tryAttachStringIncludes() {
7905 // Need one string argument.
7906 if (argc_ != 1 || !args_[0].isString()) {
7907 return AttachDecision::NoAction;
7910 // Ensure |this| is a primitive string value.
7911 if (!thisval_.isString()) {
7912 return AttachDecision::NoAction;
7915 // Initialize the input operand.
7916 initializeInputOperand();
7918 // Guard callee is the 'includes' native function.
7919 emitNativeCalleeGuard();
7921 // Guard this is a string.
7922 ValOperandId thisValId =
7923 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
7924 StringOperandId strId = writer.guardToString(thisValId);
7926 // Guard string argument.
7927 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7928 StringOperandId searchStrId = writer.guardToString(argId);
7930 writer.stringIncludesResult(strId, searchStrId);
7931 writer.returnFromIC();
7933 trackAttached("StringIncludes");
7934 return AttachDecision::Attach;
7937 AttachDecision InlinableNativeIRGenerator::tryAttachStringIndexOf() {
7938 // Need one string argument.
7939 if (argc_ != 1 || !args_[0].isString()) {
7940 return AttachDecision::NoAction;
7943 // Ensure |this| is a primitive string value.
7944 if (!thisval_.isString()) {
7945 return AttachDecision::NoAction;
7948 // Initialize the input operand.
7949 initializeInputOperand();
7951 // Guard callee is the 'indexOf' native function.
7952 emitNativeCalleeGuard();
7954 // Guard this is a string.
7955 ValOperandId thisValId =
7956 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
7957 StringOperandId strId = writer.guardToString(thisValId);
7959 // Guard string argument.
7960 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7961 StringOperandId searchStrId = writer.guardToString(argId);
7963 writer.stringIndexOfResult(strId, searchStrId);
7964 writer.returnFromIC();
7966 trackAttached("StringIndexOf");
7967 return AttachDecision::Attach;
7970 AttachDecision InlinableNativeIRGenerator::tryAttachStringLastIndexOf() {
7971 // Need one string argument.
7972 if (argc_ != 1 || !args_[0].isString()) {
7973 return AttachDecision::NoAction;
7976 // Ensure |this| is a primitive string value.
7977 if (!thisval_.isString()) {
7978 return AttachDecision::NoAction;
7981 // Initialize the input operand.
7982 initializeInputOperand();
7984 // Guard callee is the 'lastIndexOf' native function.
7985 emitNativeCalleeGuard();
7987 // Guard this is a string.
7988 ValOperandId thisValId =
7989 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
7990 StringOperandId strId = writer.guardToString(thisValId);
7992 // Guard string argument.
7993 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
7994 StringOperandId searchStrId = writer.guardToString(argId);
7996 writer.stringLastIndexOfResult(strId, searchStrId);
7997 writer.returnFromIC();
7999 trackAttached("StringLastIndexOf");
8000 return AttachDecision::Attach;
8003 AttachDecision InlinableNativeIRGenerator::tryAttachStringStartsWith() {
8004 // Need one string argument.
8005 if (argc_ != 1 || !args_[0].isString()) {
8006 return AttachDecision::NoAction;
8009 // Ensure |this| is a primitive string value.
8010 if (!thisval_.isString()) {
8011 return AttachDecision::NoAction;
8014 // Initialize the input operand.
8015 initializeInputOperand();
8017 // Guard callee is the 'startsWith' native function.
8018 emitNativeCalleeGuard();
8020 // Guard this is a string.
8021 ValOperandId thisValId =
8022 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8023 StringOperandId strId = writer.guardToString(thisValId);
8025 // Guard string argument.
8026 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8027 StringOperandId searchStrId = writer.guardToString(argId);
8029 writer.stringStartsWithResult(strId, searchStrId);
8030 writer.returnFromIC();
8032 trackAttached("StringStartsWith");
8033 return AttachDecision::Attach;
8036 AttachDecision InlinableNativeIRGenerator::tryAttachStringEndsWith() {
8037 // Need one string argument.
8038 if (argc_ != 1 || !args_[0].isString()) {
8039 return AttachDecision::NoAction;
8042 // Ensure |this| is a primitive string value.
8043 if (!thisval_.isString()) {
8044 return AttachDecision::NoAction;
8047 // Initialize the input operand.
8048 initializeInputOperand();
8050 // Guard callee is the 'endsWith' native function.
8051 emitNativeCalleeGuard();
8053 // Guard this is a string.
8054 ValOperandId thisValId =
8055 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8056 StringOperandId strId = writer.guardToString(thisValId);
8058 // Guard string argument.
8059 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8060 StringOperandId searchStrId = writer.guardToString(argId);
8062 writer.stringEndsWithResult(strId, searchStrId);
8063 writer.returnFromIC();
8065 trackAttached("StringEndsWith");
8066 return AttachDecision::Attach;
8069 AttachDecision InlinableNativeIRGenerator::tryAttachStringToLowerCase() {
8070 // Expecting no arguments.
8071 if (argc_ != 0) {
8072 return AttachDecision::NoAction;
8075 // Ensure |this| is a primitive string value.
8076 if (!thisval_.isString()) {
8077 return AttachDecision::NoAction;
8080 // Initialize the input operand.
8081 initializeInputOperand();
8083 // Guard callee is the 'toLowerCase' native function.
8084 emitNativeCalleeGuard();
8086 // Guard this is a string.
8087 ValOperandId thisValId =
8088 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8089 StringOperandId strId = writer.guardToString(thisValId);
8091 // Return string converted to lower-case.
8092 writer.stringToLowerCaseResult(strId);
8093 writer.returnFromIC();
8095 trackAttached("StringToLowerCase");
8096 return AttachDecision::Attach;
8099 AttachDecision InlinableNativeIRGenerator::tryAttachStringToUpperCase() {
8100 // Expecting no arguments.
8101 if (argc_ != 0) {
8102 return AttachDecision::NoAction;
8105 // Ensure |this| is a primitive string value.
8106 if (!thisval_.isString()) {
8107 return AttachDecision::NoAction;
8110 // Initialize the input operand.
8111 initializeInputOperand();
8113 // Guard callee is the 'toUpperCase' native function.
8114 emitNativeCalleeGuard();
8116 // Guard this is a string.
8117 ValOperandId thisValId =
8118 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8119 StringOperandId strId = writer.guardToString(thisValId);
8121 // Return string converted to upper-case.
8122 writer.stringToUpperCaseResult(strId);
8123 writer.returnFromIC();
8125 trackAttached("StringToUpperCase");
8126 return AttachDecision::Attach;
8129 AttachDecision InlinableNativeIRGenerator::tryAttachStringTrim() {
8130 // Expecting no arguments.
8131 if (argc_ != 0) {
8132 return AttachDecision::NoAction;
8135 // Ensure |this| is a primitive string value.
8136 if (!thisval_.isString()) {
8137 return AttachDecision::NoAction;
8140 // Initialize the input operand.
8141 initializeInputOperand();
8143 // Guard callee is the 'trim' native function.
8144 emitNativeCalleeGuard();
8146 // Guard this is a string.
8147 ValOperandId thisValId =
8148 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8149 StringOperandId strId = writer.guardToString(thisValId);
8151 writer.stringTrimResult(strId);
8152 writer.returnFromIC();
8154 trackAttached("StringTrim");
8155 return AttachDecision::Attach;
8158 AttachDecision InlinableNativeIRGenerator::tryAttachStringTrimStart() {
8159 // Expecting no arguments.
8160 if (argc_ != 0) {
8161 return AttachDecision::NoAction;
8164 // Ensure |this| is a primitive string value.
8165 if (!thisval_.isString()) {
8166 return AttachDecision::NoAction;
8169 // Initialize the input operand.
8170 initializeInputOperand();
8172 // Guard callee is the 'trimStart' native function.
8173 emitNativeCalleeGuard();
8175 // Guard this is a string.
8176 ValOperandId thisValId =
8177 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8178 StringOperandId strId = writer.guardToString(thisValId);
8180 writer.stringTrimStartResult(strId);
8181 writer.returnFromIC();
8183 trackAttached("StringTrimStart");
8184 return AttachDecision::Attach;
8187 AttachDecision InlinableNativeIRGenerator::tryAttachStringTrimEnd() {
8188 // Expecting no arguments.
8189 if (argc_ != 0) {
8190 return AttachDecision::NoAction;
8193 // Ensure |this| is a primitive string value.
8194 if (!thisval_.isString()) {
8195 return AttachDecision::NoAction;
8198 // Initialize the input operand.
8199 initializeInputOperand();
8201 // Guard callee is the 'trimEnd' native function.
8202 emitNativeCalleeGuard();
8204 // Guard this is a string.
8205 ValOperandId thisValId =
8206 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
8207 StringOperandId strId = writer.guardToString(thisValId);
8209 writer.stringTrimEndResult(strId);
8210 writer.returnFromIC();
8212 trackAttached("StringTrimEnd");
8213 return AttachDecision::Attach;
8216 AttachDecision InlinableNativeIRGenerator::tryAttachMathRandom() {
8217 // Expecting no arguments.
8218 if (argc_ != 0) {
8219 return AttachDecision::NoAction;
8222 MOZ_ASSERT(cx_->realm() == callee_->realm(),
8223 "Shouldn't inline cross-realm Math.random because per-realm RNG");
8225 // Initialize the input operand.
8226 initializeInputOperand();
8228 // Guard callee is the 'random' native function.
8229 emitNativeCalleeGuard();
8231 mozilla::non_crypto::XorShift128PlusRNG* rng =
8232 &cx_->realm()->getOrCreateRandomNumberGenerator();
8233 writer.mathRandomResult(rng);
8235 writer.returnFromIC();
8237 trackAttached("MathRandom");
8238 return AttachDecision::Attach;
8241 AttachDecision InlinableNativeIRGenerator::tryAttachMathAbs() {
8242 // Need one argument.
8243 if (argc_ != 1) {
8244 return AttachDecision::NoAction;
8247 if (!args_[0].isNumber()) {
8248 return AttachDecision::NoAction;
8251 // Initialize the input operand.
8252 initializeInputOperand();
8254 // Guard callee is the 'abs' native function.
8255 emitNativeCalleeGuard();
8257 ValOperandId argumentId =
8258 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8260 // abs(INT_MIN) is a double.
8261 if (args_[0].isInt32() && args_[0].toInt32() != INT_MIN) {
8262 Int32OperandId int32Id = writer.guardToInt32(argumentId);
8263 writer.mathAbsInt32Result(int32Id);
8264 } else {
8265 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8266 writer.mathAbsNumberResult(numberId);
8269 writer.returnFromIC();
8271 trackAttached("MathAbs");
8272 return AttachDecision::Attach;
8275 AttachDecision InlinableNativeIRGenerator::tryAttachMathClz32() {
8276 // Need one (number) argument.
8277 if (argc_ != 1 || !args_[0].isNumber()) {
8278 return AttachDecision::NoAction;
8281 // Initialize the input operand.
8282 initializeInputOperand();
8284 // Guard callee is the 'clz32' native function.
8285 emitNativeCalleeGuard();
8287 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8289 Int32OperandId int32Id;
8290 if (args_[0].isInt32()) {
8291 int32Id = writer.guardToInt32(argId);
8292 } else {
8293 MOZ_ASSERT(args_[0].isDouble());
8294 NumberOperandId numId = writer.guardIsNumber(argId);
8295 int32Id = writer.truncateDoubleToUInt32(numId);
8297 writer.mathClz32Result(int32Id);
8298 writer.returnFromIC();
8300 trackAttached("MathClz32");
8301 return AttachDecision::Attach;
8304 AttachDecision InlinableNativeIRGenerator::tryAttachMathSign() {
8305 // Need one (number) argument.
8306 if (argc_ != 1 || !args_[0].isNumber()) {
8307 return AttachDecision::NoAction;
8310 // Initialize the input operand.
8311 initializeInputOperand();
8313 // Guard callee is the 'sign' native function.
8314 emitNativeCalleeGuard();
8316 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8318 if (args_[0].isInt32()) {
8319 Int32OperandId int32Id = writer.guardToInt32(argId);
8320 writer.mathSignInt32Result(int32Id);
8321 } else {
8322 // Math.sign returns a double only if the input is -0 or NaN so try to
8323 // optimize the common Number => Int32 case.
8324 double d = math_sign_impl(args_[0].toDouble());
8325 int32_t unused;
8326 bool resultIsInt32 = mozilla::NumberIsInt32(d, &unused);
8328 NumberOperandId numId = writer.guardIsNumber(argId);
8329 if (resultIsInt32) {
8330 writer.mathSignNumberToInt32Result(numId);
8331 } else {
8332 writer.mathSignNumberResult(numId);
8336 writer.returnFromIC();
8338 trackAttached("MathSign");
8339 return AttachDecision::Attach;
8342 AttachDecision InlinableNativeIRGenerator::tryAttachMathImul() {
8343 // Need two (number) arguments.
8344 if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) {
8345 return AttachDecision::NoAction;
8348 // Initialize the input operand.
8349 initializeInputOperand();
8351 // Guard callee is the 'imul' native function.
8352 emitNativeCalleeGuard();
8354 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8355 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8357 Int32OperandId int32Arg0Id, int32Arg1Id;
8358 if (args_[0].isInt32() && args_[1].isInt32()) {
8359 int32Arg0Id = writer.guardToInt32(arg0Id);
8360 int32Arg1Id = writer.guardToInt32(arg1Id);
8361 } else {
8362 // Treat both arguments as numbers if at least one of them is non-int32.
8363 NumberOperandId numArg0Id = writer.guardIsNumber(arg0Id);
8364 NumberOperandId numArg1Id = writer.guardIsNumber(arg1Id);
8365 int32Arg0Id = writer.truncateDoubleToUInt32(numArg0Id);
8366 int32Arg1Id = writer.truncateDoubleToUInt32(numArg1Id);
8368 writer.mathImulResult(int32Arg0Id, int32Arg1Id);
8369 writer.returnFromIC();
8371 trackAttached("MathImul");
8372 return AttachDecision::Attach;
8375 AttachDecision InlinableNativeIRGenerator::tryAttachMathFloor() {
8376 // Need one (number) argument.
8377 if (argc_ != 1 || !args_[0].isNumber()) {
8378 return AttachDecision::NoAction;
8381 // Check if the result fits in int32.
8382 double res = math_floor_impl(args_[0].toNumber());
8383 int32_t unused;
8384 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
8386 // Initialize the input operand.
8387 initializeInputOperand();
8389 // Guard callee is the 'floor' native function.
8390 emitNativeCalleeGuard();
8392 ValOperandId argumentId =
8393 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8395 if (args_[0].isInt32()) {
8396 MOZ_ASSERT(resultIsInt32);
8398 // Use an indirect truncation to inform the optimizer it needs to preserve
8399 // a bailout when the input can't be represented as an int32, even if the
8400 // final result is fully truncated.
8401 Int32OperandId intId = writer.guardToInt32(argumentId);
8402 writer.indirectTruncateInt32Result(intId);
8403 } else {
8404 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8406 if (resultIsInt32) {
8407 writer.mathFloorToInt32Result(numberId);
8408 } else {
8409 writer.mathFloorNumberResult(numberId);
8413 writer.returnFromIC();
8415 trackAttached("MathFloor");
8416 return AttachDecision::Attach;
8419 AttachDecision InlinableNativeIRGenerator::tryAttachMathCeil() {
8420 // Need one (number) argument.
8421 if (argc_ != 1 || !args_[0].isNumber()) {
8422 return AttachDecision::NoAction;
8425 // Check if the result fits in int32.
8426 double res = math_ceil_impl(args_[0].toNumber());
8427 int32_t unused;
8428 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
8430 // Initialize the input operand.
8431 initializeInputOperand();
8433 // Guard callee is the 'ceil' native function.
8434 emitNativeCalleeGuard();
8436 ValOperandId argumentId =
8437 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8439 if (args_[0].isInt32()) {
8440 MOZ_ASSERT(resultIsInt32);
8442 // Use an indirect truncation to inform the optimizer it needs to preserve
8443 // a bailout when the input can't be represented as an int32, even if the
8444 // final result is fully truncated.
8445 Int32OperandId intId = writer.guardToInt32(argumentId);
8446 writer.indirectTruncateInt32Result(intId);
8447 } else {
8448 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8450 if (resultIsInt32) {
8451 writer.mathCeilToInt32Result(numberId);
8452 } else {
8453 writer.mathCeilNumberResult(numberId);
8457 writer.returnFromIC();
8459 trackAttached("MathCeil");
8460 return AttachDecision::Attach;
8463 AttachDecision InlinableNativeIRGenerator::tryAttachMathTrunc() {
8464 // Need one (number) argument.
8465 if (argc_ != 1 || !args_[0].isNumber()) {
8466 return AttachDecision::NoAction;
8469 // Check if the result fits in int32.
8470 double res = math_trunc_impl(args_[0].toNumber());
8471 int32_t unused;
8472 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
8474 // Initialize the input operand.
8475 initializeInputOperand();
8477 // Guard callee is the 'trunc' native function.
8478 emitNativeCalleeGuard();
8480 ValOperandId argumentId =
8481 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8483 if (args_[0].isInt32()) {
8484 MOZ_ASSERT(resultIsInt32);
8486 // We don't need an indirect truncation barrier here, because Math.trunc
8487 // always truncates, but never rounds its input away from zero.
8488 Int32OperandId intId = writer.guardToInt32(argumentId);
8489 writer.loadInt32Result(intId);
8490 } else {
8491 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8493 if (resultIsInt32) {
8494 writer.mathTruncToInt32Result(numberId);
8495 } else {
8496 writer.mathTruncNumberResult(numberId);
8500 writer.returnFromIC();
8502 trackAttached("MathTrunc");
8503 return AttachDecision::Attach;
8506 AttachDecision InlinableNativeIRGenerator::tryAttachMathRound() {
8507 // Need one (number) argument.
8508 if (argc_ != 1 || !args_[0].isNumber()) {
8509 return AttachDecision::NoAction;
8512 // Check if the result fits in int32.
8513 double res = math_round_impl(args_[0].toNumber());
8514 int32_t unused;
8515 bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
8517 // Initialize the input operand.
8518 initializeInputOperand();
8520 // Guard callee is the 'round' native function.
8521 emitNativeCalleeGuard();
8523 ValOperandId argumentId =
8524 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8526 if (args_[0].isInt32()) {
8527 MOZ_ASSERT(resultIsInt32);
8529 // Use an indirect truncation to inform the optimizer it needs to preserve
8530 // a bailout when the input can't be represented as an int32, even if the
8531 // final result is fully truncated.
8532 Int32OperandId intId = writer.guardToInt32(argumentId);
8533 writer.indirectTruncateInt32Result(intId);
8534 } else {
8535 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8537 if (resultIsInt32) {
8538 writer.mathRoundToInt32Result(numberId);
8539 } else {
8540 writer.mathFunctionNumberResult(numberId, UnaryMathFunction::Round);
8544 writer.returnFromIC();
8546 trackAttached("MathRound");
8547 return AttachDecision::Attach;
8550 AttachDecision InlinableNativeIRGenerator::tryAttachMathSqrt() {
8551 // Need one (number) argument.
8552 if (argc_ != 1 || !args_[0].isNumber()) {
8553 return AttachDecision::NoAction;
8556 // Initialize the input operand.
8557 initializeInputOperand();
8559 // Guard callee is the 'sqrt' native function.
8560 emitNativeCalleeGuard();
8562 ValOperandId argumentId =
8563 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8564 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8565 writer.mathSqrtNumberResult(numberId);
8566 writer.returnFromIC();
8568 trackAttached("MathSqrt");
8569 return AttachDecision::Attach;
8572 AttachDecision InlinableNativeIRGenerator::tryAttachMathFRound() {
8573 // Need one (number) argument.
8574 if (argc_ != 1 || !args_[0].isNumber()) {
8575 return AttachDecision::NoAction;
8578 // Initialize the input operand.
8579 initializeInputOperand();
8581 // Guard callee is the 'fround' native function.
8582 emitNativeCalleeGuard();
8584 ValOperandId argumentId =
8585 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8586 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8587 writer.mathFRoundNumberResult(numberId);
8588 writer.returnFromIC();
8590 trackAttached("MathFRound");
8591 return AttachDecision::Attach;
8594 static bool CanAttachInt32Pow(const Value& baseVal, const Value& powerVal) {
8595 auto valToInt32 = [](const Value& v) {
8596 if (v.isInt32()) {
8597 return v.toInt32();
8599 if (v.isBoolean()) {
8600 return int32_t(v.toBoolean());
8602 MOZ_ASSERT(v.isNull());
8603 return 0;
8605 int32_t base = valToInt32(baseVal);
8606 int32_t power = valToInt32(powerVal);
8608 // x^y where y < 0 is most of the time not an int32, except when x is 1 or y
8609 // gets large enough. It's hard to determine when exactly y is "large enough",
8610 // so we don't use Int32PowResult when x != 1 and y < 0.
8611 // Note: it's important for this condition to match the code generated by
8612 // MacroAssembler::pow32 to prevent failure loops.
8613 if (power < 0) {
8614 return base == 1;
8617 double res = powi(base, power);
8618 int32_t unused;
8619 return mozilla::NumberIsInt32(res, &unused);
8622 AttachDecision InlinableNativeIRGenerator::tryAttachMathPow() {
8623 // Need two number arguments.
8624 if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) {
8625 return AttachDecision::NoAction;
8628 // Initialize the input operand.
8629 initializeInputOperand();
8631 // Guard callee is the 'pow' function.
8632 emitNativeCalleeGuard();
8634 ValOperandId baseId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8635 ValOperandId exponentId =
8636 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8638 if (args_[0].isInt32() && args_[1].isInt32() &&
8639 CanAttachInt32Pow(args_[0], args_[1])) {
8640 Int32OperandId baseInt32Id = writer.guardToInt32(baseId);
8641 Int32OperandId exponentInt32Id = writer.guardToInt32(exponentId);
8642 writer.int32PowResult(baseInt32Id, exponentInt32Id);
8643 } else {
8644 NumberOperandId baseNumberId = writer.guardIsNumber(baseId);
8645 NumberOperandId exponentNumberId = writer.guardIsNumber(exponentId);
8646 writer.doublePowResult(baseNumberId, exponentNumberId);
8649 writer.returnFromIC();
8651 trackAttached("MathPow");
8652 return AttachDecision::Attach;
8655 AttachDecision InlinableNativeIRGenerator::tryAttachMathHypot() {
8656 // Only optimize if there are 2-4 arguments.
8657 if (argc_ < 2 || argc_ > 4) {
8658 return AttachDecision::NoAction;
8661 for (size_t i = 0; i < argc_; i++) {
8662 if (!args_[i].isNumber()) {
8663 return AttachDecision::NoAction;
8667 // Initialize the input operand.
8668 initializeInputOperand();
8670 // Guard callee is the 'hypot' native function.
8671 emitNativeCalleeGuard();
8673 ValOperandId firstId =
8674 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8675 ValOperandId secondId =
8676 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8678 NumberOperandId firstNumId = writer.guardIsNumber(firstId);
8679 NumberOperandId secondNumId = writer.guardIsNumber(secondId);
8681 ValOperandId thirdId;
8682 ValOperandId fourthId;
8683 NumberOperandId thirdNumId;
8684 NumberOperandId fourthNumId;
8686 switch (argc_) {
8687 case 2:
8688 writer.mathHypot2NumberResult(firstNumId, secondNumId);
8689 break;
8690 case 3:
8691 thirdId = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
8692 thirdNumId = writer.guardIsNumber(thirdId);
8693 writer.mathHypot3NumberResult(firstNumId, secondNumId, thirdNumId);
8694 break;
8695 case 4:
8696 thirdId = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
8697 fourthId = writer.loadArgumentFixedSlot(ArgumentKind::Arg3, argc_);
8698 thirdNumId = writer.guardIsNumber(thirdId);
8699 fourthNumId = writer.guardIsNumber(fourthId);
8700 writer.mathHypot4NumberResult(firstNumId, secondNumId, thirdNumId,
8701 fourthNumId);
8702 break;
8703 default:
8704 MOZ_CRASH("Unexpected number of arguments to hypot function.");
8707 writer.returnFromIC();
8709 trackAttached("MathHypot");
8710 return AttachDecision::Attach;
8713 AttachDecision InlinableNativeIRGenerator::tryAttachMathATan2() {
8714 // Requires two numbers as arguments.
8715 if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) {
8716 return AttachDecision::NoAction;
8719 // Initialize the input operand.
8720 initializeInputOperand();
8722 // Guard callee is the 'atan2' native function.
8723 emitNativeCalleeGuard();
8725 ValOperandId yId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8726 ValOperandId xId = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8728 NumberOperandId yNumberId = writer.guardIsNumber(yId);
8729 NumberOperandId xNumberId = writer.guardIsNumber(xId);
8731 writer.mathAtan2NumberResult(yNumberId, xNumberId);
8732 writer.returnFromIC();
8734 trackAttached("MathAtan2");
8735 return AttachDecision::Attach;
8738 AttachDecision InlinableNativeIRGenerator::tryAttachMathMinMax(bool isMax) {
8739 // For now only optimize if there are 1-4 arguments.
8740 if (argc_ < 1 || argc_ > 4) {
8741 return AttachDecision::NoAction;
8744 // Ensure all arguments are numbers.
8745 bool allInt32 = true;
8746 for (size_t i = 0; i < argc_; i++) {
8747 if (!args_[i].isNumber()) {
8748 return AttachDecision::NoAction;
8750 if (!args_[i].isInt32()) {
8751 allInt32 = false;
8755 // Initialize the input operand.
8756 initializeInputOperand();
8758 // Guard callee is this Math function.
8759 emitNativeCalleeGuard();
8761 if (allInt32) {
8762 ValOperandId valId =
8763 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8764 Int32OperandId resId = writer.guardToInt32(valId);
8765 for (size_t i = 1; i < argc_; i++) {
8766 ValOperandId argId =
8767 writer.loadArgumentFixedSlot(ArgumentKindForArgIndex(i), argc_);
8768 Int32OperandId argInt32Id = writer.guardToInt32(argId);
8769 resId = writer.int32MinMax(isMax, resId, argInt32Id);
8771 writer.loadInt32Result(resId);
8772 } else {
8773 ValOperandId valId =
8774 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8775 NumberOperandId resId = writer.guardIsNumber(valId);
8776 for (size_t i = 1; i < argc_; i++) {
8777 ValOperandId argId =
8778 writer.loadArgumentFixedSlot(ArgumentKindForArgIndex(i), argc_);
8779 NumberOperandId argNumId = writer.guardIsNumber(argId);
8780 resId = writer.numberMinMax(isMax, resId, argNumId);
8782 writer.loadDoubleResult(resId);
8785 writer.returnFromIC();
8787 trackAttached(isMax ? "MathMax" : "MathMin");
8788 return AttachDecision::Attach;
8791 AttachDecision InlinableNativeIRGenerator::tryAttachSpreadMathMinMax(
8792 bool isMax) {
8793 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Spread ||
8794 flags_.getArgFormat() == CallFlags::FunApplyArray);
8796 // The result will be an int32 if there is at least one argument,
8797 // and all the arguments are int32.
8798 bool int32Result = args_.length() > 0;
8799 for (size_t i = 0; i < args_.length(); i++) {
8800 if (!args_[i].isNumber()) {
8801 return AttachDecision::NoAction;
8803 if (!args_[i].isInt32()) {
8804 int32Result = false;
8808 // Initialize the input operand.
8809 initializeInputOperand();
8811 // Guard callee is this Math function.
8812 emitNativeCalleeGuard();
8814 // Load the argument array.
8815 ObjOperandId argsId = emitLoadArgsArray();
8817 if (int32Result) {
8818 writer.int32MinMaxArrayResult(argsId, isMax);
8819 } else {
8820 writer.numberMinMaxArrayResult(argsId, isMax);
8823 writer.returnFromIC();
8825 trackAttached(isMax ? "MathMaxArray" : "MathMinArray");
8826 return AttachDecision::Attach;
8829 AttachDecision InlinableNativeIRGenerator::tryAttachMathFunction(
8830 UnaryMathFunction fun) {
8831 // Need one argument.
8832 if (argc_ != 1) {
8833 return AttachDecision::NoAction;
8836 if (!args_[0].isNumber()) {
8837 return AttachDecision::NoAction;
8840 if (math_use_fdlibm_for_sin_cos_tan() ||
8841 callee_->realm()->creationOptions().alwaysUseFdlibm()) {
8842 switch (fun) {
8843 case UnaryMathFunction::SinNative:
8844 fun = UnaryMathFunction::SinFdlibm;
8845 break;
8846 case UnaryMathFunction::CosNative:
8847 fun = UnaryMathFunction::CosFdlibm;
8848 break;
8849 case UnaryMathFunction::TanNative:
8850 fun = UnaryMathFunction::TanFdlibm;
8851 break;
8852 default:
8853 break;
8857 // Initialize the input operand.
8858 initializeInputOperand();
8860 // Guard callee is this Math function.
8861 emitNativeCalleeGuard();
8863 ValOperandId argumentId =
8864 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8865 NumberOperandId numberId = writer.guardIsNumber(argumentId);
8866 writer.mathFunctionNumberResult(numberId, fun);
8867 writer.returnFromIC();
8869 trackAttached("MathFunction");
8870 return AttachDecision::Attach;
8873 AttachDecision InlinableNativeIRGenerator::tryAttachNumber() {
8874 // Expect a single string argument.
8875 if (argc_ != 1 || !args_[0].isString()) {
8876 return AttachDecision::NoAction;
8879 double num;
8880 if (!StringToNumber(cx_, args_[0].toString(), &num)) {
8881 cx_->recoverFromOutOfMemory();
8882 return AttachDecision::NoAction;
8885 // Initialize the input operand.
8886 initializeInputOperand();
8888 // Guard callee is the `Number` function.
8889 emitNativeCalleeGuard();
8891 // Guard that the argument is a string.
8892 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8893 StringOperandId strId = writer.guardToString(argId);
8895 // Return either an Int32 or Double result.
8896 int32_t unused;
8897 if (mozilla::NumberIsInt32(num, &unused)) {
8898 Int32OperandId resultId = writer.guardStringToInt32(strId);
8899 writer.loadInt32Result(resultId);
8900 } else {
8901 NumberOperandId resultId = writer.guardStringToNumber(strId);
8902 writer.loadDoubleResult(resultId);
8904 writer.returnFromIC();
8906 trackAttached("Number");
8907 return AttachDecision::Attach;
8910 AttachDecision InlinableNativeIRGenerator::tryAttachNumberParseInt() {
8911 // Expected arguments: input (string or number), optional radix (int32).
8912 if (argc_ < 1 || argc_ > 2) {
8913 return AttachDecision::NoAction;
8915 if (!args_[0].isString() && !args_[0].isNumber()) {
8916 return AttachDecision::NoAction;
8918 if (args_[0].isDouble()) {
8919 double d = args_[0].toDouble();
8921 // See num_parseInt for why we have to reject numbers smaller than 1.0e-6.
8922 // Negative numbers in the exclusive range (-1, -0) return -0.
8923 bool canTruncateToInt32 =
8924 (DOUBLE_DECIMAL_IN_SHORTEST_LOW <= d && d <= double(INT32_MAX)) ||
8925 (double(INT32_MIN) <= d && d <= -1.0) || (d == 0.0);
8926 if (!canTruncateToInt32) {
8927 return AttachDecision::NoAction;
8930 if (argc_ > 1 && !args_[1].isInt32(10)) {
8931 return AttachDecision::NoAction;
8934 // Initialize the input operand.
8935 initializeInputOperand();
8937 // Guard callee is the 'parseInt' native function.
8938 emitNativeCalleeGuard();
8940 auto guardRadix = [&]() {
8941 ValOperandId radixId =
8942 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
8943 Int32OperandId intRadixId = writer.guardToInt32(radixId);
8944 writer.guardSpecificInt32(intRadixId, 10);
8945 return intRadixId;
8948 ValOperandId inputId =
8949 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
8951 if (args_[0].isString()) {
8952 StringOperandId strId = writer.guardToString(inputId);
8954 Int32OperandId intRadixId;
8955 if (argc_ > 1) {
8956 intRadixId = guardRadix();
8957 } else {
8958 intRadixId = writer.loadInt32Constant(0);
8961 writer.numberParseIntResult(strId, intRadixId);
8962 } else if (args_[0].isInt32()) {
8963 Int32OperandId intId = writer.guardToInt32(inputId);
8964 if (argc_ > 1) {
8965 guardRadix();
8967 writer.loadInt32Result(intId);
8968 } else {
8969 MOZ_ASSERT(args_[0].isDouble());
8971 NumberOperandId numId = writer.guardIsNumber(inputId);
8972 if (argc_ > 1) {
8973 guardRadix();
8975 writer.doubleParseIntResult(numId);
8978 writer.returnFromIC();
8980 trackAttached("NumberParseInt");
8981 return AttachDecision::Attach;
8984 StringOperandId IRGenerator::emitToStringGuard(ValOperandId id,
8985 const Value& v) {
8986 MOZ_ASSERT(CanConvertToString(v));
8987 if (v.isString()) {
8988 return writer.guardToString(id);
8990 if (v.isBoolean()) {
8991 BooleanOperandId boolId = writer.guardToBoolean(id);
8992 return writer.booleanToString(boolId);
8994 if (v.isNull()) {
8995 writer.guardIsNull(id);
8996 return writer.loadConstantString(cx_->names().null);
8998 if (v.isUndefined()) {
8999 writer.guardIsUndefined(id);
9000 return writer.loadConstantString(cx_->names().undefined);
9002 if (v.isInt32()) {
9003 Int32OperandId intId = writer.guardToInt32(id);
9004 return writer.callInt32ToString(intId);
9006 // At this point we are creating an IC that will handle
9007 // both Int32 and Double cases.
9008 MOZ_ASSERT(v.isNumber());
9009 NumberOperandId numId = writer.guardIsNumber(id);
9010 return writer.callNumberToString(numId);
9013 AttachDecision InlinableNativeIRGenerator::tryAttachNumberToString() {
9014 // Expecting no arguments or a single int32 argument.
9015 if (argc_ > 1) {
9016 return AttachDecision::NoAction;
9018 if (argc_ == 1 && !args_[0].isInt32()) {
9019 return AttachDecision::NoAction;
9022 // Ensure |this| is a primitive number value.
9023 if (!thisval_.isNumber()) {
9024 return AttachDecision::NoAction;
9027 // No arguments means base 10.
9028 int32_t base = 10;
9029 if (argc_ > 0) {
9030 base = args_[0].toInt32();
9031 if (base < 2 || base > 36) {
9032 return AttachDecision::NoAction;
9035 // Non-decimal bases currently only support int32 inputs.
9036 if (base != 10 && !thisval_.isInt32()) {
9037 return AttachDecision::NoAction;
9040 MOZ_ASSERT(2 <= base && base <= 36);
9042 // Initialize the input operand.
9043 initializeInputOperand();
9045 // Guard callee is the 'toString' native function.
9046 emitNativeCalleeGuard();
9048 // Initialize the |this| operand.
9049 ValOperandId thisValId =
9050 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
9052 // Guard on number and convert to string.
9053 if (base == 10) {
9054 // If an explicit base was passed, guard its value.
9055 if (argc_ > 0) {
9056 // Guard the `base` argument is an int32.
9057 ValOperandId baseId =
9058 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9059 Int32OperandId intBaseId = writer.guardToInt32(baseId);
9061 // Guard `base` is 10 for decimal toString representation.
9062 writer.guardSpecificInt32(intBaseId, 10);
9065 StringOperandId strId = emitToStringGuard(thisValId, thisval_);
9067 // Return the string.
9068 writer.loadStringResult(strId);
9069 } else {
9070 MOZ_ASSERT(argc_ > 0);
9072 // Guard the |this| value is an int32.
9073 Int32OperandId thisIntId = writer.guardToInt32(thisValId);
9075 // Guard the `base` argument is an int32.
9076 ValOperandId baseId =
9077 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9078 Int32OperandId intBaseId = writer.guardToInt32(baseId);
9080 // Return the string.
9081 writer.int32ToStringWithBaseResult(thisIntId, intBaseId);
9084 writer.returnFromIC();
9086 trackAttached("NumberToString");
9087 return AttachDecision::Attach;
9090 AttachDecision InlinableNativeIRGenerator::tryAttachReflectGetPrototypeOf() {
9091 // Need one argument.
9092 if (argc_ != 1) {
9093 return AttachDecision::NoAction;
9096 if (!args_[0].isObject()) {
9097 return AttachDecision::NoAction;
9100 // Initialize the input operand.
9101 initializeInputOperand();
9103 // Guard callee is the 'getPrototypeOf' native function.
9104 emitNativeCalleeGuard();
9106 ValOperandId argumentId =
9107 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9108 ObjOperandId objId = writer.guardToObject(argumentId);
9110 writer.reflectGetPrototypeOfResult(objId);
9111 writer.returnFromIC();
9113 trackAttached("ReflectGetPrototypeOf");
9114 return AttachDecision::Attach;
9117 static bool AtomicsMeetsPreconditions(TypedArrayObject* typedArray,
9118 const Value& index) {
9119 switch (typedArray->type()) {
9120 case Scalar::Int8:
9121 case Scalar::Uint8:
9122 case Scalar::Int16:
9123 case Scalar::Uint16:
9124 case Scalar::Int32:
9125 case Scalar::Uint32:
9126 case Scalar::BigInt64:
9127 case Scalar::BigUint64:
9128 break;
9130 case Scalar::Float32:
9131 case Scalar::Float64:
9132 case Scalar::Uint8Clamped:
9133 // Exclude floating types and Uint8Clamped.
9134 return false;
9136 case Scalar::MaxTypedArrayViewType:
9137 case Scalar::Int64:
9138 case Scalar::Simd128:
9139 MOZ_CRASH("Unsupported TypedArray type");
9142 // Bounds check the index argument.
9143 int64_t indexInt64;
9144 if (!ValueIsInt64Index(index, &indexInt64)) {
9145 return false;
9147 if (indexInt64 < 0 ||
9148 uint64_t(indexInt64) >= typedArray->length().valueOr(0)) {
9149 return false;
9152 return true;
9155 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsCompareExchange() {
9156 if (!JitSupportsAtomics()) {
9157 return AttachDecision::NoAction;
9160 // Need four arguments.
9161 if (argc_ != 4) {
9162 return AttachDecision::NoAction;
9165 // Arguments: typedArray, index (number), expected, replacement.
9166 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
9167 return AttachDecision::NoAction;
9169 if (!args_[1].isNumber()) {
9170 return AttachDecision::NoAction;
9173 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9174 if (!AtomicsMeetsPreconditions(typedArray, args_[1])) {
9175 return AttachDecision::NoAction;
9178 Scalar::Type elementType = typedArray->type();
9179 if (!ValueCanConvertToNumeric(elementType, args_[2])) {
9180 return AttachDecision::NoAction;
9182 if (!ValueCanConvertToNumeric(elementType, args_[3])) {
9183 return AttachDecision::NoAction;
9186 // Initialize the input operand.
9187 initializeInputOperand();
9189 // Guard callee is the `compareExchange` native function.
9190 emitNativeCalleeGuard();
9192 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9193 ObjOperandId objId = writer.guardToObject(arg0Id);
9194 writer.guardShapeForClass(objId, typedArray->shape());
9196 // Convert index to intPtr.
9197 ValOperandId indexId =
9198 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9199 IntPtrOperandId intPtrIndexId =
9200 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false);
9202 // Convert expected value to int32/BigInt.
9203 ValOperandId expectedId =
9204 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
9205 OperandId numericExpectedId =
9206 emitNumericGuard(expectedId, args_[2], elementType);
9208 // Convert replacement value to int32/BigInt.
9209 ValOperandId replacementId =
9210 writer.loadArgumentFixedSlot(ArgumentKind::Arg3, argc_);
9211 OperandId numericReplacementId =
9212 emitNumericGuard(replacementId, args_[3], elementType);
9214 auto viewKind = ToArrayBufferViewKind(typedArray);
9215 writer.atomicsCompareExchangeResult(objId, intPtrIndexId, numericExpectedId,
9216 numericReplacementId, typedArray->type(),
9217 viewKind);
9218 writer.returnFromIC();
9220 trackAttached("AtomicsCompareExchange");
9221 return AttachDecision::Attach;
9224 bool InlinableNativeIRGenerator::canAttachAtomicsReadWriteModify() {
9225 if (!JitSupportsAtomics()) {
9226 return false;
9229 // Need three arguments.
9230 if (argc_ != 3) {
9231 return false;
9234 // Arguments: typedArray, index (number), value.
9235 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
9236 return false;
9238 if (!args_[1].isNumber()) {
9239 return false;
9242 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9243 if (!AtomicsMeetsPreconditions(typedArray, args_[1])) {
9244 return false;
9246 if (!ValueCanConvertToNumeric(typedArray->type(), args_[2])) {
9247 return false;
9249 return true;
9252 InlinableNativeIRGenerator::AtomicsReadWriteModifyOperands
9253 InlinableNativeIRGenerator::emitAtomicsReadWriteModifyOperands() {
9254 MOZ_ASSERT(canAttachAtomicsReadWriteModify());
9256 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9258 // Initialize the input operand.
9259 initializeInputOperand();
9261 // Guard callee is this Atomics function.
9262 emitNativeCalleeGuard();
9264 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9265 ObjOperandId objId = writer.guardToObject(arg0Id);
9266 writer.guardShapeForClass(objId, typedArray->shape());
9268 // Convert index to intPtr.
9269 ValOperandId indexId =
9270 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9271 IntPtrOperandId intPtrIndexId =
9272 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false);
9274 // Convert value to int32/BigInt.
9275 ValOperandId valueId =
9276 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
9277 OperandId numericValueId =
9278 emitNumericGuard(valueId, args_[2], typedArray->type());
9280 return {objId, intPtrIndexId, numericValueId};
9283 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsExchange() {
9284 if (!canAttachAtomicsReadWriteModify()) {
9285 return AttachDecision::NoAction;
9288 auto [objId, intPtrIndexId, numericValueId] =
9289 emitAtomicsReadWriteModifyOperands();
9291 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9292 auto viewKind = ToArrayBufferViewKind(typedArray);
9294 writer.atomicsExchangeResult(objId, intPtrIndexId, numericValueId,
9295 typedArray->type(), viewKind);
9296 writer.returnFromIC();
9298 trackAttached("AtomicsExchange");
9299 return AttachDecision::Attach;
9302 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsAdd() {
9303 if (!canAttachAtomicsReadWriteModify()) {
9304 return AttachDecision::NoAction;
9307 auto [objId, intPtrIndexId, numericValueId] =
9308 emitAtomicsReadWriteModifyOperands();
9310 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9311 bool forEffect = ignoresResult();
9312 auto viewKind = ToArrayBufferViewKind(typedArray);
9314 writer.atomicsAddResult(objId, intPtrIndexId, numericValueId,
9315 typedArray->type(), forEffect, viewKind);
9316 writer.returnFromIC();
9318 trackAttached("AtomicsAdd");
9319 return AttachDecision::Attach;
9322 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsSub() {
9323 if (!canAttachAtomicsReadWriteModify()) {
9324 return AttachDecision::NoAction;
9327 auto [objId, intPtrIndexId, numericValueId] =
9328 emitAtomicsReadWriteModifyOperands();
9330 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9331 bool forEffect = ignoresResult();
9332 auto viewKind = ToArrayBufferViewKind(typedArray);
9334 writer.atomicsSubResult(objId, intPtrIndexId, numericValueId,
9335 typedArray->type(), forEffect, viewKind);
9336 writer.returnFromIC();
9338 trackAttached("AtomicsSub");
9339 return AttachDecision::Attach;
9342 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsAnd() {
9343 if (!canAttachAtomicsReadWriteModify()) {
9344 return AttachDecision::NoAction;
9347 auto [objId, intPtrIndexId, numericValueId] =
9348 emitAtomicsReadWriteModifyOperands();
9350 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9351 bool forEffect = ignoresResult();
9352 auto viewKind = ToArrayBufferViewKind(typedArray);
9354 writer.atomicsAndResult(objId, intPtrIndexId, numericValueId,
9355 typedArray->type(), forEffect, viewKind);
9356 writer.returnFromIC();
9358 trackAttached("AtomicsAnd");
9359 return AttachDecision::Attach;
9362 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsOr() {
9363 if (!canAttachAtomicsReadWriteModify()) {
9364 return AttachDecision::NoAction;
9367 auto [objId, intPtrIndexId, numericValueId] =
9368 emitAtomicsReadWriteModifyOperands();
9370 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9371 bool forEffect = ignoresResult();
9372 auto viewKind = ToArrayBufferViewKind(typedArray);
9374 writer.atomicsOrResult(objId, intPtrIndexId, numericValueId,
9375 typedArray->type(), forEffect, viewKind);
9376 writer.returnFromIC();
9378 trackAttached("AtomicsOr");
9379 return AttachDecision::Attach;
9382 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsXor() {
9383 if (!canAttachAtomicsReadWriteModify()) {
9384 return AttachDecision::NoAction;
9387 auto [objId, intPtrIndexId, numericValueId] =
9388 emitAtomicsReadWriteModifyOperands();
9390 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9391 bool forEffect = ignoresResult();
9392 auto viewKind = ToArrayBufferViewKind(typedArray);
9394 writer.atomicsXorResult(objId, intPtrIndexId, numericValueId,
9395 typedArray->type(), forEffect, viewKind);
9396 writer.returnFromIC();
9398 trackAttached("AtomicsXor");
9399 return AttachDecision::Attach;
9402 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsLoad() {
9403 if (!JitSupportsAtomics()) {
9404 return AttachDecision::NoAction;
9407 // Need two arguments.
9408 if (argc_ != 2) {
9409 return AttachDecision::NoAction;
9412 // Arguments: typedArray, index (number).
9413 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
9414 return AttachDecision::NoAction;
9416 if (!args_[1].isNumber()) {
9417 return AttachDecision::NoAction;
9420 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9421 if (!AtomicsMeetsPreconditions(typedArray, args_[1])) {
9422 return AttachDecision::NoAction;
9425 // Initialize the input operand.
9426 initializeInputOperand();
9428 // Guard callee is the `load` native function.
9429 emitNativeCalleeGuard();
9431 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9432 ObjOperandId objId = writer.guardToObject(arg0Id);
9433 writer.guardShapeForClass(objId, typedArray->shape());
9435 // Convert index to intPtr.
9436 ValOperandId indexId =
9437 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9438 IntPtrOperandId intPtrIndexId =
9439 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false);
9441 auto viewKind = ToArrayBufferViewKind(typedArray);
9442 writer.atomicsLoadResult(objId, intPtrIndexId, typedArray->type(), viewKind);
9443 writer.returnFromIC();
9445 trackAttached("AtomicsLoad");
9446 return AttachDecision::Attach;
9449 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsStore() {
9450 if (!JitSupportsAtomics()) {
9451 return AttachDecision::NoAction;
9454 // Need three arguments.
9455 if (argc_ != 3) {
9456 return AttachDecision::NoAction;
9459 // Atomics.store() is annoying because it returns the result of converting the
9460 // value by ToInteger(), not the input value, nor the result of converting the
9461 // value by ToInt32(). It is especially annoying because almost nobody uses
9462 // the result value.
9464 // As an expedient compromise, therefore, we inline only if the result is
9465 // obviously unused or if the argument is already Int32 and thus requires no
9466 // conversion.
9468 // Arguments: typedArray, index (number), value.
9469 if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
9470 return AttachDecision::NoAction;
9472 if (!args_[1].isNumber()) {
9473 return AttachDecision::NoAction;
9476 auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
9477 if (!AtomicsMeetsPreconditions(typedArray, args_[1])) {
9478 return AttachDecision::NoAction;
9481 Scalar::Type elementType = typedArray->type();
9482 if (!ValueCanConvertToNumeric(elementType, args_[2])) {
9483 return AttachDecision::NoAction;
9486 bool guardIsInt32 = !Scalar::isBigIntType(elementType) && !ignoresResult();
9488 if (guardIsInt32 && !args_[2].isInt32()) {
9489 return AttachDecision::NoAction;
9492 // Initialize the input operand.
9493 initializeInputOperand();
9495 // Guard callee is the `store` native function.
9496 emitNativeCalleeGuard();
9498 ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9499 ObjOperandId objId = writer.guardToObject(arg0Id);
9500 writer.guardShapeForClass(objId, typedArray->shape());
9502 // Convert index to intPtr.
9503 ValOperandId indexId =
9504 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9505 IntPtrOperandId intPtrIndexId =
9506 guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false);
9508 // Ensure value is int32 or BigInt.
9509 ValOperandId valueId =
9510 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
9511 OperandId numericValueId;
9512 if (guardIsInt32) {
9513 numericValueId = writer.guardToInt32(valueId);
9514 } else {
9515 numericValueId = emitNumericGuard(valueId, args_[2], elementType);
9518 auto viewKind = ToArrayBufferViewKind(typedArray);
9519 writer.atomicsStoreResult(objId, intPtrIndexId, numericValueId,
9520 typedArray->type(), viewKind);
9521 writer.returnFromIC();
9523 trackAttached("AtomicsStore");
9524 return AttachDecision::Attach;
9527 AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsIsLockFree() {
9528 // Need one argument.
9529 if (argc_ != 1) {
9530 return AttachDecision::NoAction;
9533 if (!args_[0].isInt32()) {
9534 return AttachDecision::NoAction;
9537 // Initialize the input operand.
9538 initializeInputOperand();
9540 // Guard callee is the `isLockFree` native function.
9541 emitNativeCalleeGuard();
9543 // Ensure value is int32.
9544 ValOperandId valueId =
9545 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9546 Int32OperandId int32ValueId = writer.guardToInt32(valueId);
9548 writer.atomicsIsLockFreeResult(int32ValueId);
9549 writer.returnFromIC();
9551 trackAttached("AtomicsIsLockFree");
9552 return AttachDecision::Attach;
9555 AttachDecision InlinableNativeIRGenerator::tryAttachBoolean() {
9556 // Need zero or one argument.
9557 if (argc_ > 1) {
9558 return AttachDecision::NoAction;
9561 // Initialize the input operand.
9562 initializeInputOperand();
9564 // Guard callee is the 'Boolean' native function.
9565 emitNativeCalleeGuard();
9567 if (argc_ == 0) {
9568 writer.loadBooleanResult(false);
9569 } else {
9570 ValOperandId valId =
9571 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9573 writer.loadValueTruthyResult(valId);
9576 writer.returnFromIC();
9578 trackAttached("Boolean");
9579 return AttachDecision::Attach;
9582 AttachDecision InlinableNativeIRGenerator::tryAttachBailout() {
9583 // Expecting no arguments.
9584 if (argc_ != 0) {
9585 return AttachDecision::NoAction;
9588 // Initialize the input operand.
9589 initializeInputOperand();
9591 // Guard callee is the 'bailout' native function.
9592 emitNativeCalleeGuard();
9594 writer.bailout();
9595 writer.loadUndefinedResult();
9596 writer.returnFromIC();
9598 trackAttached("Bailout");
9599 return AttachDecision::Attach;
9602 AttachDecision InlinableNativeIRGenerator::tryAttachAssertFloat32() {
9603 // Expecting two arguments.
9604 if (argc_ != 2) {
9605 return AttachDecision::NoAction;
9608 // Initialize the input operand.
9609 initializeInputOperand();
9611 // Guard callee is the 'assertFloat32' native function.
9612 emitNativeCalleeGuard();
9614 // TODO: Warp doesn't yet optimize Float32 (bug 1655773).
9616 // NOP when not in IonMonkey.
9617 writer.loadUndefinedResult();
9618 writer.returnFromIC();
9620 trackAttached("AssertFloat32");
9621 return AttachDecision::Attach;
9624 AttachDecision InlinableNativeIRGenerator::tryAttachAssertRecoveredOnBailout() {
9625 // Expecting two arguments.
9626 if (argc_ != 2) {
9627 return AttachDecision::NoAction;
9630 // (Fuzzing unsafe) testing function which must be called with a constant
9631 // boolean as its second argument.
9632 bool mustBeRecovered = args_[1].toBoolean();
9634 // Initialize the input operand.
9635 initializeInputOperand();
9637 // Guard callee is the 'assertRecoveredOnBailout' native function.
9638 emitNativeCalleeGuard();
9640 ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9642 writer.assertRecoveredOnBailoutResult(valId, mustBeRecovered);
9643 writer.returnFromIC();
9645 trackAttached("AssertRecoveredOnBailout");
9646 return AttachDecision::Attach;
9649 AttachDecision InlinableNativeIRGenerator::tryAttachObjectIs() {
9650 // Need two arguments.
9651 if (argc_ != 2) {
9652 return AttachDecision::NoAction;
9655 // Initialize the input operand.
9656 initializeInputOperand();
9658 // Guard callee is the `is` native function.
9659 emitNativeCalleeGuard();
9661 ValOperandId lhsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9662 ValOperandId rhsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9664 HandleValue lhs = args_[0];
9665 HandleValue rhs = args_[1];
9667 if (!isFirstStub()) {
9668 writer.sameValueResult(lhsId, rhsId);
9669 } else if (lhs.isNumber() && rhs.isNumber() &&
9670 !(lhs.isInt32() && rhs.isInt32())) {
9671 NumberOperandId lhsNumId = writer.guardIsNumber(lhsId);
9672 NumberOperandId rhsNumId = writer.guardIsNumber(rhsId);
9673 writer.compareDoubleSameValueResult(lhsNumId, rhsNumId);
9674 } else if (!SameType(lhs, rhs)) {
9675 // Compare tags for strictly different types.
9676 ValueTagOperandId lhsTypeId = writer.loadValueTag(lhsId);
9677 ValueTagOperandId rhsTypeId = writer.loadValueTag(rhsId);
9678 writer.guardTagNotEqual(lhsTypeId, rhsTypeId);
9679 writer.loadBooleanResult(false);
9680 } else {
9681 MOZ_ASSERT(lhs.type() == rhs.type());
9682 MOZ_ASSERT(lhs.type() != JS::ValueType::Double);
9684 switch (lhs.type()) {
9685 case JS::ValueType::Int32: {
9686 Int32OperandId lhsIntId = writer.guardToInt32(lhsId);
9687 Int32OperandId rhsIntId = writer.guardToInt32(rhsId);
9688 writer.compareInt32Result(JSOp::StrictEq, lhsIntId, rhsIntId);
9689 break;
9691 case JS::ValueType::Boolean: {
9692 Int32OperandId lhsIntId = writer.guardBooleanToInt32(lhsId);
9693 Int32OperandId rhsIntId = writer.guardBooleanToInt32(rhsId);
9694 writer.compareInt32Result(JSOp::StrictEq, lhsIntId, rhsIntId);
9695 break;
9697 case JS::ValueType::Undefined: {
9698 writer.guardIsUndefined(lhsId);
9699 writer.guardIsUndefined(rhsId);
9700 writer.loadBooleanResult(true);
9701 break;
9703 case JS::ValueType::Null: {
9704 writer.guardIsNull(lhsId);
9705 writer.guardIsNull(rhsId);
9706 writer.loadBooleanResult(true);
9707 break;
9709 case JS::ValueType::String: {
9710 StringOperandId lhsStrId = writer.guardToString(lhsId);
9711 StringOperandId rhsStrId = writer.guardToString(rhsId);
9712 writer.compareStringResult(JSOp::StrictEq, lhsStrId, rhsStrId);
9713 break;
9715 case JS::ValueType::Symbol: {
9716 SymbolOperandId lhsSymId = writer.guardToSymbol(lhsId);
9717 SymbolOperandId rhsSymId = writer.guardToSymbol(rhsId);
9718 writer.compareSymbolResult(JSOp::StrictEq, lhsSymId, rhsSymId);
9719 break;
9721 case JS::ValueType::BigInt: {
9722 BigIntOperandId lhsBigIntId = writer.guardToBigInt(lhsId);
9723 BigIntOperandId rhsBigIntId = writer.guardToBigInt(rhsId);
9724 writer.compareBigIntResult(JSOp::StrictEq, lhsBigIntId, rhsBigIntId);
9725 break;
9727 case JS::ValueType::Object: {
9728 ObjOperandId lhsObjId = writer.guardToObject(lhsId);
9729 ObjOperandId rhsObjId = writer.guardToObject(rhsId);
9730 writer.compareObjectResult(JSOp::StrictEq, lhsObjId, rhsObjId);
9731 break;
9734 #ifdef ENABLE_RECORD_TUPLE
9735 case ValueType::ExtendedPrimitive:
9736 #endif
9737 case JS::ValueType::Double:
9738 case JS::ValueType::Magic:
9739 case JS::ValueType::PrivateGCThing:
9740 MOZ_CRASH("Unexpected type");
9744 writer.returnFromIC();
9746 trackAttached("ObjectIs");
9747 return AttachDecision::Attach;
9750 AttachDecision InlinableNativeIRGenerator::tryAttachObjectIsPrototypeOf() {
9751 // Ensure |this| is an object.
9752 if (!thisval_.isObject()) {
9753 return AttachDecision::NoAction;
9756 // Need a single argument.
9757 if (argc_ != 1) {
9758 return AttachDecision::NoAction;
9761 // Initialize the input operand.
9762 initializeInputOperand();
9764 // Guard callee is the `isPrototypeOf` native function.
9765 emitNativeCalleeGuard();
9767 // Guard that |this| is an object.
9768 ValOperandId thisValId =
9769 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
9770 ObjOperandId thisObjId = writer.guardToObject(thisValId);
9772 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9774 writer.loadInstanceOfObjectResult(argId, thisObjId);
9775 writer.returnFromIC();
9777 trackAttached("ObjectIsPrototypeOf");
9778 return AttachDecision::Attach;
9781 AttachDecision InlinableNativeIRGenerator::tryAttachObjectKeys() {
9782 // Only handle argc <= 1.
9783 if (argc_ != 1) {
9784 return AttachDecision::NoAction;
9787 // Do not attach any IC if the argument is not an object.
9788 if (!args_[0].isObject()) {
9789 return AttachDecision::NoAction;
9791 // Do not attach any IC if the argument is a Proxy. While implementation could
9792 // work with proxies the goal of this implementation is to provide an
9793 // optimization for calls of `Object.keys(obj)` where there is no side-effect,
9794 // and where the computation of the array of property name can be moved.
9795 const JSClass* clasp = args_[0].toObject().getClass();
9796 if (clasp->isProxyObject()) {
9797 return AttachDecision::NoAction;
9800 // Generate cache IR code to attach a new inline cache which will delegate the
9801 // call to Object.keys to the native function.
9802 initializeInputOperand();
9804 // Guard callee is the 'keys' native function.
9805 emitNativeCalleeGuard();
9807 // Implicit: Note `Object.keys` is a property of the `Object` global. The fact
9808 // that we are in this function implies that we already identify the function
9809 // as being the proper one. Thus there should not be any need to validate that
9810 // this is the proper function. (test: ion/object-keys-05)
9812 // Guard `arg0` is an object.
9813 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9814 ObjOperandId argObjId = writer.guardToObject(argId);
9816 // Guard against proxies.
9817 writer.guardIsNotProxy(argObjId);
9819 // Compute the keys array.
9820 writer.objectKeysResult(argObjId);
9822 writer.returnFromIC();
9824 trackAttached("ObjectKeys");
9825 return AttachDecision::Attach;
9828 AttachDecision InlinableNativeIRGenerator::tryAttachObjectToString() {
9829 // Expecting no arguments.
9830 if (argc_ != 0) {
9831 return AttachDecision::NoAction;
9834 // Ensure |this| is an object.
9835 if (!thisval_.isObject()) {
9836 return AttachDecision::NoAction;
9839 // Don't attach if the object has @@toStringTag or is a proxy.
9840 if (!ObjectClassToString(cx_, &thisval_.toObject())) {
9841 return AttachDecision::NoAction;
9844 // Initialize the input operand.
9845 initializeInputOperand();
9847 // Guard callee is the 'toString' native function.
9848 emitNativeCalleeGuard();
9850 // Guard that |this| is an object.
9851 ValOperandId thisValId =
9852 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
9853 ObjOperandId thisObjId = writer.guardToObject(thisValId);
9855 writer.objectToStringResult(thisObjId);
9856 writer.returnFromIC();
9858 trackAttached("ObjectToString");
9859 return AttachDecision::Attach;
9862 AttachDecision InlinableNativeIRGenerator::tryAttachBigIntAsIntN() {
9863 // Need two arguments (Int32, BigInt).
9864 if (argc_ != 2 || !args_[0].isInt32() || !args_[1].isBigInt()) {
9865 return AttachDecision::NoAction;
9868 // Negative bits throws an error.
9869 if (args_[0].toInt32() < 0) {
9870 return AttachDecision::NoAction;
9873 // Initialize the input operand.
9874 initializeInputOperand();
9876 // Guard callee is the 'BigInt.asIntN' native function.
9877 emitNativeCalleeGuard();
9879 // Convert bits to int32.
9880 ValOperandId bitsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9881 Int32OperandId int32BitsId = writer.guardToInt32Index(bitsId);
9883 // Number of bits mustn't be negative.
9884 writer.guardInt32IsNonNegative(int32BitsId);
9886 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9887 BigIntOperandId bigIntId = writer.guardToBigInt(arg1Id);
9889 writer.bigIntAsIntNResult(int32BitsId, bigIntId);
9890 writer.returnFromIC();
9892 trackAttached("BigIntAsIntN");
9893 return AttachDecision::Attach;
9896 AttachDecision InlinableNativeIRGenerator::tryAttachBigIntAsUintN() {
9897 // Need two arguments (Int32, BigInt).
9898 if (argc_ != 2 || !args_[0].isInt32() || !args_[1].isBigInt()) {
9899 return AttachDecision::NoAction;
9902 // Negative bits throws an error.
9903 if (args_[0].toInt32() < 0) {
9904 return AttachDecision::NoAction;
9907 // Initialize the input operand.
9908 initializeInputOperand();
9910 // Guard callee is the 'BigInt.asUintN' native function.
9911 emitNativeCalleeGuard();
9913 // Convert bits to int32.
9914 ValOperandId bitsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9915 Int32OperandId int32BitsId = writer.guardToInt32Index(bitsId);
9917 // Number of bits mustn't be negative.
9918 writer.guardInt32IsNonNegative(int32BitsId);
9920 ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
9921 BigIntOperandId bigIntId = writer.guardToBigInt(arg1Id);
9923 writer.bigIntAsUintNResult(int32BitsId, bigIntId);
9924 writer.returnFromIC();
9926 trackAttached("BigIntAsUintN");
9927 return AttachDecision::Attach;
9930 AttachDecision InlinableNativeIRGenerator::tryAttachSetHas() {
9931 // Ensure |this| is a SetObject.
9932 if (!thisval_.isObject() || !thisval_.toObject().is<SetObject>()) {
9933 return AttachDecision::NoAction;
9936 // Need a single argument.
9937 if (argc_ != 1) {
9938 return AttachDecision::NoAction;
9941 // Initialize the input operand.
9942 initializeInputOperand();
9944 // Guard callee is the 'has' native function.
9945 emitNativeCalleeGuard();
9947 // Guard |this| is a SetObject.
9948 ValOperandId thisValId =
9949 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
9950 ObjOperandId objId = writer.guardToObject(thisValId);
9951 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Set);
9953 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
9955 #ifndef JS_CODEGEN_X86
9956 // Assume the hash key will likely always have the same type when attaching
9957 // the first stub. If the call is polymorphic on the hash key, attach a stub
9958 // which handles any value.
9959 if (isFirstStub()) {
9960 switch (args_[0].type()) {
9961 case ValueType::Double:
9962 case ValueType::Int32:
9963 case ValueType::Boolean:
9964 case ValueType::Undefined:
9965 case ValueType::Null: {
9966 writer.guardToNonGCThing(argId);
9967 writer.setHasNonGCThingResult(objId, argId);
9968 break;
9970 case ValueType::String: {
9971 StringOperandId strId = writer.guardToString(argId);
9972 writer.setHasStringResult(objId, strId);
9973 break;
9975 case ValueType::Symbol: {
9976 SymbolOperandId symId = writer.guardToSymbol(argId);
9977 writer.setHasSymbolResult(objId, symId);
9978 break;
9980 case ValueType::BigInt: {
9981 BigIntOperandId bigIntId = writer.guardToBigInt(argId);
9982 writer.setHasBigIntResult(objId, bigIntId);
9983 break;
9985 case ValueType::Object: {
9986 // Currently only supported on 64-bit platforms.
9987 # ifdef JS_PUNBOX64
9988 ObjOperandId valId = writer.guardToObject(argId);
9989 writer.setHasObjectResult(objId, valId);
9990 # else
9991 writer.setHasResult(objId, argId);
9992 # endif
9993 break;
9996 # ifdef ENABLE_RECORD_TUPLE
9997 case ValueType::ExtendedPrimitive:
9998 # endif
9999 case ValueType::Magic:
10000 case ValueType::PrivateGCThing:
10001 MOZ_CRASH("Unexpected type");
10003 } else {
10004 writer.setHasResult(objId, argId);
10006 #else
10007 // The optimized versions require too many registers on x86.
10008 writer.setHasResult(objId, argId);
10009 #endif
10011 writer.returnFromIC();
10013 trackAttached("SetHas");
10014 return AttachDecision::Attach;
10017 AttachDecision InlinableNativeIRGenerator::tryAttachSetSize() {
10018 // Ensure |this| is a SetObject.
10019 if (!thisval_.isObject() || !thisval_.toObject().is<SetObject>()) {
10020 return AttachDecision::NoAction;
10023 // Expecting no arguments.
10024 if (argc_ != 0) {
10025 return AttachDecision::NoAction;
10028 // Initialize the input operand.
10029 initializeInputOperand();
10031 // Guard callee is the 'size' native function.
10032 emitNativeCalleeGuard();
10034 // Guard |this| is a SetObject.
10035 ValOperandId thisValId =
10036 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
10037 ObjOperandId objId = writer.guardToObject(thisValId);
10038 writer.guardClass(objId, GuardClassKind::Set);
10040 writer.setSizeResult(objId);
10041 writer.returnFromIC();
10043 trackAttached("SetSize");
10044 return AttachDecision::Attach;
10047 AttachDecision InlinableNativeIRGenerator::tryAttachMapHas() {
10048 // Ensure |this| is a MapObject.
10049 if (!thisval_.isObject() || !thisval_.toObject().is<MapObject>()) {
10050 return AttachDecision::NoAction;
10053 // Need a single argument.
10054 if (argc_ != 1) {
10055 return AttachDecision::NoAction;
10058 // Initialize the input operand.
10059 initializeInputOperand();
10061 // Guard callee is the 'has' native function.
10062 emitNativeCalleeGuard();
10064 // Guard |this| is a MapObject.
10065 ValOperandId thisValId =
10066 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
10067 ObjOperandId objId = writer.guardToObject(thisValId);
10068 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Map);
10070 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10072 #ifndef JS_CODEGEN_X86
10073 // Assume the hash key will likely always have the same type when attaching
10074 // the first stub. If the call is polymorphic on the hash key, attach a stub
10075 // which handles any value.
10076 if (isFirstStub()) {
10077 switch (args_[0].type()) {
10078 case ValueType::Double:
10079 case ValueType::Int32:
10080 case ValueType::Boolean:
10081 case ValueType::Undefined:
10082 case ValueType::Null: {
10083 writer.guardToNonGCThing(argId);
10084 writer.mapHasNonGCThingResult(objId, argId);
10085 break;
10087 case ValueType::String: {
10088 StringOperandId strId = writer.guardToString(argId);
10089 writer.mapHasStringResult(objId, strId);
10090 break;
10092 case ValueType::Symbol: {
10093 SymbolOperandId symId = writer.guardToSymbol(argId);
10094 writer.mapHasSymbolResult(objId, symId);
10095 break;
10097 case ValueType::BigInt: {
10098 BigIntOperandId bigIntId = writer.guardToBigInt(argId);
10099 writer.mapHasBigIntResult(objId, bigIntId);
10100 break;
10102 case ValueType::Object: {
10103 // Currently only supported on 64-bit platforms.
10104 # ifdef JS_PUNBOX64
10105 ObjOperandId valId = writer.guardToObject(argId);
10106 writer.mapHasObjectResult(objId, valId);
10107 # else
10108 writer.mapHasResult(objId, argId);
10109 # endif
10110 break;
10113 # ifdef ENABLE_RECORD_TUPLE
10114 case ValueType::ExtendedPrimitive:
10115 # endif
10116 case ValueType::Magic:
10117 case ValueType::PrivateGCThing:
10118 MOZ_CRASH("Unexpected type");
10120 } else {
10121 writer.mapHasResult(objId, argId);
10123 #else
10124 // The optimized versions require too many registers on x86.
10125 writer.mapHasResult(objId, argId);
10126 #endif
10128 writer.returnFromIC();
10130 trackAttached("MapHas");
10131 return AttachDecision::Attach;
10134 AttachDecision InlinableNativeIRGenerator::tryAttachMapGet() {
10135 // Ensure |this| is a MapObject.
10136 if (!thisval_.isObject() || !thisval_.toObject().is<MapObject>()) {
10137 return AttachDecision::NoAction;
10140 // Need a single argument.
10141 if (argc_ != 1) {
10142 return AttachDecision::NoAction;
10145 // Initialize the input operand.
10146 initializeInputOperand();
10148 // Guard callee is the 'get' native function.
10149 emitNativeCalleeGuard();
10151 // Guard |this| is a MapObject.
10152 ValOperandId thisValId =
10153 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
10154 ObjOperandId objId = writer.guardToObject(thisValId);
10155 emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Map);
10157 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10159 #ifndef JS_CODEGEN_X86
10160 // Assume the hash key will likely always have the same type when attaching
10161 // the first stub. If the call is polymorphic on the hash key, attach a stub
10162 // which handles any value.
10163 if (isFirstStub()) {
10164 switch (args_[0].type()) {
10165 case ValueType::Double:
10166 case ValueType::Int32:
10167 case ValueType::Boolean:
10168 case ValueType::Undefined:
10169 case ValueType::Null: {
10170 writer.guardToNonGCThing(argId);
10171 writer.mapGetNonGCThingResult(objId, argId);
10172 break;
10174 case ValueType::String: {
10175 StringOperandId strId = writer.guardToString(argId);
10176 writer.mapGetStringResult(objId, strId);
10177 break;
10179 case ValueType::Symbol: {
10180 SymbolOperandId symId = writer.guardToSymbol(argId);
10181 writer.mapGetSymbolResult(objId, symId);
10182 break;
10184 case ValueType::BigInt: {
10185 BigIntOperandId bigIntId = writer.guardToBigInt(argId);
10186 writer.mapGetBigIntResult(objId, bigIntId);
10187 break;
10189 case ValueType::Object: {
10190 // Currently only supported on 64-bit platforms.
10191 # ifdef JS_PUNBOX64
10192 ObjOperandId valId = writer.guardToObject(argId);
10193 writer.mapGetObjectResult(objId, valId);
10194 # else
10195 writer.mapGetResult(objId, argId);
10196 # endif
10197 break;
10200 # ifdef ENABLE_RECORD_TUPLE
10201 case ValueType::ExtendedPrimitive:
10202 # endif
10203 case ValueType::Magic:
10204 case ValueType::PrivateGCThing:
10205 MOZ_CRASH("Unexpected type");
10207 } else {
10208 writer.mapGetResult(objId, argId);
10210 #else
10211 // The optimized versions require too many registers on x86.
10212 writer.mapGetResult(objId, argId);
10213 #endif
10215 writer.returnFromIC();
10217 trackAttached("MapGet");
10218 return AttachDecision::Attach;
10221 AttachDecision CallIRGenerator::tryAttachFunCall(HandleFunction callee) {
10222 MOZ_ASSERT(callee->isNativeWithoutJitEntry());
10224 if (callee->native() != fun_call) {
10225 return AttachDecision::NoAction;
10228 if (!thisval_.isObject() || !thisval_.toObject().is<JSFunction>()) {
10229 return AttachDecision::NoAction;
10231 RootedFunction target(cx_, &thisval_.toObject().as<JSFunction>());
10233 bool isScripted = target->hasJitEntry();
10234 MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry());
10236 if (target->isClassConstructor()) {
10237 return AttachDecision::NoAction;
10239 Int32OperandId argcId(writer.setInputOperandId(0));
10241 CallFlags targetFlags(CallFlags::FunCall);
10242 if (mode_ == ICState::Mode::Specialized) {
10243 if (cx_->realm() == target->realm()) {
10244 targetFlags.setIsSameRealm();
10248 if (mode_ == ICState::Mode::Specialized && !isScripted && argc_ > 0) {
10249 // The stack layout is already in the correct form for calls with at least
10250 // one argument.
10252 // clang-format off
10254 // *** STACK LAYOUT (bottom to top) *** *** INDEX ***
10255 // Callee <-- argc+1
10256 // ThisValue <-- argc
10257 // Args: | Arg0 | <-- argc-1
10258 // | Arg1 | <-- argc-2
10259 // | ... | <-- ...
10260 // | ArgN | <-- 0
10262 // When passing |argc-1| as the number of arguments, we get:
10264 // *** STACK LAYOUT (bottom to top) *** *** INDEX ***
10265 // Callee <-- (argc-1)+1 = argc = ThisValue
10266 // ThisValue <-- (argc-1) = argc-1 = Arg0
10267 // Args: | Arg0 | <-- (argc-1)-1 = argc-2 = Arg1
10268 // | Arg1 | <-- (argc-1)-2 = argc-3 = Arg2
10269 // | ... | <-- ...
10271 // clang-format on
10273 // This allows to call |loadArgumentFixedSlot(ArgumentKind::Arg0)| and we
10274 // still load the correct argument index from |ArgumentKind::Arg1|.
10276 // When no arguments are passed, i.e. |argc==0|, we have to replace
10277 // |ArgumentKind::Arg0| with the undefined value. But we don't yet support
10278 // this case.
10279 HandleValue newTarget = NullHandleValue;
10280 HandleValue thisValue = args_[0];
10281 HandleValueArray args =
10282 HandleValueArray::subarray(args_, 1, args_.length() - 1);
10284 // Check for specific native-function optimizations.
10285 InlinableNativeIRGenerator nativeGen(*this, target, newTarget, thisValue,
10286 args, targetFlags);
10287 TRY_ATTACH(nativeGen.tryAttachStub());
10290 ObjOperandId thisObjId = emitFunCallGuard(argcId);
10292 if (mode_ == ICState::Mode::Specialized) {
10293 // Ensure that |this| is the expected target function.
10294 emitCalleeGuard(thisObjId, target);
10296 if (isScripted) {
10297 writer.callScriptedFunction(thisObjId, argcId, targetFlags,
10298 ClampFixedArgc(argc_));
10299 } else {
10300 writer.callNativeFunction(thisObjId, argcId, op_, target, targetFlags,
10301 ClampFixedArgc(argc_));
10303 } else {
10304 // Guard that |this| is a function.
10305 writer.guardClass(thisObjId, GuardClassKind::JSFunction);
10307 // Guard that function is not a class constructor.
10308 writer.guardNotClassConstructor(thisObjId);
10310 if (isScripted) {
10311 writer.guardFunctionHasJitEntry(thisObjId, /*isConstructing =*/false);
10312 writer.callScriptedFunction(thisObjId, argcId, targetFlags,
10313 ClampFixedArgc(argc_));
10314 } else {
10315 writer.guardFunctionHasNoJitEntry(thisObjId);
10316 writer.callAnyNativeFunction(thisObjId, argcId, targetFlags,
10317 ClampFixedArgc(argc_));
10321 writer.returnFromIC();
10323 if (isScripted) {
10324 trackAttached("Scripted fun_call");
10325 } else {
10326 trackAttached("Native fun_call");
10329 return AttachDecision::Attach;
10332 AttachDecision InlinableNativeIRGenerator::tryAttachIsTypedArray(
10333 bool isPossiblyWrapped) {
10334 // Self-hosted code calls this with a single object argument.
10335 MOZ_ASSERT(argc_ == 1);
10336 MOZ_ASSERT(args_[0].isObject());
10338 // Initialize the input operand.
10339 initializeInputOperand();
10341 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10343 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10344 ObjOperandId objArgId = writer.guardToObject(argId);
10345 writer.isTypedArrayResult(objArgId, isPossiblyWrapped);
10346 writer.returnFromIC();
10348 trackAttached(isPossiblyWrapped ? "IsPossiblyWrappedTypedArray"
10349 : "IsTypedArray");
10350 return AttachDecision::Attach;
10353 AttachDecision InlinableNativeIRGenerator::tryAttachIsTypedArrayConstructor() {
10354 // Self-hosted code calls this with a single object argument.
10355 MOZ_ASSERT(argc_ == 1);
10356 MOZ_ASSERT(args_[0].isObject());
10358 // Initialize the input operand.
10359 initializeInputOperand();
10361 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10363 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10364 ObjOperandId objArgId = writer.guardToObject(argId);
10365 writer.isTypedArrayConstructorResult(objArgId);
10366 writer.returnFromIC();
10368 trackAttached("IsTypedArrayConstructor");
10369 return AttachDecision::Attach;
10372 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayByteOffset() {
10373 // Self-hosted code calls this with a single TypedArrayObject argument.
10374 MOZ_ASSERT(argc_ == 1);
10375 MOZ_ASSERT(args_[0].isObject());
10376 MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>());
10378 auto* tarr = &args_[0].toObject().as<TypedArrayObject>();
10380 // Initialize the input operand.
10381 initializeInputOperand();
10383 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10385 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10386 ObjOperandId objArgId = writer.guardToObject(argId);
10388 EmitGuardTypedArray(writer, tarr, objArgId);
10390 size_t byteOffset = tarr->byteOffsetMaybeOutOfBounds();
10391 if (tarr->is<FixedLengthTypedArrayObject>()) {
10392 if (byteOffset <= INT32_MAX) {
10393 writer.arrayBufferViewByteOffsetInt32Result(objArgId);
10394 } else {
10395 writer.arrayBufferViewByteOffsetDoubleResult(objArgId);
10397 } else {
10398 if (byteOffset <= INT32_MAX) {
10399 writer.resizableTypedArrayByteOffsetMaybeOutOfBoundsInt32Result(objArgId);
10400 } else {
10401 writer.resizableTypedArrayByteOffsetMaybeOutOfBoundsDoubleResult(
10402 objArgId);
10406 writer.returnFromIC();
10408 trackAttached("IntrinsicTypedArrayByteOffset");
10409 return AttachDecision::Attach;
10412 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayElementSize() {
10413 // Self-hosted code calls this with a single TypedArrayObject argument.
10414 MOZ_ASSERT(argc_ == 1);
10415 MOZ_ASSERT(args_[0].isObject());
10416 MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>());
10418 // Initialize the input operand.
10419 initializeInputOperand();
10421 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10423 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10424 ObjOperandId objArgId = writer.guardToObject(argId);
10425 writer.typedArrayElementSizeResult(objArgId);
10426 writer.returnFromIC();
10428 trackAttached("TypedArrayElementSize");
10429 return AttachDecision::Attach;
10432 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayLength(
10433 bool isPossiblyWrapped, bool allowOutOfBounds) {
10434 // Self-hosted code calls this with a single, possibly wrapped,
10435 // TypedArrayObject argument.
10436 MOZ_ASSERT(argc_ == 1);
10437 MOZ_ASSERT(args_[0].isObject());
10439 // Only optimize when the object isn't a wrapper.
10440 if (isPossiblyWrapped && IsWrapper(&args_[0].toObject())) {
10441 return AttachDecision::NoAction;
10444 MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>());
10446 auto* tarr = &args_[0].toObject().as<TypedArrayObject>();
10448 // Don't optimize when a resizable TypedArray is out-of-bounds and
10449 // out-of-bounds isn't allowed.
10450 auto length = tarr->length();
10451 if (length.isNothing() && !tarr->hasDetachedBuffer()) {
10452 MOZ_ASSERT(tarr->is<ResizableTypedArrayObject>());
10453 MOZ_ASSERT(tarr->isOutOfBounds());
10455 if (!allowOutOfBounds) {
10456 return AttachDecision::NoAction;
10460 // Initialize the input operand.
10461 initializeInputOperand();
10463 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10465 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10466 ObjOperandId objArgId = writer.guardToObject(argId);
10468 if (isPossiblyWrapped) {
10469 writer.guardIsNotProxy(objArgId);
10472 EmitGuardTypedArray(writer, tarr, objArgId);
10474 if (tarr->is<FixedLengthTypedArrayObject>()) {
10475 if (length.valueOr(0) <= INT32_MAX) {
10476 writer.loadArrayBufferViewLengthInt32Result(objArgId);
10477 } else {
10478 writer.loadArrayBufferViewLengthDoubleResult(objArgId);
10480 } else {
10481 if (!allowOutOfBounds) {
10482 writer.guardResizableArrayBufferViewInBoundsOrDetached(objArgId);
10485 if (length.valueOr(0) <= INT32_MAX) {
10486 writer.resizableTypedArrayLengthInt32Result(objArgId);
10487 } else {
10488 writer.resizableTypedArrayLengthDoubleResult(objArgId);
10491 writer.returnFromIC();
10493 trackAttached("IntrinsicTypedArrayLength");
10494 return AttachDecision::Attach;
10497 AttachDecision InlinableNativeIRGenerator::tryAttachArrayBufferByteLength(
10498 bool isPossiblyWrapped) {
10499 // Self-hosted code calls this with a single, possibly wrapped,
10500 // ArrayBufferObject argument.
10501 MOZ_ASSERT(argc_ == 1);
10502 MOZ_ASSERT(args_[0].isObject());
10504 // Only optimize when the object isn't a wrapper.
10505 if (isPossiblyWrapped && IsWrapper(&args_[0].toObject())) {
10506 return AttachDecision::NoAction;
10509 MOZ_ASSERT(args_[0].toObject().is<ArrayBufferObject>());
10511 auto* buffer = &args_[0].toObject().as<ArrayBufferObject>();
10513 // Initialize the input operand.
10514 initializeInputOperand();
10516 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10518 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10519 ObjOperandId objArgId = writer.guardToObject(argId);
10521 if (isPossiblyWrapped) {
10522 writer.guardIsNotProxy(objArgId);
10525 if (buffer->byteLength() <= INT32_MAX) {
10526 writer.loadArrayBufferByteLengthInt32Result(objArgId);
10527 } else {
10528 writer.loadArrayBufferByteLengthDoubleResult(objArgId);
10530 writer.returnFromIC();
10532 trackAttached("ArrayBufferByteLength");
10533 return AttachDecision::Attach;
10536 AttachDecision InlinableNativeIRGenerator::tryAttachIsConstructing() {
10537 // Self-hosted code calls this with no arguments in function scripts.
10538 MOZ_ASSERT(argc_ == 0);
10539 MOZ_ASSERT(script()->isFunction());
10541 // Initialize the input operand.
10542 initializeInputOperand();
10544 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10546 writer.frameIsConstructingResult();
10547 writer.returnFromIC();
10549 trackAttached("IsConstructing");
10550 return AttachDecision::Attach;
10553 AttachDecision
10554 InlinableNativeIRGenerator::tryAttachGetNextMapSetEntryForIterator(bool isMap) {
10555 // Self-hosted code calls this with two objects.
10556 MOZ_ASSERT(argc_ == 2);
10557 if (isMap) {
10558 MOZ_ASSERT(args_[0].toObject().is<MapIteratorObject>());
10559 } else {
10560 MOZ_ASSERT(args_[0].toObject().is<SetIteratorObject>());
10562 MOZ_ASSERT(args_[1].toObject().is<ArrayObject>());
10564 // Initialize the input operand.
10565 initializeInputOperand();
10567 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10569 ValOperandId iterId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10570 ObjOperandId objIterId = writer.guardToObject(iterId);
10572 ValOperandId resultArrId =
10573 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
10574 ObjOperandId objResultArrId = writer.guardToObject(resultArrId);
10576 writer.getNextMapSetEntryForIteratorResult(objIterId, objResultArrId, isMap);
10577 writer.returnFromIC();
10579 trackAttached("GetNextMapSetEntryForIterator");
10580 return AttachDecision::Attach;
10583 AttachDecision InlinableNativeIRGenerator::tryAttachNewArrayIterator() {
10584 // Self-hosted code calls this without any arguments
10585 MOZ_ASSERT(argc_ == 0);
10587 JSObject* templateObj = NewArrayIteratorTemplate(cx_);
10588 if (!templateObj) {
10589 cx_->recoverFromOutOfMemory();
10590 return AttachDecision::NoAction;
10593 // Initialize the input operand.
10594 initializeInputOperand();
10596 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10598 writer.newArrayIteratorResult(templateObj);
10599 writer.returnFromIC();
10601 trackAttached("NewArrayIterator");
10602 return AttachDecision::Attach;
10605 AttachDecision InlinableNativeIRGenerator::tryAttachNewStringIterator() {
10606 // Self-hosted code calls this without any arguments
10607 MOZ_ASSERT(argc_ == 0);
10609 JSObject* templateObj = NewStringIteratorTemplate(cx_);
10610 if (!templateObj) {
10611 cx_->recoverFromOutOfMemory();
10612 return AttachDecision::NoAction;
10615 // Initialize the input operand.
10616 initializeInputOperand();
10618 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10620 writer.newStringIteratorResult(templateObj);
10621 writer.returnFromIC();
10623 trackAttached("NewStringIterator");
10624 return AttachDecision::Attach;
10627 AttachDecision InlinableNativeIRGenerator::tryAttachNewRegExpStringIterator() {
10628 // Self-hosted code calls this without any arguments
10629 MOZ_ASSERT(argc_ == 0);
10631 JSObject* templateObj = NewRegExpStringIteratorTemplate(cx_);
10632 if (!templateObj) {
10633 cx_->recoverFromOutOfMemory();
10634 return AttachDecision::NoAction;
10637 // Initialize the input operand.
10638 initializeInputOperand();
10640 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10642 writer.newRegExpStringIteratorResult(templateObj);
10643 writer.returnFromIC();
10645 trackAttached("NewRegExpStringIterator");
10646 return AttachDecision::Attach;
10649 AttachDecision
10650 InlinableNativeIRGenerator::tryAttachArrayIteratorPrototypeOptimizable() {
10651 // Self-hosted code calls this without any arguments
10652 MOZ_ASSERT(argc_ == 0);
10654 if (!isFirstStub()) {
10655 // Attach only once to prevent slowdowns for polymorphic calls.
10656 return AttachDecision::NoAction;
10659 Rooted<NativeObject*> arrayIteratorProto(cx_);
10660 uint32_t slot;
10661 Rooted<JSFunction*> nextFun(cx_);
10662 if (!IsArrayIteratorPrototypeOptimizable(cx_, AllowIteratorReturn::Yes,
10663 &arrayIteratorProto, &slot,
10664 &nextFun)) {
10665 return AttachDecision::NoAction;
10668 // Initialize the input operand.
10669 initializeInputOperand();
10671 // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
10673 ObjOperandId protoId = writer.loadObject(arrayIteratorProto);
10674 ObjOperandId nextId = writer.loadObject(nextFun);
10676 writer.guardShape(protoId, arrayIteratorProto->shape());
10678 // Ensure that proto[slot] == nextFun.
10679 writer.guardDynamicSlotIsSpecificObject(protoId, nextId, slot);
10680 writer.loadBooleanResult(true);
10681 writer.returnFromIC();
10683 trackAttached("ArrayIteratorPrototypeOptimizable");
10684 return AttachDecision::Attach;
10687 AttachDecision InlinableNativeIRGenerator::tryAttachObjectCreate() {
10688 // Need a single object-or-null argument.
10689 if (argc_ != 1 || !args_[0].isObjectOrNull()) {
10690 return AttachDecision::NoAction;
10693 if (!isFirstStub()) {
10694 // Attach only once to prevent slowdowns for polymorphic calls.
10695 return AttachDecision::NoAction;
10698 RootedObject proto(cx_, args_[0].toObjectOrNull());
10699 JSObject* templateObj = ObjectCreateImpl(cx_, proto, TenuredObject);
10700 if (!templateObj) {
10701 cx_->recoverFromOutOfMemory();
10702 return AttachDecision::NoAction;
10705 // Initialize the input operand.
10706 initializeInputOperand();
10708 // Guard callee is the 'create' native function.
10709 emitNativeCalleeGuard();
10711 // Guard on the proto argument.
10712 ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
10713 if (proto) {
10714 ObjOperandId protoId = writer.guardToObject(argId);
10715 writer.guardSpecificObject(protoId, proto);
10716 } else {
10717 writer.guardIsNull(argId);
10720 writer.objectCreateResult(templateObj);
10721 writer.returnFromIC();
10723 trackAttached("ObjectCreate");
10724 return AttachDecision::Attach;
10727 AttachDecision InlinableNativeIRGenerator::tryAttachObjectConstructor() {
10728 // Expecting no arguments or a single object argument.
10729 // TODO(Warp): Support all or more conversions to object.
10730 if (argc_ > 1) {
10731 return AttachDecision::NoAction;
10733 if (argc_ == 1 && !args_[0].isObject()) {
10734 return AttachDecision::NoAction;
10737 PlainObject* templateObj = nullptr;
10738 if (argc_ == 0) {
10739 // Stub doesn't support metadata builder
10740 if (cx_->realm()->hasAllocationMetadataBuilder()) {
10741 return AttachDecision::NoAction;
10744 // Create a temporary object to act as the template object.
10745 templateObj = NewPlainObjectWithAllocKind(cx_, NewObjectGCKind());
10746 if (!templateObj) {
10747 cx_->recoverFromOutOfMemory();
10748 return AttachDecision::NoAction;
10752 // Initialize the input operand.
10753 initializeInputOperand();
10755 // Guard callee and newTarget (if constructing) are this Object constructor
10756 // function.
10757 emitNativeCalleeGuard();
10759 if (argc_ == 0) {
10760 // TODO: Support pre-tenuring.
10761 gc::AllocSite* site =
10762 script()->zone()->unknownAllocSite(JS::TraceKind::Object);
10763 MOZ_ASSERT(site);
10765 uint32_t numFixedSlots = templateObj->numUsedFixedSlots();
10766 uint32_t numDynamicSlots = templateObj->numDynamicSlots();
10767 gc::AllocKind allocKind = templateObj->allocKindForTenure();
10768 Shape* shape = templateObj->shape();
10770 writer.guardNoAllocationMetadataBuilder(
10771 cx_->realm()->addressOfMetadataBuilder());
10772 writer.newPlainObjectResult(numFixedSlots, numDynamicSlots, allocKind,
10773 shape, site);
10774 } else {
10775 // Use standard call flags when this is an inline Function.prototype.call(),
10776 // because GetIndexOfArgument() doesn't yet support |CallFlags::FunCall|.
10777 CallFlags flags = flags_;
10778 if (flags.getArgFormat() == CallFlags::FunCall) {
10779 flags = CallFlags(CallFlags::Standard);
10782 // Guard that the argument is an object.
10783 ValOperandId argId =
10784 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags);
10785 ObjOperandId objId = writer.guardToObject(argId);
10787 // Return the object.
10788 writer.loadObjectResult(objId);
10791 writer.returnFromIC();
10793 trackAttached("ObjectConstructor");
10794 return AttachDecision::Attach;
10797 AttachDecision InlinableNativeIRGenerator::tryAttachArrayConstructor() {
10798 // Only optimize the |Array()| and |Array(n)| cases (with or without |new|)
10799 // for now. Note that self-hosted code calls this without |new| via std_Array.
10800 if (argc_ > 1) {
10801 return AttachDecision::NoAction;
10803 if (argc_ == 1 && !args_[0].isInt32()) {
10804 return AttachDecision::NoAction;
10807 int32_t length = (argc_ == 1) ? args_[0].toInt32() : 0;
10808 if (length < 0 || uint32_t(length) > ArrayObject::EagerAllocationMaxLength) {
10809 return AttachDecision::NoAction;
10812 // We allow inlining this function across realms so make sure the template
10813 // object is allocated in that realm. See CanInlineNativeCrossRealm.
10814 JSObject* templateObj;
10816 AutoRealm ar(cx_, callee_);
10817 templateObj = NewDenseFullyAllocatedArray(cx_, length, TenuredObject);
10818 if (!templateObj) {
10819 cx_->clearPendingException();
10820 return AttachDecision::NoAction;
10824 // Initialize the input operand.
10825 initializeInputOperand();
10827 // Guard callee and newTarget (if constructing) are this Array constructor
10828 // function.
10829 emitNativeCalleeGuard();
10831 Int32OperandId lengthId;
10832 if (argc_ == 1) {
10833 // Use standard call flags when this is an inline Function.prototype.call(),
10834 // because GetIndexOfArgument() doesn't yet support |CallFlags::FunCall|.
10835 CallFlags flags = flags_;
10836 if (flags.getArgFormat() == CallFlags::FunCall) {
10837 flags = CallFlags(CallFlags::Standard);
10840 ValOperandId arg0Id =
10841 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags);
10842 lengthId = writer.guardToInt32(arg0Id);
10843 } else {
10844 MOZ_ASSERT(argc_ == 0);
10845 lengthId = writer.loadInt32Constant(0);
10848 writer.newArrayFromLengthResult(templateObj, lengthId);
10849 writer.returnFromIC();
10851 trackAttached("ArrayConstructor");
10852 return AttachDecision::Attach;
10855 AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayConstructor() {
10856 MOZ_ASSERT(flags_.isConstructing());
10858 if (argc_ == 0 || argc_ > 3) {
10859 return AttachDecision::NoAction;
10862 if (!isFirstStub()) {
10863 // Attach only once to prevent slowdowns for polymorphic calls.
10864 return AttachDecision::NoAction;
10867 // The first argument must be int32 or a non-proxy object.
10868 if (!args_[0].isInt32() && !args_[0].isObject()) {
10869 return AttachDecision::NoAction;
10871 if (args_[0].isObject() && args_[0].toObject().is<ProxyObject>()) {
10872 return AttachDecision::NoAction;
10875 #ifdef JS_CODEGEN_X86
10876 // Unfortunately NewTypedArrayFromArrayBufferResult needs more registers than
10877 // we can easily support on 32-bit x86 for now.
10878 if (args_[0].isObject() &&
10879 args_[0].toObject().is<ArrayBufferObjectMaybeShared>()) {
10880 return AttachDecision::NoAction;
10882 #endif
10884 RootedObject templateObj(cx_);
10885 if (!TypedArrayObject::GetTemplateObjectForNative(cx_, callee_->native(),
10886 args_, &templateObj)) {
10887 cx_->recoverFromOutOfMemory();
10888 return AttachDecision::NoAction;
10891 if (!templateObj) {
10892 // This can happen for large length values.
10893 MOZ_ASSERT(args_[0].isInt32());
10894 return AttachDecision::NoAction;
10897 // Initialize the input operand.
10898 initializeInputOperand();
10900 // Guard callee and newTarget are this TypedArray constructor function.
10901 emitNativeCalleeGuard();
10903 ValOperandId arg0Id =
10904 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags_);
10906 if (args_[0].isInt32()) {
10907 // From length.
10908 Int32OperandId lengthId = writer.guardToInt32(arg0Id);
10909 writer.newTypedArrayFromLengthResult(templateObj, lengthId);
10910 } else {
10911 JSObject* obj = &args_[0].toObject();
10912 ObjOperandId objId = writer.guardToObject(arg0Id);
10914 if (obj->is<ArrayBufferObjectMaybeShared>()) {
10915 // From ArrayBuffer.
10916 if (obj->is<FixedLengthArrayBufferObject>()) {
10917 writer.guardClass(objId, GuardClassKind::FixedLengthArrayBuffer);
10918 } else if (obj->is<FixedLengthSharedArrayBufferObject>()) {
10919 writer.guardClass(objId, GuardClassKind::FixedLengthSharedArrayBuffer);
10920 } else if (obj->is<ResizableArrayBufferObject>()) {
10921 writer.guardClass(objId, GuardClassKind::ResizableArrayBuffer);
10922 } else {
10923 MOZ_ASSERT(obj->is<GrowableSharedArrayBufferObject>());
10924 writer.guardClass(objId, GuardClassKind::GrowableSharedArrayBuffer);
10926 ValOperandId byteOffsetId;
10927 if (argc_ > 1) {
10928 byteOffsetId =
10929 writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_, flags_);
10930 } else {
10931 byteOffsetId = writer.loadUndefined();
10933 ValOperandId lengthId;
10934 if (argc_ > 2) {
10935 lengthId =
10936 writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_, flags_);
10937 } else {
10938 lengthId = writer.loadUndefined();
10940 writer.newTypedArrayFromArrayBufferResult(templateObj, objId,
10941 byteOffsetId, lengthId);
10942 } else {
10943 // From Array-like.
10944 writer.guardIsNotArrayBufferMaybeShared(objId);
10945 writer.guardIsNotProxy(objId);
10946 writer.newTypedArrayFromArrayResult(templateObj, objId);
10950 writer.returnFromIC();
10952 trackAttached("TypedArrayConstructor");
10953 return AttachDecision::Attach;
10956 AttachDecision InlinableNativeIRGenerator::tryAttachSpecializedFunctionBind(
10957 Handle<JSObject*> target, Handle<BoundFunctionObject*> templateObj) {
10958 // Try to attach a faster stub that's more specialized than what we emit in
10959 // tryAttachFunctionBind. This lets us allocate and initialize a bound
10960 // function object in Ion without calling into C++.
10962 // We can do this if:
10964 // * The target's prototype is Function.prototype, because that's the proto we
10965 // use for the template object.
10966 // * All bound arguments can be stored inline.
10967 // * The `.name`, `.length`, and `IsConstructor` values match `target`.
10969 // We initialize the template object with the bound function's name, length,
10970 // and flags. At runtime we then only have to clone the template object and
10971 // initialize the slots for the target, the bound `this` and the bound
10972 // arguments.
10974 if (!isFirstStub()) {
10975 return AttachDecision::NoAction;
10977 if (!target->is<JSFunction>() && !target->is<BoundFunctionObject>()) {
10978 return AttachDecision::NoAction;
10980 if (target->staticPrototype() != &cx_->global()->getFunctionPrototype()) {
10981 return AttachDecision::NoAction;
10983 size_t numBoundArgs = argc_ > 0 ? argc_ - 1 : 0;
10984 if (numBoundArgs > BoundFunctionObject::MaxInlineBoundArgs) {
10985 return AttachDecision::NoAction;
10988 const bool targetIsConstructor = target->isConstructor();
10989 Rooted<JSAtom*> targetName(cx_);
10990 uint32_t targetLength = 0;
10992 if (target->is<JSFunction>()) {
10993 Rooted<JSFunction*> fun(cx_, &target->as<JSFunction>());
10994 if (fun->isNativeFun()) {
10995 return AttachDecision::NoAction;
10997 if (fun->hasResolvedLength() || fun->hasResolvedName()) {
10998 return AttachDecision::NoAction;
11000 uint16_t len;
11001 if (!JSFunction::getUnresolvedLength(cx_, fun, &len)) {
11002 cx_->clearPendingException();
11003 return AttachDecision::NoAction;
11005 targetName = fun->getUnresolvedName(cx_);
11006 if (!targetName) {
11007 cx_->clearPendingException();
11008 return AttachDecision::NoAction;
11011 targetLength = len;
11012 } else {
11013 BoundFunctionObject* bound = &target->as<BoundFunctionObject>();
11014 if (!targetIsConstructor) {
11015 // Only support constructors for now. This lets us use
11016 // GuardBoundFunctionIsConstructor.
11017 return AttachDecision::NoAction;
11019 Shape* initialShape =
11020 cx_->global()->maybeBoundFunctionShapeWithDefaultProto();
11021 if (bound->shape() != initialShape) {
11022 return AttachDecision::NoAction;
11024 Value lenVal = bound->getLengthForInitialShape();
11025 Value nameVal = bound->getNameForInitialShape();
11026 if (!lenVal.isInt32() || lenVal.toInt32() < 0 || !nameVal.isString() ||
11027 !nameVal.toString()->isAtom()) {
11028 return AttachDecision::NoAction;
11030 targetName = &nameVal.toString()->asAtom();
11031 targetLength = uint32_t(lenVal.toInt32());
11034 if (!templateObj->initTemplateSlotsForSpecializedBind(
11035 cx_, numBoundArgs, targetIsConstructor, targetLength, targetName)) {
11036 cx_->recoverFromOutOfMemory();
11037 return AttachDecision::NoAction;
11040 initializeInputOperand();
11041 emitNativeCalleeGuard();
11043 ValOperandId thisValId =
11044 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
11045 ObjOperandId targetId = writer.guardToObject(thisValId);
11047 // Ensure the JSClass and proto match, and that the `length` and `name`
11048 // properties haven't been redefined.
11049 writer.guardShape(targetId, target->shape());
11051 // Emit guards for the `IsConstructor`, `.length`, and `.name` values.
11052 if (target->is<JSFunction>()) {
11053 // Guard on:
11054 // * The BaseScript (because that's what JSFunction uses for the `length`).
11055 // Because MGuardFunctionScript doesn't support self-hosted functions yet,
11056 // we use GuardSpecificFunction instead in this case.
11057 // See assertion in MGuardFunctionScript::getAliasSet.
11058 // * The flags slot (for the CONSTRUCTOR, RESOLVED_NAME, RESOLVED_LENGTH,
11059 // HAS_INFERRED_NAME, and HAS_GUESSED_ATOM flags).
11060 // * The atom slot.
11061 JSFunction* fun = &target->as<JSFunction>();
11062 if (fun->isSelfHostedBuiltin()) {
11063 writer.guardSpecificFunction(targetId, fun);
11064 } else {
11065 writer.guardFunctionScript(targetId, fun->baseScript());
11067 writer.guardFixedSlotValue(
11068 targetId, JSFunction::offsetOfFlagsAndArgCount(),
11069 fun->getReservedSlot(JSFunction::FlagsAndArgCountSlot));
11070 writer.guardFixedSlotValue(targetId, JSFunction::offsetOfAtom(),
11071 fun->getReservedSlot(JSFunction::AtomSlot));
11072 } else {
11073 BoundFunctionObject* bound = &target->as<BoundFunctionObject>();
11074 writer.guardBoundFunctionIsConstructor(targetId);
11075 writer.guardFixedSlotValue(targetId,
11076 BoundFunctionObject::offsetOfLengthSlot(),
11077 bound->getLengthForInitialShape());
11078 writer.guardFixedSlotValue(targetId,
11079 BoundFunctionObject::offsetOfNameSlot(),
11080 bound->getNameForInitialShape());
11083 writer.specializedBindFunctionResult(targetId, argc_, templateObj);
11084 writer.returnFromIC();
11086 trackAttached("SpecializedFunctionBind");
11087 return AttachDecision::Attach;
11090 AttachDecision InlinableNativeIRGenerator::tryAttachFunctionBind() {
11091 // Ensure |this| (the target) is a function object or a bound function object.
11092 // We could support other callables too, but note that we rely on the target
11093 // having a static prototype in BoundFunctionObject::functionBindImpl.
11094 if (!thisval_.isObject()) {
11095 return AttachDecision::NoAction;
11097 Rooted<JSObject*> target(cx_, &thisval_.toObject());
11098 if (!target->is<JSFunction>() && !target->is<BoundFunctionObject>()) {
11099 return AttachDecision::NoAction;
11102 // Only support standard, non-spread calls.
11103 if (flags_.getArgFormat() != CallFlags::Standard) {
11104 return AttachDecision::NoAction;
11107 // Only optimize if the number of arguments is small. This ensures we don't
11108 // compile a lot of different stubs (because we bake in argc) and that we
11109 // don't get anywhere near ARGS_LENGTH_MAX.
11110 static constexpr size_t MaxArguments = 6;
11111 if (argc_ > MaxArguments) {
11112 return AttachDecision::NoAction;
11115 Rooted<BoundFunctionObject*> templateObj(
11116 cx_, BoundFunctionObject::createTemplateObject(cx_));
11117 if (!templateObj) {
11118 cx_->recoverFromOutOfMemory();
11119 return AttachDecision::NoAction;
11122 TRY_ATTACH(tryAttachSpecializedFunctionBind(target, templateObj));
11124 initializeInputOperand();
11126 emitNativeCalleeGuard();
11128 // Guard |this| is a function object or a bound function object.
11129 ValOperandId thisValId =
11130 writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
11131 ObjOperandId targetId = writer.guardToObject(thisValId);
11132 if (target->is<JSFunction>()) {
11133 writer.guardClass(targetId, GuardClassKind::JSFunction);
11134 } else {
11135 MOZ_ASSERT(target->is<BoundFunctionObject>());
11136 writer.guardClass(targetId, GuardClassKind::BoundFunction);
11139 writer.bindFunctionResult(targetId, argc_, templateObj);
11140 writer.returnFromIC();
11142 trackAttached("FunctionBind");
11143 return AttachDecision::Attach;
11146 AttachDecision CallIRGenerator::tryAttachFunApply(HandleFunction calleeFunc) {
11147 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
11149 if (calleeFunc->native() != fun_apply) {
11150 return AttachDecision::NoAction;
11153 if (argc_ > 2) {
11154 return AttachDecision::NoAction;
11157 if (!thisval_.isObject() || !thisval_.toObject().is<JSFunction>()) {
11158 return AttachDecision::NoAction;
11160 Rooted<JSFunction*> target(cx_, &thisval_.toObject().as<JSFunction>());
11162 bool isScripted = target->hasJitEntry();
11163 MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry());
11165 if (target->isClassConstructor()) {
11166 return AttachDecision::NoAction;
11169 CallFlags::ArgFormat format = CallFlags::Standard;
11170 if (argc_ < 2) {
11171 // |fun.apply()| and |fun.apply(thisValue)| are equivalent to |fun.call()|
11172 // resp. |fun.call(thisValue)|.
11173 format = CallFlags::FunCall;
11174 } else if (args_[1].isNullOrUndefined()) {
11175 // |fun.apply(thisValue, null)| and |fun.apply(thisValue, undefined)| are
11176 // also equivalent to |fun.call(thisValue)|, but we can't use FunCall
11177 // because we have to discard the second argument.
11178 format = CallFlags::FunApplyNullUndefined;
11179 } else if (args_[1].isObject() && args_[1].toObject().is<ArgumentsObject>()) {
11180 auto* argsObj = &args_[1].toObject().as<ArgumentsObject>();
11181 if (argsObj->hasOverriddenElement() || argsObj->anyArgIsForwarded() ||
11182 argsObj->hasOverriddenLength() ||
11183 argsObj->initialLength() > JIT_ARGS_LENGTH_MAX) {
11184 return AttachDecision::NoAction;
11186 format = CallFlags::FunApplyArgsObj;
11187 } else if (args_[1].isObject() && args_[1].toObject().is<ArrayObject>() &&
11188 args_[1].toObject().as<ArrayObject>().length() <=
11189 JIT_ARGS_LENGTH_MAX &&
11190 IsPackedArray(&args_[1].toObject())) {
11191 format = CallFlags::FunApplyArray;
11192 } else {
11193 return AttachDecision::NoAction;
11196 Int32OperandId argcId(writer.setInputOperandId(0));
11198 CallFlags targetFlags(format);
11199 if (mode_ == ICState::Mode::Specialized) {
11200 if (cx_->realm() == target->realm()) {
11201 targetFlags.setIsSameRealm();
11205 if (mode_ == ICState::Mode::Specialized && !isScripted &&
11206 format == CallFlags::FunApplyArray) {
11207 HandleValue newTarget = NullHandleValue;
11208 HandleValue thisValue = args_[0];
11209 Rooted<ArrayObject*> aobj(cx_, &args_[1].toObject().as<ArrayObject>());
11210 HandleValueArray args = HandleValueArray::fromMarkedLocation(
11211 aobj->length(), aobj->getDenseElements());
11213 // Check for specific native-function optimizations.
11214 InlinableNativeIRGenerator nativeGen(*this, target, newTarget, thisValue,
11215 args, targetFlags);
11216 TRY_ATTACH(nativeGen.tryAttachStub());
11219 // Don't inline when no arguments are passed, cf. |tryAttachFunCall()|.
11220 if (mode_ == ICState::Mode::Specialized && !isScripted &&
11221 format == CallFlags::FunCall && argc_ > 0) {
11222 MOZ_ASSERT(argc_ == 1);
11224 HandleValue newTarget = NullHandleValue;
11225 HandleValue thisValue = args_[0];
11226 HandleValueArray args = HandleValueArray::empty();
11228 // Check for specific native-function optimizations.
11229 InlinableNativeIRGenerator nativeGen(*this, target, newTarget, thisValue,
11230 args, targetFlags);
11231 TRY_ATTACH(nativeGen.tryAttachStub());
11234 ObjOperandId thisObjId = emitFunApplyGuard(argcId);
11236 uint32_t fixedArgc;
11237 if (format == CallFlags::FunApplyArray ||
11238 format == CallFlags::FunApplyArgsObj ||
11239 format == CallFlags::FunApplyNullUndefined) {
11240 emitFunApplyArgsGuard(format);
11242 // We always use MaxUnrolledArgCopy here because the fixed argc is
11243 // meaningless in a FunApply case.
11244 fixedArgc = MaxUnrolledArgCopy;
11245 } else {
11246 MOZ_ASSERT(format == CallFlags::FunCall);
11248 // Whereas for the FunCall case we need to use the actual fixed argc value.
11249 fixedArgc = ClampFixedArgc(argc_);
11252 if (mode_ == ICState::Mode::Specialized) {
11253 // Ensure that |this| is the expected target function.
11254 emitCalleeGuard(thisObjId, target);
11256 if (isScripted) {
11257 writer.callScriptedFunction(thisObjId, argcId, targetFlags, fixedArgc);
11258 } else {
11259 writer.callNativeFunction(thisObjId, argcId, op_, target, targetFlags,
11260 fixedArgc);
11262 } else {
11263 // Guard that |this| is a function.
11264 writer.guardClass(thisObjId, GuardClassKind::JSFunction);
11266 // Guard that function is not a class constructor.
11267 writer.guardNotClassConstructor(thisObjId);
11269 if (isScripted) {
11270 // Guard that function is scripted.
11271 writer.guardFunctionHasJitEntry(thisObjId, /*constructing =*/false);
11272 writer.callScriptedFunction(thisObjId, argcId, targetFlags, fixedArgc);
11273 } else {
11274 // Guard that function is native.
11275 writer.guardFunctionHasNoJitEntry(thisObjId);
11276 writer.callAnyNativeFunction(thisObjId, argcId, targetFlags, fixedArgc);
11280 writer.returnFromIC();
11282 if (isScripted) {
11283 trackAttached("Call.ScriptedFunApply");
11284 } else {
11285 trackAttached("Call.NativeFunApply");
11288 return AttachDecision::Attach;
11291 AttachDecision CallIRGenerator::tryAttachWasmCall(HandleFunction calleeFunc) {
11292 // Try to optimize calls into Wasm code by emitting the CallWasmFunction
11293 // CacheIR op. Baseline ICs currently treat this as a CallScriptedFunction op
11294 // (calling Wasm's JitEntry stub) but Warp transpiles it to a more direct call
11295 // into Wasm code.
11297 // Note: some code refers to these optimized Wasm calls as "inlined" calls.
11299 MOZ_ASSERT(calleeFunc->isWasmWithJitEntry());
11301 if (!JitOptions.enableWasmIonFastCalls) {
11302 return AttachDecision::NoAction;
11304 if (!isFirstStub_) {
11305 return AttachDecision::NoAction;
11307 JSOp op = JSOp(*pc_);
11308 if (op != JSOp::Call && op != JSOp::CallContent &&
11309 op != JSOp::CallIgnoresRv) {
11310 return AttachDecision::NoAction;
11312 if (cx_->realm() != calleeFunc->realm()) {
11313 return AttachDecision::NoAction;
11316 wasm::Instance& inst = wasm::ExportedFunctionToInstance(calleeFunc);
11317 uint32_t funcIndex = inst.code().getFuncIndex(calleeFunc);
11319 auto bestTier = inst.code().bestTier();
11320 const wasm::FuncExport& funcExport =
11321 inst.metadata(bestTier).lookupFuncExport(funcIndex);
11322 const wasm::FuncType& sig = inst.metadata().getFuncExportType(funcExport);
11324 MOZ_ASSERT(!IsInsideNursery(inst.object()));
11325 MOZ_ASSERT(sig.canHaveJitEntry(), "Function should allow a Wasm JitEntry");
11327 // If there are too many arguments, don't optimize (we won't be able to store
11328 // the arguments in the LIR node).
11329 static_assert(wasm::MaxArgsForJitInlineCall <= ArgumentKindArgIndexLimit);
11330 if (sig.args().length() > wasm::MaxArgsForJitInlineCall ||
11331 argc_ > ArgumentKindArgIndexLimit) {
11332 return AttachDecision::NoAction;
11335 // If there are too many results, don't optimize as Warp currently doesn't
11336 // have code to handle this.
11337 if (sig.results().length() > wasm::MaxResultsForJitInlineCall) {
11338 return AttachDecision::NoAction;
11341 // Bug 1631656 - Don't try to optimize with I64 args on 32-bit platforms
11342 // because it is more difficult (because it requires multiple LIR arguments
11343 // per I64).
11345 // Bug 1631650 - On 64-bit platforms, we also give up optimizing for I64 args
11346 // spilled to the stack because it causes problems with register allocation.
11347 #ifdef JS_64BIT
11348 constexpr bool optimizeWithI64 = true;
11349 #else
11350 constexpr bool optimizeWithI64 = false;
11351 #endif
11352 ABIArgGenerator abi;
11353 for (const auto& valType : sig.args()) {
11354 MIRType mirType = valType.toMIRType();
11355 ABIArg abiArg = abi.next(mirType);
11356 if (mirType != MIRType::Int64) {
11357 continue;
11359 if (!optimizeWithI64 || abiArg.kind() == ABIArg::Stack) {
11360 return AttachDecision::NoAction;
11364 // Check that all arguments can be converted to the Wasm type in Warp code
11365 // without bailing out.
11366 for (size_t i = 0; i < sig.args().length(); i++) {
11367 Value argVal = i < argc_ ? args_[i] : UndefinedValue();
11368 switch (sig.args()[i].kind()) {
11369 case wasm::ValType::I32:
11370 case wasm::ValType::F32:
11371 case wasm::ValType::F64:
11372 if (!argVal.isNumber() && !argVal.isBoolean() &&
11373 !argVal.isUndefined()) {
11374 return AttachDecision::NoAction;
11376 break;
11377 case wasm::ValType::I64:
11378 if (!argVal.isBigInt() && !argVal.isBoolean() && !argVal.isString()) {
11379 return AttachDecision::NoAction;
11381 break;
11382 case wasm::ValType::V128:
11383 MOZ_CRASH("Function should not have a Wasm JitEntry");
11384 case wasm::ValType::Ref:
11385 // canHaveJitEntry restricts args to externref, where all JS values are
11386 // valid and can be boxed.
11387 MOZ_ASSERT(sig.args()[i].refType().isExtern(),
11388 "Unexpected type for Wasm JitEntry");
11389 break;
11393 CallFlags flags(/* isConstructing = */ false, /* isSpread = */ false,
11394 /* isSameRealm = */ true);
11396 // Load argc.
11397 Int32OperandId argcId(writer.setInputOperandId(0));
11399 // Load the callee and ensure it is an object
11400 ValOperandId calleeValId =
11401 writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags);
11402 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
11404 // Ensure the callee is this Wasm function.
11405 emitCalleeGuard(calleeObjId, calleeFunc);
11407 // Guard the argument types.
11408 uint32_t guardedArgs = std::min<uint32_t>(sig.args().length(), argc_);
11409 for (uint32_t i = 0; i < guardedArgs; i++) {
11410 ArgumentKind argKind = ArgumentKindForArgIndex(i);
11411 ValOperandId argId = writer.loadArgumentFixedSlot(argKind, argc_, flags);
11412 writer.guardWasmArg(argId, sig.args()[i].kind());
11415 writer.callWasmFunction(calleeObjId, argcId, flags, ClampFixedArgc(argc_),
11416 &funcExport, inst.object());
11417 writer.returnFromIC();
11419 trackAttached("Call.WasmCall");
11421 return AttachDecision::Attach;
11424 AttachDecision CallIRGenerator::tryAttachInlinableNative(HandleFunction callee,
11425 CallFlags flags) {
11426 MOZ_ASSERT(mode_ == ICState::Mode::Specialized);
11427 MOZ_ASSERT(callee->isNativeWithoutJitEntry());
11428 MOZ_ASSERT(flags.getArgFormat() == CallFlags::Standard ||
11429 flags.getArgFormat() == CallFlags::Spread);
11431 // Special case functions are only optimized for normal calls.
11432 if (op_ != JSOp::Call && op_ != JSOp::CallContent && op_ != JSOp::New &&
11433 op_ != JSOp::NewContent && op_ != JSOp::CallIgnoresRv &&
11434 op_ != JSOp::SpreadCall) {
11435 return AttachDecision::NoAction;
11438 InlinableNativeIRGenerator nativeGen(*this, callee, newTarget_, thisval_,
11439 args_, flags);
11440 return nativeGen.tryAttachStub();
11443 #ifdef FUZZING_JS_FUZZILLI
11444 AttachDecision InlinableNativeIRGenerator::tryAttachFuzzilliHash() {
11445 if (argc_ != 1) {
11446 return AttachDecision::NoAction;
11449 // Initialize the input operand.
11450 initializeInputOperand();
11452 // Guard callee is the 'fuzzilli_hash' native function.
11453 emitNativeCalleeGuard();
11455 ValOperandId argValId =
11456 writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
11458 writer.fuzzilliHashResult(argValId);
11459 writer.returnFromIC();
11461 trackAttached("FuzzilliHash");
11462 return AttachDecision::Attach;
11464 #endif
11466 AttachDecision InlinableNativeIRGenerator::tryAttachStub() {
11467 if (!callee_->hasJitInfo() ||
11468 callee_->jitInfo()->type() != JSJitInfo::InlinableNative) {
11469 return AttachDecision::NoAction;
11472 InlinableNative native = callee_->jitInfo()->inlinableNative;
11474 // Not all natives can be inlined cross-realm.
11475 if (cx_->realm() != callee_->realm() && !CanInlineNativeCrossRealm(native)) {
11476 return AttachDecision::NoAction;
11479 // Check for special-cased native constructors.
11480 if (flags_.isConstructing()) {
11481 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard);
11483 // newTarget must match the callee. CacheIR for this is emitted in
11484 // emitNativeCalleeGuard.
11485 if (ObjectValue(*callee_) != newTarget_) {
11486 return AttachDecision::NoAction;
11488 switch (native) {
11489 case InlinableNative::Array:
11490 return tryAttachArrayConstructor();
11491 case InlinableNative::TypedArrayConstructor:
11492 return tryAttachTypedArrayConstructor();
11493 case InlinableNative::String:
11494 return tryAttachStringConstructor();
11495 case InlinableNative::Object:
11496 return tryAttachObjectConstructor();
11497 default:
11498 break;
11500 return AttachDecision::NoAction;
11503 // Check for special-cased native spread calls.
11504 if (flags_.getArgFormat() == CallFlags::Spread ||
11505 flags_.getArgFormat() == CallFlags::FunApplyArray) {
11506 switch (native) {
11507 case InlinableNative::MathMin:
11508 return tryAttachSpreadMathMinMax(/*isMax = */ false);
11509 case InlinableNative::MathMax:
11510 return tryAttachSpreadMathMinMax(/*isMax = */ true);
11511 default:
11512 break;
11514 return AttachDecision::NoAction;
11517 MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard ||
11518 flags_.getArgFormat() == CallFlags::FunCall);
11520 // Check for special-cased native functions.
11521 switch (native) {
11522 // Array natives.
11523 case InlinableNative::Array:
11524 return tryAttachArrayConstructor();
11525 case InlinableNative::ArrayPush:
11526 return tryAttachArrayPush();
11527 case InlinableNative::ArrayPop:
11528 case InlinableNative::ArrayShift:
11529 return tryAttachArrayPopShift(native);
11530 case InlinableNative::ArrayJoin:
11531 return tryAttachArrayJoin();
11532 case InlinableNative::ArraySlice:
11533 return tryAttachArraySlice();
11534 case InlinableNative::ArrayIsArray:
11535 return tryAttachArrayIsArray();
11537 // DataView natives.
11538 case InlinableNative::DataViewGetInt8:
11539 return tryAttachDataViewGet(Scalar::Int8);
11540 case InlinableNative::DataViewGetUint8:
11541 return tryAttachDataViewGet(Scalar::Uint8);
11542 case InlinableNative::DataViewGetInt16:
11543 return tryAttachDataViewGet(Scalar::Int16);
11544 case InlinableNative::DataViewGetUint16:
11545 return tryAttachDataViewGet(Scalar::Uint16);
11546 case InlinableNative::DataViewGetInt32:
11547 return tryAttachDataViewGet(Scalar::Int32);
11548 case InlinableNative::DataViewGetUint32:
11549 return tryAttachDataViewGet(Scalar::Uint32);
11550 case InlinableNative::DataViewGetFloat32:
11551 return tryAttachDataViewGet(Scalar::Float32);
11552 case InlinableNative::DataViewGetFloat64:
11553 return tryAttachDataViewGet(Scalar::Float64);
11554 case InlinableNative::DataViewGetBigInt64:
11555 return tryAttachDataViewGet(Scalar::BigInt64);
11556 case InlinableNative::DataViewGetBigUint64:
11557 return tryAttachDataViewGet(Scalar::BigUint64);
11558 case InlinableNative::DataViewSetInt8:
11559 return tryAttachDataViewSet(Scalar::Int8);
11560 case InlinableNative::DataViewSetUint8:
11561 return tryAttachDataViewSet(Scalar::Uint8);
11562 case InlinableNative::DataViewSetInt16:
11563 return tryAttachDataViewSet(Scalar::Int16);
11564 case InlinableNative::DataViewSetUint16:
11565 return tryAttachDataViewSet(Scalar::Uint16);
11566 case InlinableNative::DataViewSetInt32:
11567 return tryAttachDataViewSet(Scalar::Int32);
11568 case InlinableNative::DataViewSetUint32:
11569 return tryAttachDataViewSet(Scalar::Uint32);
11570 case InlinableNative::DataViewSetFloat32:
11571 return tryAttachDataViewSet(Scalar::Float32);
11572 case InlinableNative::DataViewSetFloat64:
11573 return tryAttachDataViewSet(Scalar::Float64);
11574 case InlinableNative::DataViewSetBigInt64:
11575 return tryAttachDataViewSet(Scalar::BigInt64);
11576 case InlinableNative::DataViewSetBigUint64:
11577 return tryAttachDataViewSet(Scalar::BigUint64);
11579 // Function natives.
11580 case InlinableNative::FunctionBind:
11581 return tryAttachFunctionBind();
11583 // Intl natives.
11584 case InlinableNative::IntlGuardToCollator:
11585 case InlinableNative::IntlGuardToDateTimeFormat:
11586 case InlinableNative::IntlGuardToDisplayNames:
11587 case InlinableNative::IntlGuardToListFormat:
11588 case InlinableNative::IntlGuardToNumberFormat:
11589 case InlinableNative::IntlGuardToPluralRules:
11590 case InlinableNative::IntlGuardToRelativeTimeFormat:
11591 case InlinableNative::IntlGuardToSegmenter:
11592 case InlinableNative::IntlGuardToSegments:
11593 case InlinableNative::IntlGuardToSegmentIterator:
11594 return tryAttachGuardToClass(native);
11596 // Slot intrinsics.
11597 case InlinableNative::IntrinsicUnsafeGetReservedSlot:
11598 case InlinableNative::IntrinsicUnsafeGetObjectFromReservedSlot:
11599 case InlinableNative::IntrinsicUnsafeGetInt32FromReservedSlot:
11600 case InlinableNative::IntrinsicUnsafeGetStringFromReservedSlot:
11601 return tryAttachUnsafeGetReservedSlot(native);
11602 case InlinableNative::IntrinsicUnsafeSetReservedSlot:
11603 return tryAttachUnsafeSetReservedSlot();
11605 // Intrinsics.
11606 case InlinableNative::IntrinsicIsSuspendedGenerator:
11607 return tryAttachIsSuspendedGenerator();
11608 case InlinableNative::IntrinsicToObject:
11609 return tryAttachToObject();
11610 case InlinableNative::IntrinsicToInteger:
11611 return tryAttachToInteger();
11612 case InlinableNative::IntrinsicToLength:
11613 return tryAttachToLength();
11614 case InlinableNative::IntrinsicIsObject:
11615 return tryAttachIsObject();
11616 case InlinableNative::IntrinsicIsPackedArray:
11617 return tryAttachIsPackedArray();
11618 case InlinableNative::IntrinsicIsCallable:
11619 return tryAttachIsCallable();
11620 case InlinableNative::IntrinsicIsConstructor:
11621 return tryAttachIsConstructor();
11622 case InlinableNative::IntrinsicIsCrossRealmArrayConstructor:
11623 return tryAttachIsCrossRealmArrayConstructor();
11624 case InlinableNative::IntrinsicGuardToArrayIterator:
11625 case InlinableNative::IntrinsicGuardToMapIterator:
11626 case InlinableNative::IntrinsicGuardToSetIterator:
11627 case InlinableNative::IntrinsicGuardToStringIterator:
11628 case InlinableNative::IntrinsicGuardToRegExpStringIterator:
11629 case InlinableNative::IntrinsicGuardToWrapForValidIterator:
11630 case InlinableNative::IntrinsicGuardToIteratorHelper:
11631 case InlinableNative::IntrinsicGuardToAsyncIteratorHelper:
11632 return tryAttachGuardToClass(native);
11633 case InlinableNative::IntrinsicSubstringKernel:
11634 return tryAttachSubstringKernel();
11635 case InlinableNative::IntrinsicIsConstructing:
11636 return tryAttachIsConstructing();
11637 case InlinableNative::IntrinsicNewArrayIterator:
11638 return tryAttachNewArrayIterator();
11639 case InlinableNative::IntrinsicNewStringIterator:
11640 return tryAttachNewStringIterator();
11641 case InlinableNative::IntrinsicNewRegExpStringIterator:
11642 return tryAttachNewRegExpStringIterator();
11643 case InlinableNative::IntrinsicArrayIteratorPrototypeOptimizable:
11644 return tryAttachArrayIteratorPrototypeOptimizable();
11645 case InlinableNative::IntrinsicObjectHasPrototype:
11646 return tryAttachObjectHasPrototype();
11648 // RegExp natives.
11649 case InlinableNative::IsRegExpObject:
11650 return tryAttachHasClass(&RegExpObject::class_,
11651 /* isPossiblyWrapped = */ false);
11652 case InlinableNative::IsPossiblyWrappedRegExpObject:
11653 return tryAttachHasClass(&RegExpObject::class_,
11654 /* isPossiblyWrapped = */ true);
11655 case InlinableNative::RegExpMatcher:
11656 case InlinableNative::RegExpSearcher:
11657 return tryAttachRegExpMatcherSearcher(native);
11658 case InlinableNative::RegExpSearcherLastLimit:
11659 return tryAttachRegExpSearcherLastLimit();
11660 case InlinableNative::RegExpHasCaptureGroups:
11661 return tryAttachRegExpHasCaptureGroups();
11662 case InlinableNative::RegExpPrototypeOptimizable:
11663 return tryAttachRegExpPrototypeOptimizable();
11664 case InlinableNative::RegExpInstanceOptimizable:
11665 return tryAttachRegExpInstanceOptimizable();
11666 case InlinableNative::GetFirstDollarIndex:
11667 return tryAttachGetFirstDollarIndex();
11668 case InlinableNative::IntrinsicRegExpBuiltinExec:
11669 case InlinableNative::IntrinsicRegExpBuiltinExecForTest:
11670 return tryAttachIntrinsicRegExpBuiltinExec(native);
11671 case InlinableNative::IntrinsicRegExpExec:
11672 case InlinableNative::IntrinsicRegExpExecForTest:
11673 return tryAttachIntrinsicRegExpExec(native);
11675 // String natives.
11676 case InlinableNative::String:
11677 return tryAttachString();
11678 case InlinableNative::StringToString:
11679 case InlinableNative::StringValueOf:
11680 return tryAttachStringToStringValueOf();
11681 case InlinableNative::StringCharCodeAt:
11682 return tryAttachStringCharCodeAt();
11683 case InlinableNative::StringCodePointAt:
11684 return tryAttachStringCodePointAt();
11685 case InlinableNative::StringCharAt:
11686 return tryAttachStringCharAt();
11687 case InlinableNative::StringAt:
11688 return tryAttachStringAt();
11689 case InlinableNative::StringFromCharCode:
11690 return tryAttachStringFromCharCode();
11691 case InlinableNative::StringFromCodePoint:
11692 return tryAttachStringFromCodePoint();
11693 case InlinableNative::StringIncludes:
11694 return tryAttachStringIncludes();
11695 case InlinableNative::StringIndexOf:
11696 return tryAttachStringIndexOf();
11697 case InlinableNative::StringLastIndexOf:
11698 return tryAttachStringLastIndexOf();
11699 case InlinableNative::StringStartsWith:
11700 return tryAttachStringStartsWith();
11701 case InlinableNative::StringEndsWith:
11702 return tryAttachStringEndsWith();
11703 case InlinableNative::StringToLowerCase:
11704 return tryAttachStringToLowerCase();
11705 case InlinableNative::StringToUpperCase:
11706 return tryAttachStringToUpperCase();
11707 case InlinableNative::StringTrim:
11708 return tryAttachStringTrim();
11709 case InlinableNative::StringTrimStart:
11710 return tryAttachStringTrimStart();
11711 case InlinableNative::StringTrimEnd:
11712 return tryAttachStringTrimEnd();
11713 case InlinableNative::IntrinsicStringReplaceString:
11714 return tryAttachStringReplaceString();
11715 case InlinableNative::IntrinsicStringSplitString:
11716 return tryAttachStringSplitString();
11718 // Math natives.
11719 case InlinableNative::MathRandom:
11720 return tryAttachMathRandom();
11721 case InlinableNative::MathAbs:
11722 return tryAttachMathAbs();
11723 case InlinableNative::MathClz32:
11724 return tryAttachMathClz32();
11725 case InlinableNative::MathSign:
11726 return tryAttachMathSign();
11727 case InlinableNative::MathImul:
11728 return tryAttachMathImul();
11729 case InlinableNative::MathFloor:
11730 return tryAttachMathFloor();
11731 case InlinableNative::MathCeil:
11732 return tryAttachMathCeil();
11733 case InlinableNative::MathTrunc:
11734 return tryAttachMathTrunc();
11735 case InlinableNative::MathRound:
11736 return tryAttachMathRound();
11737 case InlinableNative::MathSqrt:
11738 return tryAttachMathSqrt();
11739 case InlinableNative::MathFRound:
11740 return tryAttachMathFRound();
11741 case InlinableNative::MathHypot:
11742 return tryAttachMathHypot();
11743 case InlinableNative::MathATan2:
11744 return tryAttachMathATan2();
11745 case InlinableNative::MathSin:
11746 return tryAttachMathFunction(UnaryMathFunction::SinNative);
11747 case InlinableNative::MathTan:
11748 return tryAttachMathFunction(UnaryMathFunction::TanNative);
11749 case InlinableNative::MathCos:
11750 return tryAttachMathFunction(UnaryMathFunction::CosNative);
11751 case InlinableNative::MathExp:
11752 return tryAttachMathFunction(UnaryMathFunction::Exp);
11753 case InlinableNative::MathLog:
11754 return tryAttachMathFunction(UnaryMathFunction::Log);
11755 case InlinableNative::MathASin:
11756 return tryAttachMathFunction(UnaryMathFunction::ASin);
11757 case InlinableNative::MathATan:
11758 return tryAttachMathFunction(UnaryMathFunction::ATan);
11759 case InlinableNative::MathACos:
11760 return tryAttachMathFunction(UnaryMathFunction::ACos);
11761 case InlinableNative::MathLog10:
11762 return tryAttachMathFunction(UnaryMathFunction::Log10);
11763 case InlinableNative::MathLog2:
11764 return tryAttachMathFunction(UnaryMathFunction::Log2);
11765 case InlinableNative::MathLog1P:
11766 return tryAttachMathFunction(UnaryMathFunction::Log1P);
11767 case InlinableNative::MathExpM1:
11768 return tryAttachMathFunction(UnaryMathFunction::ExpM1);
11769 case InlinableNative::MathCosH:
11770 return tryAttachMathFunction(UnaryMathFunction::CosH);
11771 case InlinableNative::MathSinH:
11772 return tryAttachMathFunction(UnaryMathFunction::SinH);
11773 case InlinableNative::MathTanH:
11774 return tryAttachMathFunction(UnaryMathFunction::TanH);
11775 case InlinableNative::MathACosH:
11776 return tryAttachMathFunction(UnaryMathFunction::ACosH);
11777 case InlinableNative::MathASinH:
11778 return tryAttachMathFunction(UnaryMathFunction::ASinH);
11779 case InlinableNative::MathATanH:
11780 return tryAttachMathFunction(UnaryMathFunction::ATanH);
11781 case InlinableNative::MathCbrt:
11782 return tryAttachMathFunction(UnaryMathFunction::Cbrt);
11783 case InlinableNative::MathPow:
11784 return tryAttachMathPow();
11785 case InlinableNative::MathMin:
11786 return tryAttachMathMinMax(/* isMax = */ false);
11787 case InlinableNative::MathMax:
11788 return tryAttachMathMinMax(/* isMax = */ true);
11790 // Map intrinsics.
11791 case InlinableNative::IntrinsicGuardToMapObject:
11792 return tryAttachGuardToClass(GuardClassKind::Map);
11793 case InlinableNative::IntrinsicGetNextMapEntryForIterator:
11794 return tryAttachGetNextMapSetEntryForIterator(/* isMap = */ true);
11796 // Number natives.
11797 case InlinableNative::Number:
11798 return tryAttachNumber();
11799 case InlinableNative::NumberParseInt:
11800 return tryAttachNumberParseInt();
11801 case InlinableNative::NumberToString:
11802 return tryAttachNumberToString();
11804 // Object natives.
11805 case InlinableNative::Object:
11806 return tryAttachObjectConstructor();
11807 case InlinableNative::ObjectCreate:
11808 return tryAttachObjectCreate();
11809 case InlinableNative::ObjectIs:
11810 return tryAttachObjectIs();
11811 case InlinableNative::ObjectIsPrototypeOf:
11812 return tryAttachObjectIsPrototypeOf();
11813 case InlinableNative::ObjectKeys:
11814 return tryAttachObjectKeys();
11815 case InlinableNative::ObjectToString:
11816 return tryAttachObjectToString();
11818 // Set intrinsics.
11819 case InlinableNative::IntrinsicGuardToSetObject:
11820 return tryAttachGuardToClass(GuardClassKind::Set);
11821 case InlinableNative::IntrinsicGetNextSetEntryForIterator:
11822 return tryAttachGetNextMapSetEntryForIterator(/* isMap = */ false);
11824 // ArrayBuffer intrinsics.
11825 case InlinableNative::IntrinsicGuardToArrayBuffer:
11826 return tryAttachGuardToArrayBuffer();
11827 case InlinableNative::IntrinsicArrayBufferByteLength:
11828 return tryAttachArrayBufferByteLength(/* isPossiblyWrapped = */ false);
11829 case InlinableNative::IntrinsicPossiblyWrappedArrayBufferByteLength:
11830 return tryAttachArrayBufferByteLength(/* isPossiblyWrapped = */ true);
11832 // SharedArrayBuffer intrinsics.
11833 case InlinableNative::IntrinsicGuardToSharedArrayBuffer:
11834 return tryAttachGuardToSharedArrayBuffer();
11836 // TypedArray intrinsics.
11837 case InlinableNative::TypedArrayConstructor:
11838 return AttachDecision::NoAction; // Not callable.
11839 case InlinableNative::IntrinsicIsTypedArray:
11840 return tryAttachIsTypedArray(/* isPossiblyWrapped = */ false);
11841 case InlinableNative::IntrinsicIsPossiblyWrappedTypedArray:
11842 return tryAttachIsTypedArray(/* isPossiblyWrapped = */ true);
11843 case InlinableNative::IntrinsicIsTypedArrayConstructor:
11844 return tryAttachIsTypedArrayConstructor();
11845 case InlinableNative::IntrinsicTypedArrayByteOffset:
11846 return tryAttachTypedArrayByteOffset();
11847 case InlinableNative::IntrinsicTypedArrayElementSize:
11848 return tryAttachTypedArrayElementSize();
11849 case InlinableNative::IntrinsicTypedArrayLength:
11850 return tryAttachTypedArrayLength(/* isPossiblyWrapped = */ false,
11851 /* allowOutOfBounds = */ false);
11852 case InlinableNative::IntrinsicTypedArrayLengthZeroOnOutOfBounds:
11853 return tryAttachTypedArrayLength(/* isPossiblyWrapped = */ false,
11854 /* allowOutOfBounds = */ true);
11855 case InlinableNative::IntrinsicPossiblyWrappedTypedArrayLength:
11856 return tryAttachTypedArrayLength(/* isPossiblyWrapped = */ true,
11857 /* allowOutOfBounds = */ false);
11859 // Reflect natives.
11860 case InlinableNative::ReflectGetPrototypeOf:
11861 return tryAttachReflectGetPrototypeOf();
11863 // Atomics intrinsics:
11864 case InlinableNative::AtomicsCompareExchange:
11865 return tryAttachAtomicsCompareExchange();
11866 case InlinableNative::AtomicsExchange:
11867 return tryAttachAtomicsExchange();
11868 case InlinableNative::AtomicsAdd:
11869 return tryAttachAtomicsAdd();
11870 case InlinableNative::AtomicsSub:
11871 return tryAttachAtomicsSub();
11872 case InlinableNative::AtomicsAnd:
11873 return tryAttachAtomicsAnd();
11874 case InlinableNative::AtomicsOr:
11875 return tryAttachAtomicsOr();
11876 case InlinableNative::AtomicsXor:
11877 return tryAttachAtomicsXor();
11878 case InlinableNative::AtomicsLoad:
11879 return tryAttachAtomicsLoad();
11880 case InlinableNative::AtomicsStore:
11881 return tryAttachAtomicsStore();
11882 case InlinableNative::AtomicsIsLockFree:
11883 return tryAttachAtomicsIsLockFree();
11885 // BigInt natives.
11886 case InlinableNative::BigIntAsIntN:
11887 return tryAttachBigIntAsIntN();
11888 case InlinableNative::BigIntAsUintN:
11889 return tryAttachBigIntAsUintN();
11891 // Boolean natives.
11892 case InlinableNative::Boolean:
11893 return tryAttachBoolean();
11895 // Set natives.
11896 case InlinableNative::SetHas:
11897 return tryAttachSetHas();
11898 case InlinableNative::SetSize:
11899 return tryAttachSetSize();
11901 // Map natives.
11902 case InlinableNative::MapHas:
11903 return tryAttachMapHas();
11904 case InlinableNative::MapGet:
11905 return tryAttachMapGet();
11907 // Testing functions.
11908 case InlinableNative::TestBailout:
11909 if (js::SupportDifferentialTesting()) {
11910 return AttachDecision::NoAction;
11912 return tryAttachBailout();
11913 case InlinableNative::TestAssertFloat32:
11914 return tryAttachAssertFloat32();
11915 case InlinableNative::TestAssertRecoveredOnBailout:
11916 if (js::SupportDifferentialTesting()) {
11917 return AttachDecision::NoAction;
11919 return tryAttachAssertRecoveredOnBailout();
11921 #ifdef FUZZING_JS_FUZZILLI
11922 // Fuzzilli function
11923 case InlinableNative::FuzzilliHash:
11924 return tryAttachFuzzilliHash();
11925 #endif
11927 case InlinableNative::Limit:
11928 break;
11931 MOZ_CRASH("Shouldn't get here");
11934 // Remember the shape of the this object for any script being called as a
11935 // constructor, for later use during Ion compilation.
11936 ScriptedThisResult CallIRGenerator::getThisShapeForScripted(
11937 HandleFunction calleeFunc, Handle<JSObject*> newTarget,
11938 MutableHandle<Shape*> result) {
11939 // Some constructors allocate their own |this| object.
11940 if (calleeFunc->constructorNeedsUninitializedThis()) {
11941 return ScriptedThisResult::UninitializedThis;
11944 // Only attach a stub if the newTarget is a function with a
11945 // nonconfigurable prototype.
11946 if (!newTarget->is<JSFunction>() ||
11947 !newTarget->as<JSFunction>().hasNonConfigurablePrototypeDataProperty()) {
11948 return ScriptedThisResult::NoAction;
11951 AutoRealm ar(cx_, calleeFunc);
11952 Shape* thisShape = ThisShapeForFunction(cx_, calleeFunc, newTarget);
11953 if (!thisShape) {
11954 cx_->clearPendingException();
11955 return ScriptedThisResult::NoAction;
11958 MOZ_ASSERT(thisShape->realm() == calleeFunc->realm());
11959 result.set(thisShape);
11960 return ScriptedThisResult::PlainObjectShape;
11963 static bool CanOptimizeScriptedCall(JSFunction* callee, bool isConstructing) {
11964 if (!callee->hasJitEntry()) {
11965 return false;
11968 // If callee is not an interpreted constructor, we have to throw.
11969 if (isConstructing && !callee->isConstructor()) {
11970 return false;
11973 // Likewise, if the callee is a class constructor, we have to throw.
11974 if (!isConstructing && callee->isClassConstructor()) {
11975 return false;
11978 return true;
11981 void CallIRGenerator::emitCallScriptedGuards(ObjOperandId calleeObjId,
11982 JSFunction* calleeFunc,
11983 Int32OperandId argcId,
11984 CallFlags flags, Shape* thisShape,
11985 bool isBoundFunction) {
11986 bool isConstructing = flags.isConstructing();
11988 if (mode_ == ICState::Mode::Specialized) {
11989 MOZ_ASSERT_IF(isConstructing, thisShape || flags.needsUninitializedThis());
11991 // Ensure callee matches this stub's callee
11992 emitCalleeGuard(calleeObjId, calleeFunc);
11993 if (thisShape) {
11994 // Emit guards to ensure the newTarget's .prototype property is what we
11995 // expect. Note that getThisForScripted checked newTarget is a function
11996 // with a non-configurable .prototype data property.
11998 JSFunction* newTarget;
11999 ObjOperandId newTargetObjId;
12000 if (isBoundFunction) {
12001 newTarget = calleeFunc;
12002 newTargetObjId = calleeObjId;
12003 } else {
12004 newTarget = &newTarget_.toObject().as<JSFunction>();
12005 ValOperandId newTargetValId = writer.loadArgumentDynamicSlot(
12006 ArgumentKind::NewTarget, argcId, flags);
12007 newTargetObjId = writer.guardToObject(newTargetValId);
12010 Maybe<PropertyInfo> prop = newTarget->lookupPure(cx_->names().prototype);
12011 MOZ_ASSERT(prop.isSome());
12012 uint32_t slot = prop->slot();
12013 MOZ_ASSERT(slot >= newTarget->numFixedSlots(),
12014 "Stub code relies on this");
12016 writer.guardShape(newTargetObjId, newTarget->shape());
12018 const Value& value = newTarget->getSlot(slot);
12019 if (value.isObject()) {
12020 JSObject* prototypeObject = &value.toObject();
12022 ObjOperandId protoId = writer.loadObject(prototypeObject);
12023 writer.guardDynamicSlotIsSpecificObject(
12024 newTargetObjId, protoId, slot - newTarget->numFixedSlots());
12025 } else {
12026 writer.guardDynamicSlotIsNotObject(newTargetObjId,
12027 slot - newTarget->numFixedSlots());
12030 // Call metaScriptedThisShape before emitting the call, so that Warp can
12031 // use the shape to create the |this| object before transpiling the call.
12032 writer.metaScriptedThisShape(thisShape);
12034 } else {
12035 // Guard that object is a scripted function
12036 writer.guardClass(calleeObjId, GuardClassKind::JSFunction);
12037 writer.guardFunctionHasJitEntry(calleeObjId, isConstructing);
12039 if (isConstructing) {
12040 // If callee is not a constructor, we have to throw.
12041 writer.guardFunctionIsConstructor(calleeObjId);
12042 } else {
12043 // If callee is a class constructor, we have to throw.
12044 writer.guardNotClassConstructor(calleeObjId);
12049 AttachDecision CallIRGenerator::tryAttachCallScripted(
12050 HandleFunction calleeFunc) {
12051 MOZ_ASSERT(calleeFunc->hasJitEntry());
12053 if (calleeFunc->isWasmWithJitEntry()) {
12054 TRY_ATTACH(tryAttachWasmCall(calleeFunc));
12057 bool isSpecialized = mode_ == ICState::Mode::Specialized;
12059 bool isConstructing = IsConstructPC(pc_);
12060 bool isSpread = IsSpreadPC(pc_);
12061 bool isSameRealm = isSpecialized && cx_->realm() == calleeFunc->realm();
12062 CallFlags flags(isConstructing, isSpread, isSameRealm);
12064 if (!CanOptimizeScriptedCall(calleeFunc, isConstructing)) {
12065 return AttachDecision::NoAction;
12068 if (isConstructing && !calleeFunc->hasJitScript()) {
12069 // If we're constructing, require the callee to have a JitScript. This isn't
12070 // required for correctness but avoids allocating a template object below
12071 // for constructors that aren't hot. See bug 1419758.
12072 return AttachDecision::TemporarilyUnoptimizable;
12075 // Verify that spread calls have a reasonable number of arguments.
12076 if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) {
12077 return AttachDecision::NoAction;
12080 Rooted<Shape*> thisShape(cx_);
12081 if (isConstructing && isSpecialized) {
12082 Rooted<JSObject*> newTarget(cx_, &newTarget_.toObject());
12083 switch (getThisShapeForScripted(calleeFunc, newTarget, &thisShape)) {
12084 case ScriptedThisResult::PlainObjectShape:
12085 break;
12086 case ScriptedThisResult::UninitializedThis:
12087 flags.setNeedsUninitializedThis();
12088 break;
12089 case ScriptedThisResult::NoAction:
12090 return AttachDecision::NoAction;
12094 // Load argc.
12095 Int32OperandId argcId(writer.setInputOperandId(0));
12097 // Load the callee and ensure it is an object
12098 ValOperandId calleeValId =
12099 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
12100 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
12102 emitCallScriptedGuards(calleeObjId, calleeFunc, argcId, flags, thisShape,
12103 /* isBoundFunction = */ false);
12105 writer.callScriptedFunction(calleeObjId, argcId, flags,
12106 ClampFixedArgc(argc_));
12107 writer.returnFromIC();
12109 if (isSpecialized) {
12110 trackAttached("Call.CallScripted");
12111 } else {
12112 trackAttached("Call.CallAnyScripted");
12115 return AttachDecision::Attach;
12118 AttachDecision CallIRGenerator::tryAttachCallNative(HandleFunction calleeFunc) {
12119 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
12121 bool isSpecialized = mode_ == ICState::Mode::Specialized;
12123 bool isSpread = IsSpreadPC(pc_);
12124 bool isSameRealm = isSpecialized && cx_->realm() == calleeFunc->realm();
12125 bool isConstructing = IsConstructPC(pc_);
12126 CallFlags flags(isConstructing, isSpread, isSameRealm);
12128 if (isConstructing && !calleeFunc->isConstructor()) {
12129 return AttachDecision::NoAction;
12132 // Verify that spread calls have a reasonable number of arguments.
12133 if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) {
12134 return AttachDecision::NoAction;
12137 // Check for specific native-function optimizations.
12138 if (isSpecialized) {
12139 TRY_ATTACH(tryAttachInlinableNative(calleeFunc, flags));
12142 // Load argc.
12143 Int32OperandId argcId(writer.setInputOperandId(0));
12145 // Load the callee and ensure it is an object
12146 ValOperandId calleeValId =
12147 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
12148 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
12150 // DOM calls need an additional guard so only try optimizing the first stub.
12151 // Can only optimize normal (non-spread) calls.
12152 if (isFirstStub_ && !isSpread && thisval_.isObject() &&
12153 CanAttachDOMCall(cx_, JSJitInfo::Method, &thisval_.toObject(), calleeFunc,
12154 mode_)) {
12155 MOZ_ASSERT(!isConstructing, "DOM functions are not constructors");
12157 // Guard that |this| is an object.
12158 ValOperandId thisValId =
12159 writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId, flags);
12160 ObjOperandId thisObjId = writer.guardToObject(thisValId);
12162 // Guard on the |this| shape to make sure it's the right instance. This also
12163 // ensures DOM_OBJECT_SLOT is stored in a fixed slot. See CanAttachDOMCall.
12164 writer.guardShape(thisObjId, thisval_.toObject().shape());
12166 // Ensure callee matches this stub's callee
12167 writer.guardSpecificFunction(calleeObjId, calleeFunc);
12168 writer.callDOMFunction(calleeObjId, argcId, thisObjId, calleeFunc, flags,
12169 ClampFixedArgc(argc_));
12171 trackAttached("Call.CallDOM");
12172 } else if (isSpecialized) {
12173 // Ensure callee matches this stub's callee
12174 writer.guardSpecificFunction(calleeObjId, calleeFunc);
12175 writer.callNativeFunction(calleeObjId, argcId, op_, calleeFunc, flags,
12176 ClampFixedArgc(argc_));
12178 trackAttached("Call.CallNative");
12179 } else {
12180 // Guard that object is a native function
12181 writer.guardClass(calleeObjId, GuardClassKind::JSFunction);
12182 writer.guardFunctionHasNoJitEntry(calleeObjId);
12184 if (isConstructing) {
12185 // If callee is not a constructor, we have to throw.
12186 writer.guardFunctionIsConstructor(calleeObjId);
12187 } else {
12188 // If callee is a class constructor, we have to throw.
12189 writer.guardNotClassConstructor(calleeObjId);
12191 writer.callAnyNativeFunction(calleeObjId, argcId, flags,
12192 ClampFixedArgc(argc_));
12194 trackAttached("Call.CallAnyNative");
12197 writer.returnFromIC();
12199 return AttachDecision::Attach;
12202 AttachDecision CallIRGenerator::tryAttachCallHook(HandleObject calleeObj) {
12203 if (mode_ != ICState::Mode::Specialized) {
12204 // We do not have megamorphic call hook stubs.
12205 // TODO: Should we attach specialized call hook stubs in
12206 // megamorphic mode to avoid going generic?
12207 return AttachDecision::NoAction;
12210 bool isSpread = IsSpreadPC(pc_);
12211 bool isConstructing = IsConstructPC(pc_);
12212 CallFlags flags(isConstructing, isSpread);
12213 JSNative hook =
12214 isConstructing ? calleeObj->constructHook() : calleeObj->callHook();
12215 if (!hook) {
12216 return AttachDecision::NoAction;
12219 // Bound functions have a JSClass construct hook but are not always
12220 // constructors.
12221 if (isConstructing && !calleeObj->isConstructor()) {
12222 return AttachDecision::NoAction;
12225 // We don't support spread calls in the transpiler yet.
12226 if (isSpread) {
12227 return AttachDecision::NoAction;
12230 // Load argc.
12231 Int32OperandId argcId(writer.setInputOperandId(0));
12233 // Load the callee and ensure it is an object
12234 ValOperandId calleeValId =
12235 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
12236 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
12238 // Ensure the callee's class matches the one in this stub.
12239 writer.guardAnyClass(calleeObjId, calleeObj->getClass());
12241 if (isConstructing && calleeObj->is<BoundFunctionObject>()) {
12242 writer.guardBoundFunctionIsConstructor(calleeObjId);
12245 writer.callClassHook(calleeObjId, argcId, hook, flags, ClampFixedArgc(argc_));
12246 writer.returnFromIC();
12248 trackAttached("Call.CallHook");
12250 return AttachDecision::Attach;
12253 AttachDecision CallIRGenerator::tryAttachBoundFunction(
12254 Handle<BoundFunctionObject*> calleeObj) {
12255 // The target must be a JSFunction with a JitEntry.
12256 if (!calleeObj->getTarget()->is<JSFunction>()) {
12257 return AttachDecision::NoAction;
12260 bool isSpread = IsSpreadPC(pc_);
12261 bool isConstructing = IsConstructPC(pc_);
12263 // Spread calls are not supported yet.
12264 if (isSpread) {
12265 return AttachDecision::NoAction;
12268 Rooted<JSFunction*> target(cx_, &calleeObj->getTarget()->as<JSFunction>());
12269 if (!CanOptimizeScriptedCall(target, isConstructing)) {
12270 return AttachDecision::NoAction;
12273 // Limit the number of bound arguments to prevent us from compiling many
12274 // different stubs (we bake in numBoundArgs and it's usually very small).
12275 static constexpr size_t MaxBoundArgs = 10;
12276 size_t numBoundArgs = calleeObj->numBoundArgs();
12277 if (numBoundArgs > MaxBoundArgs) {
12278 return AttachDecision::NoAction;
12281 // Ensure we don't exceed JIT_ARGS_LENGTH_MAX.
12282 if (numBoundArgs + argc_ > JIT_ARGS_LENGTH_MAX) {
12283 return AttachDecision::NoAction;
12286 CallFlags flags(isConstructing, isSpread);
12288 if (mode_ == ICState::Mode::Specialized) {
12289 if (cx_->realm() == target->realm()) {
12290 flags.setIsSameRealm();
12294 Rooted<Shape*> thisShape(cx_);
12295 if (isConstructing) {
12296 // Only optimize if newTarget == callee. This is the common case and ensures
12297 // we can always pass the bound function's target as newTarget.
12298 if (newTarget_ != ObjectValue(*calleeObj)) {
12299 return AttachDecision::NoAction;
12302 if (mode_ == ICState::Mode::Specialized) {
12303 Handle<JSFunction*> newTarget = target;
12304 switch (getThisShapeForScripted(target, newTarget, &thisShape)) {
12305 case ScriptedThisResult::PlainObjectShape:
12306 break;
12307 case ScriptedThisResult::UninitializedThis:
12308 flags.setNeedsUninitializedThis();
12309 break;
12310 case ScriptedThisResult::NoAction:
12311 return AttachDecision::NoAction;
12316 // Load argc.
12317 Int32OperandId argcId(writer.setInputOperandId(0));
12319 // Load the callee and ensure it's a bound function.
12320 ValOperandId calleeValId =
12321 writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
12322 ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
12323 writer.guardClass(calleeObjId, GuardClassKind::BoundFunction);
12325 // Ensure numBoundArgs matches.
12326 Int32OperandId numBoundArgsId = writer.loadBoundFunctionNumArgs(calleeObjId);
12327 writer.guardSpecificInt32(numBoundArgsId, numBoundArgs);
12329 if (isConstructing) {
12330 // Guard newTarget == callee. We depend on this in CallBoundScriptedFunction
12331 // and in emitCallScriptedGuards by using boundTarget as newTarget.
12332 ValOperandId newTargetValId =
12333 writer.loadArgumentDynamicSlot(ArgumentKind::NewTarget, argcId, flags);
12334 ObjOperandId newTargetObjId = writer.guardToObject(newTargetValId);
12335 writer.guardObjectIdentity(newTargetObjId, calleeObjId);
12338 ObjOperandId targetId = writer.loadBoundFunctionTarget(calleeObjId);
12340 emitCallScriptedGuards(targetId, target, argcId, flags, thisShape,
12341 /* isBoundFunction = */ true);
12343 writer.callBoundScriptedFunction(calleeObjId, targetId, argcId, flags,
12344 numBoundArgs);
12345 writer.returnFromIC();
12347 trackAttached("Call.BoundFunction");
12348 return AttachDecision::Attach;
12351 AttachDecision CallIRGenerator::tryAttachStub() {
12352 AutoAssertNoPendingException aanpe(cx_);
12354 // Some opcodes are not yet supported.
12355 switch (op_) {
12356 case JSOp::Call:
12357 case JSOp::CallContent:
12358 case JSOp::CallIgnoresRv:
12359 case JSOp::CallIter:
12360 case JSOp::CallContentIter:
12361 case JSOp::SpreadCall:
12362 case JSOp::New:
12363 case JSOp::NewContent:
12364 case JSOp::SpreadNew:
12365 case JSOp::SuperCall:
12366 case JSOp::SpreadSuperCall:
12367 break;
12368 default:
12369 return AttachDecision::NoAction;
12372 MOZ_ASSERT(mode_ != ICState::Mode::Generic);
12374 // Ensure callee is a function.
12375 if (!callee_.isObject()) {
12376 return AttachDecision::NoAction;
12379 RootedObject calleeObj(cx_, &callee_.toObject());
12380 if (calleeObj->is<BoundFunctionObject>()) {
12381 TRY_ATTACH(tryAttachBoundFunction(calleeObj.as<BoundFunctionObject>()));
12383 if (!calleeObj->is<JSFunction>()) {
12384 return tryAttachCallHook(calleeObj);
12387 HandleFunction calleeFunc = calleeObj.as<JSFunction>();
12389 // Check for scripted optimizations.
12390 if (calleeFunc->hasJitEntry()) {
12391 return tryAttachCallScripted(calleeFunc);
12394 // Check for native-function optimizations.
12395 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
12397 // Try inlining Function.prototype.{call,apply}. We don't use the
12398 // InlinableNative mechanism for this because we want to optimize these more
12399 // aggressively than other natives.
12400 if (op_ == JSOp::Call || op_ == JSOp::CallContent ||
12401 op_ == JSOp::CallIgnoresRv) {
12402 TRY_ATTACH(tryAttachFunCall(calleeFunc));
12403 TRY_ATTACH(tryAttachFunApply(calleeFunc));
12406 return tryAttachCallNative(calleeFunc);
12409 void CallIRGenerator::trackAttached(const char* name) {
12410 stubName_ = name ? name : "NotAttached";
12411 #ifdef JS_CACHEIR_SPEW
12412 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
12413 sp.valueProperty("callee", callee_);
12414 sp.valueProperty("thisval", thisval_);
12415 sp.valueProperty("argc", Int32Value(argc_));
12417 // Try to log the first two arguments.
12418 if (args_.length() >= 1) {
12419 sp.valueProperty("arg0", args_[0]);
12421 if (args_.length() >= 2) {
12422 sp.valueProperty("arg1", args_[1]);
12425 #endif
12428 // Class which holds a shape pointer for use when caches might reference data in
12429 // other zones.
12430 static const JSClass shapeContainerClass = {"ShapeContainer",
12431 JSCLASS_HAS_RESERVED_SLOTS(1)};
12433 static const size_t SHAPE_CONTAINER_SLOT = 0;
12435 static JSObject* NewWrapperWithObjectShape(JSContext* cx,
12436 Handle<NativeObject*> obj) {
12437 MOZ_ASSERT(cx->compartment() != obj->compartment());
12439 RootedObject wrapper(cx);
12441 AutoRealm ar(cx, obj);
12442 wrapper = NewBuiltinClassInstance(cx, &shapeContainerClass);
12443 if (!wrapper) {
12444 return nullptr;
12446 wrapper->as<NativeObject>().setReservedSlot(
12447 SHAPE_CONTAINER_SLOT, PrivateGCThingValue(obj->shape()));
12449 if (!JS_WrapObject(cx, &wrapper)) {
12450 return nullptr;
12452 MOZ_ASSERT(IsWrapper(wrapper));
12453 return wrapper;
12456 void jit::LoadShapeWrapperContents(MacroAssembler& masm, Register obj,
12457 Register dst, Label* failure) {
12458 masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), dst);
12459 Address privateAddr(dst,
12460 js::detail::ProxyReservedSlots::offsetOfPrivateSlot());
12461 masm.fallibleUnboxObject(privateAddr, dst, failure);
12462 masm.unboxNonDouble(
12463 Address(dst, NativeObject::getFixedSlotOffset(SHAPE_CONTAINER_SLOT)), dst,
12464 JSVAL_TYPE_PRIVATE_GCTHING);
12467 static bool CanConvertToInt32ForToNumber(const Value& v) {
12468 return v.isInt32() || v.isBoolean() || v.isNull();
12471 static Int32OperandId EmitGuardToInt32ForToNumber(CacheIRWriter& writer,
12472 ValOperandId id,
12473 const Value& v) {
12474 if (v.isInt32()) {
12475 return writer.guardToInt32(id);
12477 if (v.isNull()) {
12478 writer.guardIsNull(id);
12479 return writer.loadInt32Constant(0);
12481 MOZ_ASSERT(v.isBoolean());
12482 return writer.guardBooleanToInt32(id);
12485 static bool CanConvertToDoubleForToNumber(const Value& v) {
12486 return v.isNumber() || v.isBoolean() || v.isNullOrUndefined();
12489 static NumberOperandId EmitGuardToDoubleForToNumber(CacheIRWriter& writer,
12490 ValOperandId id,
12491 const Value& v) {
12492 if (v.isNumber()) {
12493 return writer.guardIsNumber(id);
12495 if (v.isBoolean()) {
12496 BooleanOperandId boolId = writer.guardToBoolean(id);
12497 return writer.booleanToNumber(boolId);
12499 if (v.isNull()) {
12500 writer.guardIsNull(id);
12501 return writer.loadDoubleConstant(0.0);
12503 MOZ_ASSERT(v.isUndefined());
12504 writer.guardIsUndefined(id);
12505 return writer.loadDoubleConstant(JS::GenericNaN());
12508 CompareIRGenerator::CompareIRGenerator(JSContext* cx, HandleScript script,
12509 jsbytecode* pc, ICState state, JSOp op,
12510 HandleValue lhsVal, HandleValue rhsVal)
12511 : IRGenerator(cx, script, pc, CacheKind::Compare, state),
12512 op_(op),
12513 lhsVal_(lhsVal),
12514 rhsVal_(rhsVal) {}
12516 AttachDecision CompareIRGenerator::tryAttachString(ValOperandId lhsId,
12517 ValOperandId rhsId) {
12518 if (!lhsVal_.isString() || !rhsVal_.isString()) {
12519 return AttachDecision::NoAction;
12522 StringOperandId lhsStrId = writer.guardToString(lhsId);
12523 StringOperandId rhsStrId = writer.guardToString(rhsId);
12524 writer.compareStringResult(op_, lhsStrId, rhsStrId);
12525 writer.returnFromIC();
12527 trackAttached("Compare.String");
12528 return AttachDecision::Attach;
12531 AttachDecision CompareIRGenerator::tryAttachObject(ValOperandId lhsId,
12532 ValOperandId rhsId) {
12533 MOZ_ASSERT(IsEqualityOp(op_));
12535 if (!lhsVal_.isObject() || !rhsVal_.isObject()) {
12536 return AttachDecision::NoAction;
12539 ObjOperandId lhsObjId = writer.guardToObject(lhsId);
12540 ObjOperandId rhsObjId = writer.guardToObject(rhsId);
12541 writer.compareObjectResult(op_, lhsObjId, rhsObjId);
12542 writer.returnFromIC();
12544 trackAttached("Compare.Object");
12545 return AttachDecision::Attach;
12548 AttachDecision CompareIRGenerator::tryAttachSymbol(ValOperandId lhsId,
12549 ValOperandId rhsId) {
12550 MOZ_ASSERT(IsEqualityOp(op_));
12552 if (!lhsVal_.isSymbol() || !rhsVal_.isSymbol()) {
12553 return AttachDecision::NoAction;
12556 SymbolOperandId lhsSymId = writer.guardToSymbol(lhsId);
12557 SymbolOperandId rhsSymId = writer.guardToSymbol(rhsId);
12558 writer.compareSymbolResult(op_, lhsSymId, rhsSymId);
12559 writer.returnFromIC();
12561 trackAttached("Compare.Symbol");
12562 return AttachDecision::Attach;
12565 AttachDecision CompareIRGenerator::tryAttachStrictDifferentTypes(
12566 ValOperandId lhsId, ValOperandId rhsId) {
12567 MOZ_ASSERT(IsEqualityOp(op_));
12569 if (op_ != JSOp::StrictEq && op_ != JSOp::StrictNe) {
12570 return AttachDecision::NoAction;
12573 // Probably can't hit some of these.
12574 if (SameType(lhsVal_, rhsVal_) ||
12575 (lhsVal_.isNumber() && rhsVal_.isNumber())) {
12576 return AttachDecision::NoAction;
12579 // Compare tags
12580 ValueTagOperandId lhsTypeId = writer.loadValueTag(lhsId);
12581 ValueTagOperandId rhsTypeId = writer.loadValueTag(rhsId);
12582 writer.guardTagNotEqual(lhsTypeId, rhsTypeId);
12584 // Now that we've passed the guard, we know differing types, so return the
12585 // bool result.
12586 writer.loadBooleanResult(op_ == JSOp::StrictNe ? true : false);
12587 writer.returnFromIC();
12589 trackAttached("Compare.StrictDifferentTypes");
12590 return AttachDecision::Attach;
12593 AttachDecision CompareIRGenerator::tryAttachInt32(ValOperandId lhsId,
12594 ValOperandId rhsId) {
12595 if (!CanConvertToInt32ForToNumber(lhsVal_) ||
12596 !CanConvertToInt32ForToNumber(rhsVal_)) {
12597 return AttachDecision::NoAction;
12600 // Strictly different types should have been handed by
12601 // tryAttachStrictDifferentTypes.
12602 MOZ_ASSERT_IF(op_ == JSOp::StrictEq || op_ == JSOp::StrictNe,
12603 lhsVal_.type() == rhsVal_.type());
12605 // Should have been handled by tryAttachAnyNullUndefined.
12606 MOZ_ASSERT_IF(lhsVal_.isNull() || rhsVal_.isNull(), !IsEqualityOp(op_));
12608 Int32OperandId lhsIntId = EmitGuardToInt32ForToNumber(writer, lhsId, lhsVal_);
12609 Int32OperandId rhsIntId = EmitGuardToInt32ForToNumber(writer, rhsId, rhsVal_);
12611 writer.compareInt32Result(op_, lhsIntId, rhsIntId);
12612 writer.returnFromIC();
12614 trackAttached("Compare.Int32");
12615 return AttachDecision::Attach;
12618 AttachDecision CompareIRGenerator::tryAttachNumber(ValOperandId lhsId,
12619 ValOperandId rhsId) {
12620 if (!CanConvertToDoubleForToNumber(lhsVal_) ||
12621 !CanConvertToDoubleForToNumber(rhsVal_)) {
12622 return AttachDecision::NoAction;
12625 // Strictly different types should have been handed by
12626 // tryAttachStrictDifferentTypes.
12627 MOZ_ASSERT_IF(op_ == JSOp::StrictEq || op_ == JSOp::StrictNe,
12628 lhsVal_.type() == rhsVal_.type() ||
12629 (lhsVal_.isNumber() && rhsVal_.isNumber()));
12631 // Should have been handled by tryAttachAnyNullUndefined.
12632 MOZ_ASSERT_IF(lhsVal_.isNullOrUndefined() || rhsVal_.isNullOrUndefined(),
12633 !IsEqualityOp(op_));
12635 NumberOperandId lhs = EmitGuardToDoubleForToNumber(writer, lhsId, lhsVal_);
12636 NumberOperandId rhs = EmitGuardToDoubleForToNumber(writer, rhsId, rhsVal_);
12637 writer.compareDoubleResult(op_, lhs, rhs);
12638 writer.returnFromIC();
12640 trackAttached("Compare.Number");
12641 return AttachDecision::Attach;
12644 AttachDecision CompareIRGenerator::tryAttachBigInt(ValOperandId lhsId,
12645 ValOperandId rhsId) {
12646 if (!lhsVal_.isBigInt() || !rhsVal_.isBigInt()) {
12647 return AttachDecision::NoAction;
12650 BigIntOperandId lhs = writer.guardToBigInt(lhsId);
12651 BigIntOperandId rhs = writer.guardToBigInt(rhsId);
12653 writer.compareBigIntResult(op_, lhs, rhs);
12654 writer.returnFromIC();
12656 trackAttached("Compare.BigInt");
12657 return AttachDecision::Attach;
12660 AttachDecision CompareIRGenerator::tryAttachAnyNullUndefined(
12661 ValOperandId lhsId, ValOperandId rhsId) {
12662 MOZ_ASSERT(IsEqualityOp(op_));
12664 // Either RHS or LHS needs to be null/undefined.
12665 if (!lhsVal_.isNullOrUndefined() && !rhsVal_.isNullOrUndefined()) {
12666 return AttachDecision::NoAction;
12669 // We assume that the side with null/undefined is usually constant, in
12670 // code like `if (x === undefined) { x = {}; }`.
12671 // That is why we don't attach when both sides are undefined/null,
12672 // because we would basically need to decide by chance which side is
12673 // the likely constant.
12674 // The actual generated code however handles null/undefined of course.
12675 if (lhsVal_.isNullOrUndefined() && rhsVal_.isNullOrUndefined()) {
12676 return AttachDecision::NoAction;
12679 if (rhsVal_.isNullOrUndefined()) {
12680 if (rhsVal_.isNull()) {
12681 writer.guardIsNull(rhsId);
12682 writer.compareNullUndefinedResult(op_, /* isUndefined */ false, lhsId);
12683 trackAttached("Compare.AnyNull");
12684 } else {
12685 writer.guardIsUndefined(rhsId);
12686 writer.compareNullUndefinedResult(op_, /* isUndefined */ true, lhsId);
12687 trackAttached("Compare.AnyUndefined");
12689 } else {
12690 if (lhsVal_.isNull()) {
12691 writer.guardIsNull(lhsId);
12692 writer.compareNullUndefinedResult(op_, /* isUndefined */ false, rhsId);
12693 trackAttached("Compare.NullAny");
12694 } else {
12695 writer.guardIsUndefined(lhsId);
12696 writer.compareNullUndefinedResult(op_, /* isUndefined */ true, rhsId);
12697 trackAttached("Compare.UndefinedAny");
12701 writer.returnFromIC();
12702 return AttachDecision::Attach;
12705 // Handle {null/undefined} x {null,undefined} equality comparisons
12706 AttachDecision CompareIRGenerator::tryAttachNullUndefined(ValOperandId lhsId,
12707 ValOperandId rhsId) {
12708 if (!lhsVal_.isNullOrUndefined() || !rhsVal_.isNullOrUndefined()) {
12709 return AttachDecision::NoAction;
12712 if (op_ == JSOp::Eq || op_ == JSOp::Ne) {
12713 writer.guardIsNullOrUndefined(lhsId);
12714 writer.guardIsNullOrUndefined(rhsId);
12715 // Sloppy equality means we actually only care about the op:
12716 writer.loadBooleanResult(op_ == JSOp::Eq);
12717 trackAttached("Compare.SloppyNullUndefined");
12718 } else {
12719 // Strict equality only hits this branch, and only in the
12720 // undef {!,=}== undef and null {!,=}== null cases.
12721 // The other cases should have hit tryAttachStrictDifferentTypes.
12722 MOZ_ASSERT(lhsVal_.isNull() == rhsVal_.isNull());
12723 lhsVal_.isNull() ? writer.guardIsNull(lhsId)
12724 : writer.guardIsUndefined(lhsId);
12725 rhsVal_.isNull() ? writer.guardIsNull(rhsId)
12726 : writer.guardIsUndefined(rhsId);
12727 writer.loadBooleanResult(op_ == JSOp::StrictEq);
12728 trackAttached("Compare.StrictNullUndefinedEquality");
12731 writer.returnFromIC();
12732 return AttachDecision::Attach;
12735 AttachDecision CompareIRGenerator::tryAttachStringNumber(ValOperandId lhsId,
12736 ValOperandId rhsId) {
12737 // Ensure String x {Number, Boolean, Null, Undefined}
12738 if (!(lhsVal_.isString() && CanConvertToDoubleForToNumber(rhsVal_)) &&
12739 !(rhsVal_.isString() && CanConvertToDoubleForToNumber(lhsVal_))) {
12740 return AttachDecision::NoAction;
12743 // Case should have been handled by tryAttachStrictDifferentTypes
12744 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
12746 auto createGuards = [&](const Value& v, ValOperandId vId) {
12747 if (v.isString()) {
12748 StringOperandId strId = writer.guardToString(vId);
12749 return writer.guardStringToNumber(strId);
12751 return EmitGuardToDoubleForToNumber(writer, vId, v);
12754 NumberOperandId lhsGuardedId = createGuards(lhsVal_, lhsId);
12755 NumberOperandId rhsGuardedId = createGuards(rhsVal_, rhsId);
12756 writer.compareDoubleResult(op_, lhsGuardedId, rhsGuardedId);
12757 writer.returnFromIC();
12759 trackAttached("Compare.StringNumber");
12760 return AttachDecision::Attach;
12763 AttachDecision CompareIRGenerator::tryAttachPrimitiveSymbol(
12764 ValOperandId lhsId, ValOperandId rhsId) {
12765 MOZ_ASSERT(IsEqualityOp(op_));
12767 // The set of primitive cases we want to handle here (excluding null,
12768 // undefined, and symbol)
12769 auto isPrimitive = [](const Value& x) {
12770 return x.isString() || x.isBoolean() || x.isNumber() || x.isBigInt();
12773 // Ensure Symbol x {String, Bool, Number, BigInt}.
12774 if (!(lhsVal_.isSymbol() && isPrimitive(rhsVal_)) &&
12775 !(rhsVal_.isSymbol() && isPrimitive(lhsVal_))) {
12776 return AttachDecision::NoAction;
12779 auto guardPrimitive = [&](const Value& v, ValOperandId id) {
12780 MOZ_ASSERT(isPrimitive(v));
12781 if (v.isNumber()) {
12782 writer.guardIsNumber(id);
12783 return;
12785 switch (v.extractNonDoubleType()) {
12786 case JSVAL_TYPE_STRING:
12787 writer.guardToString(id);
12788 return;
12789 case JSVAL_TYPE_BOOLEAN:
12790 writer.guardToBoolean(id);
12791 return;
12792 case JSVAL_TYPE_BIGINT:
12793 writer.guardToBigInt(id);
12794 return;
12795 default:
12796 MOZ_CRASH("unexpected type");
12797 return;
12801 if (lhsVal_.isSymbol()) {
12802 writer.guardToSymbol(lhsId);
12803 guardPrimitive(rhsVal_, rhsId);
12804 } else {
12805 guardPrimitive(lhsVal_, lhsId);
12806 writer.guardToSymbol(rhsId);
12809 // Comparing a primitive with symbol will always be true for Ne/StrictNe, and
12810 // always be false for other compare ops.
12811 writer.loadBooleanResult(op_ == JSOp::Ne || op_ == JSOp::StrictNe);
12812 writer.returnFromIC();
12814 trackAttached("Compare.PrimitiveSymbol");
12815 return AttachDecision::Attach;
12818 AttachDecision CompareIRGenerator::tryAttachBigIntInt32(ValOperandId lhsId,
12819 ValOperandId rhsId) {
12820 // Ensure BigInt x {Int32, Boolean, Null}.
12821 if (!(lhsVal_.isBigInt() && CanConvertToInt32ForToNumber(rhsVal_)) &&
12822 !(rhsVal_.isBigInt() && CanConvertToInt32ForToNumber(lhsVal_))) {
12823 return AttachDecision::NoAction;
12826 // Case should have been handled by tryAttachStrictDifferentTypes
12827 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
12829 if (lhsVal_.isBigInt()) {
12830 BigIntOperandId bigIntId = writer.guardToBigInt(lhsId);
12831 Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, rhsId, rhsVal_);
12833 writer.compareBigIntInt32Result(op_, bigIntId, intId);
12834 } else {
12835 Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, lhsId, lhsVal_);
12836 BigIntOperandId bigIntId = writer.guardToBigInt(rhsId);
12838 writer.compareBigIntInt32Result(ReverseCompareOp(op_), bigIntId, intId);
12840 writer.returnFromIC();
12842 trackAttached("Compare.BigIntInt32");
12843 return AttachDecision::Attach;
12846 AttachDecision CompareIRGenerator::tryAttachBigIntNumber(ValOperandId lhsId,
12847 ValOperandId rhsId) {
12848 // Ensure BigInt x {Number, Undefined}.
12849 if (!(lhsVal_.isBigInt() && CanConvertToDoubleForToNumber(rhsVal_)) &&
12850 !(rhsVal_.isBigInt() && CanConvertToDoubleForToNumber(lhsVal_))) {
12851 return AttachDecision::NoAction;
12854 // Case should have been handled by tryAttachStrictDifferentTypes
12855 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
12857 // Case should have been handled by tryAttachBigIntInt32.
12858 MOZ_ASSERT(!CanConvertToInt32ForToNumber(lhsVal_));
12859 MOZ_ASSERT(!CanConvertToInt32ForToNumber(rhsVal_));
12861 if (lhsVal_.isBigInt()) {
12862 BigIntOperandId bigIntId = writer.guardToBigInt(lhsId);
12863 NumberOperandId numId =
12864 EmitGuardToDoubleForToNumber(writer, rhsId, rhsVal_);
12866 writer.compareBigIntNumberResult(op_, bigIntId, numId);
12867 } else {
12868 NumberOperandId numId =
12869 EmitGuardToDoubleForToNumber(writer, lhsId, lhsVal_);
12870 BigIntOperandId bigIntId = writer.guardToBigInt(rhsId);
12872 writer.compareBigIntNumberResult(ReverseCompareOp(op_), bigIntId, numId);
12874 writer.returnFromIC();
12876 trackAttached("Compare.BigIntNumber");
12877 return AttachDecision::Attach;
12880 AttachDecision CompareIRGenerator::tryAttachBigIntString(ValOperandId lhsId,
12881 ValOperandId rhsId) {
12882 // Ensure BigInt x String.
12883 if (!(lhsVal_.isBigInt() && rhsVal_.isString()) &&
12884 !(rhsVal_.isBigInt() && lhsVal_.isString())) {
12885 return AttachDecision::NoAction;
12888 // Case should have been handled by tryAttachStrictDifferentTypes
12889 MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
12891 if (lhsVal_.isBigInt()) {
12892 BigIntOperandId bigIntId = writer.guardToBigInt(lhsId);
12893 StringOperandId strId = writer.guardToString(rhsId);
12895 writer.compareBigIntStringResult(op_, bigIntId, strId);
12896 } else {
12897 StringOperandId strId = writer.guardToString(lhsId);
12898 BigIntOperandId bigIntId = writer.guardToBigInt(rhsId);
12900 writer.compareBigIntStringResult(ReverseCompareOp(op_), bigIntId, strId);
12902 writer.returnFromIC();
12904 trackAttached("Compare.BigIntString");
12905 return AttachDecision::Attach;
12908 AttachDecision CompareIRGenerator::tryAttachStub() {
12909 MOZ_ASSERT(cacheKind_ == CacheKind::Compare);
12910 MOZ_ASSERT(IsEqualityOp(op_) || IsRelationalOp(op_));
12912 AutoAssertNoPendingException aanpe(cx_);
12914 constexpr uint8_t lhsIndex = 0;
12915 constexpr uint8_t rhsIndex = 1;
12917 ValOperandId lhsId(writer.setInputOperandId(lhsIndex));
12918 ValOperandId rhsId(writer.setInputOperandId(rhsIndex));
12920 // For sloppy equality ops, there are cases this IC does not handle:
12921 // - {Object} x {String, Symbol, Bool, Number, BigInt}.
12923 // For relational comparison ops, these cases aren't handled:
12924 // - Object x {String, Bool, Number, BigInt, Object, Null, Undefined}.
12925 // Note: |Symbol x any| always throws, so it doesn't need to be handled.
12927 // (The above lists omits the equivalent case {B} x {A} when {A} x {B} is
12928 // already present.)
12930 if (IsEqualityOp(op_)) {
12931 TRY_ATTACH(tryAttachObject(lhsId, rhsId));
12932 TRY_ATTACH(tryAttachSymbol(lhsId, rhsId));
12934 // Handles any (non null or undefined) comparison with null/undefined.
12935 TRY_ATTACH(tryAttachAnyNullUndefined(lhsId, rhsId));
12937 // This covers -strict- equality/inequality using a type tag check, so
12938 // catches all different type pairs outside of Numbers, which cannot be
12939 // checked on tags alone.
12940 TRY_ATTACH(tryAttachStrictDifferentTypes(lhsId, rhsId));
12942 TRY_ATTACH(tryAttachNullUndefined(lhsId, rhsId));
12944 TRY_ATTACH(tryAttachPrimitiveSymbol(lhsId, rhsId));
12947 // We want these to be last, to allow us to bypass the
12948 // strictly-different-types cases in the below attachment code
12949 TRY_ATTACH(tryAttachInt32(lhsId, rhsId));
12950 TRY_ATTACH(tryAttachNumber(lhsId, rhsId));
12951 TRY_ATTACH(tryAttachBigInt(lhsId, rhsId));
12952 TRY_ATTACH(tryAttachString(lhsId, rhsId));
12954 TRY_ATTACH(tryAttachStringNumber(lhsId, rhsId));
12956 TRY_ATTACH(tryAttachBigIntInt32(lhsId, rhsId));
12957 TRY_ATTACH(tryAttachBigIntNumber(lhsId, rhsId));
12958 TRY_ATTACH(tryAttachBigIntString(lhsId, rhsId));
12960 // Strict equality is always supported.
12961 MOZ_ASSERT(!IsStrictEqualityOp(op_));
12963 // Other operations are unsupported iff at least one operand is an object.
12964 MOZ_ASSERT(lhsVal_.isObject() || rhsVal_.isObject());
12966 trackAttached(IRGenerator::NotAttached);
12967 return AttachDecision::NoAction;
12970 void CompareIRGenerator::trackAttached(const char* name) {
12971 stubName_ = name ? name : "NotAttached";
12972 #ifdef JS_CACHEIR_SPEW
12973 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
12974 sp.valueProperty("lhs", lhsVal_);
12975 sp.valueProperty("rhs", rhsVal_);
12976 sp.opcodeProperty("op", op_);
12978 #endif
12981 ToBoolIRGenerator::ToBoolIRGenerator(JSContext* cx, HandleScript script,
12982 jsbytecode* pc, ICState state,
12983 HandleValue val)
12984 : IRGenerator(cx, script, pc, CacheKind::ToBool, state), val_(val) {}
12986 void ToBoolIRGenerator::trackAttached(const char* name) {
12987 stubName_ = name ? name : "NotAttached";
12988 #ifdef JS_CACHEIR_SPEW
12989 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
12990 sp.valueProperty("val", val_);
12992 #endif
12995 AttachDecision ToBoolIRGenerator::tryAttachStub() {
12996 AutoAssertNoPendingException aanpe(cx_);
12997 writer.setTypeData(TypeData(JSValueType(val_.type())));
12999 TRY_ATTACH(tryAttachBool());
13000 TRY_ATTACH(tryAttachInt32());
13001 TRY_ATTACH(tryAttachNumber());
13002 TRY_ATTACH(tryAttachString());
13003 TRY_ATTACH(tryAttachNullOrUndefined());
13004 TRY_ATTACH(tryAttachObject());
13005 TRY_ATTACH(tryAttachSymbol());
13006 TRY_ATTACH(tryAttachBigInt());
13008 trackAttached(IRGenerator::NotAttached);
13009 return AttachDecision::NoAction;
13012 AttachDecision ToBoolIRGenerator::tryAttachBool() {
13013 if (!val_.isBoolean()) {
13014 return AttachDecision::NoAction;
13017 ValOperandId valId(writer.setInputOperandId(0));
13018 writer.guardNonDoubleType(valId, ValueType::Boolean);
13019 writer.loadOperandResult(valId);
13020 writer.returnFromIC();
13021 trackAttached("ToBool.Bool");
13022 return AttachDecision::Attach;
13025 AttachDecision ToBoolIRGenerator::tryAttachInt32() {
13026 if (!val_.isInt32()) {
13027 return AttachDecision::NoAction;
13030 ValOperandId valId(writer.setInputOperandId(0));
13031 writer.guardNonDoubleType(valId, ValueType::Int32);
13032 writer.loadInt32TruthyResult(valId);
13033 writer.returnFromIC();
13034 trackAttached("ToBool.Int32");
13035 return AttachDecision::Attach;
13038 AttachDecision ToBoolIRGenerator::tryAttachNumber() {
13039 if (!val_.isNumber()) {
13040 return AttachDecision::NoAction;
13043 ValOperandId valId(writer.setInputOperandId(0));
13044 NumberOperandId numId = writer.guardIsNumber(valId);
13045 writer.loadDoubleTruthyResult(numId);
13046 writer.returnFromIC();
13047 trackAttached("ToBool.Number");
13048 return AttachDecision::Attach;
13051 AttachDecision ToBoolIRGenerator::tryAttachSymbol() {
13052 if (!val_.isSymbol()) {
13053 return AttachDecision::NoAction;
13056 ValOperandId valId(writer.setInputOperandId(0));
13057 writer.guardNonDoubleType(valId, ValueType::Symbol);
13058 writer.loadBooleanResult(true);
13059 writer.returnFromIC();
13060 trackAttached("ToBool.Symbol");
13061 return AttachDecision::Attach;
13064 AttachDecision ToBoolIRGenerator::tryAttachString() {
13065 if (!val_.isString()) {
13066 return AttachDecision::NoAction;
13069 ValOperandId valId(writer.setInputOperandId(0));
13070 StringOperandId strId = writer.guardToString(valId);
13071 writer.loadStringTruthyResult(strId);
13072 writer.returnFromIC();
13073 trackAttached("ToBool.String");
13074 return AttachDecision::Attach;
13077 AttachDecision ToBoolIRGenerator::tryAttachNullOrUndefined() {
13078 if (!val_.isNullOrUndefined()) {
13079 return AttachDecision::NoAction;
13082 ValOperandId valId(writer.setInputOperandId(0));
13083 writer.guardIsNullOrUndefined(valId);
13084 writer.loadBooleanResult(false);
13085 writer.returnFromIC();
13086 trackAttached("ToBool.NullOrUndefined");
13087 return AttachDecision::Attach;
13090 AttachDecision ToBoolIRGenerator::tryAttachObject() {
13091 if (!val_.isObject()) {
13092 return AttachDecision::NoAction;
13095 ValOperandId valId(writer.setInputOperandId(0));
13096 ObjOperandId objId = writer.guardToObject(valId);
13097 writer.loadObjectTruthyResult(objId);
13098 writer.returnFromIC();
13099 trackAttached("ToBool.Object");
13100 return AttachDecision::Attach;
13103 AttachDecision ToBoolIRGenerator::tryAttachBigInt() {
13104 if (!val_.isBigInt()) {
13105 return AttachDecision::NoAction;
13108 ValOperandId valId(writer.setInputOperandId(0));
13109 BigIntOperandId bigIntId = writer.guardToBigInt(valId);
13110 writer.loadBigIntTruthyResult(bigIntId);
13111 writer.returnFromIC();
13112 trackAttached("ToBool.BigInt");
13113 return AttachDecision::Attach;
13116 GetIntrinsicIRGenerator::GetIntrinsicIRGenerator(JSContext* cx,
13117 HandleScript script,
13118 jsbytecode* pc, ICState state,
13119 HandleValue val)
13120 : IRGenerator(cx, script, pc, CacheKind::GetIntrinsic, state), val_(val) {}
13122 void GetIntrinsicIRGenerator::trackAttached(const char* name) {
13123 stubName_ = name ? name : "NotAttached";
13124 #ifdef JS_CACHEIR_SPEW
13125 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
13126 sp.valueProperty("val", val_);
13128 #endif
13131 AttachDecision GetIntrinsicIRGenerator::tryAttachStub() {
13132 AutoAssertNoPendingException aanpe(cx_);
13133 writer.loadValueResult(val_);
13134 writer.returnFromIC();
13135 trackAttached("GetIntrinsic");
13136 return AttachDecision::Attach;
13139 UnaryArithIRGenerator::UnaryArithIRGenerator(JSContext* cx, HandleScript script,
13140 jsbytecode* pc, ICState state,
13141 JSOp op, HandleValue val,
13142 HandleValue res)
13143 : IRGenerator(cx, script, pc, CacheKind::UnaryArith, state),
13144 op_(op),
13145 val_(val),
13146 res_(res) {}
13148 void UnaryArithIRGenerator::trackAttached(const char* name) {
13149 stubName_ = name ? name : "NotAttached";
13150 #ifdef JS_CACHEIR_SPEW
13151 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
13152 sp.valueProperty("val", val_);
13153 sp.valueProperty("res", res_);
13155 #endif
13158 AttachDecision UnaryArithIRGenerator::tryAttachStub() {
13159 AutoAssertNoPendingException aanpe(cx_);
13160 TRY_ATTACH(tryAttachInt32());
13161 TRY_ATTACH(tryAttachNumber());
13162 TRY_ATTACH(tryAttachBitwise());
13163 TRY_ATTACH(tryAttachBigInt());
13164 TRY_ATTACH(tryAttachStringInt32());
13165 TRY_ATTACH(tryAttachStringNumber());
13167 trackAttached(IRGenerator::NotAttached);
13168 return AttachDecision::NoAction;
13171 AttachDecision UnaryArithIRGenerator::tryAttachInt32() {
13172 if (op_ == JSOp::BitNot) {
13173 return AttachDecision::NoAction;
13175 if (!CanConvertToInt32ForToNumber(val_) || !res_.isInt32()) {
13176 return AttachDecision::NoAction;
13179 ValOperandId valId(writer.setInputOperandId(0));
13181 Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, valId, val_);
13182 switch (op_) {
13183 case JSOp::Pos:
13184 writer.loadInt32Result(intId);
13185 trackAttached("UnaryArith.Int32Pos");
13186 break;
13187 case JSOp::Neg:
13188 writer.int32NegationResult(intId);
13189 trackAttached("UnaryArith.Int32Neg");
13190 break;
13191 case JSOp::Inc:
13192 writer.int32IncResult(intId);
13193 trackAttached("UnaryArith.Int32Inc");
13194 break;
13195 case JSOp::Dec:
13196 writer.int32DecResult(intId);
13197 trackAttached("UnaryArith.Int32Dec");
13198 break;
13199 case JSOp::ToNumeric:
13200 writer.loadInt32Result(intId);
13201 trackAttached("UnaryArith.Int32ToNumeric");
13202 break;
13203 default:
13204 MOZ_CRASH("unexpected OP");
13207 writer.returnFromIC();
13208 return AttachDecision::Attach;
13211 AttachDecision UnaryArithIRGenerator::tryAttachNumber() {
13212 if (op_ == JSOp::BitNot) {
13213 return AttachDecision::NoAction;
13215 if (!CanConvertToDoubleForToNumber(val_)) {
13216 return AttachDecision::NoAction;
13218 MOZ_ASSERT(res_.isNumber());
13220 ValOperandId valId(writer.setInputOperandId(0));
13221 NumberOperandId numId = EmitGuardToDoubleForToNumber(writer, valId, val_);
13223 switch (op_) {
13224 case JSOp::Pos:
13225 writer.loadDoubleResult(numId);
13226 trackAttached("UnaryArith.DoublePos");
13227 break;
13228 case JSOp::Neg:
13229 writer.doubleNegationResult(numId);
13230 trackAttached("UnaryArith.DoubleNeg");
13231 break;
13232 case JSOp::Inc:
13233 writer.doubleIncResult(numId);
13234 trackAttached("UnaryArith.DoubleInc");
13235 break;
13236 case JSOp::Dec:
13237 writer.doubleDecResult(numId);
13238 trackAttached("UnaryArith.DoubleDec");
13239 break;
13240 case JSOp::ToNumeric:
13241 writer.loadDoubleResult(numId);
13242 trackAttached("UnaryArith.DoubleToNumeric");
13243 break;
13244 default:
13245 MOZ_CRASH("Unexpected OP");
13248 writer.returnFromIC();
13249 return AttachDecision::Attach;
13252 static bool CanTruncateToInt32(const Value& val) {
13253 return val.isNumber() || val.isBoolean() || val.isNullOrUndefined() ||
13254 val.isString();
13257 // Convert type into int32 for the bitwise/shift operands.
13258 static Int32OperandId EmitTruncateToInt32Guard(CacheIRWriter& writer,
13259 ValOperandId id,
13260 const Value& val) {
13261 MOZ_ASSERT(CanTruncateToInt32(val));
13262 if (val.isInt32()) {
13263 return writer.guardToInt32(id);
13265 if (val.isBoolean()) {
13266 return writer.guardBooleanToInt32(id);
13268 if (val.isNullOrUndefined()) {
13269 writer.guardIsNullOrUndefined(id);
13270 return writer.loadInt32Constant(0);
13272 NumberOperandId numId;
13273 if (val.isString()) {
13274 StringOperandId strId = writer.guardToString(id);
13275 numId = writer.guardStringToNumber(strId);
13276 } else {
13277 MOZ_ASSERT(val.isDouble());
13278 numId = writer.guardIsNumber(id);
13280 return writer.truncateDoubleToUInt32(numId);
13283 AttachDecision UnaryArithIRGenerator::tryAttachBitwise() {
13284 // Only bitwise operators.
13285 if (op_ != JSOp::BitNot) {
13286 return AttachDecision::NoAction;
13289 // Check guard conditions
13290 if (!CanTruncateToInt32(val_)) {
13291 return AttachDecision::NoAction;
13294 // Bitwise operators always produce Int32 values.
13295 MOZ_ASSERT(res_.isInt32());
13297 ValOperandId valId(writer.setInputOperandId(0));
13298 Int32OperandId intId = EmitTruncateToInt32Guard(writer, valId, val_);
13299 writer.int32NotResult(intId);
13300 trackAttached("UnaryArith.BitwiseBitNot");
13302 writer.returnFromIC();
13303 return AttachDecision::Attach;
13306 AttachDecision UnaryArithIRGenerator::tryAttachBigInt() {
13307 if (!val_.isBigInt()) {
13308 return AttachDecision::NoAction;
13310 MOZ_ASSERT(res_.isBigInt());
13312 MOZ_ASSERT(op_ != JSOp::Pos,
13313 "Applying the unary + operator on BigInt values throws an error");
13315 ValOperandId valId(writer.setInputOperandId(0));
13316 BigIntOperandId bigIntId = writer.guardToBigInt(valId);
13317 switch (op_) {
13318 case JSOp::BitNot:
13319 writer.bigIntNotResult(bigIntId);
13320 trackAttached("UnaryArith.BigIntNot");
13321 break;
13322 case JSOp::Neg:
13323 writer.bigIntNegationResult(bigIntId);
13324 trackAttached("UnaryArith.BigIntNeg");
13325 break;
13326 case JSOp::Inc:
13327 writer.bigIntIncResult(bigIntId);
13328 trackAttached("UnaryArith.BigIntInc");
13329 break;
13330 case JSOp::Dec:
13331 writer.bigIntDecResult(bigIntId);
13332 trackAttached("UnaryArith.BigIntDec");
13333 break;
13334 case JSOp::ToNumeric:
13335 writer.loadBigIntResult(bigIntId);
13336 trackAttached("UnaryArith.BigIntToNumeric");
13337 break;
13338 default:
13339 MOZ_CRASH("Unexpected OP");
13342 writer.returnFromIC();
13343 return AttachDecision::Attach;
13346 AttachDecision UnaryArithIRGenerator::tryAttachStringInt32() {
13347 if (!val_.isString()) {
13348 return AttachDecision::NoAction;
13350 MOZ_ASSERT(res_.isNumber());
13352 // Case should have been handled by tryAttachBitwise.
13353 MOZ_ASSERT(op_ != JSOp::BitNot);
13355 if (!res_.isInt32()) {
13356 return AttachDecision::NoAction;
13359 ValOperandId valId(writer.setInputOperandId(0));
13360 StringOperandId stringId = writer.guardToString(valId);
13361 Int32OperandId intId = writer.guardStringToInt32(stringId);
13363 switch (op_) {
13364 case JSOp::Pos:
13365 writer.loadInt32Result(intId);
13366 trackAttached("UnaryArith.StringInt32Pos");
13367 break;
13368 case JSOp::Neg:
13369 writer.int32NegationResult(intId);
13370 trackAttached("UnaryArith.StringInt32Neg");
13371 break;
13372 case JSOp::Inc:
13373 writer.int32IncResult(intId);
13374 trackAttached("UnaryArith.StringInt32Inc");
13375 break;
13376 case JSOp::Dec:
13377 writer.int32DecResult(intId);
13378 trackAttached("UnaryArith.StringInt32Dec");
13379 break;
13380 case JSOp::ToNumeric:
13381 writer.loadInt32Result(intId);
13382 trackAttached("UnaryArith.StringInt32ToNumeric");
13383 break;
13384 default:
13385 MOZ_CRASH("Unexpected OP");
13388 writer.returnFromIC();
13389 return AttachDecision::Attach;
13392 AttachDecision UnaryArithIRGenerator::tryAttachStringNumber() {
13393 if (!val_.isString()) {
13394 return AttachDecision::NoAction;
13396 MOZ_ASSERT(res_.isNumber());
13398 // Case should have been handled by tryAttachBitwise.
13399 MOZ_ASSERT(op_ != JSOp::BitNot);
13401 ValOperandId valId(writer.setInputOperandId(0));
13402 StringOperandId stringId = writer.guardToString(valId);
13403 NumberOperandId numId = writer.guardStringToNumber(stringId);
13405 Int32OperandId truncatedId;
13406 switch (op_) {
13407 case JSOp::Pos:
13408 writer.loadDoubleResult(numId);
13409 trackAttached("UnaryArith.StringNumberPos");
13410 break;
13411 case JSOp::Neg:
13412 writer.doubleNegationResult(numId);
13413 trackAttached("UnaryArith.StringNumberNeg");
13414 break;
13415 case JSOp::Inc:
13416 writer.doubleIncResult(numId);
13417 trackAttached("UnaryArith.StringNumberInc");
13418 break;
13419 case JSOp::Dec:
13420 writer.doubleDecResult(numId);
13421 trackAttached("UnaryArith.StringNumberDec");
13422 break;
13423 case JSOp::ToNumeric:
13424 writer.loadDoubleResult(numId);
13425 trackAttached("UnaryArith.StringNumberToNumeric");
13426 break;
13427 default:
13428 MOZ_CRASH("Unexpected OP");
13431 writer.returnFromIC();
13432 return AttachDecision::Attach;
13435 ToPropertyKeyIRGenerator::ToPropertyKeyIRGenerator(JSContext* cx,
13436 HandleScript script,
13437 jsbytecode* pc,
13438 ICState state,
13439 HandleValue val)
13440 : IRGenerator(cx, script, pc, CacheKind::ToPropertyKey, state), val_(val) {}
13442 void ToPropertyKeyIRGenerator::trackAttached(const char* name) {
13443 stubName_ = name ? name : "NotAttached";
13444 #ifdef JS_CACHEIR_SPEW
13445 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
13446 sp.valueProperty("val", val_);
13448 #endif
13451 AttachDecision ToPropertyKeyIRGenerator::tryAttachStub() {
13452 AutoAssertNoPendingException aanpe(cx_);
13453 TRY_ATTACH(tryAttachInt32());
13454 TRY_ATTACH(tryAttachNumber());
13455 TRY_ATTACH(tryAttachString());
13456 TRY_ATTACH(tryAttachSymbol());
13458 trackAttached(IRGenerator::NotAttached);
13459 return AttachDecision::NoAction;
13462 AttachDecision ToPropertyKeyIRGenerator::tryAttachInt32() {
13463 if (!val_.isInt32()) {
13464 return AttachDecision::NoAction;
13467 ValOperandId valId(writer.setInputOperandId(0));
13469 Int32OperandId intId = writer.guardToInt32(valId);
13470 writer.loadInt32Result(intId);
13471 writer.returnFromIC();
13473 trackAttached("ToPropertyKey.Int32");
13474 return AttachDecision::Attach;
13477 AttachDecision ToPropertyKeyIRGenerator::tryAttachNumber() {
13478 if (!val_.isNumber()) {
13479 return AttachDecision::NoAction;
13482 // We allow negative zero here because ToPropertyKey(-0.0) is 0.
13483 int32_t unused;
13484 if (!mozilla::NumberEqualsInt32(val_.toNumber(), &unused)) {
13485 return AttachDecision::NoAction;
13488 ValOperandId valId(writer.setInputOperandId(0));
13490 Int32OperandId intId = writer.guardToInt32Index(valId);
13491 writer.loadInt32Result(intId);
13492 writer.returnFromIC();
13494 trackAttached("ToPropertyKey.Number");
13495 return AttachDecision::Attach;
13498 AttachDecision ToPropertyKeyIRGenerator::tryAttachString() {
13499 if (!val_.isString()) {
13500 return AttachDecision::NoAction;
13503 ValOperandId valId(writer.setInputOperandId(0));
13505 StringOperandId strId = writer.guardToString(valId);
13506 writer.loadStringResult(strId);
13507 writer.returnFromIC();
13509 trackAttached("ToPropertyKey.String");
13510 return AttachDecision::Attach;
13513 AttachDecision ToPropertyKeyIRGenerator::tryAttachSymbol() {
13514 if (!val_.isSymbol()) {
13515 return AttachDecision::NoAction;
13518 ValOperandId valId(writer.setInputOperandId(0));
13520 SymbolOperandId strId = writer.guardToSymbol(valId);
13521 writer.loadSymbolResult(strId);
13522 writer.returnFromIC();
13524 trackAttached("ToPropertyKey.Symbol");
13525 return AttachDecision::Attach;
13528 BinaryArithIRGenerator::BinaryArithIRGenerator(JSContext* cx,
13529 HandleScript script,
13530 jsbytecode* pc, ICState state,
13531 JSOp op, HandleValue lhs,
13532 HandleValue rhs, HandleValue res)
13533 : IRGenerator(cx, script, pc, CacheKind::BinaryArith, state),
13534 op_(op),
13535 lhs_(lhs),
13536 rhs_(rhs),
13537 res_(res) {}
13539 void BinaryArithIRGenerator::trackAttached(const char* name) {
13540 stubName_ = name ? name : "NotAttached";
13541 #ifdef JS_CACHEIR_SPEW
13542 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
13543 sp.opcodeProperty("op", op_);
13544 sp.valueProperty("rhs", rhs_);
13545 sp.valueProperty("lhs", lhs_);
13547 #endif
13550 AttachDecision BinaryArithIRGenerator::tryAttachStub() {
13551 AutoAssertNoPendingException aanpe(cx_);
13552 // Arithmetic operations with Int32 operands
13553 TRY_ATTACH(tryAttachInt32());
13555 // Bitwise operations with Int32/Double/Boolean/Null/Undefined/String
13556 // operands.
13557 TRY_ATTACH(tryAttachBitwise());
13559 // Arithmetic operations with Double operands. This needs to come after
13560 // tryAttachInt32, as the guards overlap, and we'd prefer to attach the
13561 // more specialized Int32 IC if it is possible.
13562 TRY_ATTACH(tryAttachDouble());
13564 // String x {String,Number,Boolean,Null,Undefined}
13565 TRY_ATTACH(tryAttachStringConcat());
13567 // String x Object
13568 TRY_ATTACH(tryAttachStringObjectConcat());
13570 // Arithmetic operations or bitwise operations with BigInt operands
13571 TRY_ATTACH(tryAttachBigInt());
13573 // Arithmetic operations (without addition) with String x Int32.
13574 TRY_ATTACH(tryAttachStringInt32Arith());
13576 // Arithmetic operations (without addition) with String x Number. This needs
13577 // to come after tryAttachStringInt32Arith, as the guards overlap, and we'd
13578 // prefer to attach the more specialized Int32 IC if it is possible.
13579 TRY_ATTACH(tryAttachStringNumberArith());
13581 trackAttached(IRGenerator::NotAttached);
13582 return AttachDecision::NoAction;
13585 AttachDecision BinaryArithIRGenerator::tryAttachBitwise() {
13586 // Only bit-wise and shifts.
13587 if (op_ != JSOp::BitOr && op_ != JSOp::BitXor && op_ != JSOp::BitAnd &&
13588 op_ != JSOp::Lsh && op_ != JSOp::Rsh && op_ != JSOp::Ursh) {
13589 return AttachDecision::NoAction;
13592 // Check guard conditions
13593 if (!CanTruncateToInt32(lhs_) || !CanTruncateToInt32(rhs_)) {
13594 return AttachDecision::NoAction;
13597 // All ops, with the exception of Ursh, produce Int32 values.
13598 MOZ_ASSERT_IF(op_ != JSOp::Ursh, res_.isInt32());
13600 ValOperandId lhsId(writer.setInputOperandId(0));
13601 ValOperandId rhsId(writer.setInputOperandId(1));
13603 Int32OperandId lhsIntId = EmitTruncateToInt32Guard(writer, lhsId, lhs_);
13604 Int32OperandId rhsIntId = EmitTruncateToInt32Guard(writer, rhsId, rhs_);
13606 switch (op_) {
13607 case JSOp::BitOr:
13608 writer.int32BitOrResult(lhsIntId, rhsIntId);
13609 trackAttached("BinaryArith.BitwiseBitOr");
13610 break;
13611 case JSOp::BitXor:
13612 writer.int32BitXorResult(lhsIntId, rhsIntId);
13613 trackAttached("BinaryArith.BitwiseBitXor");
13614 break;
13615 case JSOp::BitAnd:
13616 writer.int32BitAndResult(lhsIntId, rhsIntId);
13617 trackAttached("BinaryArith.BitwiseBitAnd");
13618 break;
13619 case JSOp::Lsh:
13620 writer.int32LeftShiftResult(lhsIntId, rhsIntId);
13621 trackAttached("BinaryArith.BitwiseLeftShift");
13622 break;
13623 case JSOp::Rsh:
13624 writer.int32RightShiftResult(lhsIntId, rhsIntId);
13625 trackAttached("BinaryArith.BitwiseRightShift");
13626 break;
13627 case JSOp::Ursh:
13628 writer.int32URightShiftResult(lhsIntId, rhsIntId, res_.isDouble());
13629 trackAttached("BinaryArith.BitwiseUnsignedRightShift");
13630 break;
13631 default:
13632 MOZ_CRASH("Unhandled op in tryAttachBitwise");
13635 writer.returnFromIC();
13636 return AttachDecision::Attach;
13639 AttachDecision BinaryArithIRGenerator::tryAttachDouble() {
13640 // Check valid opcodes
13641 if (op_ != JSOp::Add && op_ != JSOp::Sub && op_ != JSOp::Mul &&
13642 op_ != JSOp::Div && op_ != JSOp::Mod && op_ != JSOp::Pow) {
13643 return AttachDecision::NoAction;
13646 // Check guard conditions.
13647 if (!CanConvertToDoubleForToNumber(lhs_) ||
13648 !CanConvertToDoubleForToNumber(rhs_)) {
13649 return AttachDecision::NoAction;
13652 ValOperandId lhsId(writer.setInputOperandId(0));
13653 ValOperandId rhsId(writer.setInputOperandId(1));
13655 NumberOperandId lhs = EmitGuardToDoubleForToNumber(writer, lhsId, lhs_);
13656 NumberOperandId rhs = EmitGuardToDoubleForToNumber(writer, rhsId, rhs_);
13658 switch (op_) {
13659 case JSOp::Add:
13660 writer.doubleAddResult(lhs, rhs);
13661 trackAttached("BinaryArith.DoubleAdd");
13662 break;
13663 case JSOp::Sub:
13664 writer.doubleSubResult(lhs, rhs);
13665 trackAttached("BinaryArith.DoubleSub");
13666 break;
13667 case JSOp::Mul:
13668 writer.doubleMulResult(lhs, rhs);
13669 trackAttached("BinaryArith.DoubleMul");
13670 break;
13671 case JSOp::Div:
13672 writer.doubleDivResult(lhs, rhs);
13673 trackAttached("BinaryArith.DoubleDiv");
13674 break;
13675 case JSOp::Mod:
13676 writer.doubleModResult(lhs, rhs);
13677 trackAttached("BinaryArith.DoubleMod");
13678 break;
13679 case JSOp::Pow:
13680 writer.doublePowResult(lhs, rhs);
13681 trackAttached("BinaryArith.DoublePow");
13682 break;
13683 default:
13684 MOZ_CRASH("Unhandled Op");
13686 writer.returnFromIC();
13687 return AttachDecision::Attach;
13690 AttachDecision BinaryArithIRGenerator::tryAttachInt32() {
13691 // Check guard conditions.
13692 if (!CanConvertToInt32ForToNumber(lhs_) ||
13693 !CanConvertToInt32ForToNumber(rhs_)) {
13694 return AttachDecision::NoAction;
13697 // These ICs will failure() if result can't be encoded in an Int32:
13698 // If sample result is not Int32, we should avoid IC.
13699 if (!res_.isInt32()) {
13700 return AttachDecision::NoAction;
13703 if (op_ != JSOp::Add && op_ != JSOp::Sub && op_ != JSOp::Mul &&
13704 op_ != JSOp::Div && op_ != JSOp::Mod && op_ != JSOp::Pow) {
13705 return AttachDecision::NoAction;
13708 if (op_ == JSOp::Pow && !CanAttachInt32Pow(lhs_, rhs_)) {
13709 return AttachDecision::NoAction;
13712 ValOperandId lhsId(writer.setInputOperandId(0));
13713 ValOperandId rhsId(writer.setInputOperandId(1));
13715 Int32OperandId lhsIntId = EmitGuardToInt32ForToNumber(writer, lhsId, lhs_);
13716 Int32OperandId rhsIntId = EmitGuardToInt32ForToNumber(writer, rhsId, rhs_);
13718 switch (op_) {
13719 case JSOp::Add:
13720 writer.int32AddResult(lhsIntId, rhsIntId);
13721 trackAttached("BinaryArith.Int32Add");
13722 break;
13723 case JSOp::Sub:
13724 writer.int32SubResult(lhsIntId, rhsIntId);
13725 trackAttached("BinaryArith.Int32Sub");
13726 break;
13727 case JSOp::Mul:
13728 writer.int32MulResult(lhsIntId, rhsIntId);
13729 trackAttached("BinaryArith.Int32Mul");
13730 break;
13731 case JSOp::Div:
13732 writer.int32DivResult(lhsIntId, rhsIntId);
13733 trackAttached("BinaryArith.Int32Div");
13734 break;
13735 case JSOp::Mod:
13736 writer.int32ModResult(lhsIntId, rhsIntId);
13737 trackAttached("BinaryArith.Int32Mod");
13738 break;
13739 case JSOp::Pow:
13740 writer.int32PowResult(lhsIntId, rhsIntId);
13741 trackAttached("BinaryArith.Int32Pow");
13742 break;
13743 default:
13744 MOZ_CRASH("Unhandled op in tryAttachInt32");
13747 writer.returnFromIC();
13748 return AttachDecision::Attach;
13751 AttachDecision BinaryArithIRGenerator::tryAttachStringConcat() {
13752 // Only Addition
13753 if (op_ != JSOp::Add) {
13754 return AttachDecision::NoAction;
13757 // One side must be a string, the other side a primitive value we can easily
13758 // convert to a string.
13759 if (!(lhs_.isString() && CanConvertToString(rhs_)) &&
13760 !(CanConvertToString(lhs_) && rhs_.isString())) {
13761 return AttachDecision::NoAction;
13764 ValOperandId lhsId(writer.setInputOperandId(0));
13765 ValOperandId rhsId(writer.setInputOperandId(1));
13767 StringOperandId lhsStrId = emitToStringGuard(lhsId, lhs_);
13768 StringOperandId rhsStrId = emitToStringGuard(rhsId, rhs_);
13770 writer.callStringConcatResult(lhsStrId, rhsStrId);
13772 writer.returnFromIC();
13773 trackAttached("BinaryArith.StringConcat");
13774 return AttachDecision::Attach;
13777 AttachDecision BinaryArithIRGenerator::tryAttachStringObjectConcat() {
13778 // Only Addition
13779 if (op_ != JSOp::Add) {
13780 return AttachDecision::NoAction;
13783 // Check Guards
13784 if (!(lhs_.isObject() && rhs_.isString()) &&
13785 !(lhs_.isString() && rhs_.isObject()))
13786 return AttachDecision::NoAction;
13788 ValOperandId lhsId(writer.setInputOperandId(0));
13789 ValOperandId rhsId(writer.setInputOperandId(1));
13791 // This guard is actually overly tight, as the runtime
13792 // helper can handle lhs or rhs being a string, so long
13793 // as the other is an object.
13794 if (lhs_.isString()) {
13795 writer.guardToString(lhsId);
13796 writer.guardToObject(rhsId);
13797 } else {
13798 writer.guardToObject(lhsId);
13799 writer.guardToString(rhsId);
13802 writer.callStringObjectConcatResult(lhsId, rhsId);
13804 writer.returnFromIC();
13805 trackAttached("BinaryArith.StringObjectConcat");
13806 return AttachDecision::Attach;
13809 AttachDecision BinaryArithIRGenerator::tryAttachBigInt() {
13810 // Check Guards
13811 if (!lhs_.isBigInt() || !rhs_.isBigInt()) {
13812 return AttachDecision::NoAction;
13815 switch (op_) {
13816 case JSOp::Add:
13817 case JSOp::Sub:
13818 case JSOp::Mul:
13819 case JSOp::Div:
13820 case JSOp::Mod:
13821 case JSOp::Pow:
13822 // Arithmetic operations.
13823 break;
13825 case JSOp::BitOr:
13826 case JSOp::BitXor:
13827 case JSOp::BitAnd:
13828 case JSOp::Lsh:
13829 case JSOp::Rsh:
13830 // Bitwise operations.
13831 break;
13833 default:
13834 return AttachDecision::NoAction;
13837 ValOperandId lhsId(writer.setInputOperandId(0));
13838 ValOperandId rhsId(writer.setInputOperandId(1));
13840 BigIntOperandId lhsBigIntId = writer.guardToBigInt(lhsId);
13841 BigIntOperandId rhsBigIntId = writer.guardToBigInt(rhsId);
13843 switch (op_) {
13844 case JSOp::Add:
13845 writer.bigIntAddResult(lhsBigIntId, rhsBigIntId);
13846 trackAttached("BinaryArith.BigIntAdd");
13847 break;
13848 case JSOp::Sub:
13849 writer.bigIntSubResult(lhsBigIntId, rhsBigIntId);
13850 trackAttached("BinaryArith.BigIntSub");
13851 break;
13852 case JSOp::Mul:
13853 writer.bigIntMulResult(lhsBigIntId, rhsBigIntId);
13854 trackAttached("BinaryArith.BigIntMul");
13855 break;
13856 case JSOp::Div:
13857 writer.bigIntDivResult(lhsBigIntId, rhsBigIntId);
13858 trackAttached("BinaryArith.BigIntDiv");
13859 break;
13860 case JSOp::Mod:
13861 writer.bigIntModResult(lhsBigIntId, rhsBigIntId);
13862 trackAttached("BinaryArith.BigIntMod");
13863 break;
13864 case JSOp::Pow:
13865 writer.bigIntPowResult(lhsBigIntId, rhsBigIntId);
13866 trackAttached("BinaryArith.BigIntPow");
13867 break;
13868 case JSOp::BitOr:
13869 writer.bigIntBitOrResult(lhsBigIntId, rhsBigIntId);
13870 trackAttached("BinaryArith.BigIntBitOr");
13871 break;
13872 case JSOp::BitXor:
13873 writer.bigIntBitXorResult(lhsBigIntId, rhsBigIntId);
13874 trackAttached("BinaryArith.BigIntBitXor");
13875 break;
13876 case JSOp::BitAnd:
13877 writer.bigIntBitAndResult(lhsBigIntId, rhsBigIntId);
13878 trackAttached("BinaryArith.BigIntBitAnd");
13879 break;
13880 case JSOp::Lsh:
13881 writer.bigIntLeftShiftResult(lhsBigIntId, rhsBigIntId);
13882 trackAttached("BinaryArith.BigIntLeftShift");
13883 break;
13884 case JSOp::Rsh:
13885 writer.bigIntRightShiftResult(lhsBigIntId, rhsBigIntId);
13886 trackAttached("BinaryArith.BigIntRightShift");
13887 break;
13888 default:
13889 MOZ_CRASH("Unhandled op in tryAttachBigInt");
13892 writer.returnFromIC();
13893 return AttachDecision::Attach;
13896 AttachDecision BinaryArithIRGenerator::tryAttachStringInt32Arith() {
13897 // Check for either int32 x string or string x int32.
13898 if (!(lhs_.isInt32() && rhs_.isString()) &&
13899 !(lhs_.isString() && rhs_.isInt32())) {
13900 return AttachDecision::NoAction;
13903 // The created ICs will fail if the result can't be encoded as as int32.
13904 // Thus skip this IC, if the sample result is not an int32.
13905 if (!res_.isInt32()) {
13906 return AttachDecision::NoAction;
13909 // Must _not_ support Add, because it would be string concatenation instead.
13910 // For Pow we can't easily determine the CanAttachInt32Pow conditions so we
13911 // reject that as well.
13912 if (op_ != JSOp::Sub && op_ != JSOp::Mul && op_ != JSOp::Div &&
13913 op_ != JSOp::Mod) {
13914 return AttachDecision::NoAction;
13917 // The string operand must be convertable to an int32 value.
13918 JSString* str = lhs_.isString() ? lhs_.toString() : rhs_.toString();
13920 double num;
13921 if (!StringToNumber(cx_, str, &num)) {
13922 cx_->recoverFromOutOfMemory();
13923 return AttachDecision::NoAction;
13926 int32_t unused;
13927 if (!mozilla::NumberIsInt32(num, &unused)) {
13928 return AttachDecision::NoAction;
13931 ValOperandId lhsId(writer.setInputOperandId(0));
13932 ValOperandId rhsId(writer.setInputOperandId(1));
13934 auto guardToInt32 = [&](ValOperandId id, const Value& v) {
13935 if (v.isInt32()) {
13936 return writer.guardToInt32(id);
13939 MOZ_ASSERT(v.isString());
13940 StringOperandId strId = writer.guardToString(id);
13941 return writer.guardStringToInt32(strId);
13944 Int32OperandId lhsIntId = guardToInt32(lhsId, lhs_);
13945 Int32OperandId rhsIntId = guardToInt32(rhsId, rhs_);
13947 switch (op_) {
13948 case JSOp::Sub:
13949 writer.int32SubResult(lhsIntId, rhsIntId);
13950 trackAttached("BinaryArith.StringInt32Sub");
13951 break;
13952 case JSOp::Mul:
13953 writer.int32MulResult(lhsIntId, rhsIntId);
13954 trackAttached("BinaryArith.StringInt32Mul");
13955 break;
13956 case JSOp::Div:
13957 writer.int32DivResult(lhsIntId, rhsIntId);
13958 trackAttached("BinaryArith.StringInt32Div");
13959 break;
13960 case JSOp::Mod:
13961 writer.int32ModResult(lhsIntId, rhsIntId);
13962 trackAttached("BinaryArith.StringInt32Mod");
13963 break;
13964 default:
13965 MOZ_CRASH("Unhandled op in tryAttachStringInt32Arith");
13968 writer.returnFromIC();
13969 return AttachDecision::Attach;
13972 AttachDecision BinaryArithIRGenerator::tryAttachStringNumberArith() {
13973 // Check for either number x string or string x number.
13974 if (!(lhs_.isNumber() && rhs_.isString()) &&
13975 !(lhs_.isString() && rhs_.isNumber())) {
13976 return AttachDecision::NoAction;
13979 // Must _not_ support Add, because it would be string concatenation instead.
13980 if (op_ != JSOp::Sub && op_ != JSOp::Mul && op_ != JSOp::Div &&
13981 op_ != JSOp::Mod && op_ != JSOp::Pow) {
13982 return AttachDecision::NoAction;
13985 ValOperandId lhsId(writer.setInputOperandId(0));
13986 ValOperandId rhsId(writer.setInputOperandId(1));
13988 auto guardToNumber = [&](ValOperandId id, const Value& v) {
13989 if (v.isNumber()) {
13990 return writer.guardIsNumber(id);
13993 MOZ_ASSERT(v.isString());
13994 StringOperandId strId = writer.guardToString(id);
13995 return writer.guardStringToNumber(strId);
13998 NumberOperandId lhsIntId = guardToNumber(lhsId, lhs_);
13999 NumberOperandId rhsIntId = guardToNumber(rhsId, rhs_);
14001 switch (op_) {
14002 case JSOp::Sub:
14003 writer.doubleSubResult(lhsIntId, rhsIntId);
14004 trackAttached("BinaryArith.StringNumberSub");
14005 break;
14006 case JSOp::Mul:
14007 writer.doubleMulResult(lhsIntId, rhsIntId);
14008 trackAttached("BinaryArith.StringNumberMul");
14009 break;
14010 case JSOp::Div:
14011 writer.doubleDivResult(lhsIntId, rhsIntId);
14012 trackAttached("BinaryArith.StringNumberDiv");
14013 break;
14014 case JSOp::Mod:
14015 writer.doubleModResult(lhsIntId, rhsIntId);
14016 trackAttached("BinaryArith.StringNumberMod");
14017 break;
14018 case JSOp::Pow:
14019 writer.doublePowResult(lhsIntId, rhsIntId);
14020 trackAttached("BinaryArith.StringNumberPow");
14021 break;
14022 default:
14023 MOZ_CRASH("Unhandled op in tryAttachStringNumberArith");
14026 writer.returnFromIC();
14027 return AttachDecision::Attach;
14030 NewArrayIRGenerator::NewArrayIRGenerator(JSContext* cx, HandleScript script,
14031 jsbytecode* pc, ICState state, JSOp op,
14032 HandleObject templateObj,
14033 BaselineFrame* frame)
14034 : IRGenerator(cx, script, pc, CacheKind::NewArray, state),
14035 #ifdef JS_CACHEIR_SPEW
14036 op_(op),
14037 #endif
14038 templateObject_(templateObj),
14039 frame_(frame) {
14040 MOZ_ASSERT(templateObject_);
14043 void NewArrayIRGenerator::trackAttached(const char* name) {
14044 stubName_ = name ? name : "NotAttached";
14045 #ifdef JS_CACHEIR_SPEW
14046 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
14047 sp.opcodeProperty("op", op_);
14049 #endif
14052 // Allocation sites are usually created during baseline compilation, but we also
14053 // need to create them when an IC stub is added to a baseline compiled script
14054 // and when trial inlining.
14055 static gc::AllocSite* MaybeCreateAllocSite(jsbytecode* pc,
14056 BaselineFrame* frame) {
14057 MOZ_ASSERT(BytecodeOpCanHaveAllocSite(JSOp(*pc)));
14059 JSScript* outerScript = frame->outerScript();
14060 bool hasBaselineScript = outerScript->hasBaselineScript();
14061 bool isInlined = frame->icScript()->isInlined();
14063 if (!hasBaselineScript && !isInlined) {
14064 MOZ_ASSERT(frame->runningInInterpreter());
14065 return outerScript->zone()->unknownAllocSite(JS::TraceKind::Object);
14068 uint32_t pcOffset = frame->script()->pcToOffset(pc);
14069 return frame->icScript()->getOrCreateAllocSite(outerScript, pcOffset);
14072 AttachDecision NewArrayIRGenerator::tryAttachArrayObject() {
14073 ArrayObject* arrayObj = &templateObject_->as<ArrayObject>();
14075 MOZ_ASSERT(arrayObj->numUsedFixedSlots() == 0);
14076 MOZ_ASSERT(arrayObj->numDynamicSlots() == 0);
14077 MOZ_ASSERT(!arrayObj->isSharedMemory());
14079 // The macro assembler only supports creating arrays with fixed elements.
14080 if (arrayObj->hasDynamicElements()) {
14081 return AttachDecision::NoAction;
14084 // Stub doesn't support metadata builder
14085 if (cx_->realm()->hasAllocationMetadataBuilder()) {
14086 return AttachDecision::NoAction;
14089 writer.guardNoAllocationMetadataBuilder(
14090 cx_->realm()->addressOfMetadataBuilder());
14092 gc::AllocSite* site = MaybeCreateAllocSite(pc_, frame_);
14093 if (!site) {
14094 return AttachDecision::NoAction;
14097 Shape* shape = arrayObj->shape();
14098 uint32_t length = arrayObj->length();
14100 writer.newArrayObjectResult(length, shape, site);
14102 writer.returnFromIC();
14104 trackAttached("NewArray.Object");
14105 return AttachDecision::Attach;
14108 AttachDecision NewArrayIRGenerator::tryAttachStub() {
14109 AutoAssertNoPendingException aanpe(cx_);
14111 TRY_ATTACH(tryAttachArrayObject());
14113 trackAttached(IRGenerator::NotAttached);
14114 return AttachDecision::NoAction;
14117 NewObjectIRGenerator::NewObjectIRGenerator(JSContext* cx, HandleScript script,
14118 jsbytecode* pc, ICState state,
14119 JSOp op, HandleObject templateObj,
14120 BaselineFrame* frame)
14121 : IRGenerator(cx, script, pc, CacheKind::NewObject, state),
14122 #ifdef JS_CACHEIR_SPEW
14123 op_(op),
14124 #endif
14125 templateObject_(templateObj),
14126 frame_(frame) {
14127 MOZ_ASSERT(templateObject_);
14130 void NewObjectIRGenerator::trackAttached(const char* name) {
14131 stubName_ = name ? name : "NotAttached";
14132 #ifdef JS_CACHEIR_SPEW
14133 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
14134 sp.opcodeProperty("op", op_);
14136 #endif
14139 AttachDecision NewObjectIRGenerator::tryAttachPlainObject() {
14140 // Don't optimize allocations with too many dynamic slots. We use an unrolled
14141 // loop when initializing slots and this avoids generating too much code.
14142 static const uint32_t MaxDynamicSlotsToOptimize = 64;
14144 NativeObject* nativeObj = &templateObject_->as<NativeObject>();
14145 MOZ_ASSERT(nativeObj->is<PlainObject>());
14147 // Stub doesn't support metadata builder
14148 if (cx_->realm()->hasAllocationMetadataBuilder()) {
14149 return AttachDecision::NoAction;
14152 if (nativeObj->numDynamicSlots() > MaxDynamicSlotsToOptimize) {
14153 return AttachDecision::NoAction;
14156 MOZ_ASSERT(!nativeObj->hasDynamicElements());
14157 MOZ_ASSERT(!nativeObj->isSharedMemory());
14159 gc::AllocSite* site = MaybeCreateAllocSite(pc_, frame_);
14160 if (!site) {
14161 return AttachDecision::NoAction;
14164 uint32_t numFixedSlots = nativeObj->numUsedFixedSlots();
14165 uint32_t numDynamicSlots = nativeObj->numDynamicSlots();
14166 gc::AllocKind allocKind = nativeObj->allocKindForTenure();
14167 Shape* shape = nativeObj->shape();
14169 writer.guardNoAllocationMetadataBuilder(
14170 cx_->realm()->addressOfMetadataBuilder());
14171 writer.newPlainObjectResult(numFixedSlots, numDynamicSlots, allocKind, shape,
14172 site);
14174 writer.returnFromIC();
14176 trackAttached("NewObject.PlainObject");
14177 return AttachDecision::Attach;
14180 AttachDecision NewObjectIRGenerator::tryAttachStub() {
14181 AutoAssertNoPendingException aanpe(cx_);
14183 TRY_ATTACH(tryAttachPlainObject());
14185 trackAttached(IRGenerator::NotAttached);
14186 return AttachDecision::NoAction;
14189 CloseIterIRGenerator::CloseIterIRGenerator(JSContext* cx, HandleScript script,
14190 jsbytecode* pc, ICState state,
14191 HandleObject iter,
14192 CompletionKind kind)
14193 : IRGenerator(cx, script, pc, CacheKind::CloseIter, state),
14194 iter_(iter),
14195 kind_(kind) {}
14197 void CloseIterIRGenerator::trackAttached(const char* name) {
14198 #ifdef JS_CACHEIR_SPEW
14199 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
14200 sp.valueProperty("iter", ObjectValue(*iter_));
14202 #endif
14205 AttachDecision CloseIterIRGenerator::tryAttachNoReturnMethod() {
14206 Maybe<PropertyInfo> prop;
14207 NativeObject* holder = nullptr;
14209 // If we can guard that the iterator does not have a |return| method,
14210 // then this CloseIter is a no-op.
14211 NativeGetPropKind kind = CanAttachNativeGetProp(
14212 cx_, iter_, NameToId(cx_->names().return_), &holder, &prop, pc_);
14213 if (kind != NativeGetPropKind::Missing) {
14214 return AttachDecision::NoAction;
14216 MOZ_ASSERT(!holder);
14218 ObjOperandId objId(writer.setInputOperandId(0));
14220 EmitMissingPropGuard(writer, &iter_->as<NativeObject>(), objId);
14222 // There is no return method, so we don't have to do anything.
14223 writer.returnFromIC();
14225 trackAttached("CloseIter.NoReturn");
14226 return AttachDecision::Attach;
14229 AttachDecision CloseIterIRGenerator::tryAttachScriptedReturn() {
14230 Maybe<PropertyInfo> prop;
14231 NativeObject* holder = nullptr;
14233 NativeGetPropKind kind = CanAttachNativeGetProp(
14234 cx_, iter_, NameToId(cx_->names().return_), &holder, &prop, pc_);
14235 if (kind != NativeGetPropKind::Slot) {
14236 return AttachDecision::NoAction;
14238 MOZ_ASSERT(holder);
14239 MOZ_ASSERT(prop->isDataProperty());
14241 size_t slot = prop->slot();
14242 Value calleeVal = holder->getSlot(slot);
14243 if (!calleeVal.isObject() || !calleeVal.toObject().is<JSFunction>()) {
14244 return AttachDecision::NoAction;
14247 JSFunction* callee = &calleeVal.toObject().as<JSFunction>();
14248 if (!callee->hasJitEntry()) {
14249 return AttachDecision::NoAction;
14251 if (callee->isClassConstructor()) {
14252 return AttachDecision::NoAction;
14255 // We don't support cross-realm |return|.
14256 if (cx_->realm() != callee->realm()) {
14257 return AttachDecision::NoAction;
14260 ObjOperandId objId(writer.setInputOperandId(0));
14262 ObjOperandId holderId =
14263 EmitReadSlotGuard(writer, &iter_->as<NativeObject>(), holder, objId);
14265 ValOperandId calleeValId = EmitLoadSlot(writer, holder, holderId, slot);
14266 ObjOperandId calleeId = writer.guardToObject(calleeValId);
14267 emitCalleeGuard(calleeId, callee);
14269 writer.closeIterScriptedResult(objId, calleeId, kind_, callee->nargs());
14271 writer.returnFromIC();
14272 trackAttached("CloseIter.ScriptedReturn");
14274 return AttachDecision::Attach;
14277 AttachDecision CloseIterIRGenerator::tryAttachStub() {
14278 AutoAssertNoPendingException aanpe(cx_);
14280 TRY_ATTACH(tryAttachNoReturnMethod());
14281 TRY_ATTACH(tryAttachScriptedReturn());
14283 trackAttached(IRGenerator::NotAttached);
14284 return AttachDecision::NoAction;
14287 OptimizeGetIteratorIRGenerator::OptimizeGetIteratorIRGenerator(
14288 JSContext* cx, HandleScript script, jsbytecode* pc, ICState state,
14289 HandleValue value)
14290 : IRGenerator(cx, script, pc, CacheKind::OptimizeGetIterator, state),
14291 val_(value) {}
14293 AttachDecision OptimizeGetIteratorIRGenerator::tryAttachStub() {
14294 MOZ_ASSERT(cacheKind_ == CacheKind::OptimizeGetIterator);
14296 AutoAssertNoPendingException aanpe(cx_);
14298 TRY_ATTACH(tryAttachArray());
14299 TRY_ATTACH(tryAttachNotOptimizable());
14301 MOZ_CRASH("Failed to attach unoptimizable case.");
14304 AttachDecision OptimizeGetIteratorIRGenerator::tryAttachArray() {
14305 if (!isFirstStub_) {
14306 return AttachDecision::NoAction;
14309 // The value must be a packed array.
14310 if (!val_.isObject()) {
14311 return AttachDecision::NoAction;
14313 Rooted<JSObject*> obj(cx_, &val_.toObject());
14314 if (!IsPackedArray(obj)) {
14315 return AttachDecision::NoAction;
14318 // Prototype must be Array.prototype and Array.prototype[@@iterator] must not
14319 // be modified.
14320 Rooted<NativeObject*> arrProto(cx_);
14321 uint32_t arrProtoIterSlot;
14322 Rooted<JSFunction*> iterFun(cx_);
14323 if (!IsArrayInstanceOptimizable(cx_, obj.as<ArrayObject>(), &arrProto)) {
14324 return AttachDecision::NoAction;
14327 if (!IsArrayPrototypeOptimizable(cx_, obj.as<ArrayObject>(), arrProto,
14328 &arrProtoIterSlot, &iterFun)) {
14329 // Fuse should be popped.
14330 MOZ_ASSERT(
14331 !obj->nonCCWRealm()->realmFuses.optimizeGetIteratorFuse.intact());
14332 return AttachDecision::NoAction;
14335 // %ArrayIteratorPrototype%.next must not be modified and
14336 // %ArrayIteratorPrototype%.return must not be present.
14337 Rooted<NativeObject*> arrayIteratorProto(cx_);
14338 uint32_t slot;
14339 Rooted<JSFunction*> nextFun(cx_);
14340 if (!IsArrayIteratorPrototypeOptimizable(
14341 cx_, AllowIteratorReturn::No, &arrayIteratorProto, &slot, &nextFun)) {
14342 // Fuse should be popped.
14343 MOZ_ASSERT(
14344 !obj->nonCCWRealm()->realmFuses.optimizeGetIteratorFuse.intact());
14345 return AttachDecision::NoAction;
14348 ValOperandId valId(writer.setInputOperandId(0));
14349 ObjOperandId objId = writer.guardToObject(valId);
14351 // Guard the object is a packed array with Array.prototype as proto.
14352 MOZ_ASSERT(obj->is<ArrayObject>());
14353 writer.guardShape(objId, obj->shape());
14354 writer.guardArrayIsPacked(objId);
14355 bool intact = obj->nonCCWRealm()->realmFuses.optimizeGetIteratorFuse.intact();
14357 // If the fuse isn't intact but we've still passed all these dynamic checks
14358 // then we can attach a version of the IC that dynamically checks to ensure
14359 // the required invariants still hold.
14361 // As an example of how this could be the case, consider an assignment
14363 // Array.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]
14365 // This assignment pops the fuse, however we can still use the dynamic check
14366 // version of this IC, as the actual -value- is still correct.
14367 bool useDynamicCheck = !intact || !JS::Prefs::destructuring_fuse();
14368 if (useDynamicCheck) {
14369 // Guard on Array.prototype[@@iterator].
14370 ObjOperandId arrProtoId = writer.loadObject(arrProto);
14371 ObjOperandId iterId = writer.loadObject(iterFun);
14372 writer.guardShape(arrProtoId, arrProto->shape());
14373 writer.guardDynamicSlotIsSpecificObject(arrProtoId, iterId,
14374 arrProtoIterSlot);
14376 // Guard on %ArrayIteratorPrototype%.next.
14377 ObjOperandId iterProtoId = writer.loadObject(arrayIteratorProto);
14378 ObjOperandId nextId = writer.loadObject(nextFun);
14379 writer.guardShape(iterProtoId, arrayIteratorProto->shape());
14380 writer.guardDynamicSlotIsSpecificObject(iterProtoId, nextId, slot);
14382 // Guard on the prototype chain to ensure no "return" method is present.
14383 ShapeGuardProtoChain(writer, arrayIteratorProto, iterProtoId);
14384 } else {
14385 // Guard on Array.prototype[@@iterator] and %ArrayIteratorPrototype%.next.
14386 // This fuse also ensures the prototype chain for Array Iterator is
14387 // maintained and that no return method is added.
14388 writer.guardFuse(RealmFuses::FuseIndex::OptimizeGetIteratorFuse);
14391 writer.loadBooleanResult(true);
14392 writer.returnFromIC();
14394 if (useDynamicCheck) {
14395 trackAttached("OptimizeGetIterator.Array.Dynamic");
14396 } else {
14397 trackAttached("OptimizeGetIterator.Array.Fuse");
14399 return AttachDecision::Attach;
14402 AttachDecision OptimizeGetIteratorIRGenerator::tryAttachNotOptimizable() {
14403 ValOperandId valId(writer.setInputOperandId(0));
14405 writer.loadBooleanResult(false);
14406 writer.returnFromIC();
14408 trackAttached("OptimizeGetIterator.NotOptimizable");
14409 return AttachDecision::Attach;
14412 void OptimizeGetIteratorIRGenerator::trackAttached(const char* name) {
14413 stubName_ = name ? name : "NotAttached";
14415 #ifdef JS_CACHEIR_SPEW
14416 if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
14417 sp.valueProperty("val", val_);
14419 #endif
14422 #ifdef JS_SIMULATOR
14423 bool js::jit::CallAnyNative(JSContext* cx, unsigned argc, Value* vp) {
14424 CallArgs args = CallArgsFromVp(argc, vp);
14425 JSObject* calleeObj = &args.callee();
14427 MOZ_ASSERT(calleeObj->is<JSFunction>());
14428 auto* calleeFunc = &calleeObj->as<JSFunction>();
14429 MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
14431 JSNative native = calleeFunc->native();
14432 return native(cx, args.length(), args.base());
14435 const void* js::jit::RedirectedCallAnyNative() {
14436 // The simulator requires native calls to be redirected to a
14437 // special swi instruction. If we are calling an arbitrary native
14438 // function, we can't wrap the real target ahead of time, so we
14439 // call a wrapper function (CallAnyNative) that calls the target
14440 // itself, and redirect that wrapper.
14441 JSNative target = CallAnyNative;
14442 void* rawPtr = JS_FUNC_TO_DATA_PTR(void*, target);
14443 void* redirected = Simulator::RedirectNativeFunction(rawPtr, Args_General3);
14444 return redirected;
14446 #endif