summaryrefslogtreecommitdiffstats
path: root/js/src/jit/CacheIR.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/CacheIR.cpp')
-rw-r--r--js/src/jit/CacheIR.cpp10796
1 files changed, 10796 insertions, 0 deletions
diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp
new file mode 100644
index 0000000000..d354c9f962
--- /dev/null
+++ b/js/src/jit/CacheIR.cpp
@@ -0,0 +1,10796 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jit/CacheIR.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Unused.h"
+
+#include "jsmath.h"
+
+#include "builtin/DataViewObject.h"
+#include "builtin/MapObject.h"
+#include "builtin/ModuleObject.h"
+#include "builtin/Object.h"
+#include "jit/BaselineCacheIRCompiler.h"
+#include "jit/BaselineIC.h"
+#include "jit/CacheIRSpewer.h"
+#include "jit/InlinableNatives.h"
+#include "jit/Ion.h" // IsIonEnabled
+#include "jit/JitContext.h"
+#include "jit/JitRuntime.h"
+#include "js/experimental/JitInfo.h" // JSJitInfo
+#include "js/friend/DOMProxy.h" // JS::ExpandoAndGeneration
+#include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowIfWindowProxy
+#include "js/friend/XrayJitInfo.h" // js::jit::GetXrayJitInfo, JS::XrayJitInfo
+#include "js/ScalarType.h" // js::Scalar::Type
+#include "js/Wrapper.h"
+#include "proxy/DOMProxy.h" // js::GetDOMProxyHandlerFamily
+#include "util/Unicode.h"
+#include "vm/ArrayBufferObject.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/Iteration.h"
+#include "vm/PlainObject.h" // js::PlainObject
+#include "vm/ProxyObject.h"
+#include "vm/SelfHosting.h"
+#include "vm/ThrowMsgKind.h" // ThrowCondition
+#include "wasm/TypedObject.h"
+#include "wasm/WasmInstance.h"
+
+#include "jit/MacroAssembler-inl.h"
+#include "vm/BytecodeUtil-inl.h"
+#include "vm/EnvironmentObject-inl.h"
+#include "vm/JSContext-inl.h"
+#include "vm/JSObject-inl.h"
+#include "vm/JSScript-inl.h"
+#include "vm/NativeObject-inl.h"
+#include "vm/StringObject-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+
+using JS::DOMProxyShadowsResult;
+using JS::ExpandoAndGeneration;
+
+const char* const js::jit::CacheKindNames[] = {
+#define DEFINE_KIND(kind) #kind,
+ CACHE_IR_KINDS(DEFINE_KIND)
+#undef DEFINE_KIND
+};
+
+const char* const js::jit::CacheIROpNames[] = {
+#define OPNAME(op, ...) #op,
+ CACHE_IR_OPS(OPNAME)
+#undef OPNAME
+};
+
+const CacheIROpInfo js::jit::CacheIROpInfos[] = {
+#define OPINFO(op, len, transpile, ...) {len, transpile},
+ CACHE_IR_OPS(OPINFO)
+#undef OPINFO
+};
+
+const uint32_t js::jit::CacheIROpHealth[] = {
+#define OPHEALTH(op, len, transpile, health) health,
+ CACHE_IR_OPS(OPHEALTH)
+#undef OPHEALTH
+};
+
+#ifdef DEBUG
+size_t js::jit::NumInputsForCacheKind(CacheKind kind) {
+ switch (kind) {
+ case CacheKind::NewObject:
+ case CacheKind::GetIntrinsic:
+ return 0;
+ case CacheKind::GetProp:
+ case CacheKind::TypeOf:
+ case CacheKind::ToPropertyKey:
+ case CacheKind::GetIterator:
+ case CacheKind::ToBool:
+ case CacheKind::UnaryArith:
+ case CacheKind::GetName:
+ case CacheKind::BindName:
+ case CacheKind::Call:
+ case CacheKind::OptimizeSpreadCall:
+ return 1;
+ case CacheKind::Compare:
+ case CacheKind::GetElem:
+ case CacheKind::GetPropSuper:
+ case CacheKind::SetProp:
+ case CacheKind::In:
+ case CacheKind::HasOwn:
+ case CacheKind::CheckPrivateField:
+ case CacheKind::InstanceOf:
+ case CacheKind::BinaryArith:
+ return 2;
+ case CacheKind::GetElemSuper:
+ case CacheKind::SetElem:
+ return 3;
+ }
+ MOZ_CRASH("Invalid kind");
+}
+#endif
+
+void CacheIRWriter::assertSameCompartment(JSObject* obj) {
+ cx_->debugOnlyCheck(obj);
+}
+
+StubField CacheIRWriter::readStubFieldForIon(uint32_t offset,
+ StubField::Type type) const {
+ size_t index = 0;
+ size_t currentOffset = 0;
+
+ // If we've seen an offset earlier than this before, we know we can start the
+ // search there at least, otherwise, we start the search from the beginning.
+ if (lastOffset_ < offset) {
+ currentOffset = lastOffset_;
+ index = lastIndex_;
+ }
+
+ while (currentOffset != offset) {
+ currentOffset += StubField::sizeInBytes(stubFields_[index].type());
+ index++;
+ MOZ_ASSERT(index < stubFields_.length());
+ }
+
+ MOZ_ASSERT(stubFields_[index].type() == type);
+
+ lastOffset_ = currentOffset;
+ lastIndex_ = index;
+
+ return stubFields_[index];
+}
+
+CacheIRCloner::CacheIRCloner(ICCacheIRStub* stub)
+ : stubInfo_(stub->stubInfo()), stubData_(stub->stubDataStart()) {}
+
+void CacheIRCloner::cloneOp(CacheOp op, CacheIRReader& reader,
+ CacheIRWriter& writer) {
+ switch (op) {
+#define DEFINE_OP(op, ...) \
+ case CacheOp::op: \
+ clone##op(reader, writer); \
+ break;
+ CACHE_IR_OPS(DEFINE_OP)
+#undef DEFINE_OP
+ default:
+ MOZ_CRASH("Invalid op");
+ }
+}
+
+uintptr_t CacheIRCloner::readStubWord(uint32_t offset) {
+ return stubInfo_->getStubRawWord(stubData_, offset);
+}
+int64_t CacheIRCloner::readStubInt64(uint32_t offset) {
+ return stubInfo_->getStubRawInt64(stubData_, offset);
+}
+
+Shape* CacheIRCloner::getShapeField(uint32_t stubOffset) {
+ return reinterpret_cast<Shape*>(readStubWord(stubOffset));
+}
+ObjectGroup* CacheIRCloner::getGroupField(uint32_t stubOffset) {
+ return reinterpret_cast<ObjectGroup*>(readStubWord(stubOffset));
+}
+JSObject* CacheIRCloner::getObjectField(uint32_t stubOffset) {
+ return reinterpret_cast<JSObject*>(readStubWord(stubOffset));
+}
+JSString* CacheIRCloner::getStringField(uint32_t stubOffset) {
+ return reinterpret_cast<JSString*>(readStubWord(stubOffset));
+}
+JSAtom* CacheIRCloner::getAtomField(uint32_t stubOffset) {
+ return reinterpret_cast<JSAtom*>(readStubWord(stubOffset));
+}
+PropertyName* CacheIRCloner::getPropertyNameField(uint32_t stubOffset) {
+ return reinterpret_cast<PropertyName*>(readStubWord(stubOffset));
+}
+JS::Symbol* CacheIRCloner::getSymbolField(uint32_t stubOffset) {
+ return reinterpret_cast<JS::Symbol*>(readStubWord(stubOffset));
+}
+BaseScript* CacheIRCloner::getBaseScriptField(uint32_t stubOffset) {
+ return reinterpret_cast<BaseScript*>(readStubWord(stubOffset));
+}
+uint32_t CacheIRCloner::getRawInt32Field(uint32_t stubOffset) {
+ return uint32_t(reinterpret_cast<uintptr_t>(readStubWord(stubOffset)));
+}
+const void* CacheIRCloner::getRawPointerField(uint32_t stubOffset) {
+ return reinterpret_cast<const void*>(readStubWord(stubOffset));
+}
+uint64_t CacheIRCloner::getRawInt64Field(uint32_t stubOffset) {
+ return static_cast<uint64_t>(readStubInt64(stubOffset));
+}
+
+jsid CacheIRCloner::getIdField(uint32_t stubOffset) {
+ return jsid::fromRawBits(readStubWord(stubOffset));
+}
+const Value CacheIRCloner::getValueField(uint32_t stubOffset) {
+ return Value::fromRawBits(uint64_t(readStubInt64(stubOffset)));
+}
+
+IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
+ CacheKind cacheKind, ICState::Mode mode)
+ : writer(cx),
+ cx_(cx),
+ script_(script),
+ pc_(pc),
+ cacheKind_(cacheKind),
+ mode_(mode) {}
+
+GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script,
+ jsbytecode* pc, ICState::Mode mode,
+ CacheKind cacheKind, HandleValue val,
+ HandleValue idVal)
+ : IRGenerator(cx, script, pc, cacheKind, mode), val_(val), idVal_(idVal) {}
+
+static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderId,
+ NativeObject* holder, Shape* shape) {
+ if (holder->isFixedSlot(shape->slot())) {
+ writer.loadFixedSlotResult(holderId,
+ NativeObject::getFixedSlotOffset(shape->slot()));
+ } else {
+ size_t dynamicSlotOffset =
+ holder->dynamicSlotIndex(shape->slot()) * sizeof(Value);
+ writer.loadDynamicSlotResult(holderId, dynamicSlotOffset);
+ }
+}
+
+// DOM proxies
+// -----------
+//
+// DOM proxies are proxies that are used to implement various DOM objects like
+// HTMLDocument and NodeList. DOM proxies may have an expando object - a native
+// object that stores extra properties added to the object. The following
+// CacheIR instructions are only used with DOM proxies:
+//
+// * LoadDOMExpandoValue: returns the Value in the proxy's expando slot. This
+// returns either an UndefinedValue (no expando), ObjectValue (the expando
+// object), or PrivateValue(ExpandoAndGeneration*).
+//
+// * LoadDOMExpandoValueGuardGeneration: guards the Value in the proxy's expando
+// slot is the same PrivateValue(ExpandoAndGeneration*), then guards on its
+// generation, then returns expandoAndGeneration->expando. This Value is
+// either an UndefinedValue or ObjectValue.
+//
+// * LoadDOMExpandoValueIgnoreGeneration: assumes the Value in the proxy's
+// expando slot is a PrivateValue(ExpandoAndGeneration*), unboxes it, and
+// returns the expandoAndGeneration->expando Value.
+//
+// * GuardDOMExpandoMissingOrGuardShape: takes an expando Value as input, then
+// guards it's either UndefinedValue or an object with the expected shape.
+
+enum class ProxyStubType {
+ None,
+ DOMExpando,
+ DOMShadowed,
+ DOMUnshadowed,
+ Generic
+};
+
+static bool IsCacheableDOMProxy(JSObject* obj) {
+ const BaseProxyHandler* handler = obj->as<ProxyObject>().handler();
+ if (handler->family() != GetDOMProxyHandlerFamily()) {
+ return false;
+ }
+
+ // Some DOM proxies have dynamic prototypes. We can't really cache those very
+ // well.
+ return obj->hasStaticPrototype();
+}
+
+static ProxyStubType GetProxyStubType(JSContext* cx, HandleObject obj,
+ HandleId id) {
+ if (!obj->is<ProxyObject>()) {
+ return ProxyStubType::None;
+ }
+
+ if (!IsCacheableDOMProxy(obj)) {
+ return ProxyStubType::Generic;
+ }
+
+ DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id);
+ if (shadows == DOMProxyShadowsResult::ShadowCheckFailed) {
+ cx->clearPendingException();
+ return ProxyStubType::None;
+ }
+
+ if (DOMProxyIsShadowing(shadows)) {
+ if (shadows == DOMProxyShadowsResult::ShadowsViaDirectExpando ||
+ shadows == DOMProxyShadowsResult::ShadowsViaIndirectExpando) {
+ return ProxyStubType::DOMExpando;
+ }
+ return ProxyStubType::DOMShadowed;
+ }
+
+ MOZ_ASSERT(shadows == DOMProxyShadowsResult::DoesntShadow ||
+ shadows == DOMProxyShadowsResult::DoesntShadowUnique);
+ return ProxyStubType::DOMUnshadowed;
+}
+
+static bool ValueToNameOrSymbolId(JSContext* cx, HandleValue idval,
+ MutableHandleId id, bool* nameOrSymbol) {
+ *nameOrSymbol = false;
+
+ if (!idval.isString() && !idval.isSymbol()) {
+ return true;
+ }
+
+ if (!PrimitiveValueToId<CanGC>(cx, idval, id)) {
+ return false;
+ }
+
+ if (!JSID_IS_STRING(id) && !JSID_IS_SYMBOL(id)) {
+ id.set(JSID_VOID);
+ return true;
+ }
+
+ uint32_t dummy;
+ if (JSID_IS_STRING(id) && JSID_TO_ATOM(id)->isIndex(&dummy)) {
+ id.set(JSID_VOID);
+ return true;
+ }
+
+ *nameOrSymbol = true;
+ return true;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachStub() {
+ AutoAssertNoPendingException aanpe(cx_);
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ if (cacheKind_ != CacheKind::GetProp) {
+ MOZ_ASSERT_IF(cacheKind_ == CacheKind::GetPropSuper,
+ getSuperReceiverValueId().id() == 1);
+ MOZ_ASSERT_IF(cacheKind_ != CacheKind::GetPropSuper,
+ getElemKeyValueId().id() == 1);
+ writer.setInputOperandId(1);
+ }
+ if (cacheKind_ == CacheKind::GetElemSuper) {
+ MOZ_ASSERT(getSuperReceiverValueId().id() == 2);
+ writer.setInputOperandId(2);
+ }
+
+ RootedId id(cx_);
+ bool nameOrSymbol;
+ if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
+ cx_->clearPendingException();
+ return AttachDecision::NoAction;
+ }
+
+ // |super.prop| getter calls use a |this| value that differs from lookup
+ // object.
+ ValOperandId receiverId = isSuper() ? getSuperReceiverValueId() : valId;
+
+ if (val_.isObject()) {
+ RootedObject obj(cx_, &val_.toObject());
+ ObjOperandId objId = writer.guardToObject(valId);
+ if (nameOrSymbol) {
+ TRY_ATTACH(tryAttachObjectLength(obj, objId, id));
+ TRY_ATTACH(tryAttachTypedArrayLength(obj, objId, id));
+ TRY_ATTACH(tryAttachNative(obj, objId, id, receiverId));
+ TRY_ATTACH(tryAttachModuleNamespace(obj, objId, id));
+ TRY_ATTACH(tryAttachWindowProxy(obj, objId, id));
+ TRY_ATTACH(tryAttachCrossCompartmentWrapper(obj, objId, id));
+ TRY_ATTACH(
+ tryAttachXrayCrossCompartmentWrapper(obj, objId, id, receiverId));
+ TRY_ATTACH(tryAttachFunction(obj, objId, id));
+ TRY_ATTACH(tryAttachArgumentsObjectIterator(obj, objId, id));
+ TRY_ATTACH(tryAttachProxy(obj, objId, id, receiverId));
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
+ cacheKind_ == CacheKind::GetElemSuper);
+
+ TRY_ATTACH(tryAttachProxyElement(obj, objId));
+
+ uint32_t index;
+ Int32OperandId indexId;
+ if (maybeGuardInt32Index(idVal_, getElemKeyValueId(), &index, &indexId)) {
+ TRY_ATTACH(tryAttachTypedArrayElement(obj, objId, index, indexId));
+ TRY_ATTACH(tryAttachDenseElement(obj, objId, index, indexId));
+ TRY_ATTACH(tryAttachDenseElementHole(obj, objId, index, indexId));
+ TRY_ATTACH(tryAttachSparseElement(obj, objId, index, indexId));
+ TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, index, indexId));
+ TRY_ATTACH(tryAttachGenericElement(obj, objId, index, indexId));
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ TRY_ATTACH(tryAttachTypedArrayNonInt32Index(obj, objId));
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ if (nameOrSymbol) {
+ TRY_ATTACH(tryAttachPrimitive(valId, id));
+ TRY_ATTACH(tryAttachStringLength(valId, id));
+ TRY_ATTACH(tryAttachMagicArgumentsName(valId, id));
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ if (idVal_.isInt32()) {
+ ValOperandId indexId = getElemKeyValueId();
+ TRY_ATTACH(tryAttachStringChar(valId, indexId));
+ TRY_ATTACH(tryAttachMagicArgument(valId, indexId));
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+}
+
+static bool IsCacheableProtoChain(JSObject* obj, JSObject* holder) {
+ while (obj != holder) {
+ /*
+ * We cannot assume that we find the holder object on the prototype
+ * chain and must check for null proto. The prototype chain can be
+ * altered during the lookupProperty call.
+ */
+ JSObject* proto = obj->staticPrototype();
+ if (!proto || !proto->isNative()) {
+ return false;
+ }
+ obj = proto;
+ }
+ return true;
+}
+
+static bool IsCacheableGetPropReadSlot(JSObject* obj, JSObject* holder,
+ Shape* shape) {
+ if (!shape->isDataProperty()) {
+ return false;
+ }
+
+ if (!IsCacheableProtoChain(obj, holder)) {
+ return false;
+ }
+
+ return true;
+}
+
+enum NativeGetPropCacheability {
+ CanAttachNone,
+ CanAttachReadSlot,
+ CanAttachNativeGetter,
+ CanAttachScriptedGetter,
+};
+
+static NativeGetPropCacheability IsCacheableGetPropCall(JSObject* obj,
+ JSObject* holder,
+ Shape* shape) {
+ MOZ_ASSERT(shape);
+
+ if (!IsCacheableProtoChain(obj, holder)) {
+ return CanAttachNone;
+ }
+
+ if (!shape->hasGetterValue() || !shape->getterValue().isObject()) {
+ return CanAttachNone;
+ }
+
+ if (!shape->getterValue().toObject().is<JSFunction>()) {
+ return CanAttachNone;
+ }
+
+ JSFunction& getter = shape->getterValue().toObject().as<JSFunction>();
+
+ if (getter.isClassConstructor()) {
+ return CanAttachNone;
+ }
+
+ // For getters that need the WindowProxy (instead of the Window) as this
+ // object, don't cache if obj is the Window, since our cache will pass that
+ // instead of the WindowProxy.
+ if (IsWindow(obj)) {
+ // Check for a getter that has jitinfo and whose jitinfo says it's
+ // OK with both inner and outer objects.
+ if (!getter.hasJitInfo() || getter.jitInfo()->needsOuterizedThisObject()) {
+ return CanAttachNone;
+ }
+ }
+
+ // Scripted functions and natives with JIT entry can use the scripted path.
+ if (getter.hasJitEntry()) {
+ return CanAttachScriptedGetter;
+ }
+
+ MOZ_ASSERT(getter.isNativeWithoutJitEntry());
+ return CanAttachNativeGetter;
+}
+
+static bool CheckHasNoSuchOwnProperty(JSContext* cx, JSObject* obj, jsid id) {
+ if (!obj->isNative()) {
+ return false;
+ }
+ // Don't handle proto chains with resolve hooks.
+ if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj)) {
+ return false;
+ }
+ if (obj->as<NativeObject>().contains(cx, id)) {
+ return false;
+ }
+ return true;
+}
+
+static bool CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, jsid id) {
+ JSObject* curObj = obj;
+ do {
+ if (!CheckHasNoSuchOwnProperty(cx, curObj, id)) {
+ return false;
+ }
+
+ curObj = curObj->staticPrototype();
+ } while (curObj);
+
+ return true;
+}
+
+static bool IsCacheableNoProperty(JSContext* cx, JSObject* obj,
+ JSObject* holder, Shape* shape, jsid id,
+ jsbytecode* pc) {
+ MOZ_ASSERT(!shape);
+ MOZ_ASSERT(!holder);
+
+ // If we're doing a name lookup, we have to throw a ReferenceError.
+ if (JSOp(*pc) == JSOp::GetBoundName) {
+ return false;
+ }
+
+ return CheckHasNoSuchProperty(cx, obj, id);
+}
+
+static NativeGetPropCacheability CanAttachNativeGetProp(
+ JSContext* cx, HandleObject obj, HandleId id,
+ MutableHandleNativeObject holder, MutableHandleShape shape,
+ jsbytecode* pc) {
+ MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
+
+ // The lookup needs to be universally pure, otherwise we risk calling hooks
+ // out of turn. We don't mind doing this even when purity isn't required,
+ // because we only miss out on shape hashification, which is only a temporary
+ // perf cost. The limits were arbitrarily set, anyways.
+ JSObject* baseHolder = nullptr;
+ PropertyResult prop;
+ if (!LookupPropertyPure(cx, obj, id, &baseHolder, &prop)) {
+ return CanAttachNone;
+ }
+
+ MOZ_ASSERT(!holder);
+
+ if (prop.isNativeProperty()) {
+ MOZ_ASSERT(baseHolder);
+ holder.set(&baseHolder->as<NativeObject>());
+ shape.set(prop.shape());
+
+ if (IsCacheableGetPropReadSlot(obj, holder, shape)) {
+ return CanAttachReadSlot;
+ }
+
+ return IsCacheableGetPropCall(obj, holder, shape);
+ }
+
+ if (!prop.isFound()) {
+ if (IsCacheableNoProperty(cx, obj, holder, shape, id, pc)) {
+ return CanAttachReadSlot;
+ }
+ }
+
+ return CanAttachNone;
+}
+
+static void GuardGroupProto(CacheIRWriter& writer, JSObject* obj,
+ ObjOperandId objId) {
+ // Uses the group to determine if the prototype is unchanged. This works
+ // because groups have an immutable prototype. This can be used if the shape
+ // has the UNCACHEABLE_PROTO flag set.
+
+ ObjectGroup* group = obj->group();
+ writer.guardGroupForProto(objId, group);
+}
+
+// Guard that a given object has same class and same OwnProperties (excluding
+// dense elements and dynamic properties).
+static void TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj,
+ ObjOperandId objId) {
+ if (obj->is<TypedObject>()) {
+ writer.guardGroupForLayout(objId, obj->group());
+ } else if (obj->is<ProxyObject>()) {
+ writer.guardShapeForClass(objId, obj->as<ProxyObject>().shape());
+ } else {
+ MOZ_ASSERT(obj->is<NativeObject>());
+ writer.guardShapeForOwnProperties(objId,
+ obj->as<NativeObject>().lastProperty());
+ }
+}
+
+// Similar to |TestMatchingReceiver|, but specialized for NativeObject.
+static void TestMatchingNativeReceiver(CacheIRWriter& writer, NativeObject* obj,
+ ObjOperandId objId) {
+ writer.guardShapeForOwnProperties(objId, obj->lastProperty());
+}
+
+// Similar to |TestMatchingReceiver|, but specialized for ProxyObject.
+static void TestMatchingProxyReceiver(CacheIRWriter& writer, ProxyObject* obj,
+ ObjOperandId objId) {
+ writer.guardShapeForClass(objId, obj->shape());
+}
+
+// Adds additional guards if TestMatchingReceiver* does not also imply the
+// prototype.
+static void GeneratePrototypeGuardsForReceiver(CacheIRWriter& writer,
+ JSObject* obj,
+ ObjOperandId objId) {
+ // If receiver was marked UNCACHEABLE_PROTO, the previous shape guard
+ // doesn't ensure the prototype is unchanged. In this case we must use the
+ // group to check the prototype.
+ if (obj->hasUncacheableProto()) {
+ MOZ_ASSERT(obj->is<NativeObject>());
+ GuardGroupProto(writer, obj, objId);
+ }
+
+ // The following cases already guaranteed the prototype is unchanged.
+ MOZ_ASSERT_IF(obj->is<TypedObject>() || obj->is<ProxyObject>(),
+ !obj->hasUncacheableProto());
+}
+
+static bool ProtoChainSupportsTeleporting(JSObject* obj, JSObject* holder) {
+ // Any non-delegate should already have been handled since its checks are
+ // always required.
+ MOZ_ASSERT(obj->isDelegate());
+
+ // Prototype chain must have cacheable prototypes to ensure the cached
+ // holder is the current holder.
+ for (JSObject* tmp = obj; tmp != holder; tmp = tmp->staticPrototype()) {
+ if (tmp->hasUncacheableProto()) {
+ return false;
+ }
+ }
+
+ // The holder itself only gets reshaped by teleportation if it is not
+ // marked UNCACHEABLE_PROTO. See: ReshapeForProtoMutation.
+ return !holder->hasUncacheableProto();
+}
+
+static void GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj,
+ JSObject* holder, ObjOperandId objId) {
+ // Assuming target property is on |holder|, generate appropriate guards to
+ // ensure |holder| is still on the prototype chain of |obj| and we haven't
+ // introduced any shadowing definitions.
+ //
+ // For each item in the proto chain before holder, we must ensure that
+ // [[GetPrototypeOf]] still has the expected result, and that
+ // [[GetOwnProperty]] has no definition of the target property.
+ //
+ //
+ // [SMDOC] Shape Teleporting Optimization
+ // ------------------------------
+ //
+ // Starting with the assumption (and guideline to developers) that mutating
+ // prototypes is an uncommon and fair-to-penalize operation we move cost
+ // from the access side to the mutation side.
+ //
+ // Consider the following proto chain, with B defining a property 'x':
+ //
+ // D -> C -> B{x: 3} -> A -> null
+ //
+ // When accessing |D.x| we refer to D as the "receiver", and B as the
+ // "holder". To optimize this access we need to ensure that neither D nor C
+ // has since defined a shadowing property 'x'. Since C is a prototype that
+ // we assume is rarely mutated we would like to avoid checking each time if
+ // new properties are added. To do this we require that everytime C is
+ // mutated that in addition to generating a new shape for itself, it will
+ // walk the proto chain and generate new shapes for those objects on the
+ // chain (B and A). As a result, checking the shape of D and B is
+ // sufficient. Note that we do not care if the shape or properties of A
+ // change since the lookup of 'x' will stop at B.
+ //
+ // The second condition we must verify is that the prototype chain was not
+ // mutated. The same mechanism as above is used. When the prototype link is
+ // changed, we generate a new shape for the object. If the object whose
+ // link we are mutating is itself a prototype, we regenerate shapes down
+ // the chain. This means the same two shape checks as above are sufficient.
+ //
+ // An additional wrinkle is the UNCACHEABLE_PROTO shape flag. This
+ // indicates that the shape no longer implies any specific prototype. As
+ // well, the shape will not be updated by the teleporting optimization.
+ // If any shape from receiver to holder (inclusive) is UNCACHEABLE_PROTO,
+ // we don't apply the optimization.
+ //
+ // See:
+ // - ReshapeForProtoMutation
+ // - ReshapeForShadowedProp
+
+ MOZ_ASSERT(holder);
+ MOZ_ASSERT(obj != holder);
+
+ // Only DELEGATE objects participate in teleporting so peel off the first
+ // object in the chain if needed and handle it directly.
+ JSObject* pobj = obj;
+ if (!obj->isDelegate()) {
+ // TestMatchingReceiver does not always ensure the prototype is
+ // unchanged, so generate extra guards as needed.
+ GeneratePrototypeGuardsForReceiver(writer, obj, objId);
+
+ pobj = obj->staticPrototype();
+ }
+ MOZ_ASSERT(pobj->isDelegate());
+
+ // If teleporting is supported for this prototype chain, we are done.
+ if (ProtoChainSupportsTeleporting(pobj, holder)) {
+ return;
+ }
+
+ // If already at the holder, no further proto checks are needed.
+ if (pobj == holder) {
+ return;
+ }
+
+ // NOTE: We could be clever and look for a middle prototype to shape check
+ // and elide some (but not all) of the group checks. Unless we have
+ // real-world examples, let's avoid the complexity.
+
+ // Synchronize pobj and protoId.
+ MOZ_ASSERT(pobj == obj || pobj == obj->staticPrototype());
+ ObjOperandId protoId = (pobj == obj) ? objId : writer.loadProto(objId);
+
+ // Guard prototype links from |pobj| to |holder|.
+ while (pobj != holder) {
+ pobj = pobj->staticPrototype();
+
+ // The object's proto could be nullptr so we must use GuardProto before
+ // LoadProto (LoadProto asserts the proto is non-null).
+ writer.guardProto(protoId, pobj);
+ protoId = writer.loadProto(protoId);
+ }
+}
+
+static void GeneratePrototypeHoleGuards(CacheIRWriter& writer, JSObject* obj,
+ ObjOperandId objId,
+ bool alwaysGuardFirstProto) {
+ if (alwaysGuardFirstProto || obj->hasUncacheableProto()) {
+ GuardGroupProto(writer, obj, objId);
+ }
+
+ JSObject* pobj = obj->staticPrototype();
+ while (pobj) {
+ ObjOperandId protoId = writer.loadObject(pobj);
+
+ // If shape doesn't imply proto, additional guards are needed.
+ if (pobj->hasUncacheableProto()) {
+ GuardGroupProto(writer, pobj, protoId);
+ }
+
+ // Make sure the shape matches, to avoid non-dense elements or anything
+ // else that is being checked by CanAttachDenseElementHole.
+ writer.guardShape(protoId, pobj->as<NativeObject>().lastProperty());
+
+ // Also make sure there are no dense elements.
+ writer.guardNoDenseElements(protoId);
+
+ pobj = pobj->staticPrototype();
+ }
+}
+
+// Similar to |TestMatchingReceiver|, but for the holder object (when it
+// differs from the receiver). The holder may also be the expando of the
+// receiver if it exists.
+static void TestMatchingHolder(CacheIRWriter& writer, JSObject* obj,
+ ObjOperandId objId) {
+ // The GeneratePrototypeGuards + TestMatchingHolder checks only support
+ // prototype chains composed of NativeObject (excluding the receiver
+ // itself).
+ MOZ_ASSERT(obj->is<NativeObject>());
+
+ writer.guardShapeForOwnProperties(objId,
+ obj->as<NativeObject>().lastProperty());
+}
+
+static bool UncacheableProtoOnChain(JSObject* obj) {
+ while (true) {
+ if (obj->hasUncacheableProto()) {
+ return true;
+ }
+
+ obj = obj->staticPrototype();
+ if (!obj) {
+ return false;
+ }
+ }
+}
+
+static void ShapeGuardProtoChain(CacheIRWriter& writer, JSObject* obj,
+ ObjOperandId objId) {
+ while (true) {
+ JSObject* proto = obj->staticPrototype();
+
+ // Guard on the proto if the shape does not imply the proto.
+ if (obj->hasUncacheableProto()) {
+ if (proto) {
+ writer.guardProto(objId, proto);
+ } else {
+ writer.guardNullProto(objId);
+ }
+ }
+
+ if (!proto) {
+ return;
+ }
+
+ obj = proto;
+ objId = writer.loadProto(objId);
+
+ writer.guardShape(objId, obj->as<NativeObject>().shape());
+ }
+}
+
+// For cross compartment guards we shape-guard the prototype chain to avoid
+// referencing the holder object.
+//
+// This peels off the first layer because it's guarded against obj == holder.
+static void ShapeGuardProtoChainForCrossCompartmentHolder(
+ CacheIRWriter& writer, JSObject* obj, ObjOperandId objId, JSObject* holder,
+ Maybe<ObjOperandId>* holderId) {
+ MOZ_ASSERT(obj != holder);
+ MOZ_ASSERT(holder);
+ while (true) {
+ obj = obj->staticPrototype();
+ MOZ_ASSERT(obj);
+
+ objId = writer.loadProto(objId);
+ if (obj == holder) {
+ TestMatchingHolder(writer, obj, objId);
+ holderId->emplace(objId);
+ return;
+ } else {
+ writer.guardShapeForOwnProperties(objId, obj->as<NativeObject>().shape());
+ }
+ }
+}
+
+enum class SlotReadType { Normal, CrossCompartment };
+
+template <SlotReadType MaybeCrossCompartment = SlotReadType::Normal>
+static void EmitReadSlotGuard(CacheIRWriter& writer, JSObject* obj,
+ JSObject* holder, ObjOperandId objId,
+ Maybe<ObjOperandId>* holderId) {
+ TestMatchingReceiver(writer, obj, objId);
+
+ if (obj != holder) {
+ if (holder) {
+ if (MaybeCrossCompartment == SlotReadType::CrossCompartment) {
+ // Guard proto chain integrity.
+ // We use a variant of guards that avoid baking in any cross-compartment
+ // object pointers.
+ ShapeGuardProtoChainForCrossCompartmentHolder(writer, obj, objId,
+ holder, holderId);
+ } else {
+ // Guard proto chain integrity.
+ GeneratePrototypeGuards(writer, obj, holder, objId);
+
+ // Guard on the holder's shape.
+ holderId->emplace(writer.loadObject(holder));
+ TestMatchingHolder(writer, holder, holderId->ref());
+ }
+ } else {
+ // The property does not exist. Guard on everything in the prototype
+ // chain. This is guaranteed to see only Native objects because of
+ // CanAttachNativeGetProp().
+ ShapeGuardProtoChain(writer, obj, objId);
+ }
+ } else {
+ holderId->emplace(objId);
+ }
+}
+
+template <SlotReadType MaybeCrossCompartment = SlotReadType::Normal>
+static void EmitReadSlotResult(CacheIRWriter& writer, JSObject* obj,
+ JSObject* holder, Shape* shape,
+ ObjOperandId objId) {
+ Maybe<ObjOperandId> holderId;
+ EmitReadSlotGuard<MaybeCrossCompartment>(writer, obj, holder, objId,
+ &holderId);
+
+ // Slot access.
+ if (holder) {
+ MOZ_ASSERT(holderId->valid());
+ EmitLoadSlotResult(writer, *holderId, &holder->as<NativeObject>(), shape);
+ } else {
+ MOZ_ASSERT(holderId.isNothing());
+ writer.loadUndefinedResult();
+ }
+}
+
+static void EmitCallGetterResultNoGuards(JSContext* cx, CacheIRWriter& writer,
+ JSObject* obj, JSObject* holder,
+ Shape* shape,
+ ValOperandId receiverId) {
+ JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
+ bool sameRealm = cx->realm() == target->realm();
+
+ switch (IsCacheableGetPropCall(obj, holder, shape)) {
+ case CanAttachNativeGetter: {
+ writer.callNativeGetterResult(receiverId, target, sameRealm);
+ writer.returnFromIC();
+ break;
+ }
+ case CanAttachScriptedGetter: {
+ writer.callScriptedGetterResult(receiverId, target, sameRealm);
+ writer.returnFromIC();
+ break;
+ }
+ default:
+ // CanAttachNativeGetProp guarantees that the getter is either a native or
+ // a scripted function.
+ MOZ_ASSERT_UNREACHABLE("Can't attach getter");
+ break;
+ }
+}
+
+static void EmitCallGetterResultGuards(CacheIRWriter& writer, JSObject* obj,
+ JSObject* holder, Shape* shape,
+ ObjOperandId objId, ICState::Mode mode) {
+ // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
+ // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
+ // require outerizing).
+ if (mode == ICState::Mode::Specialized || IsWindow(obj)) {
+ TestMatchingReceiver(writer, obj, objId);
+
+ if (obj != holder) {
+ GeneratePrototypeGuards(writer, obj, holder, objId);
+
+ // Guard on the holder's shape.
+ ObjOperandId holderId = writer.loadObject(holder);
+ TestMatchingHolder(writer, holder, holderId);
+ }
+ } else {
+ writer.guardHasGetterSetter(objId, shape);
+ }
+}
+
+static void EmitCallGetterResult(JSContext* cx, CacheIRWriter& writer,
+ JSObject* obj, JSObject* holder, Shape* shape,
+ ObjOperandId objId, ValOperandId receiverId,
+ ICState::Mode mode) {
+ EmitCallGetterResultGuards(writer, obj, holder, shape, objId, mode);
+ EmitCallGetterResultNoGuards(cx, writer, obj, holder, shape, receiverId);
+}
+
+static bool CanAttachDOMCall(JSContext* cx, JSJitInfo::OpType type,
+ JSObject* obj, JSFunction* fun,
+ ICState::Mode mode) {
+ MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter ||
+ type == JSJitInfo::Method);
+
+ if (mode != ICState::Mode::Specialized) {
+ return false;
+ }
+
+ if (!fun->hasJitInfo()) {
+ return false;
+ }
+
+ if (cx->realm() != fun->realm()) {
+ return false;
+ }
+
+ const JSJitInfo* jitInfo = fun->jitInfo();
+ MOZ_ASSERT_IF(IsWindow(obj), !jitInfo->needsOuterizedThisObject());
+ if (jitInfo->type() != type) {
+ return false;
+ }
+
+ const JSClass* clasp = obj->getClass();
+ if (!clasp->isDOMClass()) {
+ return false;
+ }
+
+ if (type != JSJitInfo::Method && clasp->isProxy()) {
+ return false;
+ }
+
+ DOMInstanceClassHasProtoAtDepth instanceChecker =
+ cx->runtime()->DOMcallbacks->instanceClassMatchesProto;
+ return instanceChecker(clasp, jitInfo->protoID, jitInfo->depth);
+}
+
+static bool CanAttachDOMGetterSetter(JSContext* cx, JSJitInfo::OpType type,
+ JSObject* obj, HandleShape shape,
+ ICState::Mode mode) {
+ MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter);
+
+ Value v =
+ type == JSJitInfo::Getter ? shape->getterValue() : shape->setterValue();
+ JSFunction* fun = &v.toObject().as<JSFunction>();
+
+ return CanAttachDOMCall(cx, type, obj, fun, mode);
+}
+
+static void EmitCallDOMGetterResultNoGuards(CacheIRWriter& writer, Shape* shape,
+ ObjOperandId objId) {
+ JSFunction* getter = &shape->getterValue().toObject().as<JSFunction>();
+ writer.callDOMGetterResult(objId, getter->jitInfo());
+ writer.returnFromIC();
+}
+
+static void EmitCallDOMGetterResult(JSContext* cx, CacheIRWriter& writer,
+ JSObject* obj, JSObject* holder,
+ Shape* shape, ObjOperandId objId) {
+ // Note: this relies on EmitCallGetterResultGuards emitting a shape guard
+ // for specialized stubs.
+ // The shape guard ensures the receiver's Class is valid for this DOM getter.
+ EmitCallGetterResultGuards(writer, obj, holder, shape, objId,
+ ICState::Mode::Specialized);
+ EmitCallDOMGetterResultNoGuards(writer, shape, objId);
+}
+
+void GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId,
+ jsid id,
+ bool handleMissing) {
+ MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
+
+ if (cacheKind_ == CacheKind::GetProp ||
+ cacheKind_ == CacheKind::GetPropSuper) {
+ writer.megamorphicLoadSlotResult(objId, JSID_TO_ATOM(id)->asPropertyName());
+ } else {
+ MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
+ cacheKind_ == CacheKind::GetElemSuper);
+ writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId());
+ }
+ writer.returnFromIC();
+
+ trackAttached("MegamorphicNativeSlot");
+}
+
+AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id,
+ ValOperandId receiverId) {
+ RootedShape shape(cx_);
+ RootedNativeObject holder(cx_);
+
+ NativeGetPropCacheability type =
+ CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_);
+ switch (type) {
+ case CanAttachNone:
+ return AttachDecision::NoAction;
+ case CanAttachReadSlot:
+ if (mode_ == ICState::Mode::Megamorphic) {
+ attachMegamorphicNativeSlot(objId, id, holder == nullptr);
+ return AttachDecision::Attach;
+ }
+
+ maybeEmitIdGuard(id);
+ EmitReadSlotResult(writer, obj, holder, shape, objId);
+ writer.returnFromIC();
+
+ trackAttached("NativeSlot");
+ return AttachDecision::Attach;
+ case CanAttachScriptedGetter:
+ case CanAttachNativeGetter: {
+ maybeEmitIdGuard(id);
+
+ if (!isSuper() &&
+ CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, obj, shape, mode_)) {
+ EmitCallDOMGetterResult(cx_, writer, obj, holder, shape, objId);
+
+ trackAttached("DOMGetter");
+ return AttachDecision::Attach;
+ }
+
+ EmitCallGetterResult(cx_, writer, obj, holder, shape, objId, receiverId,
+ mode_);
+
+ trackAttached("NativeGetter");
+ return AttachDecision::Attach;
+ }
+ }
+
+ MOZ_CRASH("Bad NativeGetPropCacheability");
+}
+
+bool js::jit::IsWindowProxyForScriptGlobal(JSScript* script, JSObject* obj) {
+ if (!IsWindowProxy(obj)) {
+ return false;
+ }
+
+ MOZ_ASSERT(obj->getClass() ==
+ script->runtimeFromMainThread()->maybeWindowProxyClass());
+
+ JSObject* window = ToWindowIfWindowProxy(obj);
+
+ // Ion relies on the WindowProxy's group changing (and the group getting
+ // marked as having unknown properties) on navigation. If we ever stop
+ // transplanting same-compartment WindowProxies, this assert will fail and we
+ // need to fix that code.
+ MOZ_ASSERT(window == &obj->nonCCWGlobal());
+
+ // This must be a WindowProxy for a global in this compartment. Else it would
+ // be a cross-compartment wrapper and IsWindowProxy returns false for
+ // those.
+ MOZ_ASSERT(script->compartment() == obj->compartment());
+
+ // Only optimize lookups on the WindowProxy for the current global. Other
+ // WindowProxies in the compartment may require security checks (based on
+ // mutable document.domain). See bug 1516775.
+ return window == &script->global();
+}
+
+// Guards objId is a WindowProxy for windowObj. Returns the window's operand id.
+static ObjOperandId GuardAndLoadWindowProxyWindow(CacheIRWriter& writer,
+ ObjOperandId objId,
+ GlobalObject* windowObj) {
+ // Note: update AddCacheIRGetPropFunction in BaselineInspector.cpp when making
+ // changes here.
+ writer.guardClass(objId, GuardClassKind::WindowProxy);
+ ObjOperandId windowObjId = writer.loadWrapperTarget(objId);
+ writer.guardSpecificObject(windowObjId, windowObj);
+ return windowObjId;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id) {
+ // Attach a stub when the receiver is a WindowProxy and we can do the lookup
+ // on the Window (the global object).
+
+ if (!IsWindowProxyForScriptGlobal(script_, obj)) {
+ return AttachDecision::NoAction;
+ }
+
+ // If we're megamorphic prefer a generic proxy stub that handles a lot more
+ // cases.
+ if (mode_ == ICState::Mode::Megamorphic) {
+ return AttachDecision::NoAction;
+ }
+
+ // Now try to do the lookup on the Window (the current global).
+ Handle<GlobalObject*> windowObj = cx_->global();
+ RootedShape shape(cx_);
+ RootedNativeObject holder(cx_);
+ NativeGetPropCacheability type =
+ CanAttachNativeGetProp(cx_, windowObj, id, &holder, &shape, pc_);
+ switch (type) {
+ case CanAttachNone:
+ return AttachDecision::NoAction;
+
+ case CanAttachReadSlot: {
+ maybeEmitIdGuard(id);
+ ObjOperandId windowObjId =
+ GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
+ EmitReadSlotResult(writer, windowObj, holder, shape, windowObjId);
+ writer.returnFromIC();
+
+ trackAttached("WindowProxySlot");
+ return AttachDecision::Attach;
+ }
+
+ case CanAttachNativeGetter: {
+ // Make sure the native getter is okay with the IC passing the Window
+ // instead of the WindowProxy as |this| value.
+ JSFunction* callee = &shape->getterObject()->as<JSFunction>();
+ MOZ_ASSERT(callee->isNativeWithoutJitEntry());
+ if (!callee->hasJitInfo() ||
+ callee->jitInfo()->needsOuterizedThisObject()) {
+ return AttachDecision::NoAction;
+ }
+
+ // If a |super| access, it is not worth the complexity to attach an IC.
+ if (isSuper()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Guard the incoming object is a WindowProxy and inline a getter call
+ // based on the Window object.
+ maybeEmitIdGuard(id);
+ ObjOperandId windowObjId =
+ GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
+ ValOperandId receiverId = writer.boxObject(windowObjId);
+ EmitCallGetterResult(cx_, writer, windowObj, holder, shape, windowObjId,
+ receiverId, mode_);
+
+ trackAttached("WindowProxyGetter");
+ return AttachDecision::Attach;
+ }
+
+ case CanAttachScriptedGetter:
+ MOZ_ASSERT_UNREACHABLE("Not possible for window proxies");
+ }
+
+ MOZ_CRASH("Unreachable");
+}
+
+AttachDecision GetPropIRGenerator::tryAttachCrossCompartmentWrapper(
+ HandleObject obj, ObjOperandId objId, HandleId id) {
+ // We can only optimize this very wrapper-handler, because others might
+ // have a security policy.
+ if (!IsWrapper(obj) ||
+ Wrapper::wrapperHandler(obj) != &CrossCompartmentWrapper::singleton) {
+ return AttachDecision::NoAction;
+ }
+
+ // If we're megamorphic prefer a generic proxy stub that handles a lot more
+ // cases.
+ if (mode_ == ICState::Mode::Megamorphic) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj));
+ MOZ_ASSERT(unwrapped == UnwrapOneCheckedStatic(obj));
+ MOZ_ASSERT(!IsCrossCompartmentWrapper(unwrapped),
+ "CCWs must not wrap other CCWs");
+
+ // If we allowed different zones we would have to wrap strings.
+ if (unwrapped->compartment()->zone() != cx_->compartment()->zone()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Take the unwrapped object's global, and wrap in a
+ // this-compartment wrapper. This is what will be stored in the IC
+ // keep the compartment alive.
+ RootedObject wrappedTargetGlobal(cx_, &unwrapped->nonCCWGlobal());
+ if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal)) {
+ cx_->clearPendingException();
+ return AttachDecision::NoAction;
+ }
+
+ RootedShape shape(cx_);
+ RootedNativeObject holder(cx_);
+
+ // Enter realm of target since some checks have side-effects
+ // such as de-lazifying type info.
+ {
+ AutoRealm ar(cx_, unwrapped);
+
+ NativeGetPropCacheability canCache =
+ CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &shape, pc_);
+ if (canCache != CanAttachReadSlot) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!holder) {
+ // UNCACHEABLE_PROTO may result in guards against specific
+ // (cross-compartment) prototype objects, so don't try to attach IC if we
+ // see the flag at all.
+ if (UncacheableProtoOnChain(unwrapped)) {
+ return AttachDecision::NoAction;
+ }
+ }
+ }
+
+ maybeEmitIdGuard(id);
+ writer.guardIsProxy(objId);
+ writer.guardHasProxyHandler(objId, Wrapper::wrapperHandler(obj));
+
+ // Load the object wrapped by the CCW
+ ObjOperandId wrapperTargetId = writer.loadWrapperTarget(objId);
+
+ // If the compartment of the wrapped object is different we should fail.
+ writer.guardCompartment(wrapperTargetId, wrappedTargetGlobal,
+ unwrapped->compartment());
+
+ ObjOperandId unwrappedId = wrapperTargetId;
+ EmitReadSlotResult<SlotReadType::CrossCompartment>(writer, unwrapped, holder,
+ shape, unwrappedId);
+ writer.wrapResult();
+ writer.returnFromIC();
+
+ trackAttached("CCWSlot");
+ return AttachDecision::Attach;
+}
+
+static bool GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray,
+ MutableHandleObject wrapper) {
+ Value v = GetProxyReservedSlot(xray, GetXrayJitInfo()->xrayHolderSlot);
+ if (v.isObject()) {
+ NativeObject* holder = &v.toObject().as<NativeObject>();
+ v = holder->getFixedSlot(GetXrayJitInfo()->holderExpandoSlot);
+ if (v.isObject()) {
+ RootedNativeObject expando(
+ cx, &UncheckedUnwrap(&v.toObject())->as<NativeObject>());
+ wrapper.set(NewWrapperWithObjectShape(cx, expando));
+ return wrapper != nullptr;
+ }
+ }
+ wrapper.set(nullptr);
+ return true;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachXrayCrossCompartmentWrapper(
+ HandleObject obj, ObjOperandId objId, HandleId id,
+ ValOperandId receiverId) {
+ if (!IsProxy(obj)) {
+ return AttachDecision::NoAction;
+ }
+
+ JS::XrayJitInfo* info = GetXrayJitInfo();
+ if (!info || !info->isCrossCompartmentXray(GetProxyHandler(obj))) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!info->compartmentHasExclusiveExpandos(obj)) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedObject target(cx_, UncheckedUnwrap(obj));
+
+ RootedObject expandoShapeWrapper(cx_);
+ if (!GetXrayExpandoShapeWrapper(cx_, obj, &expandoShapeWrapper)) {
+ cx_->recoverFromOutOfMemory();
+ return AttachDecision::NoAction;
+ }
+
+ // Look for a getter we can call on the xray or its prototype chain.
+ Rooted<PropertyDescriptor> desc(cx_);
+ RootedObject holder(cx_, obj);
+ RootedObjectVector prototypes(cx_);
+ RootedObjectVector prototypeExpandoShapeWrappers(cx_);
+ while (true) {
+ if (!GetOwnPropertyDescriptor(cx_, holder, id, &desc)) {
+ cx_->clearPendingException();
+ return AttachDecision::NoAction;
+ }
+ if (desc.object()) {
+ break;
+ }
+ if (!GetPrototype(cx_, holder, &holder)) {
+ cx_->clearPendingException();
+ return AttachDecision::NoAction;
+ }
+ if (!holder || !IsProxy(holder) ||
+ !info->isCrossCompartmentXray(GetProxyHandler(holder))) {
+ return AttachDecision::NoAction;
+ }
+ RootedObject prototypeExpandoShapeWrapper(cx_);
+ if (!GetXrayExpandoShapeWrapper(cx_, holder,
+ &prototypeExpandoShapeWrapper) ||
+ !prototypes.append(holder) ||
+ !prototypeExpandoShapeWrappers.append(prototypeExpandoShapeWrapper)) {
+ cx_->recoverFromOutOfMemory();
+ return AttachDecision::NoAction;
+ }
+ }
+ if (!desc.isAccessorDescriptor()) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedObject getter(cx_, desc.getterObject());
+ if (!getter || !getter->is<JSFunction>() ||
+ !getter->as<JSFunction>().isNativeWithoutJitEntry()) {
+ return AttachDecision::NoAction;
+ }
+
+ maybeEmitIdGuard(id);
+ writer.guardIsProxy(objId);
+ writer.guardHasProxyHandler(objId, GetProxyHandler(obj));
+
+ // Load the object wrapped by the CCW
+ ObjOperandId wrapperTargetId = writer.loadWrapperTarget(objId);
+
+ // Test the wrapped object's class. The properties held by xrays or their
+ // prototypes will be invariant for objects of a given class, except for
+ // changes due to xray expandos or xray prototype mutations.
+ writer.guardAnyClass(wrapperTargetId, target->getClass());
+
+ // Make sure the expandos on the xray and its prototype chain match up with
+ // what we expect. The expando shape needs to be consistent, to ensure it
+ // has not had any shadowing properties added, and the expando cannot have
+ // any custom prototype (xray prototypes are stable otherwise).
+ //
+ // We can only do this for xrays with exclusive access to their expandos
+ // (as we checked earlier), which store a pointer to their expando
+ // directly. Xrays in other compartments may share their expandos with each
+ // other and a VM call is needed just to find the expando.
+ if (expandoShapeWrapper) {
+ writer.guardXrayExpandoShapeAndDefaultProto(objId, expandoShapeWrapper);
+ } else {
+ writer.guardXrayNoExpando(objId);
+ }
+ for (size_t i = 0; i < prototypes.length(); i++) {
+ JSObject* proto = prototypes[i];
+ ObjOperandId protoId = writer.loadObject(proto);
+ if (JSObject* protoShapeWrapper = prototypeExpandoShapeWrappers[i]) {
+ writer.guardXrayExpandoShapeAndDefaultProto(protoId, protoShapeWrapper);
+ } else {
+ writer.guardXrayNoExpando(protoId);
+ }
+ }
+
+ bool sameRealm = cx_->realm() == getter->as<JSFunction>().realm();
+ writer.callNativeGetterResult(receiverId, &getter->as<JSFunction>(),
+ sameRealm);
+ writer.returnFromIC();
+
+ trackAttached("XrayGetter");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachGenericProxy(
+ HandleObject obj, ObjOperandId objId, HandleId id, bool handleDOMProxies) {
+ MOZ_ASSERT(obj->is<ProxyObject>());
+
+ writer.guardIsProxy(objId);
+
+ if (!handleDOMProxies) {
+ // Ensure that the incoming object is not a DOM proxy, so that we can get to
+ // the specialized stubs
+ writer.guardIsNotDOMProxy(objId);
+ }
+
+ if (cacheKind_ == CacheKind::GetProp || mode_ == ICState::Mode::Specialized) {
+ MOZ_ASSERT(!isSuper());
+ maybeEmitIdGuard(id);
+ writer.proxyGetResult(objId, id);
+ } else {
+ // Attach a stub that handles every id.
+ MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
+ MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
+ MOZ_ASSERT(!isSuper());
+ writer.proxyGetByValueResult(objId, getElemKeyValueId());
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("GenericProxy");
+ return AttachDecision::Attach;
+}
+
+ObjOperandId IRGenerator::guardDOMProxyExpandoObjectAndShape(
+ JSObject* obj, ObjOperandId objId, const Value& expandoVal,
+ JSObject* expandoObj) {
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+
+ TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
+
+ // Shape determines Class, so now it must be a DOM proxy.
+ ValOperandId expandoValId;
+ if (expandoVal.isObject()) {
+ expandoValId = writer.loadDOMExpandoValue(objId);
+ } else {
+ expandoValId = writer.loadDOMExpandoValueIgnoreGeneration(objId);
+ }
+
+ // Guard the expando is an object and shape guard.
+ ObjOperandId expandoObjId = writer.guardToObject(expandoValId);
+ TestMatchingHolder(writer, expandoObj, expandoObjId);
+ return expandoObjId;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachDOMProxyExpando(
+ HandleObject obj, ObjOperandId objId, HandleId id,
+ ValOperandId receiverId) {
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+
+ RootedValue expandoVal(cx_, GetProxyPrivate(obj));
+ RootedObject expandoObj(cx_);
+ if (expandoVal.isObject()) {
+ expandoObj = &expandoVal.toObject();
+ } else {
+ MOZ_ASSERT(!expandoVal.isUndefined(),
+ "How did a missing expando manage to shadow things?");
+ auto expandoAndGeneration =
+ static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
+ MOZ_ASSERT(expandoAndGeneration);
+ expandoObj = &expandoAndGeneration->expando.toObject();
+ }
+
+ // Try to do the lookup on the expando object.
+ RootedNativeObject holder(cx_);
+ RootedShape propShape(cx_);
+ NativeGetPropCacheability canCache =
+ CanAttachNativeGetProp(cx_, expandoObj, id, &holder, &propShape, pc_);
+ if (canCache == CanAttachNone) {
+ return AttachDecision::NoAction;
+ }
+ if (!holder) {
+ return AttachDecision::NoAction;
+ }
+
+ MOZ_ASSERT(holder == expandoObj);
+
+ maybeEmitIdGuard(id);
+ ObjOperandId expandoObjId =
+ guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj);
+
+ if (canCache == CanAttachReadSlot) {
+ // Load from the expando's slots.
+ EmitLoadSlotResult(writer, expandoObjId, &expandoObj->as<NativeObject>(),
+ propShape);
+ writer.returnFromIC();
+ } else {
+ // Call the getter. Note that we pass objId, the DOM proxy, as |this|
+ // and not the expando object.
+ MOZ_ASSERT(canCache == CanAttachNativeGetter ||
+ canCache == CanAttachScriptedGetter);
+ EmitCallGetterResultNoGuards(cx_, writer, expandoObj, expandoObj, propShape,
+ receiverId);
+ }
+
+ trackAttached("DOMProxyExpando");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachDOMProxyShadowed(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id) {
+ MOZ_ASSERT(!isSuper());
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+
+ maybeEmitIdGuard(id);
+ TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
+ writer.proxyGetResult(objId, id);
+ writer.returnFromIC();
+
+ trackAttached("DOMProxyShadowed");
+ return AttachDecision::Attach;
+}
+
+// Callers are expected to have already guarded on the shape of the
+// object, which guarantees the object is a DOM proxy.
+static void CheckDOMProxyExpandoDoesNotShadow(CacheIRWriter& writer,
+ JSObject* obj, jsid id,
+ ObjOperandId objId) {
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+
+ Value expandoVal = GetProxyPrivate(obj);
+
+ ValOperandId expandoId;
+ if (!expandoVal.isObject() && !expandoVal.isUndefined()) {
+ auto expandoAndGeneration =
+ static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
+ uint64_t generation = expandoAndGeneration->generation;
+ expandoId = writer.loadDOMExpandoValueGuardGeneration(
+ objId, expandoAndGeneration, generation);
+ expandoVal = expandoAndGeneration->expando;
+ } else {
+ expandoId = writer.loadDOMExpandoValue(objId);
+ }
+
+ if (expandoVal.isUndefined()) {
+ // Guard there's no expando object.
+ writer.guardNonDoubleType(expandoId, ValueType::Undefined);
+ } else if (expandoVal.isObject()) {
+ // Guard the proxy either has no expando object or, if it has one, that
+ // the shape matches the current expando object.
+ NativeObject& expandoObj = expandoVal.toObject().as<NativeObject>();
+ MOZ_ASSERT(!expandoObj.containsPure(id));
+ writer.guardDOMExpandoMissingOrGuardShape(expandoId,
+ expandoObj.lastProperty());
+ } else {
+ MOZ_CRASH("Invalid expando value");
+ }
+}
+
+AttachDecision GetPropIRGenerator::tryAttachDOMProxyUnshadowed(
+ HandleObject obj, ObjOperandId objId, HandleId id,
+ ValOperandId receiverId) {
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+
+ RootedObject checkObj(cx_, obj->staticPrototype());
+ if (!checkObj) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedNativeObject holder(cx_);
+ RootedShape shape(cx_);
+ NativeGetPropCacheability canCache =
+ CanAttachNativeGetProp(cx_, checkObj, id, &holder, &shape, pc_);
+ if (canCache == CanAttachNone) {
+ return AttachDecision::NoAction;
+ }
+
+ maybeEmitIdGuard(id);
+
+ // Guard that our expando object hasn't started shadowing this property.
+ TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
+ CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId);
+
+ if (holder) {
+ // Found the property on the prototype chain. Treat it like a native
+ // getprop.
+ GeneratePrototypeGuards(writer, obj, holder, objId);
+
+ // Guard on the holder of the property.
+ ObjOperandId holderId = writer.loadObject(holder);
+ TestMatchingHolder(writer, holder, holderId);
+
+ if (canCache == CanAttachReadSlot) {
+ EmitLoadSlotResult(writer, holderId, holder, shape);
+ writer.returnFromIC();
+ } else {
+ // EmitCallGetterResultNoGuards expects |obj| to be the object the
+ // property is on to do some checks. Since we actually looked at
+ // checkObj, and no extra guards will be generated, we can just
+ // pass that instead.
+ MOZ_ASSERT(canCache == CanAttachNativeGetter ||
+ canCache == CanAttachScriptedGetter);
+ MOZ_ASSERT(!isSuper());
+ EmitCallGetterResultNoGuards(cx_, writer, checkObj, holder, shape,
+ receiverId);
+ }
+ } else {
+ // Property was not found on the prototype chain. Deoptimize down to
+ // proxy get call.
+ MOZ_ASSERT(!isSuper());
+ writer.proxyGetResult(objId, id);
+ writer.returnFromIC();
+ }
+
+ trackAttached("DOMProxyUnshadowed");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachProxy(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id,
+ ValOperandId receiverId) {
+ ProxyStubType type = GetProxyStubType(cx_, obj, id);
+ if (type == ProxyStubType::None) {
+ return AttachDecision::NoAction;
+ }
+
+ // The proxy stubs don't currently support |super| access.
+ if (isSuper()) {
+ return AttachDecision::NoAction;
+ }
+
+ if (mode_ == ICState::Mode::Megamorphic) {
+ return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ true);
+ }
+
+ switch (type) {
+ case ProxyStubType::None:
+ break;
+ case ProxyStubType::DOMExpando:
+ TRY_ATTACH(tryAttachDOMProxyExpando(obj, objId, id, receiverId));
+ [[fallthrough]]; // Fall through to the generic shadowed case.
+ case ProxyStubType::DOMShadowed:
+ return tryAttachDOMProxyShadowed(obj, objId, id);
+ case ProxyStubType::DOMUnshadowed:
+ TRY_ATTACH(tryAttachDOMProxyUnshadowed(obj, objId, id, receiverId));
+ return tryAttachGenericProxy(obj, objId, id,
+ /* handleDOMProxies = */ true);
+ case ProxyStubType::Generic:
+ return tryAttachGenericProxy(obj, objId, id,
+ /* handleDOMProxies = */ false);
+ }
+
+ MOZ_CRASH("Unexpected ProxyStubType");
+}
+
+AttachDecision GetPropIRGenerator::tryAttachObjectLength(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id) {
+ if (!JSID_IS_ATOM(id, cx_->names().length)) {
+ return AttachDecision::NoAction;
+ }
+
+ if (obj->is<ArrayObject>()) {
+ if (obj->as<ArrayObject>().length() > INT32_MAX) {
+ return AttachDecision::NoAction;
+ }
+
+ maybeEmitIdGuard(id);
+ writer.guardClass(objId, GuardClassKind::Array);
+ writer.loadInt32ArrayLengthResult(objId);
+ writer.returnFromIC();
+
+ trackAttached("ArrayLength");
+ return AttachDecision::Attach;
+ }
+
+ if (obj->is<ArgumentsObject>() &&
+ !obj->as<ArgumentsObject>().hasOverriddenLength()) {
+ maybeEmitIdGuard(id);
+ if (obj->is<MappedArgumentsObject>()) {
+ writer.guardClass(objId, GuardClassKind::MappedArguments);
+ } else {
+ MOZ_ASSERT(obj->is<UnmappedArgumentsObject>());
+ writer.guardClass(objId, GuardClassKind::UnmappedArguments);
+ }
+ writer.loadArgumentsObjectLengthResult(objId);
+ writer.returnFromIC();
+
+ trackAttached("ArgumentsObjectLength");
+ return AttachDecision::Attach;
+ }
+
+ return AttachDecision::NoAction;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachTypedArrayLength(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id) {
+ if (!JSID_IS_ATOM(id, cx_->names().length)) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!obj->is<TypedArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ // For now only optimize when the result fits in an int32.
+ auto* tarr = &obj->as<TypedArrayObject>();
+ if (tarr->length().get() > INT32_MAX) {
+ return AttachDecision::NoAction;
+ }
+
+ if (mode_ != ICState::Mode::Specialized) {
+ return AttachDecision::NoAction;
+ }
+
+ // Receiver should be the object.
+ if (isSuper()) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedShape shape(cx_);
+ RootedNativeObject holder(cx_);
+ NativeGetPropCacheability type =
+ CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_);
+ if (type != CanAttachNativeGetter) {
+ return AttachDecision::NoAction;
+ }
+
+ JSFunction& fun = shape->getterValue().toObject().as<JSFunction>();
+ if (!TypedArrayObject::isOriginalLengthGetter(fun.native())) {
+ return AttachDecision::NoAction;
+ }
+
+ maybeEmitIdGuard(id);
+ // Emit all the normal guards for calling this native, but specialize
+ // callNativeGetterResult.
+ EmitCallGetterResultGuards(writer, obj, holder, shape, objId, mode_);
+ writer.loadTypedArrayLengthResult(objId);
+ writer.returnFromIC();
+
+ trackAttached("TypedArrayLength");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachFunction(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id) {
+ // Function properties are lazily resolved so they might not be defined yet.
+ // And we might end up in a situation where we always have a fresh function
+ // object during the IC generation.
+ if (!obj->is<JSFunction>()) {
+ return AttachDecision::NoAction;
+ }
+
+ bool isLength = JSID_IS_ATOM(id, cx_->names().length);
+ if (!isLength && !JSID_IS_ATOM(id, cx_->names().name)) {
+ return AttachDecision::NoAction;
+ }
+
+ JSObject* holder = nullptr;
+ PropertyResult prop;
+ // If this property exists already, don't attach the stub.
+ if (LookupPropertyPure(cx_, obj, id, &holder, &prop)) {
+ return AttachDecision::NoAction;
+ }
+
+ JSFunction* fun = &obj->as<JSFunction>();
+
+ if (isLength) {
+ // length was probably deleted from the function.
+ if (fun->hasResolvedLength()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Lazy functions don't store the length.
+ if (!fun->hasBytecode()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Length can be non-int32 for bound functions.
+ if (fun->isBoundFunction()) {
+ constexpr auto lengthSlot = FunctionExtended::BOUND_FUNCTION_LENGTH_SLOT;
+ if (!fun->getExtendedSlot(lengthSlot).isInt32()) {
+ return AttachDecision::NoAction;
+ }
+ }
+ } else {
+ // name was probably deleted from the function.
+ if (fun->hasResolvedName()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Unless the bound function name prefix is present, we need to call into
+ // the VM to compute the full name.
+ if (fun->isBoundFunction() && !fun->hasBoundFunctionNamePrefix()) {
+ return AttachDecision::NoAction;
+ }
+ }
+
+ maybeEmitIdGuard(id);
+ writer.guardClass(objId, GuardClassKind::JSFunction);
+ if (isLength) {
+ writer.loadFunctionLengthResult(objId);
+ writer.returnFromIC();
+ trackAttached("FunctionLength");
+ } else {
+ writer.loadFunctionNameResult(objId);
+ writer.returnFromIC();
+ trackAttached("FunctionName");
+ }
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectIterator(
+ HandleObject obj, ObjOperandId objId, HandleId id) {
+ if (!obj->is<ArgumentsObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!JSID_IS_SYMBOL(id) ||
+ JSID_TO_SYMBOL(id) != cx_->wellKnownSymbols().iterator) {
+ return AttachDecision::NoAction;
+ }
+
+ Handle<ArgumentsObject*> args = obj.as<ArgumentsObject>();
+ if (args->hasOverriddenIterator()) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedValue iterator(cx_);
+ if (!ArgumentsObject::getArgumentsIterator(cx_, &iterator)) {
+ cx_->recoverFromOutOfMemory();
+ return AttachDecision::NoAction;
+ }
+ MOZ_ASSERT(iterator.isObject());
+
+ maybeEmitIdGuard(id);
+ if (args->is<MappedArgumentsObject>()) {
+ writer.guardClass(objId, GuardClassKind::MappedArguments);
+ } else {
+ MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
+ writer.guardClass(objId, GuardClassKind::UnmappedArguments);
+ }
+ writer.guardArgumentsObjectNotOverriddenIterator(objId);
+
+ ObjOperandId iterId = writer.loadObject(&iterator.toObject());
+ writer.loadObjectResult(iterId);
+ writer.returnFromIC();
+
+ trackAttached("ArgumentsObjectIterator");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachModuleNamespace(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id) {
+ if (!obj->is<ModuleNamespaceObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ Rooted<ModuleNamespaceObject*> ns(cx_, &obj->as<ModuleNamespaceObject>());
+ RootedModuleEnvironmentObject env(cx_);
+ RootedShape shape(cx_);
+ if (!ns->bindings().lookup(id, env.address(), shape.address())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Don't emit a stub until the target binding has been initialized.
+ if (env->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check for the specific namespace object.
+ maybeEmitIdGuard(id);
+ writer.guardSpecificObject(objId, ns);
+
+ ObjOperandId envId = writer.loadObject(env);
+ EmitLoadSlotResult(writer, envId, env, shape);
+ writer.returnFromIC();
+
+ trackAttached("ModuleNamespace");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId,
+ HandleId id) {
+ MOZ_ASSERT(!isSuper(), "SuperBase is guaranteed to be an object");
+
+ JSProtoKey protoKey;
+ switch (val_.type()) {
+ case ValueType::String:
+ if (JSID_IS_ATOM(id, cx_->names().length)) {
+ // String length is special-cased, see js::GetProperty.
+ return AttachDecision::NoAction;
+ }
+ protoKey = JSProto_String;
+ break;
+ case ValueType::Int32:
+ case ValueType::Double:
+ protoKey = JSProto_Number;
+ break;
+ case ValueType::Boolean:
+ protoKey = JSProto_Boolean;
+ break;
+ case ValueType::Symbol:
+ protoKey = JSProto_Symbol;
+ break;
+ case ValueType::BigInt:
+ protoKey = JSProto_BigInt;
+ break;
+ case ValueType::Null:
+ case ValueType::Undefined:
+ case ValueType::Magic:
+ return AttachDecision::NoAction;
+ case ValueType::Object:
+ case ValueType::PrivateGCThing:
+ MOZ_CRASH("unexpected type");
+ }
+
+ RootedObject proto(cx_, cx_->global()->maybeGetPrototype(protoKey));
+ if (!proto) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedShape shape(cx_);
+ RootedNativeObject holder(cx_);
+ NativeGetPropCacheability type =
+ CanAttachNativeGetProp(cx_, proto, id, &holder, &shape, pc_);
+ switch (type) {
+ case CanAttachNone:
+ return AttachDecision::NoAction;
+ case CanAttachReadSlot: {
+ if (val_.isNumber()) {
+ writer.guardIsNumber(valId);
+ } else {
+ writer.guardNonDoubleType(valId, val_.type());
+ }
+ maybeEmitIdGuard(id);
+
+ ObjOperandId protoId = writer.loadObject(proto);
+ EmitReadSlotResult(writer, proto, holder, shape, protoId);
+ writer.returnFromIC();
+
+ trackAttached("PrimitiveSlot");
+ return AttachDecision::Attach;
+ }
+ case CanAttachScriptedGetter:
+ case CanAttachNativeGetter: {
+ if (val_.isNumber()) {
+ writer.guardIsNumber(valId);
+ } else {
+ writer.guardNonDoubleType(valId, val_.type());
+ }
+ maybeEmitIdGuard(id);
+
+ ObjOperandId protoId = writer.loadObject(proto);
+ EmitCallGetterResult(cx_, writer, proto, holder, shape, protoId, valId,
+ mode_);
+
+ trackAttached("PrimitiveGetter");
+ return AttachDecision::Attach;
+ }
+ }
+
+ MOZ_CRASH("Bad NativeGetPropCacheability");
+}
+
+AttachDecision GetPropIRGenerator::tryAttachStringLength(ValOperandId valId,
+ HandleId id) {
+ if (!val_.isString() || !JSID_IS_ATOM(id, cx_->names().length)) {
+ return AttachDecision::NoAction;
+ }
+
+ StringOperandId strId = writer.guardToString(valId);
+ maybeEmitIdGuard(id);
+ writer.loadStringLengthResult(strId);
+ writer.returnFromIC();
+
+ trackAttached("StringLength");
+ return AttachDecision::Attach;
+}
+
+static bool CanAttachStringChar(HandleValue val, HandleValue idVal) {
+ if (!val.isString() || !idVal.isInt32()) {
+ return false;
+ }
+
+ int32_t index = idVal.toInt32();
+ if (index < 0) {
+ return false;
+ }
+
+ JSString* str = val.toString();
+ if (size_t(index) >= str->length()) {
+ return false;
+ }
+
+ // This follows JSString::getChar, otherwise we fail to attach getChar in a
+ // lot of cases.
+ if (str->isRope()) {
+ JSRope* rope = &str->asRope();
+
+ // Make sure the left side contains the index.
+ if (size_t(index) >= rope->leftChild()->length()) {
+ return false;
+ }
+
+ str = rope->leftChild();
+ }
+
+ if (!str->isLinear()) {
+ return false;
+ }
+
+ return true;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachStringChar(ValOperandId valId,
+ ValOperandId indexId) {
+ MOZ_ASSERT(idVal_.isInt32());
+
+ if (!CanAttachStringChar(val_, idVal_)) {
+ return AttachDecision::NoAction;
+ }
+
+ StringOperandId strId = writer.guardToString(valId);
+ Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
+ writer.loadStringCharResult(strId, int32IndexId);
+ writer.returnFromIC();
+
+ trackAttached("StringChar");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachMagicArgumentsName(
+ ValOperandId valId, HandleId id) {
+ if (!val_.isMagic(JS_OPTIMIZED_ARGUMENTS)) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!JSID_IS_ATOM(id, cx_->names().length) &&
+ !JSID_IS_ATOM(id, cx_->names().callee)) {
+ return AttachDecision::NoAction;
+ }
+
+ maybeEmitIdGuard(id);
+ writer.guardMagicValue(valId, JS_OPTIMIZED_ARGUMENTS);
+ writer.guardFrameHasNoArgumentsObject();
+
+ if (JSID_IS_ATOM(id, cx_->names().length)) {
+ writer.loadFrameNumActualArgsResult();
+ } else {
+ MOZ_ASSERT(JSID_IS_ATOM(id, cx_->names().callee));
+ writer.loadFrameCalleeResult();
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("MagicArgumentsName");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachMagicArgument(
+ ValOperandId valId, ValOperandId indexId) {
+ MOZ_ASSERT(idVal_.isInt32());
+
+ if (!val_.isMagic(JS_OPTIMIZED_ARGUMENTS)) {
+ return AttachDecision::NoAction;
+ }
+
+ writer.guardMagicValue(valId, JS_OPTIMIZED_ARGUMENTS);
+ writer.guardFrameHasNoArgumentsObject();
+
+ Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
+ writer.loadFrameArgumentResult(int32IndexId);
+ writer.returnFromIC();
+
+ trackAttached("MagicArgument");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectArg(
+ HandleObject obj, ObjOperandId objId, uint32_t index,
+ Int32OperandId indexId) {
+ if (!obj->is<ArgumentsObject>()) {
+ return AttachDecision::NoAction;
+ }
+ auto* args = &obj->as<ArgumentsObject>();
+
+ // No elements must have been overriden.
+ if (args->hasOverriddenElement()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check bounds.
+ if (index >= args->initialLength()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Ensure no elements were ever deleted.
+ if (args->isAnyElementDeleted()) {
+ return AttachDecision::NoAction;
+ }
+
+ // And finally also check that the argument isn't forwarded.
+ if (args->argIsForwarded(index)) {
+ return AttachDecision::NoAction;
+ }
+
+ if (args->is<MappedArgumentsObject>()) {
+ writer.guardClass(objId, GuardClassKind::MappedArguments);
+ } else {
+ MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
+ writer.guardClass(objId, GuardClassKind::UnmappedArguments);
+ }
+
+ writer.loadArgumentsObjectArgResult(objId, indexId);
+ writer.returnFromIC();
+
+ trackAttached("ArgumentsObjectArg");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachDenseElement(
+ HandleObject obj, ObjOperandId objId, uint32_t index,
+ Int32OperandId indexId) {
+ if (!obj->isNative()) {
+ return AttachDecision::NoAction;
+ }
+
+ NativeObject* nobj = &obj->as<NativeObject>();
+ if (!nobj->containsDenseElement(index)) {
+ return AttachDecision::NoAction;
+ }
+
+ TestMatchingNativeReceiver(writer, nobj, objId);
+ writer.loadDenseElementResult(objId, indexId);
+ writer.returnFromIC();
+
+ trackAttached("DenseElement");
+ return AttachDecision::Attach;
+}
+
+static bool ClassCanHaveExtraProperties(const JSClass* clasp) {
+ return clasp->getResolve() || clasp->getOpsLookupProperty() ||
+ clasp->getOpsGetProperty() || IsTypedArrayClass(clasp);
+}
+
+static bool CanAttachDenseElementHole(NativeObject* obj, bool ownProp,
+ bool allowIndexedReceiver = false) {
+ // Make sure the objects on the prototype don't have any indexed properties
+ // or that such properties can't appear without a shape change.
+ // Otherwise returning undefined for holes would obviously be incorrect,
+ // because we would have to lookup a property on the prototype instead.
+ do {
+ // The first two checks are also relevant to the receiver object.
+ if (!allowIndexedReceiver && obj->isIndexed()) {
+ return false;
+ }
+ allowIndexedReceiver = false;
+
+ if (ClassCanHaveExtraProperties(obj->getClass())) {
+ return false;
+ }
+
+ // Don't need to check prototype for OwnProperty checks
+ if (ownProp) {
+ return true;
+ }
+
+ JSObject* proto = obj->staticPrototype();
+ if (!proto) {
+ break;
+ }
+
+ if (!proto->isNative()) {
+ return false;
+ }
+
+ // Make sure objects on the prototype don't have dense elements.
+ if (proto->as<NativeObject>().getDenseInitializedLength() != 0) {
+ return false;
+ }
+
+ obj = &proto->as<NativeObject>();
+ } while (true);
+
+ return true;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachDenseElementHole(
+ HandleObject obj, ObjOperandId objId, uint32_t index,
+ Int32OperandId indexId) {
+ if (!obj->isNative()) {
+ return AttachDecision::NoAction;
+ }
+
+ NativeObject* nobj = &obj->as<NativeObject>();
+ if (nobj->containsDenseElement(index)) {
+ return AttachDecision::NoAction;
+ }
+ if (!CanAttachDenseElementHole(nobj, false)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Guard on the shape, to prevent non-dense elements from appearing.
+ TestMatchingNativeReceiver(writer, nobj, objId);
+ GeneratePrototypeHoleGuards(writer, nobj, objId,
+ /* alwaysGuardFirstProto = */ false);
+ writer.loadDenseElementHoleResult(objId, indexId);
+ writer.returnFromIC();
+
+ trackAttached("DenseElementHole");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachSparseElement(
+ HandleObject obj, ObjOperandId objId, uint32_t index,
+ Int32OperandId indexId) {
+ if (!obj->isNative()) {
+ return AttachDecision::NoAction;
+ }
+ NativeObject* nobj = &obj->as<NativeObject>();
+
+ // Stub doesn't handle negative indices.
+ if (index > INT_MAX) {
+ return AttachDecision::NoAction;
+ }
+
+ // We also need to be past the end of the dense capacity, to ensure sparse.
+ if (index < nobj->getDenseInitializedLength()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Only handle Array objects in this stub.
+ if (!nobj->is<ArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Here, we ensure that the prototype chain does not define any sparse
+ // indexed properties on the shape lineage. This allows us to guard on
+ // the shapes up the prototype chain to ensure that no indexed properties
+ // exist outside of the dense elements.
+ //
+ // The `GeneratePrototypeHoleGuards` call below will guard on the shapes,
+ // as well as ensure that no prototypes contain dense elements, allowing
+ // us to perform a pure shape-search for out-of-bounds integer-indexed
+ // properties on the recevier object.
+ if ((nobj->staticPrototype() != nullptr) &&
+ ObjectMayHaveExtraIndexedProperties(nobj->staticPrototype())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Ensure that obj is an Array.
+ writer.guardClass(objId, GuardClassKind::Array);
+
+ // The helper we are going to call only applies to non-dense elements.
+ writer.guardIndexGreaterThanDenseInitLength(objId, indexId);
+
+ // Ensures we are able to efficiently able to map to an integral jsid.
+ writer.guardInt32IsNonNegative(indexId);
+
+ // Shape guard the prototype chain to avoid shadowing indexes from appearing.
+ // The helper function also ensures that the index does not appear within the
+ // dense element set of the prototypes.
+ GeneratePrototypeHoleGuards(writer, nobj, objId,
+ /* alwaysGuardFirstProto = */ true);
+
+ // At this point, we are guaranteed that the indexed property will not
+ // be found on one of the prototypes. We are assured that we only have
+ // to check that the receiving object has the property.
+
+ writer.callGetSparseElementResult(objId, indexId);
+ writer.returnFromIC();
+
+ trackAttached("GetSparseElement");
+ return AttachDecision::Attach;
+}
+
+// For Uint32Array we let the stub return a double only if the current result is
+// a double, to allow better codegen in Warp.
+static bool AllowDoubleForUint32Array(TypedArrayObject* tarr, uint32_t index) {
+ if (tarr->type() != Scalar::Type::Uint32) {
+ // Return value is only relevant for Uint32Array.
+ return false;
+ }
+
+ // TODO: audit callers to check they do the right thing if index > INT32_MAX.
+ if (index >= tarr->length().deprecatedGetUint32()) {
+ return false;
+ }
+ MOZ_ASSERT(index <= INT32_MAX);
+
+ Value res;
+ MOZ_ALWAYS_TRUE(tarr->getElementPure(index, &res));
+ MOZ_ASSERT(res.isNumber());
+ return res.isDouble();
+}
+
+AttachDecision GetPropIRGenerator::tryAttachTypedArrayElement(
+ HandleObject obj, ObjOperandId objId, uint32_t index,
+ Int32OperandId indexId) {
+ if (!obj->is<TypedArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+ TypedArrayObject* tarr = &obj->as<TypedArrayObject>();
+
+ writer.guardShapeForClass(objId, tarr->shape());
+
+ bool handleOOB = index >= tarr->length().get();
+ bool allowDoubleForUint32 = AllowDoubleForUint32Array(tarr, index);
+ writer.loadTypedArrayElementResult(objId, indexId, tarr->type(), handleOOB,
+ allowDoubleForUint32);
+ writer.returnFromIC();
+
+ trackAttached("TypedElement");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachTypedArrayNonInt32Index(
+ HandleObject obj, ObjOperandId objId) {
+ if (!obj->is<TypedArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!idVal_.isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ TypedArrayObject* tarr = &obj->as<TypedArrayObject>();
+
+ // Try to convert the number to a typed array index. Use NumberEqualsInt32
+ // because ToPropertyKey(-0) is 0. If the number is not representable as an
+ // int32 the result will be |undefined| so we leave |allowDoubleForUint32| as
+ // false.
+ bool allowDoubleForUint32 = false;
+ int32_t indexInt32;
+ if (mozilla::NumberEqualsInt32(idVal_.toNumber(), &indexInt32) &&
+ indexInt32 >= 0) {
+ uint32_t index = uint32_t(indexInt32);
+ allowDoubleForUint32 = AllowDoubleForUint32Array(tarr, index);
+ }
+
+ ValOperandId keyId = getElemKeyValueId();
+ Int32OperandId indexId = writer.guardToTypedArrayIndex(keyId);
+
+ writer.guardShapeForClass(objId, tarr->shape());
+
+ writer.loadTypedArrayElementResult(objId, indexId, tarr->type(),
+ /* handleOOB = */ true,
+ allowDoubleForUint32);
+ writer.returnFromIC();
+
+ trackAttached("TypedArrayNonInt32Index");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachGenericElement(
+ HandleObject obj, ObjOperandId objId, uint32_t index,
+ Int32OperandId indexId) {
+ if (!obj->isNative()) {
+ return AttachDecision::NoAction;
+ }
+
+ // To allow other types to attach in the non-megamorphic case we test the
+ // specific matching native receiver; however, once megamorphic we can attach
+ // for any native
+ if (mode_ == ICState::Mode::Megamorphic) {
+ writer.guardIsNativeObject(objId);
+ } else {
+ NativeObject* nobj = &obj->as<NativeObject>();
+ TestMatchingNativeReceiver(writer, nobj, objId);
+ }
+ writer.guardIndexGreaterThanDenseInitLength(objId, indexId);
+ writer.callNativeGetElementResult(objId, indexId);
+ writer.returnFromIC();
+
+ trackAttached(mode_ == ICState::Mode::Megamorphic
+ ? "GenericElementMegamorphic"
+ : "GenericElement");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetPropIRGenerator::tryAttachProxyElement(HandleObject obj,
+ ObjOperandId objId) {
+ if (!obj->is<ProxyObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ // The proxy stubs don't currently support |super| access.
+ if (isSuper()) {
+ return AttachDecision::NoAction;
+ }
+
+ writer.guardIsProxy(objId);
+
+ // We are not guarding against DOM proxies here, because there is no other
+ // specialized DOM IC we could attach.
+ // We could call maybeEmitIdGuard here and then emit ProxyGetResult,
+ // but for GetElem we prefer to attach a stub that can handle any Value
+ // so we don't attach a new stub for every id.
+ MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
+ MOZ_ASSERT(!isSuper());
+ writer.proxyGetByValueResult(objId, getElemKeyValueId());
+ writer.returnFromIC();
+
+ trackAttached("ProxyElement");
+ return AttachDecision::Attach;
+}
+
+void GetPropIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("base", val_);
+ sp.valueProperty("property", idVal_);
+ }
+#endif
+}
+
+void IRGenerator::emitIdGuard(ValOperandId valId, jsid id) {
+ if (JSID_IS_SYMBOL(id)) {
+ SymbolOperandId symId = writer.guardToSymbol(valId);
+ writer.guardSpecificSymbol(symId, JSID_TO_SYMBOL(id));
+ } else {
+ MOZ_ASSERT(JSID_IS_ATOM(id));
+ StringOperandId strId = writer.guardToString(valId);
+ writer.guardSpecificAtom(strId, JSID_TO_ATOM(id));
+ }
+}
+
+void GetPropIRGenerator::maybeEmitIdGuard(jsid id) {
+ if (cacheKind_ == CacheKind::GetProp ||
+ cacheKind_ == CacheKind::GetPropSuper) {
+ // Constant PropertyName, no guards necessary.
+ MOZ_ASSERT(&idVal_.toString()->asAtom() == JSID_TO_ATOM(id));
+ return;
+ }
+
+ MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
+ cacheKind_ == CacheKind::GetElemSuper);
+ emitIdGuard(getElemKeyValueId(), id);
+}
+
+void SetPropIRGenerator::maybeEmitIdGuard(jsid id) {
+ if (cacheKind_ == CacheKind::SetProp) {
+ // Constant PropertyName, no guards necessary.
+ MOZ_ASSERT(&idVal_.toString()->asAtom() == JSID_TO_ATOM(id));
+ return;
+ }
+
+ MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
+ emitIdGuard(setElemKeyValueId(), id);
+}
+
+GetNameIRGenerator::GetNameIRGenerator(JSContext* cx, HandleScript script,
+ jsbytecode* pc, ICState::Mode mode,
+ HandleObject env,
+ HandlePropertyName name)
+ : IRGenerator(cx, script, pc, CacheKind::GetName, mode),
+ env_(env),
+ name_(name) {}
+
+AttachDecision GetNameIRGenerator::tryAttachStub() {
+ MOZ_ASSERT(cacheKind_ == CacheKind::GetName);
+
+ AutoAssertNoPendingException aanpe(cx_);
+
+ ObjOperandId envId(writer.setInputOperandId(0));
+ RootedId id(cx_, NameToId(name_));
+
+ TRY_ATTACH(tryAttachGlobalNameValue(envId, id));
+ TRY_ATTACH(tryAttachGlobalNameGetter(envId, id));
+ TRY_ATTACH(tryAttachEnvironmentName(envId, id));
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+}
+
+bool CanAttachGlobalName(JSContext* cx,
+ Handle<LexicalEnvironmentObject*> globalLexical,
+ HandleId id, MutableHandleNativeObject holder,
+ MutableHandleShape shape) {
+ // The property must be found, and it must be found as a normal data property.
+ RootedNativeObject current(cx, globalLexical);
+ while (true) {
+ shape.set(current->lookup(cx, id));
+ if (shape) {
+ break;
+ }
+
+ if (current == globalLexical) {
+ current = &globalLexical->global();
+ } else {
+ // In the browser the global prototype chain should be immutable.
+ if (!current->staticPrototypeIsImmutable()) {
+ return false;
+ }
+
+ JSObject* proto = current->staticPrototype();
+ if (!proto || !proto->is<NativeObject>()) {
+ return false;
+ }
+
+ current = &proto->as<NativeObject>();
+ }
+ }
+
+ holder.set(current);
+ return true;
+}
+
+AttachDecision GetNameIRGenerator::tryAttachGlobalNameValue(ObjOperandId objId,
+ HandleId id) {
+ if (!IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
+ return AttachDecision::NoAction;
+ }
+
+ Handle<LexicalEnvironmentObject*> globalLexical =
+ env_.as<LexicalEnvironmentObject>();
+ MOZ_ASSERT(globalLexical->isGlobal());
+
+ RootedNativeObject holder(cx_);
+ RootedShape shape(cx_);
+ if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &shape)) {
+ return AttachDecision::NoAction;
+ }
+
+ // The property must be found, and it must be found as a normal data property.
+ if (!shape->isDataProperty()) {
+ return AttachDecision::NoAction;
+ }
+
+ // This might still be an uninitialized lexical.
+ if (holder->getSlot(shape->slot()).isMagic()) {
+ return AttachDecision::NoAction;
+ }
+
+ if (holder == globalLexical) {
+ // There is no need to guard on the shape. Lexical bindings are
+ // non-configurable, and this stub cannot be shared across globals.
+ size_t dynamicSlotOffset =
+ holder->dynamicSlotIndex(shape->slot()) * sizeof(Value);
+ writer.loadDynamicSlotResult(objId, dynamicSlotOffset);
+ } else {
+ // Check the prototype chain from the global to the holder
+ // prototype. Ignore the global lexical scope as it doesn't figure
+ // into the prototype chain. We guard on the global lexical
+ // scope's shape independently.
+ if (!IsCacheableGetPropReadSlot(&globalLexical->global(), holder, shape)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Shape guard for global lexical.
+ writer.guardShape(objId, globalLexical->lastProperty());
+
+ // Guard on the shape of the GlobalObject.
+ ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
+ writer.guardShape(globalId, globalLexical->global().lastProperty());
+
+ ObjOperandId holderId = globalId;
+ if (holder != &globalLexical->global()) {
+ // Shape guard holder.
+ holderId = writer.loadObject(holder);
+ writer.guardShape(holderId, holder->lastProperty());
+ }
+
+ EmitLoadSlotResult(writer, holderId, holder, shape);
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("GlobalNameValue");
+ return AttachDecision::Attach;
+}
+
+AttachDecision GetNameIRGenerator::tryAttachGlobalNameGetter(ObjOperandId objId,
+ HandleId id) {
+ if (!IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
+ return AttachDecision::NoAction;
+ }
+
+ Handle<LexicalEnvironmentObject*> globalLexical =
+ env_.as<LexicalEnvironmentObject>();
+ MOZ_ASSERT(globalLexical->isGlobal());
+
+ RootedNativeObject holder(cx_);
+ RootedShape shape(cx_);
+ if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &shape)) {
+ return AttachDecision::NoAction;
+ }
+
+ if (holder == globalLexical) {
+ return AttachDecision::NoAction;
+ }
+
+ if (IsCacheableGetPropCall(&globalLexical->global(), holder, shape) !=
+ CanAttachNativeGetter) {
+ return AttachDecision::NoAction;
+ }
+
+ // Shape guard for global lexical.
+ writer.guardShape(objId, globalLexical->lastProperty());
+
+ // Guard on the shape of the GlobalObject.
+ ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
+ writer.guardShape(globalId, globalLexical->global().lastProperty());
+
+ if (holder != &globalLexical->global()) {
+ // Shape guard holder.
+ ObjOperandId holderId = writer.loadObject(holder);
+ writer.guardShape(holderId, holder->lastProperty());
+ }
+
+ if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, &globalLexical->global(),
+ shape, mode_)) {
+ // The global shape guard above ensures the instance JSClass is correct.
+ EmitCallDOMGetterResultNoGuards(writer, shape, globalId);
+ trackAttached("GlobalNameDOMGetter");
+ } else {
+ ValOperandId receiverId = writer.boxObject(globalId);
+ EmitCallGetterResultNoGuards(cx_, writer, &globalLexical->global(), holder,
+ shape, receiverId);
+ trackAttached("GlobalNameGetter");
+ }
+
+ return AttachDecision::Attach;
+}
+
+static bool NeedEnvironmentShapeGuard(JSObject* envObj) {
+ if (!envObj->is<CallObject>()) {
+ return true;
+ }
+
+ // We can skip a guard on the call object if the script's bindings are
+ // guaranteed to be immutable (and thus cannot introduce shadowing variables).
+ // If the function is a relazified self-hosted function it has no BaseScript
+ // and we pessimistically create the guard.
+ CallObject* callObj = &envObj->as<CallObject>();
+ JSFunction* fun = &callObj->callee();
+ if (!fun->hasBaseScript() || fun->baseScript()->funHasExtensibleScope()) {
+ return true;
+ }
+
+ return false;
+}
+
+AttachDecision GetNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
+ HandleId id) {
+ if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedObject env(cx_, env_);
+ RootedShape shape(cx_);
+ RootedNativeObject holder(cx_);
+
+ while (env) {
+ if (env->is<GlobalObject>()) {
+ shape = env->as<GlobalObject>().lookup(cx_, id);
+ if (shape) {
+ break;
+ }
+ return AttachDecision::NoAction;
+ }
+
+ if (!env->is<EnvironmentObject>() || env->is<WithEnvironmentObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check for an 'own' property on the env. There is no need to
+ // check the prototype as non-with scopes do not inherit properties
+ // from any prototype.
+ shape = env->as<NativeObject>().lookup(cx_, id);
+ if (shape) {
+ break;
+ }
+
+ env = env->enclosingEnvironment();
+ }
+
+ holder = &env->as<NativeObject>();
+ if (!IsCacheableGetPropReadSlot(holder, holder, shape)) {
+ return AttachDecision::NoAction;
+ }
+ if (holder->getSlot(shape->slot()).isMagic()) {
+ return AttachDecision::NoAction;
+ }
+
+ ObjOperandId lastObjId = objId;
+ env = env_;
+ while (env) {
+ if (NeedEnvironmentShapeGuard(env)) {
+ writer.guardShape(lastObjId, env->shape());
+ }
+
+ if (env == holder) {
+ break;
+ }
+
+ lastObjId = writer.loadEnclosingEnvironment(lastObjId);
+ env = env->enclosingEnvironment();
+ }
+
+ if (holder->isFixedSlot(shape->slot())) {
+ writer.loadEnvironmentFixedSlotResult(
+ lastObjId, NativeObject::getFixedSlotOffset(shape->slot()));
+ } else {
+ size_t dynamicSlotOffset =
+ holder->dynamicSlotIndex(shape->slot()) * sizeof(Value);
+ writer.loadEnvironmentDynamicSlotResult(lastObjId, dynamicSlotOffset);
+ }
+ writer.returnFromIC();
+
+ trackAttached("EnvironmentName");
+ return AttachDecision::Attach;
+}
+
+void GetNameIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("base", ObjectValue(*env_));
+ sp.valueProperty("property", StringValue(name_));
+ }
+#endif
+}
+
+BindNameIRGenerator::BindNameIRGenerator(JSContext* cx, HandleScript script,
+ jsbytecode* pc, ICState::Mode mode,
+ HandleObject env,
+ HandlePropertyName name)
+ : IRGenerator(cx, script, pc, CacheKind::BindName, mode),
+ env_(env),
+ name_(name) {}
+
+AttachDecision BindNameIRGenerator::tryAttachStub() {
+ MOZ_ASSERT(cacheKind_ == CacheKind::BindName);
+
+ AutoAssertNoPendingException aanpe(cx_);
+
+ ObjOperandId envId(writer.setInputOperandId(0));
+ RootedId id(cx_, NameToId(name_));
+
+ TRY_ATTACH(tryAttachGlobalName(envId, id));
+ TRY_ATTACH(tryAttachEnvironmentName(envId, id));
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+}
+
+AttachDecision BindNameIRGenerator::tryAttachGlobalName(ObjOperandId objId,
+ HandleId id) {
+ if (!IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
+ return AttachDecision::NoAction;
+ }
+
+ Handle<LexicalEnvironmentObject*> globalLexical =
+ env_.as<LexicalEnvironmentObject>();
+ MOZ_ASSERT(globalLexical->isGlobal());
+
+ JSObject* result = nullptr;
+ if (Shape* shape = globalLexical->lookup(cx_, id)) {
+ // If this is an uninitialized lexical or a const, we need to return a
+ // RuntimeLexicalErrorObject.
+ if (globalLexical->getSlot(shape->slot()).isMagic() || !shape->writable()) {
+ return AttachDecision::NoAction;
+ }
+ result = globalLexical;
+ } else {
+ result = &globalLexical->global();
+ }
+
+ if (result == globalLexical) {
+ // Lexical bindings are non-configurable so we can just return the
+ // global lexical.
+ writer.loadObjectResult(objId);
+ } else {
+ // If the property exists on the global and is non-configurable, it cannot
+ // be shadowed by the lexical scope so we can just return the global without
+ // a shape guard.
+ Shape* shape = result->as<GlobalObject>().lookup(cx_, id);
+ if (!shape || shape->configurable()) {
+ writer.guardShape(objId, globalLexical->lastProperty());
+ }
+ ObjOperandId globalId = writer.loadEnclosingEnvironment(objId);
+ writer.loadObjectResult(globalId);
+ }
+ writer.returnFromIC();
+
+ trackAttached("GlobalName");
+ return AttachDecision::Attach;
+}
+
+AttachDecision BindNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId,
+ HandleId id) {
+ if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedObject env(cx_, env_);
+ RootedShape shape(cx_);
+ while (true) {
+ if (!env->is<GlobalObject>() && !env->is<EnvironmentObject>()) {
+ return AttachDecision::NoAction;
+ }
+ if (env->is<WithEnvironmentObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ // When we reach an unqualified variables object (like the global) we
+ // have to stop looking and return that object.
+ if (env->isUnqualifiedVarObj()) {
+ break;
+ }
+
+ // Check for an 'own' property on the env. There is no need to
+ // check the prototype as non-with scopes do not inherit properties
+ // from any prototype.
+ shape = env->as<NativeObject>().lookup(cx_, id);
+ if (shape) {
+ break;
+ }
+
+ env = env->enclosingEnvironment();
+ }
+
+ // If this is an uninitialized lexical or a const, we need to return a
+ // RuntimeLexicalErrorObject.
+ RootedNativeObject holder(cx_, &env->as<NativeObject>());
+ if (shape && holder->is<EnvironmentObject>() &&
+ (holder->getSlot(shape->slot()).isMagic() || !shape->writable())) {
+ return AttachDecision::NoAction;
+ }
+
+ ObjOperandId lastObjId = objId;
+ env = env_;
+ while (env) {
+ if (NeedEnvironmentShapeGuard(env) && !env->is<GlobalObject>()) {
+ writer.guardShape(lastObjId, env->shape());
+ }
+
+ if (env == holder) {
+ break;
+ }
+
+ lastObjId = writer.loadEnclosingEnvironment(lastObjId);
+ env = env->enclosingEnvironment();
+ }
+ writer.loadObjectResult(lastObjId);
+ writer.returnFromIC();
+
+ trackAttached("EnvironmentName");
+ return AttachDecision::Attach;
+}
+
+void BindNameIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("base", ObjectValue(*env_));
+ sp.valueProperty("property", StringValue(name_));
+ }
+#endif
+}
+
+HasPropIRGenerator::HasPropIRGenerator(JSContext* cx, HandleScript script,
+ jsbytecode* pc, ICState::Mode mode,
+ CacheKind cacheKind, HandleValue idVal,
+ HandleValue val)
+ : IRGenerator(cx, script, pc, cacheKind, mode), val_(val), idVal_(idVal) {}
+
+AttachDecision HasPropIRGenerator::tryAttachDense(HandleObject obj,
+ ObjOperandId objId,
+ uint32_t index,
+ Int32OperandId indexId) {
+ if (!obj->isNative()) {
+ return AttachDecision::NoAction;
+ }
+
+ NativeObject* nobj = &obj->as<NativeObject>();
+ if (!nobj->containsDenseElement(index)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Guard shape to ensure object class is NativeObject.
+ TestMatchingNativeReceiver(writer, nobj, objId);
+ writer.loadDenseElementExistsResult(objId, indexId);
+ writer.returnFromIC();
+
+ trackAttached("DenseHasProp");
+ return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachDenseHole(HandleObject obj,
+ ObjOperandId objId,
+ uint32_t index,
+ Int32OperandId indexId) {
+ bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
+
+ if (!obj->isNative()) {
+ return AttachDecision::NoAction;
+ }
+
+ NativeObject* nobj = &obj->as<NativeObject>();
+ if (nobj->containsDenseElement(index)) {
+ return AttachDecision::NoAction;
+ }
+ if (!CanAttachDenseElementHole(nobj, hasOwn)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Guard shape to ensure class is NativeObject and to prevent non-dense
+ // elements being added. Also ensures prototype doesn't change if dynamic
+ // checks aren't emitted.
+ TestMatchingNativeReceiver(writer, nobj, objId);
+
+ // Generate prototype guards if needed. This includes monitoring that
+ // properties were not added in the chain.
+ if (!hasOwn) {
+ GeneratePrototypeHoleGuards(writer, nobj, objId,
+ /* alwaysGuardFirstProto = */ false);
+ }
+
+ writer.loadDenseElementHoleExistsResult(objId, indexId);
+ writer.returnFromIC();
+
+ trackAttached("DenseHasPropHole");
+ return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachSparse(HandleObject obj,
+ ObjOperandId objId,
+ Int32OperandId indexId) {
+ bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
+
+ if (!obj->isNative()) {
+ return AttachDecision::NoAction;
+ }
+ if (!obj->as<NativeObject>().isIndexed()) {
+ return AttachDecision::NoAction;
+ }
+ if (!CanAttachDenseElementHole(&obj->as<NativeObject>(), hasOwn,
+ /* allowIndexedReceiver = */ true)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Guard that this is a native object.
+ writer.guardIsNativeObject(objId);
+
+ // Generate prototype guards if needed. This includes monitoring that
+ // properties were not added in the chain.
+ if (!hasOwn) {
+ GeneratePrototypeHoleGuards(writer, obj, objId,
+ /* alwaysGuardFirstProto = */ true);
+ }
+
+ // Because of the prototype guard we know that the prototype chain
+ // does not include any dense or sparse (i.e indexed) properties.
+ writer.callObjectHasSparseElementResult(objId, indexId);
+ writer.returnFromIC();
+
+ trackAttached("Sparse");
+ return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachNamedProp(HandleObject obj,
+ ObjOperandId objId,
+ HandleId key,
+ ValOperandId keyId) {
+ bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
+
+ JSObject* holder = nullptr;
+ PropertyResult prop;
+
+ if (hasOwn) {
+ if (!LookupOwnPropertyPure(cx_, obj, key, &prop)) {
+ return AttachDecision::NoAction;
+ }
+
+ holder = obj;
+ } else {
+ if (!LookupPropertyPure(cx_, obj, key, &holder, &prop)) {
+ return AttachDecision::NoAction;
+ }
+ }
+ if (!prop) {
+ return AttachDecision::NoAction;
+ }
+
+ TRY_ATTACH(tryAttachMegamorphic(objId, keyId));
+ TRY_ATTACH(tryAttachNative(obj, objId, key, keyId, prop, holder));
+
+ return AttachDecision::NoAction;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachMegamorphic(ObjOperandId objId,
+ ValOperandId keyId) {
+ bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
+
+ if (mode_ != ICState::Mode::Megamorphic) {
+ return AttachDecision::NoAction;
+ }
+
+ writer.megamorphicHasPropResult(objId, keyId, hasOwn);
+ writer.returnFromIC();
+ trackAttached("MegamorphicHasProp");
+ return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachNative(JSObject* obj,
+ ObjOperandId objId, jsid key,
+ ValOperandId keyId,
+ PropertyResult prop,
+ JSObject* holder) {
+ if (!prop.isNativeProperty()) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!IsCacheableProtoChain(obj, holder)) {
+ return AttachDecision::NoAction;
+ }
+
+ Maybe<ObjOperandId> tempId;
+ emitIdGuard(keyId, key);
+ EmitReadSlotGuard(writer, obj, holder, objId, &tempId);
+ writer.loadBooleanResult(true);
+ writer.returnFromIC();
+
+ trackAttached("NativeHasProp");
+ return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachTypedArray(HandleObject obj,
+ ObjOperandId objId,
+ Int32OperandId indexId) {
+ if (!obj->is<TypedArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ writer.guardIsTypedArray(objId);
+ writer.loadTypedArrayElementExistsResult(objId, indexId);
+ writer.returnFromIC();
+
+ trackAttached("TypedArrayObject");
+ return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachTypedArrayNonInt32Index(
+ HandleObject obj, ObjOperandId objId, ValOperandId keyId) {
+ if (!obj->is<TypedArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!idVal_.isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ Int32OperandId indexId = writer.guardToTypedArrayIndex(keyId);
+
+ writer.guardIsTypedArray(objId);
+ writer.loadTypedArrayElementExistsResult(objId, indexId);
+ writer.returnFromIC();
+
+ trackAttached("TypedArrayObjectNonInt32Index");
+ return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachSlotDoesNotExist(
+ JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId) {
+ bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
+
+ emitIdGuard(keyId, key);
+ if (hasOwn) {
+ TestMatchingReceiver(writer, obj, objId);
+ } else {
+ Maybe<ObjOperandId> tempId;
+ EmitReadSlotGuard(writer, obj, nullptr, objId, &tempId);
+ }
+ writer.loadBooleanResult(false);
+ writer.returnFromIC();
+
+ trackAttached("DoesNotExist");
+ return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachDoesNotExist(HandleObject obj,
+ ObjOperandId objId,
+ HandleId key,
+ ValOperandId keyId) {
+ bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
+
+ // Check that property doesn't exist on |obj| or it's prototype chain. These
+ // checks allow NativeObjects with a NativeObject prototype chain. They return
+ // NoAction if unknown such as resolve hooks or proxies.
+ if (hasOwn) {
+ if (!CheckHasNoSuchOwnProperty(cx_, obj, key)) {
+ return AttachDecision::NoAction;
+ }
+ } else {
+ if (!CheckHasNoSuchProperty(cx_, obj, key)) {
+ return AttachDecision::NoAction;
+ }
+ }
+
+ TRY_ATTACH(tryAttachMegamorphic(objId, keyId));
+ TRY_ATTACH(tryAttachSlotDoesNotExist(obj, objId, key, keyId));
+
+ return AttachDecision::NoAction;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachProxyElement(HandleObject obj,
+ ObjOperandId objId,
+ ValOperandId keyId) {
+ bool hasOwn = (cacheKind_ == CacheKind::HasOwn);
+
+ if (!obj->is<ProxyObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ writer.guardIsProxy(objId);
+ writer.proxyHasPropResult(objId, keyId, hasOwn);
+ writer.returnFromIC();
+
+ trackAttached("ProxyHasProp");
+ return AttachDecision::Attach;
+}
+
+AttachDecision HasPropIRGenerator::tryAttachStub() {
+ MOZ_ASSERT(cacheKind_ == CacheKind::In || cacheKind_ == CacheKind::HasOwn);
+
+ AutoAssertNoPendingException aanpe(cx_);
+
+ // NOTE: Argument order is PROPERTY, OBJECT
+ ValOperandId keyId(writer.setInputOperandId(0));
+ ValOperandId valId(writer.setInputOperandId(1));
+
+ if (!val_.isObject()) {
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+ RootedObject obj(cx_, &val_.toObject());
+ ObjOperandId objId = writer.guardToObject(valId);
+
+ // Optimize Proxies
+ TRY_ATTACH(tryAttachProxyElement(obj, objId, keyId));
+
+ RootedId id(cx_);
+ bool nameOrSymbol;
+ if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
+ cx_->clearPendingException();
+ return AttachDecision::NoAction;
+ }
+
+ if (nameOrSymbol) {
+ TRY_ATTACH(tryAttachNamedProp(obj, objId, id, keyId));
+ TRY_ATTACH(tryAttachDoesNotExist(obj, objId, id, keyId));
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ uint32_t index;
+ Int32OperandId indexId;
+ if (maybeGuardInt32Index(idVal_, keyId, &index, &indexId)) {
+ TRY_ATTACH(tryAttachDense(obj, objId, index, indexId));
+ TRY_ATTACH(tryAttachDenseHole(obj, objId, index, indexId));
+ TRY_ATTACH(tryAttachTypedArray(obj, objId, indexId));
+ TRY_ATTACH(tryAttachSparse(obj, objId, indexId));
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ TRY_ATTACH(tryAttachTypedArrayNonInt32Index(obj, objId, keyId));
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+}
+
+void HasPropIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("base", val_);
+ sp.valueProperty("property", idVal_);
+ }
+#endif
+}
+
+CheckPrivateFieldIRGenerator::CheckPrivateFieldIRGenerator(
+ JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode,
+ CacheKind cacheKind, HandleValue idVal, HandleValue val)
+ : IRGenerator(cx, script, pc, cacheKind, mode), val_(val), idVal_(idVal) {
+ MOZ_ASSERT(idVal.isSymbol() && idVal.toSymbol()->isPrivateName());
+}
+
+AttachDecision CheckPrivateFieldIRGenerator::tryAttachStub() {
+ AutoAssertNoPendingException aanpe(cx_);
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ ValOperandId keyId(writer.setInputOperandId(1));
+
+ if (!val_.isObject()) {
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+ RootedObject obj(cx_, &val_.toObject());
+ ObjOperandId objId = writer.guardToObject(valId);
+ RootedId key(cx_, SYMBOL_TO_JSID(idVal_.toSymbol()));
+
+ ThrowCondition condition;
+ ThrowMsgKind msgKind;
+ GetCheckPrivateFieldOperands(pc_, &condition, &msgKind);
+
+ bool hasOwn = false;
+ if (!HasOwnDataPropertyPure(cx_, obj, key, &hasOwn)) {
+ // Can't determine if HasOwnProperty purely.
+ return AttachDecision::NoAction;
+ }
+
+ if (CheckPrivateFieldWillThrow(condition, hasOwn)) {
+ // Don't attach a stub if the operation will throw.
+ return AttachDecision::NoAction;
+ }
+
+ TRY_ATTACH(tryAttachNative(obj, objId, key, keyId, hasOwn));
+
+ return AttachDecision::NoAction;
+}
+
+AttachDecision CheckPrivateFieldIRGenerator::tryAttachNative(JSObject* obj,
+ ObjOperandId objId,
+ jsid key,
+ ValOperandId keyId,
+ bool hasOwn) {
+ if (!obj->isNative()) {
+ return AttachDecision::NoAction;
+ }
+
+ Maybe<ObjOperandId> tempId;
+ emitIdGuard(keyId, key);
+ EmitReadSlotGuard(writer, obj, obj, objId, &tempId);
+ writer.loadBooleanResult(hasOwn);
+ writer.returnFromIC();
+
+ trackAttached("NativeCheckPrivateField");
+ return AttachDecision::Attach;
+}
+
+void CheckPrivateFieldIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("base", val_);
+ sp.valueProperty("property", idVal_);
+ }
+#endif
+}
+
+bool IRGenerator::maybeGuardInt32Index(const Value& index, ValOperandId indexId,
+ uint32_t* int32Index,
+ Int32OperandId* int32IndexId) {
+ if (index.isNumber()) {
+ int32_t indexSigned;
+ if (index.isInt32()) {
+ indexSigned = index.toInt32();
+ } else {
+ // We allow negative zero here.
+ if (!mozilla::NumberEqualsInt32(index.toDouble(), &indexSigned)) {
+ return false;
+ }
+ }
+
+ if (indexSigned < 0) {
+ return false;
+ }
+
+ *int32Index = uint32_t(indexSigned);
+ *int32IndexId = writer.guardToInt32Index(indexId);
+ return true;
+ }
+
+ if (index.isString()) {
+ int32_t indexSigned = GetIndexFromString(index.toString());
+ if (indexSigned < 0) {
+ return false;
+ }
+
+ StringOperandId strId = writer.guardToString(indexId);
+ *int32Index = uint32_t(indexSigned);
+ *int32IndexId = writer.guardStringToIndex(strId);
+ return true;
+ }
+
+ return false;
+}
+
+SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, HandleScript script,
+ jsbytecode* pc, CacheKind cacheKind,
+ ICState::Mode mode, HandleValue lhsVal,
+ HandleValue idVal, HandleValue rhsVal)
+ : IRGenerator(cx, script, pc, cacheKind, mode),
+ lhsVal_(lhsVal),
+ idVal_(idVal),
+ rhsVal_(rhsVal) {}
+
+AttachDecision SetPropIRGenerator::tryAttachStub() {
+ AutoAssertNoPendingException aanpe(cx_);
+
+ ValOperandId objValId(writer.setInputOperandId(0));
+ ValOperandId rhsValId;
+ if (cacheKind_ == CacheKind::SetProp) {
+ rhsValId = ValOperandId(writer.setInputOperandId(1));
+ } else {
+ MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
+ MOZ_ASSERT(setElemKeyValueId().id() == 1);
+ writer.setInputOperandId(1);
+ rhsValId = ValOperandId(writer.setInputOperandId(2));
+ }
+
+ RootedId id(cx_);
+ bool nameOrSymbol;
+ if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
+ cx_->clearPendingException();
+ return AttachDecision::NoAction;
+ }
+
+ if (lhsVal_.isObject()) {
+ RootedObject obj(cx_, &lhsVal_.toObject());
+
+ ObjOperandId objId = writer.guardToObject(objValId);
+ if (IsPropertySetOp(JSOp(*pc_))) {
+ TRY_ATTACH(tryAttachMegamorphicSetElement(obj, objId, rhsValId));
+ }
+ if (nameOrSymbol) {
+ TRY_ATTACH(tryAttachNativeSetSlot(obj, objId, id, rhsValId));
+ if (IsPropertySetOp(JSOp(*pc_))) {
+ TRY_ATTACH(tryAttachSetArrayLength(obj, objId, id, rhsValId));
+ TRY_ATTACH(tryAttachSetter(obj, objId, id, rhsValId));
+ TRY_ATTACH(tryAttachWindowProxy(obj, objId, id, rhsValId));
+ TRY_ATTACH(tryAttachProxy(obj, objId, id, rhsValId));
+ }
+ if (canAttachAddSlotStub(obj, id)) {
+ deferType_ = DeferType::AddSlot;
+ return AttachDecision::Deferred;
+ }
+ return AttachDecision::NoAction;
+ }
+
+ MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
+
+ if (IsPropertySetOp(JSOp(*pc_))) {
+ TRY_ATTACH(tryAttachProxyElement(obj, objId, rhsValId));
+ }
+
+ uint32_t index;
+ Int32OperandId indexId;
+ if (maybeGuardInt32Index(idVal_, setElemKeyValueId(), &index, &indexId)) {
+ TRY_ATTACH(
+ tryAttachSetDenseElement(obj, objId, index, indexId, rhsValId));
+ TRY_ATTACH(
+ tryAttachSetDenseElementHole(obj, objId, index, indexId, rhsValId));
+ TRY_ATTACH(
+ tryAttachSetTypedArrayElement(obj, objId, index, indexId, rhsValId));
+ TRY_ATTACH(tryAttachAddOrUpdateSparseElement(obj, objId, index, indexId,
+ rhsValId));
+ return AttachDecision::NoAction;
+ }
+
+ TRY_ATTACH(
+ tryAttachSetTypedArrayElementNonInt32Index(obj, objId, rhsValId));
+ }
+ return AttachDecision::NoAction;
+}
+
+static void EmitStoreSlotAndReturn(CacheIRWriter& writer, ObjOperandId objId,
+ NativeObject* nobj, Shape* shape,
+ ValOperandId rhsId) {
+ if (nobj->isFixedSlot(shape->slot())) {
+ size_t offset = NativeObject::getFixedSlotOffset(shape->slot());
+ writer.storeFixedSlot(objId, offset, rhsId);
+ } else {
+ size_t offset = nobj->dynamicSlotIndex(shape->slot()) * sizeof(Value);
+ writer.storeDynamicSlot(objId, offset, rhsId);
+ }
+ writer.returnFromIC();
+}
+
+static Shape* LookupShapeForSetSlot(JSOp op, NativeObject* obj, jsid id) {
+ Shape* shape = obj->lookupPure(id);
+ if (!shape || !shape->isDataProperty() || !shape->writable()) {
+ return nullptr;
+ }
+
+ // If this is an op like JSOp::InitElem / [[DefineOwnProperty]], the
+ // property's attributes may have to be changed too, so make sure it's a
+ // simple data property.
+ if (IsPropertyInitOp(op) &&
+ (!shape->configurable() || !shape->enumerable())) {
+ return nullptr;
+ }
+
+ return shape;
+}
+
+static bool CanAttachNativeSetSlot(JSOp op, HandleObject obj, HandleId id,
+ MutableHandleShape propShape) {
+ if (!obj->isNative()) {
+ return false;
+ }
+
+ propShape.set(LookupShapeForSetSlot(op, &obj->as<NativeObject>(), id));
+ if (!propShape) {
+ return false;
+ }
+
+ return true;
+}
+
+// There is no need to guard on the shape. Global lexical bindings are
+// non-configurable and can not be shadowed.
+static bool IsGlobalLexicalSetGName(JSOp op, NativeObject* obj,
+ HandleShape shape) {
+ // Ensure that the env can't change.
+ if (op != JSOp::SetGName && op != JSOp::StrictSetGName) {
+ return false;
+ }
+
+ if (!obj->is<LexicalEnvironmentObject>() ||
+ !obj->as<LexicalEnvironmentObject>().isGlobal()) {
+ return false;
+ }
+
+ // Uninitialized let bindings use a RuntimeLexicalErrorObject.
+ MOZ_ASSERT(!obj->getSlot(shape->slot()).isMagic());
+ MOZ_ASSERT(shape->writable());
+ MOZ_ASSERT(!shape->configurable());
+ return true;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id,
+ ValOperandId rhsId) {
+ RootedShape propShape(cx_);
+ if (!CanAttachNativeSetSlot(JSOp(*pc_), obj, id, &propShape)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Don't attach a megamorphic store slot stub for ops like JSOp::InitElem.
+ if (mode_ == ICState::Mode::Megamorphic && cacheKind_ == CacheKind::SetProp &&
+ IsPropertySetOp(JSOp(*pc_))) {
+ writer.megamorphicStoreSlot(objId, JSID_TO_ATOM(id)->asPropertyName(),
+ rhsId);
+ writer.returnFromIC();
+ trackAttached("MegamorphicNativeSlot");
+ return AttachDecision::Attach;
+ }
+
+ maybeEmitIdGuard(id);
+
+ NativeObject* nobj = &obj->as<NativeObject>();
+ if (!IsGlobalLexicalSetGName(JSOp(*pc_), nobj, propShape)) {
+ TestMatchingNativeReceiver(writer, nobj, objId);
+ }
+
+ EmitStoreSlotAndReturn(writer, objId, nobj, propShape, rhsId);
+
+ trackAttached("NativeSlot");
+ return AttachDecision::Attach;
+}
+
+OperandId IRGenerator::emitNumericGuard(ValOperandId valId, Scalar::Type type) {
+ switch (type) {
+ case Scalar::Int8:
+ case Scalar::Uint8:
+ case Scalar::Int16:
+ case Scalar::Uint16:
+ case Scalar::Int32:
+ case Scalar::Uint32:
+ return writer.guardToInt32ModUint32(valId);
+
+ case Scalar::Float32:
+ case Scalar::Float64:
+ return writer.guardIsNumber(valId);
+
+ case Scalar::Uint8Clamped:
+ return writer.guardToUint8Clamped(valId);
+
+ case Scalar::BigInt64:
+ case Scalar::BigUint64:
+ return writer.guardToBigInt(valId);
+
+ case Scalar::MaxTypedArrayViewType:
+ case Scalar::Int64:
+ case Scalar::Simd128:
+ break;
+ }
+ MOZ_CRASH("Unsupported TypedArray type");
+}
+
+static bool ValueIsNumeric(Scalar::Type type, const Value& val) {
+ if (Scalar::isBigIntType(type)) {
+ return val.isBigInt();
+ }
+ return val.isNumber();
+}
+
+void SetPropIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.opcodeProperty("op", JSOp(*pc_));
+ sp.valueProperty("base", lhsVal_);
+ sp.valueProperty("property", idVal_);
+ sp.valueProperty("value", rhsVal_);
+ }
+#endif
+}
+
+static bool IsCacheableSetPropCallNative(JSObject* obj, JSObject* holder,
+ Shape* shape) {
+ MOZ_ASSERT(shape);
+
+ if (!IsCacheableProtoChain(obj, holder)) {
+ return false;
+ }
+
+ if (!shape->hasSetterValue()) {
+ return false;
+ }
+
+ if (!shape->setterObject() || !shape->setterObject()->is<JSFunction>()) {
+ return false;
+ }
+
+ JSFunction& setter = shape->setterObject()->as<JSFunction>();
+ if (!setter.isNativeWithoutJitEntry()) {
+ return false;
+ }
+
+ if (setter.isClassConstructor()) {
+ return false;
+ }
+
+ if (setter.hasJitInfo() && !setter.jitInfo()->needsOuterizedThisObject()) {
+ return true;
+ }
+
+ return !IsWindow(obj);
+}
+
+static bool IsCacheableSetPropCallScripted(JSObject* obj, JSObject* holder,
+ Shape* shape) {
+ MOZ_ASSERT(shape);
+
+ if (!IsCacheableProtoChain(obj, holder)) {
+ return false;
+ }
+
+ if (IsWindow(obj)) {
+ return false;
+ }
+
+ if (!shape->hasSetterValue()) {
+ return false;
+ }
+
+ if (!shape->setterObject() || !shape->setterObject()->is<JSFunction>()) {
+ return false;
+ }
+
+ JSFunction& setter = shape->setterObject()->as<JSFunction>();
+ if (setter.isClassConstructor()) {
+ return false;
+ }
+
+ // Scripted functions and natives with JIT entry can use the scripted path.
+ return setter.hasJitEntry();
+}
+
+static bool CanAttachSetter(JSContext* cx, jsbytecode* pc, HandleObject obj,
+ HandleId id, MutableHandleObject holder,
+ MutableHandleShape propShape) {
+ // Don't attach a setter stub for ops like JSOp::InitElem.
+ MOZ_ASSERT(IsPropertySetOp(JSOp(*pc)));
+
+ PropertyResult prop;
+ if (!LookupPropertyPure(cx, obj, id, holder.address(), &prop)) {
+ return false;
+ }
+
+ if (!prop.isNativeProperty()) {
+ return false;
+ }
+
+ propShape.set(prop.shape());
+ if (!IsCacheableSetPropCallScripted(obj, holder, propShape) &&
+ !IsCacheableSetPropCallNative(obj, holder, propShape)) {
+ return false;
+ }
+
+ return true;
+}
+
+static void EmitCallSetterNoGuards(JSContext* cx, CacheIRWriter& writer,
+ JSObject* obj, JSObject* holder,
+ Shape* shape, ObjOperandId objId,
+ ValOperandId rhsId) {
+ JSFunction* target = &shape->setterValue().toObject().as<JSFunction>();
+ bool sameRealm = cx->realm() == target->realm();
+
+ if (target->isNativeWithoutJitEntry()) {
+ MOZ_ASSERT(IsCacheableSetPropCallNative(obj, holder, shape));
+ writer.callNativeSetter(objId, target, rhsId, sameRealm);
+ writer.returnFromIC();
+ return;
+ }
+
+ MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, shape));
+ writer.callScriptedSetter(objId, target, rhsId, sameRealm);
+ writer.returnFromIC();
+}
+
+static void EmitCallDOMSetterNoGuards(JSContext* cx, CacheIRWriter& writer,
+ Shape* shape, ObjOperandId objId,
+ ValOperandId rhsId) {
+ JSFunction* setter = &shape->setterValue().toObject().as<JSFunction>();
+ MOZ_ASSERT(cx->realm() == setter->realm());
+
+ writer.callDOMSetter(objId, setter->jitInfo(), rhsId);
+ writer.returnFromIC();
+}
+
+AttachDecision SetPropIRGenerator::tryAttachSetter(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id,
+ ValOperandId rhsId) {
+ RootedObject holder(cx_);
+ RootedShape propShape(cx_);
+ if (!CanAttachSetter(cx_, pc_, obj, id, &holder, &propShape)) {
+ return AttachDecision::NoAction;
+ }
+
+ maybeEmitIdGuard(id);
+
+ // Use the megamorphic guard if we're in megamorphic mode, except if |obj|
+ // is a Window as GuardHasGetterSetter doesn't support this yet (Window may
+ // require outerizing).
+ if (mode_ == ICState::Mode::Specialized || IsWindow(obj)) {
+ TestMatchingReceiver(writer, obj, objId);
+
+ if (obj != holder) {
+ GeneratePrototypeGuards(writer, obj, holder, objId);
+
+ // Guard on the holder's shape.
+ ObjOperandId holderId = writer.loadObject(holder);
+ TestMatchingHolder(writer, holder, holderId);
+ }
+ } else {
+ writer.guardHasGetterSetter(objId, propShape);
+ }
+
+ if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Setter, obj, propShape, mode_)) {
+ EmitCallDOMSetterNoGuards(cx_, writer, propShape, objId, rhsId);
+
+ trackAttached("DOMSetter");
+ return AttachDecision::Attach;
+ }
+
+ EmitCallSetterNoGuards(cx_, writer, obj, holder, propShape, objId, rhsId);
+
+ trackAttached("Setter");
+ return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachSetArrayLength(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id,
+ ValOperandId rhsId) {
+ // Don't attach an array length stub for ops like JSOp::InitElem.
+ MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
+
+ if (!obj->is<ArrayObject>() || !JSID_IS_ATOM(id, cx_->names().length) ||
+ !obj->as<ArrayObject>().lengthIsWritable()) {
+ return AttachDecision::NoAction;
+ }
+
+ maybeEmitIdGuard(id);
+ writer.guardClass(objId, GuardClassKind::Array);
+ writer.callSetArrayLength(objId, IsStrictSetPC(pc_), rhsId);
+ writer.returnFromIC();
+
+ trackAttached("SetArrayLength");
+ return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachSetDenseElement(
+ HandleObject obj, ObjOperandId objId, uint32_t index,
+ Int32OperandId indexId, ValOperandId rhsId) {
+ if (!obj->isNative()) {
+ return AttachDecision::NoAction;
+ }
+
+ NativeObject* nobj = &obj->as<NativeObject>();
+ if (!nobj->containsDenseElement(index) || nobj->denseElementsAreFrozen()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Setting holes requires extra code for marking the elements non-packed.
+ MOZ_ASSERT(!rhsVal_.isMagic(JS_ELEMENTS_HOLE));
+
+ // Don't optimize InitElem (DefineProperty) on non-extensible objects: when
+ // the elements are sealed, we have to throw an exception. Note that we have
+ // to check !isExtensible instead of denseElementsAreSealed because sealing
+ // a (non-extensible) object does not necessarily trigger a Shape change.
+ if (IsPropertyInitOp(JSOp(*pc_)) && !nobj->isExtensible()) {
+ return AttachDecision::NoAction;
+ }
+
+ TestMatchingNativeReceiver(writer, nobj, objId);
+
+ writer.storeDenseElement(objId, indexId, rhsId);
+ writer.returnFromIC();
+
+ trackAttached("SetDenseElement");
+ return AttachDecision::Attach;
+}
+
+static bool CanAttachAddElement(NativeObject* obj, bool isInit) {
+ // Make sure the objects on the prototype don't have any indexed properties
+ // or that such properties can't appear without a shape change.
+ do {
+ // The first two checks are also relevant to the receiver object.
+ if (obj->isIndexed()) {
+ return false;
+ }
+
+ const JSClass* clasp = obj->getClass();
+ if (clasp != &ArrayObject::class_ &&
+ (clasp->getAddProperty() || clasp->getResolve() ||
+ clasp->getOpsLookupProperty() || clasp->getOpsSetProperty())) {
+ return false;
+ }
+
+ // If we're initializing a property instead of setting one, the objects
+ // on the prototype are not relevant.
+ if (isInit) {
+ break;
+ }
+
+ JSObject* proto = obj->staticPrototype();
+ if (!proto) {
+ break;
+ }
+
+ if (!proto->isNative()) {
+ return false;
+ }
+
+ // We have to make sure the proto has no non-writable (frozen) elements
+ // because we're not allowed to shadow them.
+ NativeObject* nproto = &proto->as<NativeObject>();
+ if (nproto->denseElementsAreFrozen() &&
+ nproto->getDenseInitializedLength() > 0) {
+ return false;
+ }
+
+ obj = nproto;
+ } while (true);
+
+ return true;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachSetDenseElementHole(
+ HandleObject obj, ObjOperandId objId, uint32_t index,
+ Int32OperandId indexId, ValOperandId rhsId) {
+ if (!obj->isNative()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Setting holes requires extra code for marking the elements non-packed.
+ if (rhsVal_.isMagic(JS_ELEMENTS_HOLE)) {
+ return AttachDecision::NoAction;
+ }
+
+ JSOp op = JSOp(*pc_);
+ MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
+
+ if (op == JSOp::InitHiddenElem) {
+ return AttachDecision::NoAction;
+ }
+
+ NativeObject* nobj = &obj->as<NativeObject>();
+ if (!nobj->isExtensible()) {
+ return AttachDecision::NoAction;
+ }
+
+ MOZ_ASSERT(!nobj->denseElementsAreFrozen(),
+ "Extensible objects should not have frozen elements");
+
+ uint32_t initLength = nobj->getDenseInitializedLength();
+
+ // Optimize if we're adding an element at initLength or writing to a hole.
+ //
+ // In the case where index > initLength, we need noteHasDenseAdd to be called
+ // to ensure Ion is aware that writes have occurred to-out-of-bound indexes
+ // before.
+ bool isAdd = index == initLength;
+ bool isHoleInBounds =
+ index < initLength && !nobj->containsDenseElement(index);
+ if (!isAdd && !isHoleInBounds) {
+ return AttachDecision::NoAction;
+ }
+
+ // Can't add new elements to arrays with non-writable length.
+ if (isAdd && nobj->is<ArrayObject>() &&
+ !nobj->as<ArrayObject>().lengthIsWritable()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Typed arrays don't have dense elements.
+ if (nobj->is<TypedArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check for other indexed properties or class hooks.
+ if (!CanAttachAddElement(nobj, IsPropertyInitOp(op))) {
+ return AttachDecision::NoAction;
+ }
+
+ TestMatchingNativeReceiver(writer, nobj, objId);
+
+ // Also shape guard the proto chain, unless this is an InitElem.
+ if (IsPropertySetOp(op)) {
+ ShapeGuardProtoChain(writer, obj, objId);
+ }
+
+ writer.storeDenseElementHole(objId, indexId, rhsId, isAdd);
+ writer.returnFromIC();
+
+ trackAttached(isAdd ? "AddDenseElement" : "StoreDenseElementHole");
+ return AttachDecision::Attach;
+}
+
+// Add an IC for adding or updating a sparse array element.
+AttachDecision SetPropIRGenerator::tryAttachAddOrUpdateSparseElement(
+ HandleObject obj, ObjOperandId objId, uint32_t index,
+ Int32OperandId indexId, ValOperandId rhsId) {
+ JSOp op = JSOp(*pc_);
+ MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
+
+ if (op != JSOp::SetElem && op != JSOp::StrictSetElem) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!obj->isNative()) {
+ return AttachDecision::NoAction;
+ }
+ NativeObject* nobj = &obj->as<NativeObject>();
+
+ // We cannot attach a stub to a non-extensible object
+ if (!nobj->isExtensible()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Stub doesn't handle negative indices.
+ if (index > INT_MAX) {
+ return AttachDecision::NoAction;
+ }
+
+ // We also need to be past the end of the dense capacity, to ensure sparse.
+ if (index < nobj->getDenseInitializedLength()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Only handle Array objects in this stub.
+ if (!nobj->is<ArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+ ArrayObject* aobj = &nobj->as<ArrayObject>();
+
+ // Don't attach if we're adding to an array with non-writable length.
+ bool isAdd = (index >= aobj->length());
+ if (isAdd && !aobj->lengthIsWritable()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Indexed properties on the prototype chain aren't handled by the helper.
+ if ((aobj->staticPrototype() != nullptr) &&
+ ObjectMayHaveExtraIndexedProperties(aobj->staticPrototype())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Ensure we are still talking about an array class.
+ writer.guardClass(objId, GuardClassKind::Array);
+
+ // The helper we are going to call only applies to non-dense elements.
+ writer.guardIndexGreaterThanDenseInitLength(objId, indexId);
+
+ // Guard extensible: We may be trying to add a new element, and so we'd best
+ // be able to do so safely.
+ writer.guardIsExtensible(objId);
+
+ // Ensures we are able to efficiently able to map to an integral jsid.
+ writer.guardInt32IsNonNegative(indexId);
+
+ // Shape guard the prototype chain to avoid shadowing indexes from appearing.
+ // Guard the prototype of the receiver explicitly, because the receiver's
+ // shape is not being guarded as a proxy for that.
+ GuardGroupProto(writer, obj, objId);
+
+ // Dense elements may appear on the prototype chain (and prototypes may
+ // have a different notion of which elements are dense), but they can
+ // only be data properties, so our specialized Set handler is ok to bind
+ // to them.
+ ShapeGuardProtoChain(writer, obj, objId);
+
+ // Ensure that if we're adding an element to the object, the object's
+ // length is writable.
+ writer.guardIndexIsValidUpdateOrAdd(objId, indexId);
+
+ writer.callAddOrUpdateSparseElementHelper(
+ objId, indexId, rhsId,
+ /* strict = */ op == JSOp::StrictSetElem);
+ writer.returnFromIC();
+
+ trackAttached("AddOrUpdateSparseElement");
+ return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachSetTypedArrayElement(
+ HandleObject obj, ObjOperandId objId, uint32_t index,
+ Int32OperandId indexId, ValOperandId rhsId) {
+ if (!obj->is<TypedArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+ TypedArrayObject* tarr = &obj->as<TypedArrayObject>();
+
+ bool handleOutOfBounds = (index >= tarr->length().get());
+ Scalar::Type elementType = tarr->type();
+
+ // Don't attach if the input type doesn't match the guard added below.
+ if (!ValueIsNumeric(elementType, rhsVal_)) {
+ return AttachDecision::NoAction;
+ }
+
+ // InitElem (DefineProperty) has to throw an exception on out-of-bounds.
+ if (handleOutOfBounds && IsPropertyInitOp(JSOp(*pc_))) {
+ return AttachDecision::NoAction;
+ }
+
+ writer.guardShapeForClass(objId, tarr->shape());
+
+ OperandId rhsValId = emitNumericGuard(rhsId, elementType);
+
+ writer.storeTypedArrayElement(objId, elementType, indexId, rhsValId,
+ handleOutOfBounds);
+ writer.returnFromIC();
+
+ trackAttached(handleOutOfBounds ? "SetTypedElementOOB" : "SetTypedElement");
+ return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachSetTypedArrayElementNonInt32Index(
+ HandleObject obj, ObjOperandId objId, ValOperandId rhsId) {
+ if (!obj->is<TypedArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+ TypedArrayObject* tarr = &obj->as<TypedArrayObject>();
+
+ if (!idVal_.isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ Scalar::Type elementType = tarr->type();
+
+ // Don't attach if the input type doesn't match the guard added below.
+ if (!ValueIsNumeric(elementType, rhsVal_)) {
+ return AttachDecision::NoAction;
+ }
+
+ // InitElem (DefineProperty) has to throw an exception on out-of-bounds.
+ if (IsPropertyInitOp(JSOp(*pc_))) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId keyId = setElemKeyValueId();
+ Int32OperandId indexId = writer.guardToTypedArrayIndex(keyId);
+
+ writer.guardShapeForClass(objId, tarr->shape());
+
+ OperandId rhsValId = emitNumericGuard(rhsId, elementType);
+
+ // When the index isn't an int32 index, we always assume the TypedArray access
+ // can be out-of-bounds.
+ bool handleOutOfBounds = true;
+
+ writer.storeTypedArrayElement(objId, elementType, indexId, rhsValId,
+ handleOutOfBounds);
+ writer.returnFromIC();
+
+ trackAttached("SetTypedElementNonInt32Index");
+ return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachGenericProxy(
+ HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId,
+ bool handleDOMProxies) {
+ MOZ_ASSERT(obj->is<ProxyObject>());
+
+ writer.guardIsProxy(objId);
+
+ if (!handleDOMProxies) {
+ // Ensure that the incoming object is not a DOM proxy, so that we can
+ // get to the specialized stubs. If handleDOMProxies is true, we were
+ // unable to attach a specialized DOM stub, so we just handle all
+ // proxies here.
+ writer.guardIsNotDOMProxy(objId);
+ }
+
+ if (cacheKind_ == CacheKind::SetProp || mode_ == ICState::Mode::Specialized) {
+ maybeEmitIdGuard(id);
+ writer.proxySet(objId, id, rhsId, IsStrictSetPC(pc_));
+ } else {
+ // Attach a stub that handles every id.
+ MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
+ MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
+ writer.proxySetByValue(objId, setElemKeyValueId(), rhsId,
+ IsStrictSetPC(pc_));
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("GenericProxy");
+ return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachDOMProxyShadowed(
+ HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) {
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+
+ maybeEmitIdGuard(id);
+ TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
+ writer.proxySet(objId, id, rhsId, IsStrictSetPC(pc_));
+ writer.returnFromIC();
+
+ trackAttached("DOMProxyShadowed");
+ return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachDOMProxyUnshadowed(
+ HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) {
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+
+ RootedObject proto(cx_, obj->staticPrototype());
+ if (!proto) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedObject holder(cx_);
+ RootedShape propShape(cx_);
+ if (!CanAttachSetter(cx_, pc_, proto, id, &holder, &propShape)) {
+ return AttachDecision::NoAction;
+ }
+
+ maybeEmitIdGuard(id);
+
+ // Guard that our expando object hasn't started shadowing this property.
+ TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
+ CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId);
+
+ GeneratePrototypeGuards(writer, obj, holder, objId);
+
+ // Guard on the holder of the property.
+ ObjOperandId holderId = writer.loadObject(holder);
+ TestMatchingHolder(writer, holder, holderId);
+
+ // EmitCallSetterNoGuards expects |obj| to be the object the property is
+ // on to do some checks. Since we actually looked at proto, and no extra
+ // guards will be generated, we can just pass that instead.
+ EmitCallSetterNoGuards(cx_, writer, proto, holder, propShape, objId, rhsId);
+
+ trackAttached("DOMProxyUnshadowed");
+ return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachDOMProxyExpando(
+ HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) {
+ MOZ_ASSERT(IsCacheableDOMProxy(obj));
+
+ RootedValue expandoVal(cx_, GetProxyPrivate(obj));
+ RootedObject expandoObj(cx_);
+ if (expandoVal.isObject()) {
+ expandoObj = &expandoVal.toObject();
+ } else {
+ MOZ_ASSERT(!expandoVal.isUndefined(),
+ "How did a missing expando manage to shadow things?");
+ auto expandoAndGeneration =
+ static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
+ MOZ_ASSERT(expandoAndGeneration);
+ expandoObj = &expandoAndGeneration->expando.toObject();
+ }
+
+ RootedShape propShape(cx_);
+ if (CanAttachNativeSetSlot(JSOp(*pc_), expandoObj, id, &propShape)) {
+ maybeEmitIdGuard(id);
+ ObjOperandId expandoObjId =
+ guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj);
+
+ NativeObject* nativeExpandoObj = &expandoObj->as<NativeObject>();
+ EmitStoreSlotAndReturn(writer, expandoObjId, nativeExpandoObj, propShape,
+ rhsId);
+ trackAttached("DOMProxyExpandoSlot");
+ return AttachDecision::Attach;
+ }
+
+ RootedObject holder(cx_);
+ if (CanAttachSetter(cx_, pc_, expandoObj, id, &holder, &propShape)) {
+ // Note that we don't actually use the expandoObjId here after the
+ // shape guard. The DOM proxy (objId) is passed to the setter as
+ // |this|.
+ maybeEmitIdGuard(id);
+ guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj);
+
+ MOZ_ASSERT(holder == expandoObj);
+ EmitCallSetterNoGuards(cx_, writer, expandoObj, expandoObj, propShape,
+ objId, rhsId);
+ trackAttached("DOMProxyExpandoSetter");
+ return AttachDecision::Attach;
+ }
+
+ return AttachDecision::NoAction;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachProxy(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id,
+ ValOperandId rhsId) {
+ // Don't attach a proxy stub for ops like JSOp::InitElem.
+ MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
+
+ ProxyStubType type = GetProxyStubType(cx_, obj, id);
+ if (type == ProxyStubType::None) {
+ return AttachDecision::NoAction;
+ }
+
+ if (mode_ == ICState::Mode::Megamorphic) {
+ return tryAttachGenericProxy(obj, objId, id, rhsId,
+ /* handleDOMProxies = */ true);
+ }
+
+ switch (type) {
+ case ProxyStubType::None:
+ break;
+ case ProxyStubType::DOMExpando:
+ TRY_ATTACH(tryAttachDOMProxyExpando(obj, objId, id, rhsId));
+ [[fallthrough]]; // Fall through to the generic shadowed case.
+ case ProxyStubType::DOMShadowed:
+ return tryAttachDOMProxyShadowed(obj, objId, id, rhsId);
+ case ProxyStubType::DOMUnshadowed:
+ TRY_ATTACH(tryAttachDOMProxyUnshadowed(obj, objId, id, rhsId));
+ return tryAttachGenericProxy(obj, objId, id, rhsId,
+ /* handleDOMProxies = */ true);
+ case ProxyStubType::Generic:
+ return tryAttachGenericProxy(obj, objId, id, rhsId,
+ /* handleDOMProxies = */ false);
+ }
+
+ MOZ_CRASH("Unexpected ProxyStubType");
+}
+
+AttachDecision SetPropIRGenerator::tryAttachProxyElement(HandleObject obj,
+ ObjOperandId objId,
+ ValOperandId rhsId) {
+ // Don't attach a proxy stub for ops like JSOp::InitElem.
+ MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
+
+ if (!obj->is<ProxyObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ writer.guardIsProxy(objId);
+
+ // Like GetPropIRGenerator::tryAttachProxyElement, don't check for DOM
+ // proxies here as we don't have specialized DOM stubs for this.
+ MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
+ writer.proxySetByValue(objId, setElemKeyValueId(), rhsId, IsStrictSetPC(pc_));
+ writer.returnFromIC();
+
+ trackAttached("ProxyElement");
+ return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachMegamorphicSetElement(
+ HandleObject obj, ObjOperandId objId, ValOperandId rhsId) {
+ MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_)));
+
+ if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetElem) {
+ return AttachDecision::NoAction;
+ }
+
+ // The generic proxy stubs are faster.
+ if (obj->is<ProxyObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ writer.megamorphicSetElement(objId, setElemKeyValueId(), rhsId,
+ IsStrictSetPC(pc_));
+ writer.returnFromIC();
+
+ trackAttached("MegamorphicSetElement");
+ return AttachDecision::Attach;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
+ ObjOperandId objId,
+ HandleId id,
+ ValOperandId rhsId) {
+ // Attach a stub when the receiver is a WindowProxy and we can do the set
+ // on the Window (the global object).
+
+ if (!IsWindowProxyForScriptGlobal(script_, obj)) {
+ return AttachDecision::NoAction;
+ }
+
+ // If we're megamorphic prefer a generic proxy stub that handles a lot more
+ // cases.
+ if (mode_ == ICState::Mode::Megamorphic) {
+ return AttachDecision::NoAction;
+ }
+
+ // Now try to do the set on the Window (the current global).
+ Handle<GlobalObject*> windowObj = cx_->global();
+
+ RootedShape propShape(cx_);
+ if (!CanAttachNativeSetSlot(JSOp(*pc_), windowObj, id, &propShape)) {
+ return AttachDecision::NoAction;
+ }
+
+ maybeEmitIdGuard(id);
+
+ ObjOperandId windowObjId =
+ GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
+ writer.guardShape(windowObjId, windowObj->lastProperty());
+
+ EmitStoreSlotAndReturn(writer, windowObjId, windowObj, propShape, rhsId);
+
+ trackAttached("WindowProxySlot");
+ return AttachDecision::Attach;
+}
+
+bool SetPropIRGenerator::canAttachAddSlotStub(HandleObject obj, HandleId id) {
+ // Special-case JSFunction resolve hook to allow redefining the 'prototype'
+ // property without triggering lazy expansion of property and object
+ // allocation.
+ if (obj->is<JSFunction>() && JSID_IS_ATOM(id, cx_->names().prototype)) {
+ MOZ_ASSERT(ClassMayResolveId(cx_->names(), obj->getClass(), id, obj));
+
+ // We're only interested in functions that have a builtin .prototype
+ // property (needsPrototypeProperty). The stub will guard on this because
+ // the builtin .prototype property is non-configurable/non-enumerable and it
+ // would be wrong to add a property with those attributes to a function that
+ // doesn't have a builtin .prototype.
+ //
+ // Inlining needsPrototypeProperty in JIT code is complicated so we use
+ // isNonBuiltinConstructor as a stronger condition that's easier to check
+ // from JIT code.
+ JSFunction* fun = &obj->as<JSFunction>();
+ if (!fun->isNonBuiltinConstructor()) {
+ return false;
+ }
+ MOZ_ASSERT(fun->needsPrototypeProperty());
+
+ // If property exists this isn't an "add".
+ if (fun->lookupPure(id)) {
+ return false;
+ }
+ } else {
+ // Normal Case: If property exists this isn't an "add"
+ PropertyResult prop;
+ if (!LookupOwnPropertyPure(cx_, obj, id, &prop)) {
+ return false;
+ }
+ if (prop) {
+ return false;
+ }
+ }
+
+ // Object must be extensible, or we must be initializing a private
+ // elem.
+ bool canAddNewProperty = obj->nonProxyIsExtensible() || id.isPrivateName();
+ if (!canAddNewProperty) {
+ return false;
+ }
+
+ // Also watch out for addProperty hooks. Ignore the Array addProperty hook,
+ // because it doesn't do anything for non-index properties.
+ DebugOnly<uint32_t> index;
+ MOZ_ASSERT_IF(obj->is<ArrayObject>(), !IdIsIndex(id, &index));
+ if (!obj->is<ArrayObject>() && obj->getClass()->getAddProperty()) {
+ return false;
+ }
+
+ // Walk up the object prototype chain and ensure that all prototypes are
+ // native, and that all prototypes have no setter defined on the property.
+ for (JSObject* proto = obj->staticPrototype(); proto;
+ proto = proto->staticPrototype()) {
+ if (!proto->isNative()) {
+ return false;
+ }
+
+ // If prototype defines this property in a non-plain way, don't optimize.
+ Shape* protoShape = proto->as<NativeObject>().lookup(cx_, id);
+ if (protoShape && !protoShape->isDataDescriptor()) {
+ return false;
+ }
+
+ // Otherwise, if there's no such property, watch out for a resolve hook
+ // that would need to be invoked and thus prevent inlining of property
+ // addition. Allow the JSFunction resolve hook as it only defines plain
+ // data properties and we don't need to invoke it for objects on the
+ // proto chain.
+ if (ClassMayResolveId(cx_->names(), proto->getClass(), id, proto) &&
+ !proto->is<JSFunction>()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+AttachDecision SetPropIRGenerator::tryAttachAddSlotStub(HandleShape oldShape) {
+ ValOperandId objValId(writer.setInputOperandId(0));
+ ValOperandId rhsValId;
+ if (cacheKind_ == CacheKind::SetProp) {
+ rhsValId = ValOperandId(writer.setInputOperandId(1));
+ } else {
+ MOZ_ASSERT(cacheKind_ == CacheKind::SetElem);
+ MOZ_ASSERT(setElemKeyValueId().id() == 1);
+ writer.setInputOperandId(1);
+ rhsValId = ValOperandId(writer.setInputOperandId(2));
+ }
+
+ RootedId id(cx_);
+ bool nameOrSymbol;
+ if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
+ cx_->clearPendingException();
+ return AttachDecision::NoAction;
+ }
+
+ if (!lhsVal_.isObject() || !nameOrSymbol) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedObject obj(cx_, &lhsVal_.toObject());
+
+ PropertyResult prop;
+ if (!LookupOwnPropertyPure(cx_, obj, id, &prop)) {
+ return AttachDecision::NoAction;
+ }
+ if (!prop) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!obj->isNative()) {
+ return AttachDecision::NoAction;
+ }
+
+ Shape* propShape = prop.shape();
+ NativeObject* holder = &obj->as<NativeObject>();
+
+ MOZ_ASSERT(propShape);
+
+ // The property must be the last added property of the object.
+ MOZ_RELEASE_ASSERT(holder->lastProperty() == propShape);
+
+ // Old shape should be parent of new shape. Object flag updates may make this
+ // false even for simple data properties. It may be possible to support these
+ // transitions in the future, but ignore now for simplicity.
+ if (propShape->previous() != oldShape) {
+ return AttachDecision::NoAction;
+ }
+
+ // Basic shape checks.
+ if (propShape->inDictionary() || !propShape->isDataProperty() ||
+ !propShape->writable()) {
+ return AttachDecision::NoAction;
+ }
+
+ ObjOperandId objId = writer.guardToObject(objValId);
+ maybeEmitIdGuard(id);
+
+ // Shape guard the object.
+ writer.guardShape(objId, oldShape);
+
+ // If this is the special function.prototype case, we need to guard the
+ // function is a non-builtin constructor. See canAttachAddSlotStub.
+ if (obj->is<JSFunction>() && JSID_IS_ATOM(id, cx_->names().prototype)) {
+ MOZ_ASSERT(obj->as<JSFunction>().isNonBuiltinConstructor());
+ writer.guardFunctionIsNonBuiltinCtor(objId);
+ }
+
+ ShapeGuardProtoChain(writer, obj, objId);
+
+ if (holder->isFixedSlot(propShape->slot())) {
+ size_t offset = NativeObject::getFixedSlotOffset(propShape->slot());
+ writer.addAndStoreFixedSlot(objId, offset, rhsValId, propShape);
+ trackAttached("AddSlot");
+ } else {
+ size_t offset = holder->dynamicSlotIndex(propShape->slot()) * sizeof(Value);
+ uint32_t numOldSlots = NativeObject::calculateDynamicSlots(oldShape);
+ uint32_t numNewSlots = holder->numDynamicSlots();
+ if (numOldSlots == numNewSlots) {
+ writer.addAndStoreDynamicSlot(objId, offset, rhsValId, propShape);
+ trackAttached("AddSlot");
+ } else {
+ MOZ_ASSERT(numNewSlots > numOldSlots);
+ writer.allocateAndStoreDynamicSlot(objId, offset, rhsValId, propShape,
+ numNewSlots);
+ trackAttached("AllocateSlot");
+ }
+ }
+ writer.returnFromIC();
+
+ return AttachDecision::Attach;
+}
+
+InstanceOfIRGenerator::InstanceOfIRGenerator(JSContext* cx, HandleScript script,
+ jsbytecode* pc, ICState::Mode mode,
+ HandleValue lhs, HandleObject rhs)
+ : IRGenerator(cx, script, pc, CacheKind::InstanceOf, mode),
+ lhsVal_(lhs),
+ rhsObj_(rhs) {}
+
+AttachDecision InstanceOfIRGenerator::tryAttachStub() {
+ MOZ_ASSERT(cacheKind_ == CacheKind::InstanceOf);
+ AutoAssertNoPendingException aanpe(cx_);
+
+ // Ensure RHS is a function -- could be a Proxy, which the IC isn't prepared
+ // to handle.
+ if (!rhsObj_->is<JSFunction>()) {
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ HandleFunction fun = rhsObj_.as<JSFunction>();
+
+ if (fun->isBoundFunction()) {
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ // Look up the @@hasInstance property, and check that Function.__proto__ is
+ // the property holder, and that no object further down the prototype chain
+ // (including this function) has shadowed it; together with the fact that
+ // Function.__proto__[@@hasInstance] is immutable, this ensures that the
+ // hasInstance hook will not change without the need to guard on the actual
+ // property value.
+ PropertyResult hasInstanceProp;
+ JSObject* hasInstanceHolder = nullptr;
+ jsid hasInstanceID = SYMBOL_TO_JSID(cx_->wellKnownSymbols().hasInstance);
+ if (!LookupPropertyPure(cx_, fun, hasInstanceID, &hasInstanceHolder,
+ &hasInstanceProp) ||
+ !hasInstanceProp.isNativeProperty()) {
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ Value funProto = cx_->global()->getPrototype(JSProto_Function);
+ if (hasInstanceHolder != &funProto.toObject()) {
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ // If the above succeeded, then these should be true about @@hasInstance,
+ // because the property on Function.__proto__ is an immutable data property:
+ MOZ_ASSERT(hasInstanceProp.shape()->isDataProperty());
+ MOZ_ASSERT(!hasInstanceProp.shape()->configurable());
+ MOZ_ASSERT(!hasInstanceProp.shape()->writable());
+
+ if (!IsCacheableProtoChain(fun, hasInstanceHolder)) {
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ // Ensure that the function's prototype slot is the same.
+ Shape* shape = fun->lookupPure(cx_->names().prototype);
+ if (!shape || !shape->isDataProperty()) {
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ uint32_t slot = shape->slot();
+
+ MOZ_ASSERT(fun->numFixedSlots() == 0, "Stub code relies on this");
+ if (!fun->getSlot(slot).isObject()) {
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ JSObject* prototypeObject = &fun->getSlot(slot).toObject();
+
+ // Abstract Objects
+ ValOperandId lhs(writer.setInputOperandId(0));
+ ValOperandId rhs(writer.setInputOperandId(1));
+
+ ObjOperandId rhsId = writer.guardToObject(rhs);
+ writer.guardShape(rhsId, fun->lastProperty());
+
+ // Ensure that the shapes up the prototype chain for the RHS remain the same
+ // so that @@hasInstance is not shadowed by some intermediate prototype
+ // object.
+ if (hasInstanceHolder != fun) {
+ GeneratePrototypeGuards(writer, fun, hasInstanceHolder, rhsId);
+ ObjOperandId holderId = writer.loadObject(hasInstanceHolder);
+ TestMatchingHolder(writer, hasInstanceHolder, holderId);
+ }
+
+ // Load prototypeObject into the cache -- consumed twice in the IC
+ ObjOperandId protoId = writer.loadObject(prototypeObject);
+ // Ensure that rhs[slot] == prototypeObject.
+ writer.guardDynamicSlotIsSpecificObject(rhsId, protoId, slot);
+
+ // Needn't guard LHS is object, because the actual stub can handle that
+ // and correctly return false.
+ writer.loadInstanceOfObjectResult(lhs, protoId);
+ writer.returnFromIC();
+ trackAttached("InstanceOf");
+ return AttachDecision::Attach;
+}
+
+void InstanceOfIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("lhs", lhsVal_);
+ sp.valueProperty("rhs", ObjectValue(*rhsObj_));
+ }
+#else
+ // Silence Clang -Wunused-private-field warning.
+ mozilla::Unused << lhsVal_;
+#endif
+}
+
+TypeOfIRGenerator::TypeOfIRGenerator(JSContext* cx, HandleScript script,
+ jsbytecode* pc, ICState::Mode mode,
+ HandleValue value)
+ : IRGenerator(cx, script, pc, CacheKind::TypeOf, mode), val_(value) {}
+
+void TypeOfIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("val", val_);
+ }
+#endif
+}
+
+AttachDecision TypeOfIRGenerator::tryAttachStub() {
+ MOZ_ASSERT(cacheKind_ == CacheKind::TypeOf);
+
+ AutoAssertNoPendingException aanpe(cx_);
+
+ ValOperandId valId(writer.setInputOperandId(0));
+
+ TRY_ATTACH(tryAttachPrimitive(valId));
+ TRY_ATTACH(tryAttachObject(valId));
+
+ MOZ_ASSERT_UNREACHABLE("Failed to attach TypeOf");
+ return AttachDecision::NoAction;
+}
+
+AttachDecision TypeOfIRGenerator::tryAttachPrimitive(ValOperandId valId) {
+ if (!val_.isPrimitive()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Note: we don't use GuardIsNumber for int32 values because it's less
+ // efficient in Warp (unboxing to double instead of int32).
+ if (val_.isDouble()) {
+ writer.guardIsNumber(valId);
+ } else {
+ writer.guardNonDoubleType(valId, val_.type());
+ }
+
+ writer.loadConstantStringResult(
+ TypeName(js::TypeOfValue(val_), cx_->names()));
+ writer.returnFromIC();
+ trackAttached("Primitive");
+ return AttachDecision::Attach;
+}
+
+AttachDecision TypeOfIRGenerator::tryAttachObject(ValOperandId valId) {
+ if (!val_.isObject()) {
+ return AttachDecision::NoAction;
+ }
+
+ ObjOperandId objId = writer.guardToObject(valId);
+ writer.loadTypeOfObjectResult(objId);
+ writer.returnFromIC();
+ trackAttached("Object");
+ return AttachDecision::Attach;
+}
+
+GetIteratorIRGenerator::GetIteratorIRGenerator(JSContext* cx,
+ HandleScript script,
+ jsbytecode* pc,
+ ICState::Mode mode,
+ HandleValue value)
+ : IRGenerator(cx, script, pc, CacheKind::GetIterator, mode), val_(value) {}
+
+AttachDecision GetIteratorIRGenerator::tryAttachStub() {
+ MOZ_ASSERT(cacheKind_ == CacheKind::GetIterator);
+
+ AutoAssertNoPendingException aanpe(cx_);
+
+ if (mode_ == ICState::Mode::Megamorphic) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ if (!val_.isObject()) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedObject obj(cx_, &val_.toObject());
+
+ ObjOperandId objId = writer.guardToObject(valId);
+ TRY_ATTACH(tryAttachNativeIterator(objId, obj));
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+}
+
+AttachDecision GetIteratorIRGenerator::tryAttachNativeIterator(
+ ObjOperandId objId, HandleObject obj) {
+ MOZ_ASSERT(JSOp(*pc_) == JSOp::Iter);
+
+ PropertyIteratorObject* iterobj = LookupInIteratorCache(cx_, obj);
+ if (!iterobj) {
+ return AttachDecision::NoAction;
+ }
+
+ MOZ_ASSERT(obj->isNative());
+
+ // Guard on the receiver's shape.
+ TestMatchingNativeReceiver(writer, &obj->as<NativeObject>(), objId);
+
+ // Ensure the receiver has no dense elements.
+ writer.guardNoDenseElements(objId);
+
+ // Do the same for the objects on the proto chain.
+ GeneratePrototypeHoleGuards(writer, obj, objId,
+ /* alwaysGuardFirstProto = */ false);
+
+ ObjOperandId iterId = writer.guardAndGetIterator(
+ objId, iterobj, &ObjectRealm::get(obj).enumerators);
+ writer.loadObjectResult(iterId);
+ writer.returnFromIC();
+
+ trackAttached("GetIterator");
+ return AttachDecision::Attach;
+}
+
+void GetIteratorIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("val", val_);
+ }
+#endif
+}
+
+OptimizeSpreadCallIRGenerator::OptimizeSpreadCallIRGenerator(
+ JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode,
+ HandleValue value)
+ : IRGenerator(cx, script, pc, CacheKind::OptimizeSpreadCall, mode),
+ val_(value) {}
+
+AttachDecision OptimizeSpreadCallIRGenerator::tryAttachStub() {
+ MOZ_ASSERT(cacheKind_ == CacheKind::OptimizeSpreadCall);
+
+ AutoAssertNoPendingException aanpe(cx_);
+
+ TRY_ATTACH(tryAttachArray());
+ TRY_ATTACH(tryAttachNotOptimizable());
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+}
+
+static bool IsArrayPrototypeOptimizable(JSContext* cx, ArrayObject* arr,
+ NativeObject** arrProto, uint32_t* slot,
+ JSFunction** iterFun) {
+ // Prototype must be Array.prototype.
+ auto* proto = cx->global()->maybeGetArrayPrototype();
+ if (!proto || arr->staticPrototype() != proto) {
+ return false;
+ }
+ *arrProto = proto;
+
+ // The object must not have an own @@iterator property.
+ JS::PropertyKey iteratorKey = SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator);
+ if (arr->lookupPure(iteratorKey)) {
+ return false;
+ }
+
+ // Ensure that Array.prototype's @@iterator slot is unchanged.
+ Shape* shape = proto->lookupPure(iteratorKey);
+ if (!shape || !shape->isDataProperty()) {
+ return false;
+ }
+
+ *slot = shape->slot();
+ MOZ_ASSERT(proto->numFixedSlots() == 0, "Stub code relies on this");
+
+ const Value& iterVal = proto->getSlot(*slot);
+ if (!iterVal.isObject() || !iterVal.toObject().is<JSFunction>()) {
+ return false;
+ }
+
+ *iterFun = &iterVal.toObject().as<JSFunction>();
+ return IsSelfHostedFunctionWithName(*iterFun, cx->names().ArrayValues);
+}
+
+static bool IsArrayIteratorPrototypeOptimizable(JSContext* cx,
+ NativeObject** arrIterProto,
+ uint32_t* slot,
+ JSFunction** nextFun) {
+ auto* proto = cx->global()->maybeGetArrayIteratorPrototype();
+ if (!proto) {
+ return false;
+ }
+ *arrIterProto = proto;
+
+ // Ensure that %ArrayIteratorPrototype%'s "next" slot is unchanged.
+ Shape* shape = proto->lookupPure(cx->names().next);
+ if (!shape || !shape->isDataProperty()) {
+ return false;
+ }
+
+ *slot = shape->slot();
+ MOZ_ASSERT(proto->numFixedSlots() == 0, "Stub code relies on this");
+
+ const Value& nextVal = proto->getSlot(*slot);
+ if (!nextVal.isObject() || !nextVal.toObject().is<JSFunction>()) {
+ return false;
+ }
+
+ *nextFun = &nextVal.toObject().as<JSFunction>();
+ return IsSelfHostedFunctionWithName(*nextFun, cx->names().ArrayIteratorNext);
+}
+
+AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArray() {
+ // The value must be a packed array.
+ if (!val_.isObject()) {
+ return AttachDecision::NoAction;
+ }
+ JSObject* obj = &val_.toObject();
+ if (!IsPackedArray(obj)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Prototype must be Array.prototype and Array.prototype[@@iterator] must not
+ // be modified.
+ NativeObject* arrProto;
+ uint32_t arrProtoIterSlot;
+ JSFunction* iterFun;
+ if (!IsArrayPrototypeOptimizable(cx_, &obj->as<ArrayObject>(), &arrProto,
+ &arrProtoIterSlot, &iterFun)) {
+ return AttachDecision::NoAction;
+ }
+
+ // %ArrayIteratorPrototype%.next must not be modified.
+ NativeObject* arrayIteratorProto;
+ uint32_t iterNextSlot;
+ JSFunction* nextFun;
+ if (!IsArrayIteratorPrototypeOptimizable(cx_, &arrayIteratorProto,
+ &iterNextSlot, &nextFun)) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ ObjOperandId objId = writer.guardToObject(valId);
+
+ // Guard the object is a packed array with Array.prototype as proto.
+ writer.guardShape(objId, obj->as<ArrayObject>().lastProperty());
+ writer.guardArrayIsPacked(objId);
+ if (obj->hasUncacheableProto()) {
+ writer.guardProto(objId, arrProto);
+ }
+
+ // Guard on Array.prototype[@@iterator].
+ ObjOperandId arrProtoId = writer.loadObject(arrProto);
+ ObjOperandId iterId = writer.loadObject(iterFun);
+ writer.guardShape(arrProtoId, arrProto->lastProperty());
+ writer.guardDynamicSlotIsSpecificObject(arrProtoId, iterId, arrProtoIterSlot);
+
+ // Guard on %ArrayIteratorPrototype%.next.
+ ObjOperandId iterProtoId = writer.loadObject(arrayIteratorProto);
+ ObjOperandId nextId = writer.loadObject(nextFun);
+ writer.guardShape(iterProtoId, arrayIteratorProto->lastProperty());
+ writer.guardDynamicSlotIsSpecificObject(iterProtoId, nextId, iterNextSlot);
+
+ writer.loadBooleanResult(true);
+ writer.returnFromIC();
+
+ trackAttached("Array");
+ return AttachDecision::Attach;
+}
+
+AttachDecision OptimizeSpreadCallIRGenerator::tryAttachNotOptimizable() {
+ ValOperandId valId(writer.setInputOperandId(0));
+
+ writer.loadBooleanResult(false);
+ writer.returnFromIC();
+
+ trackAttached("NotOptimizable");
+ return AttachDecision::Attach;
+}
+
+void OptimizeSpreadCallIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("val", val_);
+ }
+#endif
+}
+
+CallIRGenerator::CallIRGenerator(JSContext* cx, HandleScript script,
+ jsbytecode* pc, JSOp op, ICState::Mode mode,
+ bool isFirstStub, uint32_t argc,
+ HandleValue callee, HandleValue thisval,
+ HandleValue newTarget, HandleValueArray args)
+ : IRGenerator(cx, script, pc, CacheKind::Call, mode),
+ op_(op),
+ isFirstStub_(isFirstStub),
+ argc_(argc),
+ callee_(callee),
+ thisval_(thisval),
+ newTarget_(newTarget),
+ args_(args) {}
+
+void CallIRGenerator::emitNativeCalleeGuard(HandleFunction callee) {
+ // Note: we rely on GuardSpecificFunction to also guard against the same
+ // native from a different realm.
+ MOZ_ASSERT(callee->isNativeWithoutJitEntry());
+
+ bool isConstructing = IsConstructPC(pc_);
+ CallFlags flags(isConstructing, IsSpreadPC(pc_));
+
+ ValOperandId calleeValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags);
+ ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
+ writer.guardSpecificFunction(calleeObjId, callee);
+
+ // If we're constructing we also need to guard newTarget == callee.
+ if (isConstructing) {
+ MOZ_ASSERT(&newTarget_.toObject() == callee);
+ ValOperandId newTargetValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::NewTarget, argc_, flags);
+ ObjOperandId newTargetObjId = writer.guardToObject(newTargetValId);
+ writer.guardSpecificFunction(newTargetObjId, callee);
+ }
+}
+
+void CallIRGenerator::emitCalleeGuard(ObjOperandId calleeId,
+ HandleFunction callee) {
+ // Guarding on the callee JSFunction* is most efficient, but doesn't work well
+ // for lambda clones (multiple functions with the same BaseScript). We guard
+ // on the function's BaseScript if the callee is scripted and this isn't the
+ // first IC stub.
+ if (isFirstStub_ || !callee->hasBaseScript() ||
+ callee->isSelfHostedBuiltin()) {
+ writer.guardSpecificFunction(calleeId, callee);
+ } else {
+ writer.guardClass(calleeId, GuardClassKind::JSFunction);
+ writer.guardFunctionScript(calleeId, callee->baseScript());
+ }
+}
+
+AttachDecision CallIRGenerator::tryAttachArrayPush(HandleFunction callee) {
+ // Only optimize on obj.push(val);
+ if (argc_ != 1 || !thisval_.isObject()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Where |obj| is a native array.
+ JSObject* thisobj = &thisval_.toObject();
+ if (!thisobj->is<ArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ auto* thisarray = &thisobj->as<ArrayObject>();
+
+ // Check for other indexed properties or class hooks.
+ if (!CanAttachAddElement(thisarray, /* isInit = */ false)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Can't add new elements to arrays with non-writable length.
+ if (!thisarray->lengthIsWritable()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check that array is extensible.
+ if (!thisarray->isExtensible()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check that the array is completely initialized (no holes).
+ if (thisarray->getDenseInitializedLength() != thisarray->length()) {
+ return AttachDecision::NoAction;
+ }
+
+ MOZ_ASSERT(!thisarray->denseElementsAreFrozen(),
+ "Extensible arrays should not have frozen elements");
+
+ // After this point, we can generate code fine.
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'push' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard this is an array object.
+ ValOperandId thisValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
+ ObjOperandId thisObjId = writer.guardToObject(thisValId);
+
+ // Guard that the shape matches.
+ TestMatchingNativeReceiver(writer, thisarray, thisObjId);
+
+ // Guard proto chain shapes.
+ ShapeGuardProtoChain(writer, thisobj, thisObjId);
+
+ // arr.push(x) is equivalent to arr[arr.length] = x for regular arrays.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ writer.arrayPush(thisObjId, argId);
+
+ writer.returnFromIC();
+
+ trackAttached("ArrayPush");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachArrayPopShift(HandleFunction callee,
+ InlinableNative native) {
+ // Expecting no arguments.
+ if (argc_ != 0) {
+ return AttachDecision::NoAction;
+ }
+
+ // Only optimize if |this| is a packed array.
+ if (!thisval_.isObject() || !IsPackedArray(&thisval_.toObject())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Other conditions:
+ //
+ // * The array length needs to be writable because we're changing it.
+ // * The array must be extensible. Non-extensible arrays require preserving
+ // the |initializedLength == capacity| invariant on ObjectElements.
+ // See NativeObject::shrinkCapacityToInitializedLength.
+ // This also ensures the elements aren't sealed/frozen.
+ // * There must not be a for-in iterator for the elements because the IC stub
+ // does not suppress deleted properties.
+ ArrayObject* arr = &thisval_.toObject().as<ArrayObject>();
+ if (!arr->lengthIsWritable() || !arr->isExtensible() ||
+ arr->denseElementsHaveMaybeInIterationFlag()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'pop' or 'shift' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId thisValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
+ ObjOperandId objId = writer.guardToObject(thisValId);
+ writer.guardClass(objId, GuardClassKind::Array);
+
+ if (native == InlinableNative::ArrayPop) {
+ writer.packedArrayPopResult(objId);
+ } else {
+ MOZ_ASSERT(native == InlinableNative::ArrayShift);
+ writer.packedArrayShiftResult(objId);
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("ArrayPopShift");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachArrayJoin(HandleFunction callee) {
+ // Only handle argc <= 1.
+ if (argc_ > 1) {
+ return AttachDecision::NoAction;
+ }
+
+ // Only optimize if |this| is an array.
+ if (!thisval_.isObject() || !thisval_.toObject().is<ArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ // The separator argument must be a string, if present.
+ if (argc_ > 0 && !args_[0].isString()) {
+ return AttachDecision::NoAction;
+ }
+
+ // IC stub code can handle non-packed array.
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'join' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard this is an array object.
+ ValOperandId thisValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
+ ObjOperandId thisObjId = writer.guardToObject(thisValId);
+ writer.guardClass(thisObjId, GuardClassKind::Array);
+
+ StringOperandId sepId;
+ if (argc_ == 1) {
+ // If argcount is 1, guard that the argument is a string.
+ ValOperandId argValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ sepId = writer.guardToString(argValId);
+ } else {
+ sepId = writer.loadConstantString(cx_->names().comma);
+ }
+
+ // Do the join.
+ writer.arrayJoinResult(thisObjId, sepId);
+
+ writer.returnFromIC();
+
+ trackAttached("ArrayJoin");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachArraySlice(HandleFunction callee) {
+ // Only handle argc <= 2.
+ if (argc_ > 2) {
+ return AttachDecision::NoAction;
+ }
+
+ // Only optimize if |this| is a packed array.
+ if (!thisval_.isObject() || !IsPackedArray(&thisval_.toObject())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Arguments for the sliced region must be integers.
+ if (argc_ > 0 && !args_[0].isInt32()) {
+ return AttachDecision::NoAction;
+ }
+ if (argc_ > 1 && !args_[1].isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedArrayObject arr(cx_, &thisval_.toObject().as<ArrayObject>());
+
+ JSObject* templateObj =
+ NewDenseFullyAllocatedArray(cx_, 0, /* proto = */ nullptr, TenuredObject);
+ if (!templateObj) {
+ cx_->recoverFromOutOfMemory();
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'slice' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId thisValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
+ ObjOperandId objId = writer.guardToObject(thisValId);
+ writer.guardClass(objId, GuardClassKind::Array);
+
+ Int32OperandId int32BeginId;
+ if (argc_ > 0) {
+ ValOperandId beginId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ int32BeginId = writer.guardToInt32(beginId);
+ } else {
+ int32BeginId = writer.loadInt32Constant(0);
+ }
+
+ Int32OperandId int32EndId;
+ if (argc_ > 1) {
+ ValOperandId endId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ int32EndId = writer.guardToInt32(endId);
+ } else {
+ int32EndId = writer.loadInt32ArrayLength(objId);
+ }
+
+ writer.packedArraySliceResult(templateObj, objId, int32BeginId, int32EndId);
+ writer.returnFromIC();
+
+ trackAttached("ArraySlice");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachArrayIsArray(HandleFunction callee) {
+ // Need a single argument.
+ if (argc_ != 1) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'isArray' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Check if the argument is an Array and return result.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ writer.isArrayResult(argId);
+ writer.returnFromIC();
+
+ trackAttached("ArrayIsArray");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachDataViewGet(HandleFunction callee,
+ Scalar::Type type) {
+ // Ensure |this| is a DataViewObject.
+ if (!thisval_.isObject() || !thisval_.toObject().is<DataViewObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Expected arguments: offset (number), optional littleEndian (boolean).
+ if (argc_ < 1 || argc_ > 2) {
+ return AttachDecision::NoAction;
+ }
+ if (!args_[0].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+ if (argc_ > 1 && !args_[1].isBoolean()) {
+ return AttachDecision::NoAction;
+ }
+
+ DataViewObject* dv = &thisval_.toObject().as<DataViewObject>();
+
+ // Bounds check the offset.
+ int32_t offsetInt32;
+ if (!mozilla::NumberEqualsInt32(args_[0].toNumber(), &offsetInt32)) {
+ return AttachDecision::NoAction;
+ }
+ if (offsetInt32 < 0 ||
+ !dv->offsetIsInBounds(Scalar::byteSize(type), offsetInt32)) {
+ return AttachDecision::NoAction;
+ }
+
+ // For getUint32 we let the stub return a double only if the current result is
+ // a double, to allow better codegen in Warp.
+ bool allowDoubleForUint32 = false;
+ if (type == Scalar::Uint32) {
+ bool isLittleEndian = argc_ > 1 && args_[1].toBoolean();
+ uint32_t res = dv->read<uint32_t>(offsetInt32, isLittleEndian);
+ allowDoubleForUint32 = res >= INT32_MAX;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is this DataView native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard |this| is a DataViewObject.
+ ValOperandId thisValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
+ ObjOperandId objId = writer.guardToObject(thisValId);
+ writer.guardClass(objId, GuardClassKind::DataView);
+
+ // Convert offset to int32.
+ ValOperandId offsetId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ Int32OperandId int32OffsetId = writer.guardToInt32Index(offsetId);
+
+ BooleanOperandId boolLittleEndianId;
+ if (argc_ > 1) {
+ ValOperandId littleEndianId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ boolLittleEndianId = writer.guardToBoolean(littleEndianId);
+ } else {
+ boolLittleEndianId = writer.loadBooleanConstant(false);
+ }
+
+ writer.loadDataViewValueResult(objId, int32OffsetId, boolLittleEndianId, type,
+ allowDoubleForUint32);
+ writer.returnFromIC();
+
+ trackAttached("DataViewGet");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachDataViewSet(HandleFunction callee,
+ Scalar::Type type) {
+ // Ensure |this| is a DataViewObject.
+ if (!thisval_.isObject() || !thisval_.toObject().is<DataViewObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Expected arguments: offset (number), value, optional littleEndian (boolean)
+ if (argc_ < 2 || argc_ > 3) {
+ return AttachDecision::NoAction;
+ }
+ if (!args_[0].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+ if (!ValueIsNumeric(type, args_[1])) {
+ return AttachDecision::NoAction;
+ }
+ if (argc_ > 2 && !args_[2].isBoolean()) {
+ return AttachDecision::NoAction;
+ }
+
+ DataViewObject* dv = &thisval_.toObject().as<DataViewObject>();
+
+ // Bounds check the offset.
+ int32_t offsetInt32;
+ if (!mozilla::NumberEqualsInt32(args_[0].toNumber(), &offsetInt32)) {
+ return AttachDecision::NoAction;
+ }
+ if (offsetInt32 < 0 ||
+ !dv->offsetIsInBounds(Scalar::byteSize(type), offsetInt32)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is this DataView native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard |this| is a DataViewObject.
+ ValOperandId thisValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
+ ObjOperandId objId = writer.guardToObject(thisValId);
+ writer.guardClass(objId, GuardClassKind::DataView);
+
+ // Convert offset to int32.
+ ValOperandId offsetId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ Int32OperandId int32OffsetId = writer.guardToInt32Index(offsetId);
+
+ // Convert value to number or BigInt.
+ ValOperandId valueId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ OperandId numericValueId = emitNumericGuard(valueId, type);
+
+ BooleanOperandId boolLittleEndianId;
+ if (argc_ > 2) {
+ ValOperandId littleEndianId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
+ boolLittleEndianId = writer.guardToBoolean(littleEndianId);
+ } else {
+ boolLittleEndianId = writer.loadBooleanConstant(false);
+ }
+
+ writer.storeDataViewValueResult(objId, int32OffsetId, numericValueId,
+ boolLittleEndianId, type);
+ writer.returnFromIC();
+
+ trackAttached("DataViewSet");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachUnsafeGetReservedSlot(
+ HandleFunction callee, InlinableNative native) {
+ // Self-hosted code calls this with (object, int32) arguments.
+ MOZ_ASSERT(argc_ == 2);
+ MOZ_ASSERT(args_[0].isObject());
+ MOZ_ASSERT(args_[1].isInt32());
+ MOZ_ASSERT(args_[1].toInt32() >= 0);
+
+ uint32_t slot = uint32_t(args_[1].toInt32());
+ if (slot >= NativeObject::MAX_FIXED_SLOTS) {
+ return AttachDecision::NoAction;
+ }
+ size_t offset = NativeObject::getFixedSlotOffset(slot);
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ // Guard that the first argument is an object.
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(arg0Id);
+
+ // BytecodeEmitter::checkSelfHostedUnsafeGetReservedSlot ensures that the
+ // slot argument is constant. (At least for direct calls)
+
+ switch (native) {
+ case InlinableNative::IntrinsicUnsafeGetReservedSlot:
+ writer.loadFixedSlotResult(objId, offset);
+ break;
+ case InlinableNative::IntrinsicUnsafeGetObjectFromReservedSlot:
+ writer.loadFixedSlotTypedResult(objId, offset, ValueType::Object);
+ break;
+ case InlinableNative::IntrinsicUnsafeGetInt32FromReservedSlot:
+ writer.loadFixedSlotTypedResult(objId, offset, ValueType::Int32);
+ break;
+ case InlinableNative::IntrinsicUnsafeGetStringFromReservedSlot:
+ writer.loadFixedSlotTypedResult(objId, offset, ValueType::String);
+ break;
+ case InlinableNative::IntrinsicUnsafeGetBooleanFromReservedSlot:
+ writer.loadFixedSlotTypedResult(objId, offset, ValueType::Boolean);
+ break;
+ default:
+ MOZ_CRASH("unexpected native");
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("UnsafeGetReservedSlot");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachUnsafeSetReservedSlot(
+ HandleFunction callee) {
+ // Self-hosted code calls this with (object, int32, value) arguments.
+ MOZ_ASSERT(argc_ == 3);
+ MOZ_ASSERT(args_[0].isObject());
+ MOZ_ASSERT(args_[1].isInt32());
+ MOZ_ASSERT(args_[1].toInt32() >= 0);
+
+ uint32_t slot = uint32_t(args_[1].toInt32());
+ if (slot >= NativeObject::MAX_FIXED_SLOTS) {
+ return AttachDecision::NoAction;
+ }
+ size_t offset = NativeObject::getFixedSlotOffset(slot);
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ // Guard that the first argument is an object.
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(arg0Id);
+
+ // BytecodeEmitter::checkSelfHostedUnsafeSetReservedSlot ensures that the
+ // slot argument is constant. (At least for direct calls)
+
+ // Get the value to set.
+ ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
+
+ // Set the fixed slot and return undefined.
+ writer.storeFixedSlotUndefinedResult(objId, offset, valId);
+
+ // This stub always returns undefined.
+ writer.returnFromIC();
+
+ trackAttached("UnsafeSetReservedSlot");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachIsSuspendedGenerator(
+ HandleFunction callee) {
+ // The IsSuspendedGenerator intrinsic is only called in
+ // self-hosted code, so it's safe to assume we have a single
+ // argument and the callee is our intrinsic.
+
+ MOZ_ASSERT(argc_ == 1);
+
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Stack layout here is (bottom to top):
+ // 2: Callee
+ // 1: ThisValue
+ // 0: Arg <-- Top of stack.
+ // We only care about the argument.
+ ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+
+ // Check whether the argument is a suspended generator.
+ // We don't need guards, because IsSuspendedGenerator returns
+ // false for values that are not generator objects.
+ writer.callIsSuspendedGeneratorResult(valId);
+ writer.returnFromIC();
+
+ trackAttached("IsSuspendedGenerator");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachToObject(HandleFunction callee,
+ InlinableNative native) {
+ // Self-hosted code calls this with a single argument.
+ MOZ_ASSERT_IF(native == InlinableNative::IntrinsicToObject, argc_ == 1);
+
+ // Need a single object argument.
+ // TODO(Warp): Support all or more conversions to object.
+ // Note: ToObject and Object differ in their behavior for undefined/null.
+ if (argc_ != 1 || !args_[0].isObject()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'Object' function.
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+ if (native == InlinableNative::Object) {
+ emitNativeCalleeGuard(callee);
+ }
+
+ // Guard that the argument is an object.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(argId);
+
+ // Return the object.
+ writer.loadObjectResult(objId);
+ writer.returnFromIC();
+
+ if (native == InlinableNative::IntrinsicToObject) {
+ trackAttached("ToObject");
+ } else {
+ MOZ_ASSERT(native == InlinableNative::Object);
+ trackAttached("Object");
+ }
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachToInteger(HandleFunction callee) {
+ // Self-hosted code calls this with a single argument.
+ MOZ_ASSERT(argc_ == 1);
+
+ // Need a single int32 argument.
+ // TODO(Warp): Support all or more conversions to integer.
+ // Make sure to update this code correctly if we ever start
+ // returning non-int32 integers.
+ if (!args_[0].isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ // Guard that the argument is an int32.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ Int32OperandId int32Id = writer.guardToInt32(argId);
+
+ // Return the int32.
+ writer.loadInt32Result(int32Id);
+ writer.returnFromIC();
+
+ trackAttached("ToInteger");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachToLength(HandleFunction callee) {
+ // Self-hosted code calls this with a single argument.
+ MOZ_ASSERT(argc_ == 1);
+
+ // Need a single int32 argument.
+ if (!args_[0].isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ // ToLength(int32) is equivalent to max(int32, 0).
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ Int32OperandId int32ArgId = writer.guardToInt32(argId);
+ Int32OperandId zeroId = writer.loadInt32Constant(0);
+ bool isMax = true;
+ Int32OperandId maxId = writer.int32MinMax(isMax, int32ArgId, zeroId);
+ writer.loadInt32Result(maxId);
+ writer.returnFromIC();
+
+ trackAttached("ToLength");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachIsObject(HandleFunction callee) {
+ // Self-hosted code calls this with a single argument.
+ MOZ_ASSERT(argc_ == 1);
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ // Type check the argument and return result.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ writer.isObjectResult(argId);
+ writer.returnFromIC();
+
+ trackAttached("IsObject");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachIsPackedArray(HandleFunction callee) {
+ // Self-hosted code calls this with a single object argument.
+ MOZ_ASSERT(argc_ == 1);
+ MOZ_ASSERT(args_[0].isObject());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ // Check if the argument is packed and return result.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objArgId = writer.guardToObject(argId);
+ writer.isPackedArrayResult(objArgId);
+ writer.returnFromIC();
+
+ trackAttached("IsPackedArray");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachIsCallable(HandleFunction callee) {
+ // Self-hosted code calls this with a single argument.
+ MOZ_ASSERT(argc_ == 1);
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ // Check if the argument is callable and return result.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ writer.isCallableResult(argId);
+ writer.returnFromIC();
+
+ trackAttached("IsCallable");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachIsConstructor(HandleFunction callee) {
+ // Self-hosted code calls this with a single argument.
+ MOZ_ASSERT(argc_ == 1);
+
+ // Need a single object argument.
+ if (!args_[0].isObject()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ // Guard that the argument is an object.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(argId);
+
+ // Check if the argument is a constructor and return result.
+ writer.isConstructorResult(objId);
+ writer.returnFromIC();
+
+ trackAttached("IsConstructor");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachIsCrossRealmArrayConstructor(
+ HandleFunction callee) {
+ // Self-hosted code calls this with an object argument.
+ MOZ_ASSERT(argc_ == 1);
+ MOZ_ASSERT(args_[0].isObject());
+
+ if (args_[0].toObject().is<ProxyObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(argId);
+ writer.guardIsNotProxy(objId);
+ writer.isCrossRealmArrayConstructorResult(objId);
+ writer.returnFromIC();
+
+ trackAttached("IsCrossRealmArrayConstructor");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachGuardToClass(HandleFunction callee,
+ InlinableNative native) {
+ // Self-hosted code calls this with an object argument.
+ MOZ_ASSERT(argc_ == 1);
+ MOZ_ASSERT(args_[0].isObject());
+
+ // Class must match.
+ const JSClass* clasp = InlinableNativeGuardToClass(native);
+ if (args_[0].toObject().getClass() != clasp) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ // Guard that the argument is an object.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(argId);
+
+ // Guard that the object has the correct class.
+ writer.guardAnyClass(objId, clasp);
+
+ // Return the object.
+ writer.loadObjectResult(objId);
+ writer.returnFromIC();
+
+ trackAttached("GuardToClass");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachHasClass(HandleFunction callee,
+ const JSClass* clasp,
+ bool isPossiblyWrapped) {
+ // Self-hosted code calls this with an object argument.
+ MOZ_ASSERT(argc_ == 1);
+ MOZ_ASSERT(args_[0].isObject());
+
+ // Only optimize when the object isn't a proxy.
+ if (isPossiblyWrapped && IsProxy(&args_[0].toObject())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ // Perform the Class check.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(argId);
+
+ if (isPossiblyWrapped) {
+ writer.guardIsNotProxy(objId);
+ }
+
+ writer.hasClassResult(objId, clasp);
+ writer.returnFromIC();
+
+ trackAttached("HasClass");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachRegExpMatcherSearcherTester(
+ HandleFunction callee, InlinableNative native) {
+ // Self-hosted code calls this with (object, string, number) arguments.
+ MOZ_ASSERT(argc_ == 3);
+ MOZ_ASSERT(args_[0].isObject());
+ MOZ_ASSERT(args_[1].isString());
+ MOZ_ASSERT(args_[2].isNumber());
+
+ // It's not guaranteed that the JITs have typed |lastIndex| as an Int32.
+ if (!args_[2].isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ // Guard argument types.
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId reId = writer.guardToObject(arg0Id);
+
+ ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ StringOperandId inputId = writer.guardToString(arg1Id);
+
+ ValOperandId arg2Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
+ Int32OperandId lastIndexId = writer.guardToInt32(arg2Id);
+
+ switch (native) {
+ case InlinableNative::RegExpMatcher:
+ writer.callRegExpMatcherResult(reId, inputId, lastIndexId);
+ writer.returnFromIC();
+ trackAttached("RegExpMatcher");
+ break;
+
+ case InlinableNative::RegExpSearcher:
+ writer.callRegExpSearcherResult(reId, inputId, lastIndexId);
+ writer.returnFromIC();
+ trackAttached("RegExpSearcher");
+ break;
+
+ case InlinableNative::RegExpTester:
+ writer.callRegExpTesterResult(reId, inputId, lastIndexId);
+ writer.returnFromIC();
+ trackAttached("RegExpTester");
+ break;
+
+ default:
+ MOZ_CRASH("Unexpected native");
+ }
+
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachRegExpPrototypeOptimizable(
+ HandleFunction callee) {
+ // Self-hosted code calls this with a single object argument.
+ MOZ_ASSERT(argc_ == 1);
+ MOZ_ASSERT(args_[0].isObject());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId protoId = writer.guardToObject(arg0Id);
+
+ writer.regExpPrototypeOptimizableResult(protoId);
+ writer.returnFromIC();
+
+ trackAttached("RegExpPrototypeOptimizable");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachRegExpInstanceOptimizable(
+ HandleFunction callee) {
+ // Self-hosted code calls this with two object arguments.
+ MOZ_ASSERT(argc_ == 2);
+ MOZ_ASSERT(args_[0].isObject());
+ MOZ_ASSERT(args_[1].isObject());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId regexpId = writer.guardToObject(arg0Id);
+
+ ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ ObjOperandId protoId = writer.guardToObject(arg1Id);
+
+ writer.regExpInstanceOptimizableResult(regexpId, protoId);
+ writer.returnFromIC();
+
+ trackAttached("RegExpInstanceOptimizable");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachGetFirstDollarIndex(
+ HandleFunction callee) {
+ // Self-hosted code calls this with a single string argument.
+ MOZ_ASSERT(argc_ == 1);
+ MOZ_ASSERT(args_[0].isString());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ StringOperandId strId = writer.guardToString(arg0Id);
+
+ writer.getFirstDollarIndexResult(strId);
+ writer.returnFromIC();
+
+ trackAttached("GetFirstDollarIndex");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachSubstringKernel(
+ HandleFunction callee) {
+ // Self-hosted code calls this with (string, int32, int32) arguments.
+ MOZ_ASSERT(argc_ == 3);
+ MOZ_ASSERT(args_[0].isString());
+ MOZ_ASSERT(args_[1].isInt32());
+ MOZ_ASSERT(args_[2].isInt32());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ StringOperandId strId = writer.guardToString(arg0Id);
+
+ ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ Int32OperandId beginId = writer.guardToInt32(arg1Id);
+
+ ValOperandId arg2Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
+ Int32OperandId lengthId = writer.guardToInt32(arg2Id);
+
+ writer.callSubstringKernelResult(strId, beginId, lengthId);
+ writer.returnFromIC();
+
+ trackAttached("SubstringKernel");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachObjectHasPrototype(
+ HandleFunction callee) {
+ // Self-hosted code calls this with (object, object) arguments.
+ MOZ_ASSERT(argc_ == 2);
+ MOZ_ASSERT(args_[0].isObject());
+ MOZ_ASSERT(args_[1].isObject());
+
+ auto* obj = &args_[0].toObject().as<NativeObject>();
+ auto* proto = &args_[1].toObject().as<NativeObject>();
+
+ // Only attach when obj.__proto__ is proto.
+ if (obj->staticPrototype() != proto) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(arg0Id);
+
+ writer.guardProto(objId, proto);
+ writer.loadBooleanResult(true);
+ writer.returnFromIC();
+
+ trackAttached("ObjectHasPrototype");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachString(HandleFunction callee) {
+ // Need a single string argument.
+ // TODO(Warp): Support all or more conversions to strings.
+ if (argc_ != 1 || !args_[0].isString()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'String' function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard that the argument is a string.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ StringOperandId strId = writer.guardToString(argId);
+
+ // Return the string.
+ writer.loadStringResult(strId);
+ writer.returnFromIC();
+
+ trackAttached("String");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachStringConstructor(
+ HandleFunction callee) {
+ // Need a single string argument.
+ // TODO(Warp): Support all or more conversions to strings.
+ if (argc_ != 1 || !args_[0].isString()) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedString emptyString(cx_, cx_->runtime()->emptyString);
+ JSObject* templateObj = StringObject::create(
+ cx_, emptyString, /* proto = */ nullptr, TenuredObject);
+ if (!templateObj) {
+ cx_->recoverFromOutOfMemory();
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'String' function.
+ emitNativeCalleeGuard(callee);
+
+ CallFlags flags(IsConstructPC(pc_), IsSpreadPC(pc_));
+
+ // Guard that the argument is a string.
+ ValOperandId argId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags);
+ StringOperandId strId = writer.guardToString(argId);
+ writer.newStringObjectResult(templateObj, strId);
+ writer.returnFromIC();
+
+ trackAttached("StringConstructor");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachStringToStringValueOf(
+ HandleFunction callee) {
+ // Expecting no arguments.
+ if (argc_ != 0) {
+ return AttachDecision::NoAction;
+ }
+
+ // Ensure |this| is a primitive string value.
+ if (!thisval_.isString()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'toString' OR 'valueOf' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard |this| is a string.
+ ValOperandId thisValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
+ StringOperandId strId = writer.guardToString(thisValId);
+
+ // Return the string
+ writer.loadStringResult(strId);
+ writer.returnFromIC();
+
+ trackAttached("StringToStringValueOf");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachStringReplaceString(
+ HandleFunction callee) {
+ // Self-hosted code calls this with (string, string, string) arguments.
+ MOZ_ASSERT(argc_ == 3);
+ MOZ_ASSERT(args_[0].isString());
+ MOZ_ASSERT(args_[1].isString());
+ MOZ_ASSERT(args_[2].isString());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ StringOperandId strId = writer.guardToString(arg0Id);
+
+ ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ StringOperandId patternId = writer.guardToString(arg1Id);
+
+ ValOperandId arg2Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
+ StringOperandId replacementId = writer.guardToString(arg2Id);
+
+ writer.stringReplaceStringResult(strId, patternId, replacementId);
+ writer.returnFromIC();
+
+ trackAttached("StringReplaceString");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachStringSplitString(
+ HandleFunction callee) {
+ // Self-hosted code calls this with (string, string) arguments.
+ MOZ_ASSERT(argc_ == 2);
+ MOZ_ASSERT(args_[0].isString());
+ MOZ_ASSERT(args_[1].isString());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ StringOperandId strId = writer.guardToString(arg0Id);
+
+ ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ StringOperandId separatorId = writer.guardToString(arg1Id);
+
+ writer.stringSplitStringResult(strId, separatorId);
+ writer.returnFromIC();
+
+ trackAttached("StringSplitString");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachStringChar(HandleFunction callee,
+ StringChar kind) {
+ // Need one argument.
+ if (argc_ != 1) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!CanAttachStringChar(thisval_, args_[0])) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'charCodeAt' or 'charAt' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard this is a string.
+ ValOperandId thisValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
+ StringOperandId strId = writer.guardToString(thisValId);
+
+ // Guard int32 index.
+ ValOperandId indexId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
+
+ // Load string char or code.
+ if (kind == StringChar::CodeAt) {
+ writer.loadStringCharCodeResult(strId, int32IndexId);
+ } else {
+ writer.loadStringCharResult(strId, int32IndexId);
+ }
+
+ writer.returnFromIC();
+
+ if (kind == StringChar::CodeAt) {
+ trackAttached("StringCharCodeAt");
+ } else {
+ trackAttached("StringCharAt");
+ }
+
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachStringCharCodeAt(
+ HandleFunction callee) {
+ return tryAttachStringChar(callee, StringChar::CodeAt);
+}
+
+AttachDecision CallIRGenerator::tryAttachStringCharAt(HandleFunction callee) {
+ return tryAttachStringChar(callee, StringChar::At);
+}
+
+AttachDecision CallIRGenerator::tryAttachStringFromCharCode(
+ HandleFunction callee) {
+ // Need one int32 argument.
+ if (argc_ != 1 || !args_[0].isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'fromCharCode' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard int32 argument.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ Int32OperandId codeId = writer.guardToInt32(argId);
+
+ // Return string created from code.
+ writer.stringFromCharCodeResult(codeId);
+ writer.returnFromIC();
+
+ trackAttached("StringFromCharCode");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachStringFromCodePoint(
+ HandleFunction callee) {
+ // Need one int32 argument.
+ if (argc_ != 1 || !args_[0].isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ // String.fromCodePoint throws for invalid code points.
+ int32_t codePoint = args_[0].toInt32();
+ if (codePoint < 0 || codePoint > int32_t(unicode::NonBMPMax)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'fromCodePoint' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard int32 argument.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ Int32OperandId codeId = writer.guardToInt32(argId);
+
+ // Return string created from code point.
+ writer.stringFromCodePointResult(codeId);
+ writer.returnFromIC();
+
+ trackAttached("StringFromCodePoint");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachStringToLowerCase(
+ HandleFunction callee) {
+ // Expecting no arguments.
+ if (argc_ != 0) {
+ return AttachDecision::NoAction;
+ }
+
+ // Ensure |this| is a primitive string value.
+ if (!thisval_.isString()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'toLowerCase' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard this is a string.
+ ValOperandId thisValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
+ StringOperandId strId = writer.guardToString(thisValId);
+
+ // Return string converted to lower-case.
+ writer.stringToLowerCaseResult(strId);
+ writer.returnFromIC();
+
+ trackAttached("StringToLowerCase");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachStringToUpperCase(
+ HandleFunction callee) {
+ // Expecting no arguments.
+ if (argc_ != 0) {
+ return AttachDecision::NoAction;
+ }
+
+ // Ensure |this| is a primitive string value.
+ if (!thisval_.isString()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'toUpperCase' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard this is a string.
+ ValOperandId thisValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
+ StringOperandId strId = writer.guardToString(thisValId);
+
+ // Return string converted to upper-case.
+ writer.stringToUpperCaseResult(strId);
+ writer.returnFromIC();
+
+ trackAttached("StringToUpperCase");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathRandom(HandleFunction callee) {
+ // Expecting no arguments.
+ if (argc_ != 0) {
+ return AttachDecision::NoAction;
+ }
+
+ MOZ_ASSERT(cx_->realm() == callee->realm(),
+ "Shouldn't inline cross-realm Math.random because per-realm RNG");
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'random' native function.
+ emitNativeCalleeGuard(callee);
+
+ mozilla::non_crypto::XorShift128PlusRNG* rng =
+ &cx_->realm()->getOrCreateRandomNumberGenerator();
+ writer.mathRandomResult(rng);
+
+ writer.returnFromIC();
+
+ trackAttached("MathRandom");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathAbs(HandleFunction callee) {
+ // Need one argument.
+ if (argc_ != 1) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!args_[0].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'abs' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId argumentId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+
+ // abs(INT_MIN) is a double.
+ if (args_[0].isInt32() && args_[0].toInt32() != INT_MIN) {
+ Int32OperandId int32Id = writer.guardToInt32(argumentId);
+ writer.mathAbsInt32Result(int32Id);
+ } else {
+ NumberOperandId numberId = writer.guardIsNumber(argumentId);
+ writer.mathAbsNumberResult(numberId);
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("MathAbs");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathClz32(HandleFunction callee) {
+ // Need one (number) argument.
+ if (argc_ != 1 || !args_[0].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'clz32' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+
+ Int32OperandId int32Id;
+ if (args_[0].isInt32()) {
+ int32Id = writer.guardToInt32(argId);
+ } else {
+ MOZ_ASSERT(args_[0].isDouble());
+ NumberOperandId numId = writer.guardIsNumber(argId);
+ int32Id = writer.truncateDoubleToUInt32(numId);
+ }
+ writer.mathClz32Result(int32Id);
+ writer.returnFromIC();
+
+ trackAttached("MathClz32");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathSign(HandleFunction callee) {
+ // Need one (number) argument.
+ if (argc_ != 1 || !args_[0].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'sign' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+
+ if (args_[0].isInt32()) {
+ Int32OperandId int32Id = writer.guardToInt32(argId);
+ writer.mathSignInt32Result(int32Id);
+ } else {
+ // Math.sign returns a double only if the input is -0 or NaN so try to
+ // optimize the common Number => Int32 case.
+ double d = math_sign_impl(args_[0].toDouble());
+ int32_t unused;
+ bool resultIsInt32 = mozilla::NumberIsInt32(d, &unused);
+
+ NumberOperandId numId = writer.guardIsNumber(argId);
+ if (resultIsInt32) {
+ writer.mathSignNumberToInt32Result(numId);
+ } else {
+ writer.mathSignNumberResult(numId);
+ }
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("MathSign");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathImul(HandleFunction callee) {
+ // Need two (number) arguments.
+ if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'imul' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+
+ Int32OperandId int32Arg0Id, int32Arg1Id;
+ if (args_[0].isInt32() && args_[1].isInt32()) {
+ int32Arg0Id = writer.guardToInt32(arg0Id);
+ int32Arg1Id = writer.guardToInt32(arg1Id);
+ } else {
+ // Treat both arguments as numbers if at least one of them is non-int32.
+ NumberOperandId numArg0Id = writer.guardIsNumber(arg0Id);
+ NumberOperandId numArg1Id = writer.guardIsNumber(arg1Id);
+ int32Arg0Id = writer.truncateDoubleToUInt32(numArg0Id);
+ int32Arg1Id = writer.truncateDoubleToUInt32(numArg1Id);
+ }
+ writer.mathImulResult(int32Arg0Id, int32Arg1Id);
+ writer.returnFromIC();
+
+ trackAttached("MathImul");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathFloor(HandleFunction callee) {
+ // Need one (number) argument.
+ if (argc_ != 1 || !args_[0].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check if the result fits in int32.
+ double res = math_floor_impl(args_[0].toNumber());
+ int32_t unused;
+ bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'floor' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId argumentId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+
+ if (args_[0].isInt32()) {
+ MOZ_ASSERT(resultIsInt32);
+
+ // Use an indirect truncation to inform the optimizer it needs to preserve
+ // a bailout when the input can't be represented as an int32, even if the
+ // final result is fully truncated.
+ Int32OperandId intId = writer.guardToInt32(argumentId);
+ writer.indirectTruncateInt32Result(intId);
+ } else {
+ NumberOperandId numberId = writer.guardIsNumber(argumentId);
+
+ if (resultIsInt32) {
+ writer.mathFloorToInt32Result(numberId);
+ } else {
+ writer.mathFloorNumberResult(numberId);
+ }
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("MathFloor");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathCeil(HandleFunction callee) {
+ // Need one (number) argument.
+ if (argc_ != 1 || !args_[0].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check if the result fits in int32.
+ double res = math_ceil_impl(args_[0].toNumber());
+ int32_t unused;
+ bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'ceil' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId argumentId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+
+ if (args_[0].isInt32()) {
+ MOZ_ASSERT(resultIsInt32);
+
+ // Use an indirect truncation to inform the optimizer it needs to preserve
+ // a bailout when the input can't be represented as an int32, even if the
+ // final result is fully truncated.
+ Int32OperandId intId = writer.guardToInt32(argumentId);
+ writer.indirectTruncateInt32Result(intId);
+ } else {
+ NumberOperandId numberId = writer.guardIsNumber(argumentId);
+
+ if (resultIsInt32) {
+ writer.mathCeilToInt32Result(numberId);
+ } else {
+ writer.mathCeilNumberResult(numberId);
+ }
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("MathCeil");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathTrunc(HandleFunction callee) {
+ // Need one (number) argument.
+ if (argc_ != 1 || !args_[0].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check if the result fits in int32.
+ double res = math_trunc_impl(args_[0].toNumber());
+ int32_t unused;
+ bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'trunc' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId argumentId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+
+ if (args_[0].isInt32()) {
+ MOZ_ASSERT(resultIsInt32);
+
+ // We don't need an indirect truncation barrier here, because Math.trunc
+ // always truncates, but never rounds its input away from zero.
+ Int32OperandId intId = writer.guardToInt32(argumentId);
+ writer.loadInt32Result(intId);
+ } else {
+ NumberOperandId numberId = writer.guardIsNumber(argumentId);
+
+ if (resultIsInt32) {
+ writer.mathTruncToInt32Result(numberId);
+ } else {
+ writer.mathTruncNumberResult(numberId);
+ }
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("MathTrunc");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathRound(HandleFunction callee) {
+ // Need one (number) argument.
+ if (argc_ != 1 || !args_[0].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check if the result fits in int32.
+ double res = math_round_impl(args_[0].toNumber());
+ int32_t unused;
+ bool resultIsInt32 = mozilla::NumberIsInt32(res, &unused);
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'round' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId argumentId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+
+ if (args_[0].isInt32()) {
+ MOZ_ASSERT(resultIsInt32);
+
+ // Use an indirect truncation to inform the optimizer it needs to preserve
+ // a bailout when the input can't be represented as an int32, even if the
+ // final result is fully truncated.
+ Int32OperandId intId = writer.guardToInt32(argumentId);
+ writer.indirectTruncateInt32Result(intId);
+ } else {
+ NumberOperandId numberId = writer.guardIsNumber(argumentId);
+
+ if (resultIsInt32) {
+ writer.mathRoundToInt32Result(numberId);
+ } else {
+ writer.mathFunctionNumberResult(numberId, UnaryMathFunction::Round);
+ }
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("MathRound");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathSqrt(HandleFunction callee) {
+ // Need one (number) argument.
+ if (argc_ != 1 || !args_[0].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'sqrt' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId argumentId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ NumberOperandId numberId = writer.guardIsNumber(argumentId);
+ writer.mathSqrtNumberResult(numberId);
+ writer.returnFromIC();
+
+ trackAttached("MathSqrt");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathFRound(HandleFunction callee) {
+ // Need one (number) argument.
+ if (argc_ != 1 || !args_[0].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'fround' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId argumentId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ NumberOperandId numberId = writer.guardIsNumber(argumentId);
+ writer.mathFRoundNumberResult(numberId);
+ writer.returnFromIC();
+
+ trackAttached("MathFRound");
+ return AttachDecision::Attach;
+}
+
+static bool CanAttachInt32Pow(const Value& baseVal, const Value& powerVal) {
+ MOZ_ASSERT(baseVal.isInt32() || baseVal.isBoolean());
+ MOZ_ASSERT(powerVal.isInt32() || powerVal.isBoolean());
+
+ int32_t base = baseVal.isInt32() ? baseVal.toInt32() : baseVal.toBoolean();
+ int32_t power =
+ powerVal.isInt32() ? powerVal.toInt32() : powerVal.toBoolean();
+
+ // x^y where y < 0 is most of the time not an int32, except when x is 1 or y
+ // gets large enough. It's hard to determine when exactly y is "large enough",
+ // so we don't use Int32PowResult when x != 1 and y < 0.
+ // Note: it's important for this condition to match the code generated by
+ // MacroAssembler::pow32 to prevent failure loops.
+ if (power < 0) {
+ return base == 1;
+ }
+
+ double res = powi(base, power);
+ int32_t unused;
+ return mozilla::NumberIsInt32(res, &unused);
+}
+
+AttachDecision CallIRGenerator::tryAttachMathPow(HandleFunction callee) {
+ // Need two number arguments.
+ if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'pow' function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId baseId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ValOperandId exponentId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+
+ if (args_[0].isInt32() && args_[1].isInt32() &&
+ CanAttachInt32Pow(args_[0], args_[1])) {
+ Int32OperandId baseInt32Id = writer.guardToInt32(baseId);
+ Int32OperandId exponentInt32Id = writer.guardToInt32(exponentId);
+ writer.int32PowResult(baseInt32Id, exponentInt32Id);
+ } else {
+ NumberOperandId baseNumberId = writer.guardIsNumber(baseId);
+ NumberOperandId exponentNumberId = writer.guardIsNumber(exponentId);
+ writer.doublePowResult(baseNumberId, exponentNumberId);
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("MathPow");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathHypot(HandleFunction callee) {
+ // Only optimize if there are 2-4 arguments.
+ if (argc_ < 2 || argc_ > 4) {
+ return AttachDecision::NoAction;
+ }
+
+ for (size_t i = 0; i < argc_; i++) {
+ if (!args_[i].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'hypot' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId firstId = writer.loadStandardCallArgument(0, argc_);
+ ValOperandId secondId = writer.loadStandardCallArgument(1, argc_);
+
+ NumberOperandId firstNumId = writer.guardIsNumber(firstId);
+ NumberOperandId secondNumId = writer.guardIsNumber(secondId);
+
+ ValOperandId thirdId;
+ ValOperandId fourthId;
+ NumberOperandId thirdNumId;
+ NumberOperandId fourthNumId;
+
+ switch (argc_) {
+ case 2:
+ writer.mathHypot2NumberResult(firstNumId, secondNumId);
+ break;
+ case 3:
+ thirdId = writer.loadStandardCallArgument(2, argc_);
+ thirdNumId = writer.guardIsNumber(thirdId);
+ writer.mathHypot3NumberResult(firstNumId, secondNumId, thirdNumId);
+ break;
+ case 4:
+ thirdId = writer.loadStandardCallArgument(2, argc_);
+ fourthId = writer.loadStandardCallArgument(3, argc_);
+ thirdNumId = writer.guardIsNumber(thirdId);
+ fourthNumId = writer.guardIsNumber(fourthId);
+ writer.mathHypot4NumberResult(firstNumId, secondNumId, thirdNumId,
+ fourthNumId);
+ break;
+ default:
+ MOZ_CRASH("Unexpected number of arguments to hypot function.");
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("MathHypot");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathATan2(HandleFunction callee) {
+ // Requires two numbers as arguments.
+ if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'atan2' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId yId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ValOperandId xId = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+
+ NumberOperandId yNumberId = writer.guardIsNumber(yId);
+ NumberOperandId xNumberId = writer.guardIsNumber(xId);
+
+ writer.mathAtan2NumberResult(yNumberId, xNumberId);
+ writer.returnFromIC();
+
+ trackAttached("MathAtan2");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathMinMax(HandleFunction callee,
+ bool isMax) {
+ // For now only optimize if there are 1-4 arguments.
+ if (argc_ < 1 || argc_ > 4) {
+ return AttachDecision::NoAction;
+ }
+
+ // Ensure all arguments are numbers.
+ bool allInt32 = true;
+ for (size_t i = 0; i < argc_; i++) {
+ if (!args_[i].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+ if (!args_[i].isInt32()) {
+ allInt32 = false;
+ }
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is this Math function.
+ emitNativeCalleeGuard(callee);
+
+ if (allInt32) {
+ ValOperandId valId = writer.loadStandardCallArgument(0, argc_);
+ Int32OperandId resId = writer.guardToInt32(valId);
+ for (size_t i = 1; i < argc_; i++) {
+ ValOperandId argId = writer.loadStandardCallArgument(i, argc_);
+ Int32OperandId argInt32Id = writer.guardToInt32(argId);
+ resId = writer.int32MinMax(isMax, resId, argInt32Id);
+ }
+ writer.loadInt32Result(resId);
+ } else {
+ ValOperandId valId = writer.loadStandardCallArgument(0, argc_);
+ NumberOperandId resId = writer.guardIsNumber(valId);
+ for (size_t i = 1; i < argc_; i++) {
+ ValOperandId argId = writer.loadStandardCallArgument(i, argc_);
+ NumberOperandId argNumId = writer.guardIsNumber(argId);
+ resId = writer.numberMinMax(isMax, resId, argNumId);
+ }
+ writer.loadDoubleResult(resId);
+ }
+
+ writer.returnFromIC();
+
+ trackAttached(isMax ? "MathMax" : "MathMin");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachMathFunction(HandleFunction callee,
+ UnaryMathFunction fun) {
+ // Need one argument.
+ if (argc_ != 1) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!args_[0].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is this Math function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId argumentId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ NumberOperandId numberId = writer.guardIsNumber(argumentId);
+ writer.mathFunctionNumberResult(numberId, fun);
+ writer.returnFromIC();
+
+ trackAttached("MathFunction");
+ return AttachDecision::Attach;
+}
+
+StringOperandId EmitToStringGuard(CacheIRWriter& writer, ValOperandId id,
+ HandleValue v) {
+ if (v.isString()) {
+ return writer.guardToString(id);
+ }
+ if (v.isInt32()) {
+ Int32OperandId intId = writer.guardToInt32(id);
+ return writer.callInt32ToString(intId);
+ }
+ // At this point we are creating an IC that will handle
+ // both Int32 and Double cases.
+ MOZ_ASSERT(v.isNumber());
+ NumberOperandId numId = writer.guardIsNumber(id);
+ return writer.callNumberToString(numId);
+}
+
+AttachDecision CallIRGenerator::tryAttachNumberToString(HandleFunction callee) {
+ // Expecting no arguments, which means base 10.
+ if (argc_ != 0) {
+ return AttachDecision::NoAction;
+ }
+
+ // Ensure |this| is a primitive number value.
+ if (!thisval_.isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'toString' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Initialize the |this| operand.
+ ValOperandId thisValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::This, argc_);
+
+ // Guard on number and convert to string.
+ StringOperandId strId = EmitToStringGuard(writer, thisValId, thisval_);
+
+ // Return the string.
+ writer.loadStringResult(strId);
+ writer.returnFromIC();
+
+ trackAttached("NumberToString");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachReflectGetPrototypeOf(
+ HandleFunction callee) {
+ // Need one argument.
+ if (argc_ != 1) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!args_[0].isObject()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'getPrototypeOf' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId argumentId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(argumentId);
+
+ writer.reflectGetPrototypeOfResult(objId);
+ writer.returnFromIC();
+
+ trackAttached("ReflectGetPrototypeOf");
+ return AttachDecision::Attach;
+}
+
+static bool AtomicsMeetsPreconditions(TypedArrayObject* typedArray,
+ double index) {
+ switch (typedArray->type()) {
+ case Scalar::Int8:
+ case Scalar::Uint8:
+ case Scalar::Int16:
+ case Scalar::Uint16:
+ case Scalar::Int32:
+ case Scalar::Uint32:
+ break;
+
+ case Scalar::BigInt64:
+ case Scalar::BigUint64:
+ // Bug 1638295: Not yet implemented.
+ return false;
+
+ case Scalar::Float32:
+ case Scalar::Float64:
+ case Scalar::Uint8Clamped:
+ // Exclude floating types and Uint8Clamped.
+ return false;
+
+ case Scalar::MaxTypedArrayViewType:
+ case Scalar::Int64:
+ case Scalar::Simd128:
+ MOZ_CRASH("Unsupported TypedArray type");
+ }
+
+ // Bounds check the index argument.
+ int32_t indexInt32;
+ if (!mozilla::NumberEqualsInt32(index, &indexInt32)) {
+ return false;
+ }
+ if (indexInt32 < 0 ||
+ uint32_t(indexInt32) >= typedArray->length().deprecatedGetUint32()) {
+ return false;
+ }
+
+ return true;
+}
+
+AttachDecision CallIRGenerator::tryAttachAtomicsCompareExchange(
+ HandleFunction callee) {
+ if (!JitSupportsAtomics()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Need four arguments.
+ if (argc_ != 4) {
+ return AttachDecision::NoAction;
+ }
+
+ // Arguments: typedArray, index (number), expected, replacement.
+ if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+ if (!args_[1].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+ if (!args_[2].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+ if (!args_[3].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
+ if (!AtomicsMeetsPreconditions(typedArray, args_[1].toNumber())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the `compareExchange` native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(arg0Id);
+ writer.guardShapeForClass(objId, typedArray->shape());
+
+ // Convert index to int32.
+ ValOperandId indexId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
+
+ // Convert expected value to int32.
+ ValOperandId expectedId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
+ Int32OperandId int32ExpectedId = writer.guardToInt32ModUint32(expectedId);
+
+ // Convert replacement value to int32.
+ ValOperandId replacementId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg3, argc_);
+ Int32OperandId int32ReplacementId =
+ writer.guardToInt32ModUint32(replacementId);
+
+ writer.atomicsCompareExchangeResult(objId, int32IndexId, int32ExpectedId,
+ int32ReplacementId, typedArray->type());
+ writer.returnFromIC();
+
+ trackAttached("AtomicsCompareExchange");
+ return AttachDecision::Attach;
+}
+
+bool CallIRGenerator::canAttachAtomicsReadWriteModify() {
+ if (!JitSupportsAtomics()) {
+ return false;
+ }
+
+ // Need three arguments.
+ if (argc_ != 3) {
+ return false;
+ }
+
+ // Arguments: typedArray, index (number), value.
+ if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
+ return false;
+ }
+ if (!args_[1].isNumber()) {
+ return false;
+ }
+ if (!args_[2].isNumber()) {
+ return false;
+ }
+
+ auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
+ if (!AtomicsMeetsPreconditions(typedArray, args_[1].toNumber())) {
+ return false;
+ }
+
+ return true;
+}
+
+CallIRGenerator::AtomicsReadWriteModifyOperands
+CallIRGenerator::emitAtomicsReadWriteModifyOperands(HandleFunction callee) {
+ MOZ_ASSERT(canAttachAtomicsReadWriteModify());
+
+ auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is this Atomics function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(arg0Id);
+ writer.guardShapeForClass(objId, typedArray->shape());
+
+ // Convert index to int32.
+ ValOperandId indexId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
+
+ // Convert value to int32.
+ ValOperandId valueId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
+ Int32OperandId int32ValueId = writer.guardToInt32ModUint32(valueId);
+
+ return {objId, int32IndexId, int32ValueId};
+}
+
+AttachDecision CallIRGenerator::tryAttachAtomicsExchange(
+ HandleFunction callee) {
+ if (!canAttachAtomicsReadWriteModify()) {
+ return AttachDecision::NoAction;
+ }
+
+ auto [objId, int32IndexId, int32ValueId] =
+ emitAtomicsReadWriteModifyOperands(callee);
+
+ auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
+
+ writer.atomicsExchangeResult(objId, int32IndexId, int32ValueId,
+ typedArray->type());
+ writer.returnFromIC();
+
+ trackAttached("AtomicsExchange");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachAtomicsAdd(HandleFunction callee) {
+ if (!canAttachAtomicsReadWriteModify()) {
+ return AttachDecision::NoAction;
+ }
+
+ auto [objId, int32IndexId, int32ValueId] =
+ emitAtomicsReadWriteModifyOperands(callee);
+
+ auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
+
+ writer.atomicsAddResult(objId, int32IndexId, int32ValueId,
+ typedArray->type());
+ writer.returnFromIC();
+
+ trackAttached("AtomicsAdd");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachAtomicsSub(HandleFunction callee) {
+ if (!canAttachAtomicsReadWriteModify()) {
+ return AttachDecision::NoAction;
+ }
+
+ auto [objId, int32IndexId, int32ValueId] =
+ emitAtomicsReadWriteModifyOperands(callee);
+
+ auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
+
+ writer.atomicsSubResult(objId, int32IndexId, int32ValueId,
+ typedArray->type());
+ writer.returnFromIC();
+
+ trackAttached("AtomicsSub");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachAtomicsAnd(HandleFunction callee) {
+ if (!canAttachAtomicsReadWriteModify()) {
+ return AttachDecision::NoAction;
+ }
+
+ auto [objId, int32IndexId, int32ValueId] =
+ emitAtomicsReadWriteModifyOperands(callee);
+
+ auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
+
+ writer.atomicsAndResult(objId, int32IndexId, int32ValueId,
+ typedArray->type());
+ writer.returnFromIC();
+
+ trackAttached("AtomicsAnd");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachAtomicsOr(HandleFunction callee) {
+ if (!canAttachAtomicsReadWriteModify()) {
+ return AttachDecision::NoAction;
+ }
+
+ auto [objId, int32IndexId, int32ValueId] =
+ emitAtomicsReadWriteModifyOperands(callee);
+
+ auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
+
+ writer.atomicsOrResult(objId, int32IndexId, int32ValueId, typedArray->type());
+ writer.returnFromIC();
+
+ trackAttached("AtomicsOr");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachAtomicsXor(HandleFunction callee) {
+ if (!canAttachAtomicsReadWriteModify()) {
+ return AttachDecision::NoAction;
+ }
+
+ auto [objId, int32IndexId, int32ValueId] =
+ emitAtomicsReadWriteModifyOperands(callee);
+
+ auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
+
+ writer.atomicsXorResult(objId, int32IndexId, int32ValueId,
+ typedArray->type());
+ writer.returnFromIC();
+
+ trackAttached("AtomicsXor");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachAtomicsLoad(HandleFunction callee) {
+ if (!JitSupportsAtomics()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Need two arguments.
+ if (argc_ != 2) {
+ return AttachDecision::NoAction;
+ }
+
+ // Arguments: typedArray, index (number).
+ if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+ if (!args_[1].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
+ if (!AtomicsMeetsPreconditions(typedArray, args_[1].toNumber())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the `load` native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(arg0Id);
+ writer.guardShapeForClass(objId, typedArray->shape());
+
+ // Convert index to int32.
+ ValOperandId indexId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
+
+ writer.atomicsLoadResult(objId, int32IndexId, typedArray->type());
+ writer.returnFromIC();
+
+ trackAttached("AtomicsLoad");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachAtomicsStore(HandleFunction callee) {
+ if (!JitSupportsAtomics()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Need three arguments.
+ if (argc_ != 3) {
+ return AttachDecision::NoAction;
+ }
+
+ // Atomics.store() is annoying because it returns the result of converting the
+ // value by ToInteger(), not the input value, nor the result of converting the
+ // value by ToInt32(). It is especially annoying because almost nobody uses
+ // the result value.
+ //
+ // As an expedient compromise, therefore, we inline only if the result is
+ // obviously unused or if the argument is already Int32 and thus requires no
+ // conversion.
+
+ // Arguments: typedArray, index (number), value.
+ if (!args_[0].isObject() || !args_[0].toObject().is<TypedArrayObject>()) {
+ return AttachDecision::NoAction;
+ }
+ if (!args_[1].isNumber()) {
+ return AttachDecision::NoAction;
+ }
+ if (op_ == JSOp::CallIgnoresRv ? !args_[2].isNumber() : !args_[2].isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ auto* typedArray = &args_[0].toObject().as<TypedArrayObject>();
+ if (!AtomicsMeetsPreconditions(typedArray, args_[1].toNumber())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the `store` native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objId = writer.guardToObject(arg0Id);
+ writer.guardShapeForClass(objId, typedArray->shape());
+
+ // Convert index to int32.
+ ValOperandId indexId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ Int32OperandId int32IndexId = writer.guardToInt32Index(indexId);
+
+ // Ensure value is int32.
+ ValOperandId valueId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
+ Int32OperandId int32ValueId;
+ if (op_ == JSOp::CallIgnoresRv) {
+ int32ValueId = writer.guardToInt32ModUint32(valueId);
+ } else {
+ int32ValueId = writer.guardToInt32(valueId);
+ }
+
+ writer.atomicsStoreResult(objId, int32IndexId, int32ValueId,
+ typedArray->type());
+ writer.returnFromIC();
+
+ trackAttached("AtomicsStore");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachAtomicsIsLockFree(
+ HandleFunction callee) {
+ // Need one argument.
+ if (argc_ != 1) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!args_[0].isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the `isLockFree` native function.
+ emitNativeCalleeGuard(callee);
+
+ // Ensure value is int32.
+ ValOperandId valueId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ Int32OperandId int32ValueId = writer.guardToInt32(valueId);
+
+ writer.atomicsIsLockFreeResult(int32ValueId);
+ writer.returnFromIC();
+
+ trackAttached("AtomicsIsLockFree");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachBoolean(HandleFunction callee) {
+ // Need zero or one argument.
+ if (argc_ > 1) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'Boolean' native function.
+ emitNativeCalleeGuard(callee);
+
+ if (argc_ == 0) {
+ writer.loadBooleanResult(false);
+ } else {
+ ValOperandId valId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+
+ writer.loadValueTruthyResult(valId);
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("Boolean");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachBailout(HandleFunction callee) {
+ // Expecting no arguments.
+ if (argc_ != 0) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'bailout' native function.
+ emitNativeCalleeGuard(callee);
+
+ writer.bailout();
+ writer.loadUndefinedResult();
+ writer.returnFromIC();
+
+ trackAttached("Bailout");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachAssertFloat32(HandleFunction callee) {
+ // Expecting two arguments.
+ if (argc_ != 2) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'assertFloat32' native function.
+ emitNativeCalleeGuard(callee);
+
+ // TODO: Warp doesn't yet optimize Float32 (bug 1655773).
+
+ // NOP when not in IonMonkey.
+ writer.loadUndefinedResult();
+ writer.returnFromIC();
+
+ trackAttached("AssertFloat32");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachAssertRecoveredOnBailout(
+ HandleFunction callee) {
+ // Expecting two arguments.
+ if (argc_ != 2) {
+ return AttachDecision::NoAction;
+ }
+
+ // (Fuzzing unsafe) testing function which must be called with a constant
+ // boolean as its second argument.
+ bool mustBeRecovered = args_[1].toBoolean();
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'assertRecoveredOnBailout' native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+
+ writer.assertRecoveredOnBailoutResult(valId, mustBeRecovered);
+ writer.returnFromIC();
+
+ trackAttached("AssertRecoveredOnBailout");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachObjectIs(HandleFunction callee) {
+ // Need two arguments.
+ if (argc_ != 2) {
+ return AttachDecision::NoAction;
+ }
+
+ // TODO(Warp): attach this stub just once to prevent slowdowns for polymorphic
+ // calls.
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the `is` native function.
+ emitNativeCalleeGuard(callee);
+
+ ValOperandId lhsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ValOperandId rhsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+
+ HandleValue lhs = args_[0];
+ HandleValue rhs = args_[1];
+
+ if (lhs.isNumber() && rhs.isNumber() && !(lhs.isInt32() && rhs.isInt32())) {
+ NumberOperandId lhsNumId = writer.guardIsNumber(lhsId);
+ NumberOperandId rhsNumId = writer.guardIsNumber(rhsId);
+ writer.compareDoubleSameValueResult(lhsNumId, rhsNumId);
+ } else if (!SameType(lhs, rhs)) {
+ // Compare tags for strictly different types.
+ ValueTagOperandId lhsTypeId = writer.loadValueTag(lhsId);
+ ValueTagOperandId rhsTypeId = writer.loadValueTag(rhsId);
+ writer.guardTagNotEqual(lhsTypeId, rhsTypeId);
+ writer.loadBooleanResult(false);
+ } else {
+ MOZ_ASSERT(lhs.type() == rhs.type());
+ MOZ_ASSERT(lhs.type() != JS::ValueType::Double);
+
+ switch (lhs.type()) {
+ case JS::ValueType::Int32: {
+ Int32OperandId lhsIntId = writer.guardToInt32(lhsId);
+ Int32OperandId rhsIntId = writer.guardToInt32(rhsId);
+ writer.compareInt32Result(JSOp::StrictEq, lhsIntId, rhsIntId);
+ break;
+ }
+ case JS::ValueType::Boolean: {
+ Int32OperandId lhsIntId = writer.guardBooleanToInt32(lhsId);
+ Int32OperandId rhsIntId = writer.guardBooleanToInt32(rhsId);
+ writer.compareInt32Result(JSOp::StrictEq, lhsIntId, rhsIntId);
+ break;
+ }
+ case JS::ValueType::Undefined: {
+ writer.guardIsUndefined(lhsId);
+ writer.guardIsUndefined(rhsId);
+ writer.loadBooleanResult(true);
+ break;
+ }
+ case JS::ValueType::Null: {
+ writer.guardIsNull(lhsId);
+ writer.guardIsNull(rhsId);
+ writer.loadBooleanResult(true);
+ break;
+ }
+ case JS::ValueType::String: {
+ StringOperandId lhsStrId = writer.guardToString(lhsId);
+ StringOperandId rhsStrId = writer.guardToString(rhsId);
+ writer.compareStringResult(JSOp::StrictEq, lhsStrId, rhsStrId);
+ break;
+ }
+ case JS::ValueType::Symbol: {
+ SymbolOperandId lhsSymId = writer.guardToSymbol(lhsId);
+ SymbolOperandId rhsSymId = writer.guardToSymbol(rhsId);
+ writer.compareSymbolResult(JSOp::StrictEq, lhsSymId, rhsSymId);
+ break;
+ }
+ case JS::ValueType::BigInt: {
+ BigIntOperandId lhsBigIntId = writer.guardToBigInt(lhsId);
+ BigIntOperandId rhsBigIntId = writer.guardToBigInt(rhsId);
+ writer.compareBigIntResult(JSOp::StrictEq, lhsBigIntId, rhsBigIntId);
+ break;
+ }
+ case JS::ValueType::Object: {
+ ObjOperandId lhsObjId = writer.guardToObject(lhsId);
+ ObjOperandId rhsObjId = writer.guardToObject(rhsId);
+ writer.compareObjectResult(JSOp::StrictEq, lhsObjId, rhsObjId);
+ break;
+ }
+
+ case JS::ValueType::Double:
+ case JS::ValueType::Magic:
+ case JS::ValueType::PrivateGCThing:
+ MOZ_CRASH("Unexpected type");
+ }
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("ObjectIs");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachObjectIsPrototypeOf(
+ HandleFunction callee) {
+ // Ensure |this| is an object.
+ if (!thisval_.isObject()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Need a single argument.
+ if (argc_ != 1) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the `isPrototypeOf` native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard that |this| is an object.
+ ValOperandId thisValId =
+ writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId);
+ ObjOperandId thisObjId = writer.guardToObject(thisValId);
+
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+
+ writer.loadInstanceOfObjectResult(argId, thisObjId);
+ writer.returnFromIC();
+
+ trackAttached("ObjectIsPrototypeOf");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachObjectToString(HandleFunction callee) {
+ // Expecting no arguments.
+ if (argc_ != 0) {
+ return AttachDecision::NoAction;
+ }
+
+ // Ensure |this| is an object.
+ if (!thisval_.isObject()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Don't attach if the object has @@toStringTag or is a proxy.
+ if (!ObjectClassToString(cx_, &thisval_.toObject())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'toString' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard that |this| is an object.
+ ValOperandId thisValId =
+ writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId);
+ ObjOperandId thisObjId = writer.guardToObject(thisValId);
+
+ writer.objectToStringResult(thisObjId);
+ writer.returnFromIC();
+
+ trackAttached("ObjectToString");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachBigIntAsIntN(HandleFunction callee) {
+ // Need two arguments (Int32, BigInt).
+ if (argc_ != 2 || !args_[0].isInt32() || !args_[1].isBigInt()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Negative bits throws an error.
+ if (args_[0].toInt32() < 0) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'BigInt.asIntN' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Convert bits to int32.
+ ValOperandId bitsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ Int32OperandId int32BitsId = writer.guardToInt32Index(bitsId);
+
+ // Number of bits mustn't be negative.
+ writer.guardInt32IsNonNegative(int32BitsId);
+
+ ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ BigIntOperandId bigIntId = writer.guardToBigInt(arg1Id);
+
+ writer.bigIntAsIntNResult(int32BitsId, bigIntId);
+ writer.returnFromIC();
+
+ trackAttached("BigIntAsIntN");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachBigIntAsUintN(HandleFunction callee) {
+ // Need two arguments (Int32, BigInt).
+ if (argc_ != 2 || !args_[0].isInt32() || !args_[1].isBigInt()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Negative bits throws an error.
+ if (args_[0].toInt32() < 0) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'BigInt.asUintN' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Convert bits to int32.
+ ValOperandId bitsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ Int32OperandId int32BitsId = writer.guardToInt32Index(bitsId);
+
+ // Number of bits mustn't be negative.
+ writer.guardInt32IsNonNegative(int32BitsId);
+
+ ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ BigIntOperandId bigIntId = writer.guardToBigInt(arg1Id);
+
+ writer.bigIntAsUintNResult(int32BitsId, bigIntId);
+ writer.returnFromIC();
+
+ trackAttached("BigIntAsUintN");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachFunCall(HandleFunction callee) {
+ if (!callee->isNativeWithoutJitEntry() || callee->native() != fun_call) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!thisval_.isObject() || !thisval_.toObject().is<JSFunction>()) {
+ return AttachDecision::NoAction;
+ }
+ RootedFunction target(cx_, &thisval_.toObject().as<JSFunction>());
+
+ bool isScripted = target->hasJitEntry();
+ MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry());
+
+ if (target->isClassConstructor()) {
+ return AttachDecision::NoAction;
+ }
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard that callee is the |fun_call| native function.
+ ValOperandId calleeValId =
+ writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId);
+ ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
+ writer.guardSpecificFunction(calleeObjId, callee);
+
+ // Guard that |this| is an object.
+ ValOperandId thisValId =
+ writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId);
+ ObjOperandId thisObjId = writer.guardToObject(thisValId);
+
+ if (mode_ == ICState::Mode::Specialized) {
+ // Ensure that |this| is the expected target function.
+ emitCalleeGuard(thisObjId, target);
+
+ CallFlags targetFlags(CallFlags::FunCall);
+ if (isScripted) {
+ writer.callScriptedFunction(thisObjId, argcId, targetFlags);
+ } else {
+ writer.callNativeFunction(thisObjId, argcId, op_, target, targetFlags);
+ }
+ } else {
+ // Guard that |this| is a function.
+ writer.guardClass(thisObjId, GuardClassKind::JSFunction);
+
+ // Guard that function is not a class constructor.
+ writer.guardNotClassConstructor(thisObjId);
+
+ CallFlags targetFlags(CallFlags::FunCall);
+ if (isScripted) {
+ writer.guardFunctionHasJitEntry(thisObjId, /*isConstructing =*/false);
+ writer.callScriptedFunction(thisObjId, argcId, targetFlags);
+ } else {
+ writer.guardFunctionHasNoJitEntry(thisObjId);
+ writer.callAnyNativeFunction(thisObjId, argcId, targetFlags);
+ }
+ }
+
+ writer.returnFromIC();
+
+ if (isScripted) {
+ trackAttached("Scripted fun_call");
+ } else {
+ trackAttached("Native fun_call");
+ }
+
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachIsTypedArray(HandleFunction callee,
+ bool isPossiblyWrapped) {
+ // Self-hosted code calls this with a single object argument.
+ MOZ_ASSERT(argc_ == 1);
+ MOZ_ASSERT(args_[0].isObject());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objArgId = writer.guardToObject(argId);
+ writer.isTypedArrayResult(objArgId, isPossiblyWrapped);
+ writer.returnFromIC();
+
+ trackAttached(isPossiblyWrapped ? "IsPossiblyWrappedTypedArray"
+ : "IsTypedArray");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachIsTypedArrayConstructor(
+ HandleFunction callee) {
+ // Self-hosted code calls this with a single object argument.
+ MOZ_ASSERT(argc_ == 1);
+ MOZ_ASSERT(args_[0].isObject());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objArgId = writer.guardToObject(argId);
+ writer.isTypedArrayConstructorResult(objArgId);
+ writer.returnFromIC();
+
+ trackAttached("IsTypedArrayConstructor");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachTypedArrayByteOffset(
+ HandleFunction callee) {
+ // Self-hosted code calls this with a single TypedArrayObject argument.
+ MOZ_ASSERT(argc_ == 1);
+ MOZ_ASSERT(args_[0].isObject());
+ MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objArgId = writer.guardToObject(argId);
+ writer.typedArrayByteOffsetResult(objArgId);
+ writer.returnFromIC();
+
+ trackAttached("TypedArrayByteOffset");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachTypedArrayElementShift(
+ HandleFunction callee) {
+ // Self-hosted code calls this with a single TypedArrayObject argument.
+ MOZ_ASSERT(argc_ == 1);
+ MOZ_ASSERT(args_[0].isObject());
+ MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objArgId = writer.guardToObject(argId);
+ writer.typedArrayElementShiftResult(objArgId);
+ writer.returnFromIC();
+
+ trackAttached("TypedArrayElementShift");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachTypedArrayLength(
+ HandleFunction callee, bool isPossiblyWrapped) {
+ // Self-hosted code calls this with a single, possibly wrapped,
+ // TypedArrayObject argument.
+ MOZ_ASSERT(argc_ == 1);
+ MOZ_ASSERT(args_[0].isObject());
+
+ // Only optimize when the object isn't a wrapper.
+ if (isPossiblyWrapped && IsWrapper(&args_[0].toObject())) {
+ return AttachDecision::NoAction;
+ }
+
+ MOZ_ASSERT(args_[0].toObject().is<TypedArrayObject>());
+
+ // For now only optimize when the result fits in an int32.
+ auto* tarr = &args_[0].toObject().as<TypedArrayObject>();
+ if (tarr->length().get() > INT32_MAX) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objArgId = writer.guardToObject(argId);
+
+ if (isPossiblyWrapped) {
+ writer.guardIsNotProxy(objArgId);
+ }
+
+ writer.loadTypedArrayLengthResult(objArgId);
+ writer.returnFromIC();
+
+ trackAttached("TypedArrayLength");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachArrayBufferByteLength(
+ HandleFunction callee, bool isPossiblyWrapped) {
+ // Self-hosted code calls this with a single, possibly wrapped,
+ // ArrayBufferObject argument.
+ MOZ_ASSERT(argc_ == 1);
+ MOZ_ASSERT(args_[0].isObject());
+
+ // Only optimize when the object isn't a wrapper.
+ if (isPossiblyWrapped && IsWrapper(&args_[0].toObject())) {
+ return AttachDecision::NoAction;
+ }
+
+ MOZ_ASSERT(args_[0].toObject().is<ArrayBufferObject>());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objArgId = writer.guardToObject(argId);
+
+ if (isPossiblyWrapped) {
+ writer.guardIsNotProxy(objArgId);
+ }
+
+ writer.loadArrayBufferByteLengthInt32Result(objArgId);
+ writer.returnFromIC();
+
+ trackAttached("ArrayBufferByteLength");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachIsConstructing(HandleFunction callee) {
+ // Self-hosted code calls this with no arguments in function scripts.
+ MOZ_ASSERT(argc_ == 0);
+ MOZ_ASSERT(script_->isFunction());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ writer.frameIsConstructingResult();
+ writer.returnFromIC();
+
+ trackAttached("IsConstructing");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachGetNextMapSetEntryForIterator(
+ HandleFunction callee, bool isMap) {
+ // Self-hosted code calls this with two objects.
+ MOZ_ASSERT(argc_ == 2);
+ if (isMap) {
+ MOZ_ASSERT(args_[0].toObject().is<MapIteratorObject>());
+ } else {
+ MOZ_ASSERT(args_[0].toObject().is<SetIteratorObject>());
+ }
+ MOZ_ASSERT(args_[1].toObject().is<ArrayObject>());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId iterId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objIterId = writer.guardToObject(iterId);
+
+ ValOperandId resultArrId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ ObjOperandId objResultArrId = writer.guardToObject(resultArrId);
+
+ writer.getNextMapSetEntryForIteratorResult(objIterId, objResultArrId, isMap);
+ writer.returnFromIC();
+
+ trackAttached("GetNextMapSetEntryForIterator");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachFinishBoundFunctionInit(
+ HandleFunction callee) {
+ // Self-hosted code calls this with (boundFunction, targetObj, argCount)
+ // arguments.
+ MOZ_ASSERT(argc_ == 3);
+ MOZ_ASSERT(args_[0].toObject().is<JSFunction>());
+ MOZ_ASSERT(args_[1].isObject());
+ MOZ_ASSERT(args_[2].isInt32());
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ValOperandId boundId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ ObjOperandId objBoundId = writer.guardToObject(boundId);
+
+ ValOperandId targetId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ ObjOperandId objTargetId = writer.guardToObject(targetId);
+
+ ValOperandId argCountId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_);
+ Int32OperandId int32ArgCountId = writer.guardToInt32(argCountId);
+
+ writer.finishBoundFunctionInitResult(objBoundId, objTargetId,
+ int32ArgCountId);
+ writer.returnFromIC();
+
+ trackAttached("FinishBoundFunctionInit");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachNewArrayIterator(
+ HandleFunction callee) {
+ // Self-hosted code calls this without any arguments
+ MOZ_ASSERT(argc_ == 0);
+
+ JSObject* templateObj = NewArrayIteratorTemplate(cx_);
+ if (!templateObj) {
+ cx_->recoverFromOutOfMemory();
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ writer.newArrayIteratorResult(templateObj);
+ writer.returnFromIC();
+
+ trackAttached("NewArrayIterator");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachNewStringIterator(
+ HandleFunction callee) {
+ // Self-hosted code calls this without any arguments
+ MOZ_ASSERT(argc_ == 0);
+
+ JSObject* templateObj = NewStringIteratorTemplate(cx_);
+ if (!templateObj) {
+ cx_->recoverFromOutOfMemory();
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ writer.newStringIteratorResult(templateObj);
+ writer.returnFromIC();
+
+ trackAttached("NewStringIterator");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachNewRegExpStringIterator(
+ HandleFunction callee) {
+ // Self-hosted code calls this without any arguments
+ MOZ_ASSERT(argc_ == 0);
+
+ JSObject* templateObj = NewRegExpStringIteratorTemplate(cx_);
+ if (!templateObj) {
+ cx_->recoverFromOutOfMemory();
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ writer.newRegExpStringIteratorResult(templateObj);
+ writer.returnFromIC();
+
+ trackAttached("NewRegExpStringIterator");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachArrayIteratorPrototypeOptimizable(
+ HandleFunction callee) {
+ // Self-hosted code calls this without any arguments
+ MOZ_ASSERT(argc_ == 0);
+
+ // TODO(Warp): attach this stub just once to prevent slowdowns for polymorphic
+ // calls.
+
+ NativeObject* arrayIteratorProto;
+ uint32_t slot;
+ JSFunction* nextFun;
+ if (!IsArrayIteratorPrototypeOptimizable(cx_, &arrayIteratorProto, &slot,
+ &nextFun)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Note: we don't need to call emitNativeCalleeGuard for intrinsics.
+
+ ObjOperandId protoId = writer.loadObject(arrayIteratorProto);
+ ObjOperandId nextId = writer.loadObject(nextFun);
+
+ writer.guardShape(protoId, arrayIteratorProto->lastProperty());
+
+ // Ensure that proto[slot] == nextFun.
+ writer.guardDynamicSlotIsSpecificObject(protoId, nextId, slot);
+ writer.loadBooleanResult(true);
+ writer.returnFromIC();
+
+ trackAttached("ArrayIteratorPrototypeOptimizable");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachObjectCreate(HandleFunction callee) {
+ // Need a single object-or-null argument.
+ if (argc_ != 1 || !args_[0].isObjectOrNull()) {
+ return AttachDecision::NoAction;
+ }
+
+ // TODO(Warp): attach this stub just once to prevent slowdowns for
+ // polymorphic calls.
+
+ RootedObject proto(cx_, args_[0].toObjectOrNull());
+ JSObject* templateObj = ObjectCreateImpl(cx_, proto, TenuredObject);
+ if (!templateObj) {
+ cx_->recoverFromOutOfMemory();
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee is the 'create' native function.
+ emitNativeCalleeGuard(callee);
+
+ // Guard on the proto argument.
+ ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_);
+ if (proto) {
+ ObjOperandId protoId = writer.guardToObject(argId);
+ writer.guardSpecificObject(protoId, proto);
+ } else {
+ writer.guardIsNull(argId);
+ }
+
+ writer.objectCreateResult(templateObj);
+ writer.returnFromIC();
+
+ trackAttached("ObjectCreate");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachArrayConstructor(
+ HandleFunction callee) {
+ // Only optimize the |Array()| and |Array(n)| cases (with or without |new|)
+ // for now. Note that self-hosted code calls this without |new| via std_Array.
+ if (argc_ > 1) {
+ return AttachDecision::NoAction;
+ }
+ if (argc_ == 1 && !args_[0].isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ int32_t length = (argc_ == 1) ? args_[0].toInt32() : 0;
+ if (length < 0 || uint32_t(length) > ArrayObject::EagerAllocationMaxLength) {
+ return AttachDecision::NoAction;
+ }
+
+ // We allow inlining this function across realms so make sure the template
+ // object is allocated in that realm. See CanInlineNativeCrossRealm.
+ JSObject* templateObj;
+ {
+ AutoRealm ar(cx_, callee);
+ templateObj = NewDenseFullyAllocatedArray(
+ cx_, length, /* proto = */ nullptr, TenuredObject);
+ if (!templateObj) {
+ cx_->recoverFromOutOfMemory();
+ return AttachDecision::NoAction;
+ }
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee and newTarget (if constructing) are this Array constructor
+ // function.
+ emitNativeCalleeGuard(callee);
+
+ CallFlags flags(IsConstructPC(pc_), IsSpreadPC(pc_));
+
+ Int32OperandId lengthId;
+ if (argc_ == 1) {
+ ValOperandId arg0Id =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags);
+ lengthId = writer.guardToInt32(arg0Id);
+ } else {
+ MOZ_ASSERT(argc_ == 0);
+ lengthId = writer.loadInt32Constant(0);
+ }
+
+ writer.newArrayFromLengthResult(templateObj, lengthId);
+ writer.returnFromIC();
+
+ trackAttached("ArrayConstructor");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachTypedArrayConstructor(
+ HandleFunction callee) {
+ MOZ_ASSERT(IsConstructPC(pc_));
+
+ if (argc_ == 0 || argc_ > 3) {
+ return AttachDecision::NoAction;
+ }
+
+ // TODO(Warp); attach this stub just once to prevent slowdowns for
+ // polymorphic calls.
+
+ // The first argument must be int32 or a non-proxy object.
+ if (!args_[0].isInt32() && !args_[0].isObject()) {
+ return AttachDecision::NoAction;
+ }
+ if (args_[0].isObject() && args_[0].toObject().is<ProxyObject>()) {
+ return AttachDecision::NoAction;
+ }
+
+#ifdef JS_CODEGEN_X86
+ // Unfortunately NewTypedArrayFromArrayBufferResult needs more registers than
+ // we can easily support on 32-bit x86 for now.
+ if (args_[0].isObject() &&
+ args_[0].toObject().is<ArrayBufferObjectMaybeShared>()) {
+ return AttachDecision::NoAction;
+ }
+#endif
+
+ RootedObject templateObj(cx_);
+ if (!TypedArrayObject::GetTemplateObjectForNative(cx_, callee->native(),
+ args_, &templateObj)) {
+ cx_->recoverFromOutOfMemory();
+ return AttachDecision::NoAction;
+ }
+
+ if (!templateObj) {
+ // This can happen for large length values.
+ MOZ_ASSERT(args_[0].isInt32());
+ return AttachDecision::NoAction;
+ }
+
+ // Initialize the input operand.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard callee and newTarget are this TypedArray constructor function.
+ emitNativeCalleeGuard(callee);
+
+ CallFlags flags(IsConstructPC(pc_), IsSpreadPC(pc_));
+ ValOperandId arg0Id =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags);
+
+ if (args_[0].isInt32()) {
+ // From length.
+ Int32OperandId lengthId = writer.guardToInt32(arg0Id);
+ writer.newTypedArrayFromLengthResult(templateObj, lengthId);
+ } else {
+ JSObject* obj = &args_[0].toObject();
+ ObjOperandId objId = writer.guardToObject(arg0Id);
+
+ if (obj->is<ArrayBufferObjectMaybeShared>()) {
+ // From ArrayBuffer.
+ if (obj->is<ArrayBufferObject>()) {
+ writer.guardClass(objId, GuardClassKind::ArrayBuffer);
+ } else {
+ MOZ_ASSERT(obj->is<SharedArrayBufferObject>());
+ writer.guardClass(objId, GuardClassKind::SharedArrayBuffer);
+ }
+ ValOperandId byteOffsetId;
+ if (argc_ > 1) {
+ byteOffsetId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_, flags);
+ } else {
+ byteOffsetId = writer.loadUndefined();
+ }
+ ValOperandId lengthId;
+ if (argc_ > 2) {
+ lengthId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_, flags);
+ } else {
+ lengthId = writer.loadUndefined();
+ }
+ writer.newTypedArrayFromArrayBufferResult(templateObj, objId,
+ byteOffsetId, lengthId);
+ } else {
+ // From Array-like.
+ writer.guardIsNotArrayBufferMaybeShared(objId);
+ writer.guardIsNotProxy(objId);
+ writer.newTypedArrayFromArrayResult(templateObj, objId);
+ }
+ }
+
+ writer.returnFromIC();
+
+ trackAttached("TypedArrayConstructor");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachFunApply(HandleFunction calleeFunc) {
+ if (!calleeFunc->isNativeWithoutJitEntry() ||
+ calleeFunc->native() != fun_apply) {
+ return AttachDecision::NoAction;
+ }
+
+ if (argc_ != 2) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!thisval_.isObject() || !thisval_.toObject().is<JSFunction>()) {
+ return AttachDecision::NoAction;
+ }
+ RootedFunction target(cx_, &thisval_.toObject().as<JSFunction>());
+
+ bool isScripted = target->hasJitEntry();
+ MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry());
+
+ if (target->isClassConstructor()) {
+ return AttachDecision::NoAction;
+ }
+
+ CallFlags::ArgFormat format = CallFlags::Standard;
+ if (args_[1].isMagic(JS_OPTIMIZED_ARGUMENTS) && !script_->needsArgsObj()) {
+ format = CallFlags::FunApplyArgs;
+ } else if (args_[1].isObject() && args_[1].toObject().is<ArrayObject>() &&
+ args_[1].toObject().as<ArrayObject>().length() <=
+ JIT_ARGS_LENGTH_MAX) {
+ format = CallFlags::FunApplyArray;
+ } else {
+ return AttachDecision::NoAction;
+ }
+
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Guard that callee is the |fun_apply| native function.
+ ValOperandId calleeValId =
+ writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId);
+ ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
+ writer.guardSpecificFunction(calleeObjId, calleeFunc);
+
+ // Guard that |this| is an object.
+ ValOperandId thisValId =
+ writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId);
+ ObjOperandId thisObjId = writer.guardToObject(thisValId);
+
+ ValOperandId argValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_);
+ if (format == CallFlags::FunApplyArgs) {
+ writer.guardMagicValue(argValId, JS_OPTIMIZED_ARGUMENTS);
+ writer.guardFrameHasNoArgumentsObject();
+ } else {
+ MOZ_ASSERT(format == CallFlags::FunApplyArray);
+ ObjOperandId argObjId = writer.guardToObject(argValId);
+ writer.guardClass(argObjId, GuardClassKind::Array);
+ writer.guardArrayIsPacked(argObjId);
+ }
+
+ CallFlags targetFlags(format);
+ if (mode_ == ICState::Mode::Specialized) {
+ // Ensure that |this| is the expected target function.
+ emitCalleeGuard(thisObjId, target);
+
+ if (isScripted) {
+ writer.callScriptedFunction(thisObjId, argcId, targetFlags);
+ } else {
+ writer.callNativeFunction(thisObjId, argcId, op_, target, targetFlags);
+ }
+ } else {
+ // Guard that |this| is a function.
+ writer.guardClass(thisObjId, GuardClassKind::JSFunction);
+
+ // Guard that function is not a class constructor.
+ writer.guardNotClassConstructor(thisObjId);
+
+ if (isScripted) {
+ // Guard that function is scripted.
+ writer.guardFunctionHasJitEntry(thisObjId, /*constructing =*/false);
+ writer.callScriptedFunction(thisObjId, argcId, targetFlags);
+ } else {
+ // Guard that function is native.
+ writer.guardFunctionHasNoJitEntry(thisObjId);
+ writer.callAnyNativeFunction(thisObjId, argcId, targetFlags);
+ }
+ }
+
+ writer.returnFromIC();
+
+ if (isScripted) {
+ trackAttached("Scripted fun_apply");
+ } else {
+ trackAttached("Native fun_apply");
+ }
+
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachWasmCall(HandleFunction calleeFunc) {
+ // Try to optimize calls into Wasm code by emitting the CallWasmFunction
+ // CacheIR op. Baseline ICs currently treat this as a CallScriptedFunction op
+ // (calling Wasm's JitEntry stub) but Warp transpiles it to a more direct call
+ // into Wasm code.
+ //
+ // Note: some code refers to these optimized Wasm calls as "inlined" calls.
+
+ MOZ_ASSERT(calleeFunc->isWasmWithJitEntry());
+
+ if (!JitOptions.enableWasmIonFastCalls) {
+ return AttachDecision::NoAction;
+ }
+ if (!isFirstStub_) {
+ return AttachDecision::NoAction;
+ }
+ if (JSOp(*pc_) != JSOp::Call && JSOp(*pc_) != JSOp::CallIgnoresRv) {
+ return AttachDecision::NoAction;
+ }
+ if (cx_->realm() != calleeFunc->realm()) {
+ return AttachDecision::NoAction;
+ }
+
+ wasm::Instance& inst = wasm::ExportedFunctionToInstance(calleeFunc);
+ uint32_t funcIndex = inst.code().getFuncIndex(calleeFunc);
+
+ auto bestTier = inst.code().bestTier();
+ const wasm::FuncExport& funcExport =
+ inst.metadata(bestTier).lookupFuncExport(funcIndex);
+ const wasm::FuncType& sig = funcExport.funcType();
+
+ MOZ_ASSERT(!IsInsideNursery(inst.object()));
+
+ MOZ_ASSERT(!sig.temporarilyUnsupportedReftypeForInlineEntry(),
+ "Function should not have a Wasm JitEntry");
+
+ // If there are too many arguments, don't optimize (we won't be able to store
+ // the arguments in the LIR node).
+ static_assert(wasm::MaxArgsForJitInlineCall <= ArgumentKindArgIndexLimit);
+ if (sig.args().length() > wasm::MaxArgsForJitInlineCall) {
+ return AttachDecision::NoAction;
+ }
+
+ // If there are too many results, don't optimize as Warp currently doesn't
+ // have code to handle this.
+ if (sig.results().length() > wasm::MaxResultsForJitInlineCall) {
+ return AttachDecision::NoAction;
+ }
+
+ // Bug 1631656 - Don't try to optimize with I64 args on 32-bit platforms
+ // because it is more difficult (because it requires multiple LIR arguments
+ // per I64).
+ //
+ // Bug 1631650 - On 64-bit platforms, we also give up optimizing for I64 args
+ // spilled to the stack because it causes problems with register allocation.
+#ifdef JS_64BIT
+ constexpr bool optimizeWithI64 = true;
+#else
+ constexpr bool optimizeWithI64 = false;
+#endif
+ ABIArgGenerator abi;
+ for (const auto& valType : sig.args()) {
+ MIRType mirType = ToMIRType(valType);
+ ABIArg abiArg = abi.next(mirType);
+ if (mirType != MIRType::Int64) {
+ continue;
+ }
+ if (!optimizeWithI64 || abiArg.kind() == ABIArg::Stack) {
+ return AttachDecision::NoAction;
+ }
+ }
+
+ // Check that all arguments can be converted to the Wasm type in Warp code
+ // without bailing out.
+ for (size_t i = 0; i < sig.args().length(); i++) {
+ Value argVal = i < argc_ ? args_[i] : UndefinedValue();
+ switch (sig.args()[i].kind()) {
+ case wasm::ValType::I32:
+ case wasm::ValType::F32:
+ case wasm::ValType::F64:
+ if (!argVal.isNumber() && !argVal.isBoolean() &&
+ !argVal.isUndefined()) {
+ return AttachDecision::NoAction;
+ }
+ break;
+ case wasm::ValType::I64:
+ if (!argVal.isBigInt() && !argVal.isBoolean() && !argVal.isString()) {
+ return AttachDecision::NoAction;
+ }
+ break;
+ case wasm::ValType::V128:
+ MOZ_CRASH("Function should not have a Wasm JitEntry");
+ case wasm::ValType::Ref:
+ // All values can be boxed as AnyRef.
+ MOZ_ASSERT(sig.args()[i].refTypeKind() == wasm::RefType::Extern,
+ "Unexpected type for Wasm JitEntry");
+ break;
+ }
+ }
+
+ CallFlags flags(/* isConstructing = */ false, /* isSpread = */ false,
+ /* isSameRealm = */ true);
+
+ // Load argc.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Load the callee and ensure it is an object
+ ValOperandId calleeValId =
+ writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags);
+ ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
+
+ // Ensure the callee is this Wasm function.
+ emitCalleeGuard(calleeObjId, calleeFunc);
+
+ // Guard the argument types.
+ uint32_t guardedArgs = std::min<uint32_t>(sig.args().length(), argc_);
+ for (uint32_t i = 0; i < guardedArgs; i++) {
+ ArgumentKind argKind = ArgumentKindForArgIndex(i);
+ ValOperandId argId = writer.loadArgumentFixedSlot(argKind, argc_, flags);
+ writer.guardWasmArg(argId, sig.args()[i].kind());
+ }
+
+ writer.callWasmFunction(calleeObjId, argcId, flags, &funcExport,
+ inst.object());
+ writer.returnFromIC();
+
+ trackAttached("WasmCall");
+
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachInlinableNative(
+ HandleFunction callee) {
+ MOZ_ASSERT(mode_ == ICState::Mode::Specialized);
+ MOZ_ASSERT(callee->isNativeWithoutJitEntry());
+
+ // Special case functions are only optimized for normal calls.
+ if (op_ != JSOp::Call && op_ != JSOp::New && op_ != JSOp::CallIgnoresRv) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!callee->hasJitInfo() ||
+ callee->jitInfo()->type() != JSJitInfo::InlinableNative) {
+ return AttachDecision::NoAction;
+ }
+
+ InlinableNative native = callee->jitInfo()->inlinableNative;
+
+ // Not all natives can be inlined cross-realm.
+ if (cx_->realm() != callee->realm() && !CanInlineNativeCrossRealm(native)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check for special-cased native constructors.
+ if (op_ == JSOp::New) {
+ // newTarget must match the callee. CacheIR for this is emitted in
+ // emitNativeCalleeGuard.
+ if (callee_ != newTarget_) {
+ return AttachDecision::NoAction;
+ }
+ switch (native) {
+ case InlinableNative::Array:
+ return tryAttachArrayConstructor(callee);
+ case InlinableNative::TypedArrayConstructor:
+ return tryAttachTypedArrayConstructor(callee);
+ case InlinableNative::String:
+ return tryAttachStringConstructor(callee);
+ default:
+ break;
+ }
+ return AttachDecision::NoAction;
+ }
+
+ // Check for special-cased native functions.
+ switch (native) {
+ // Array natives.
+ case InlinableNative::Array:
+ return tryAttachArrayConstructor(callee);
+ case InlinableNative::ArrayPush:
+ return tryAttachArrayPush(callee);
+ case InlinableNative::ArrayPop:
+ case InlinableNative::ArrayShift:
+ return tryAttachArrayPopShift(callee, native);
+ case InlinableNative::ArrayJoin:
+ return tryAttachArrayJoin(callee);
+ case InlinableNative::ArraySlice:
+ return tryAttachArraySlice(callee);
+ case InlinableNative::ArrayIsArray:
+ return tryAttachArrayIsArray(callee);
+
+ // DataView natives.
+ case InlinableNative::DataViewGetInt8:
+ return tryAttachDataViewGet(callee, Scalar::Int8);
+ case InlinableNative::DataViewGetUint8:
+ return tryAttachDataViewGet(callee, Scalar::Uint8);
+ case InlinableNative::DataViewGetInt16:
+ return tryAttachDataViewGet(callee, Scalar::Int16);
+ case InlinableNative::DataViewGetUint16:
+ return tryAttachDataViewGet(callee, Scalar::Uint16);
+ case InlinableNative::DataViewGetInt32:
+ return tryAttachDataViewGet(callee, Scalar::Int32);
+ case InlinableNative::DataViewGetUint32:
+ return tryAttachDataViewGet(callee, Scalar::Uint32);
+ case InlinableNative::DataViewGetFloat32:
+ return tryAttachDataViewGet(callee, Scalar::Float32);
+ case InlinableNative::DataViewGetFloat64:
+ return tryAttachDataViewGet(callee, Scalar::Float64);
+ case InlinableNative::DataViewGetBigInt64:
+ return tryAttachDataViewGet(callee, Scalar::BigInt64);
+ case InlinableNative::DataViewGetBigUint64:
+ return tryAttachDataViewGet(callee, Scalar::BigUint64);
+ case InlinableNative::DataViewSetInt8:
+ return tryAttachDataViewSet(callee, Scalar::Int8);
+ case InlinableNative::DataViewSetUint8:
+ return tryAttachDataViewSet(callee, Scalar::Uint8);
+ case InlinableNative::DataViewSetInt16:
+ return tryAttachDataViewSet(callee, Scalar::Int16);
+ case InlinableNative::DataViewSetUint16:
+ return tryAttachDataViewSet(callee, Scalar::Uint16);
+ case InlinableNative::DataViewSetInt32:
+ return tryAttachDataViewSet(callee, Scalar::Int32);
+ case InlinableNative::DataViewSetUint32:
+ return tryAttachDataViewSet(callee, Scalar::Uint32);
+ case InlinableNative::DataViewSetFloat32:
+ return tryAttachDataViewSet(callee, Scalar::Float32);
+ case InlinableNative::DataViewSetFloat64:
+ return tryAttachDataViewSet(callee, Scalar::Float64);
+ case InlinableNative::DataViewSetBigInt64:
+ return tryAttachDataViewSet(callee, Scalar::BigInt64);
+ case InlinableNative::DataViewSetBigUint64:
+ return tryAttachDataViewSet(callee, Scalar::BigUint64);
+
+ // Intl natives.
+ case InlinableNative::IntlGuardToCollator:
+ case InlinableNative::IntlGuardToDateTimeFormat:
+ case InlinableNative::IntlGuardToDisplayNames:
+ case InlinableNative::IntlGuardToListFormat:
+ case InlinableNative::IntlGuardToNumberFormat:
+ case InlinableNative::IntlGuardToPluralRules:
+ case InlinableNative::IntlGuardToRelativeTimeFormat:
+ return tryAttachGuardToClass(callee, native);
+
+ // Slot intrinsics.
+ case InlinableNative::IntrinsicUnsafeGetReservedSlot:
+ case InlinableNative::IntrinsicUnsafeGetObjectFromReservedSlot:
+ case InlinableNative::IntrinsicUnsafeGetInt32FromReservedSlot:
+ case InlinableNative::IntrinsicUnsafeGetStringFromReservedSlot:
+ case InlinableNative::IntrinsicUnsafeGetBooleanFromReservedSlot:
+ return tryAttachUnsafeGetReservedSlot(callee, native);
+ case InlinableNative::IntrinsicUnsafeSetReservedSlot:
+ return tryAttachUnsafeSetReservedSlot(callee);
+
+ // Intrinsics.
+ case InlinableNative::IntrinsicIsSuspendedGenerator:
+ return tryAttachIsSuspendedGenerator(callee);
+ case InlinableNative::IntrinsicToObject:
+ return tryAttachToObject(callee, native);
+ case InlinableNative::IntrinsicToInteger:
+ return tryAttachToInteger(callee);
+ case InlinableNative::IntrinsicToLength:
+ return tryAttachToLength(callee);
+ case InlinableNative::IntrinsicIsObject:
+ return tryAttachIsObject(callee);
+ case InlinableNative::IntrinsicIsPackedArray:
+ return tryAttachIsPackedArray(callee);
+ case InlinableNative::IntrinsicIsCallable:
+ return tryAttachIsCallable(callee);
+ case InlinableNative::IntrinsicIsConstructor:
+ return tryAttachIsConstructor(callee);
+ case InlinableNative::IntrinsicIsCrossRealmArrayConstructor:
+ return tryAttachIsCrossRealmArrayConstructor(callee);
+ case InlinableNative::IntrinsicGuardToArrayIterator:
+ case InlinableNative::IntrinsicGuardToMapIterator:
+ case InlinableNative::IntrinsicGuardToSetIterator:
+ case InlinableNative::IntrinsicGuardToStringIterator:
+ case InlinableNative::IntrinsicGuardToRegExpStringIterator:
+ case InlinableNative::IntrinsicGuardToWrapForValidIterator:
+ case InlinableNative::IntrinsicGuardToIteratorHelper:
+ case InlinableNative::IntrinsicGuardToAsyncIteratorHelper:
+ return tryAttachGuardToClass(callee, native);
+ case InlinableNative::IntrinsicSubstringKernel:
+ return tryAttachSubstringKernel(callee);
+ case InlinableNative::IntrinsicIsConstructing:
+ return tryAttachIsConstructing(callee);
+ case InlinableNative::IntrinsicFinishBoundFunctionInit:
+ return tryAttachFinishBoundFunctionInit(callee);
+ case InlinableNative::IntrinsicNewArrayIterator:
+ return tryAttachNewArrayIterator(callee);
+ case InlinableNative::IntrinsicNewStringIterator:
+ return tryAttachNewStringIterator(callee);
+ case InlinableNative::IntrinsicNewRegExpStringIterator:
+ return tryAttachNewRegExpStringIterator(callee);
+ case InlinableNative::IntrinsicArrayIteratorPrototypeOptimizable:
+ return tryAttachArrayIteratorPrototypeOptimizable(callee);
+ case InlinableNative::IntrinsicObjectHasPrototype:
+ return tryAttachObjectHasPrototype(callee);
+
+ // RegExp natives.
+ case InlinableNative::IsRegExpObject:
+ return tryAttachHasClass(callee, &RegExpObject::class_,
+ /* isPossiblyWrapped = */ false);
+ case InlinableNative::IsPossiblyWrappedRegExpObject:
+ return tryAttachHasClass(callee, &RegExpObject::class_,
+ /* isPossiblyWrapped = */ true);
+ case InlinableNative::RegExpMatcher:
+ case InlinableNative::RegExpSearcher:
+ case InlinableNative::RegExpTester:
+ return tryAttachRegExpMatcherSearcherTester(callee, native);
+ case InlinableNative::RegExpPrototypeOptimizable:
+ return tryAttachRegExpPrototypeOptimizable(callee);
+ case InlinableNative::RegExpInstanceOptimizable:
+ return tryAttachRegExpInstanceOptimizable(callee);
+ case InlinableNative::GetFirstDollarIndex:
+ return tryAttachGetFirstDollarIndex(callee);
+
+ // String natives.
+ case InlinableNative::String:
+ return tryAttachString(callee);
+ case InlinableNative::StringToString:
+ case InlinableNative::StringValueOf:
+ return tryAttachStringToStringValueOf(callee);
+ case InlinableNative::StringCharCodeAt:
+ return tryAttachStringCharCodeAt(callee);
+ case InlinableNative::StringCharAt:
+ return tryAttachStringCharAt(callee);
+ case InlinableNative::StringFromCharCode:
+ return tryAttachStringFromCharCode(callee);
+ case InlinableNative::StringFromCodePoint:
+ return tryAttachStringFromCodePoint(callee);
+ case InlinableNative::StringToLowerCase:
+ return tryAttachStringToLowerCase(callee);
+ case InlinableNative::StringToUpperCase:
+ return tryAttachStringToUpperCase(callee);
+ case InlinableNative::IntrinsicStringReplaceString:
+ return tryAttachStringReplaceString(callee);
+ case InlinableNative::IntrinsicStringSplitString:
+ return tryAttachStringSplitString(callee);
+
+ // Math natives.
+ case InlinableNative::MathRandom:
+ return tryAttachMathRandom(callee);
+ case InlinableNative::MathAbs:
+ return tryAttachMathAbs(callee);
+ case InlinableNative::MathClz32:
+ return tryAttachMathClz32(callee);
+ case InlinableNative::MathSign:
+ return tryAttachMathSign(callee);
+ case InlinableNative::MathImul:
+ return tryAttachMathImul(callee);
+ case InlinableNative::MathFloor:
+ return tryAttachMathFloor(callee);
+ case InlinableNative::MathCeil:
+ return tryAttachMathCeil(callee);
+ case InlinableNative::MathTrunc:
+ return tryAttachMathTrunc(callee);
+ case InlinableNative::MathRound:
+ return tryAttachMathRound(callee);
+ case InlinableNative::MathSqrt:
+ return tryAttachMathSqrt(callee);
+ case InlinableNative::MathFRound:
+ return tryAttachMathFRound(callee);
+ case InlinableNative::MathHypot:
+ return tryAttachMathHypot(callee);
+ case InlinableNative::MathATan2:
+ return tryAttachMathATan2(callee);
+ case InlinableNative::MathSin:
+ return tryAttachMathFunction(callee, UnaryMathFunction::Sin);
+ case InlinableNative::MathTan:
+ return tryAttachMathFunction(callee, UnaryMathFunction::Tan);
+ case InlinableNative::MathCos:
+ return tryAttachMathFunction(callee, UnaryMathFunction::Cos);
+ case InlinableNative::MathExp:
+ return tryAttachMathFunction(callee, UnaryMathFunction::Exp);
+ case InlinableNative::MathLog:
+ return tryAttachMathFunction(callee, UnaryMathFunction::Log);
+ case InlinableNative::MathASin:
+ return tryAttachMathFunction(callee, UnaryMathFunction::ASin);
+ case InlinableNative::MathATan:
+ return tryAttachMathFunction(callee, UnaryMathFunction::ATan);
+ case InlinableNative::MathACos:
+ return tryAttachMathFunction(callee, UnaryMathFunction::ACos);
+ case InlinableNative::MathLog10:
+ return tryAttachMathFunction(callee, UnaryMathFunction::Log10);
+ case InlinableNative::MathLog2:
+ return tryAttachMathFunction(callee, UnaryMathFunction::Log2);
+ case InlinableNative::MathLog1P:
+ return tryAttachMathFunction(callee, UnaryMathFunction::Log1P);
+ case InlinableNative::MathExpM1:
+ return tryAttachMathFunction(callee, UnaryMathFunction::ExpM1);
+ case InlinableNative::MathCosH:
+ return tryAttachMathFunction(callee, UnaryMathFunction::CosH);
+ case InlinableNative::MathSinH:
+ return tryAttachMathFunction(callee, UnaryMathFunction::SinH);
+ case InlinableNative::MathTanH:
+ return tryAttachMathFunction(callee, UnaryMathFunction::TanH);
+ case InlinableNative::MathACosH:
+ return tryAttachMathFunction(callee, UnaryMathFunction::ACosH);
+ case InlinableNative::MathASinH:
+ return tryAttachMathFunction(callee, UnaryMathFunction::ASinH);
+ case InlinableNative::MathATanH:
+ return tryAttachMathFunction(callee, UnaryMathFunction::ATanH);
+ case InlinableNative::MathCbrt:
+ return tryAttachMathFunction(callee, UnaryMathFunction::Cbrt);
+ case InlinableNative::MathPow:
+ return tryAttachMathPow(callee);
+ case InlinableNative::MathMin:
+ return tryAttachMathMinMax(callee, /* isMax = */ false);
+ case InlinableNative::MathMax:
+ return tryAttachMathMinMax(callee, /* isMax = */ true);
+
+ // Map intrinsics.
+ case InlinableNative::IntrinsicGuardToMapObject:
+ return tryAttachGuardToClass(callee, native);
+ case InlinableNative::IntrinsicGetNextMapEntryForIterator:
+ return tryAttachGetNextMapSetEntryForIterator(callee, /* isMap = */ true);
+
+ // Number natives.
+ case InlinableNative::NumberToString:
+ return tryAttachNumberToString(callee);
+
+ // Object natives.
+ case InlinableNative::Object:
+ return tryAttachToObject(callee, native);
+ case InlinableNative::ObjectCreate:
+ return tryAttachObjectCreate(callee);
+ case InlinableNative::ObjectIs:
+ return tryAttachObjectIs(callee);
+ case InlinableNative::ObjectIsPrototypeOf:
+ return tryAttachObjectIsPrototypeOf(callee);
+ case InlinableNative::ObjectToString:
+ return tryAttachObjectToString(callee);
+
+ // Set intrinsics.
+ case InlinableNative::IntrinsicGuardToSetObject:
+ return tryAttachGuardToClass(callee, native);
+ case InlinableNative::IntrinsicGetNextSetEntryForIterator:
+ return tryAttachGetNextMapSetEntryForIterator(callee,
+ /* isMap = */ false);
+
+ // ArrayBuffer intrinsics.
+ case InlinableNative::IntrinsicGuardToArrayBuffer:
+ return tryAttachGuardToClass(callee, native);
+ case InlinableNative::IntrinsicArrayBufferByteLength:
+ return tryAttachArrayBufferByteLength(callee,
+ /* isPossiblyWrapped = */ false);
+ case InlinableNative::IntrinsicPossiblyWrappedArrayBufferByteLength:
+ return tryAttachArrayBufferByteLength(callee,
+ /* isPossiblyWrapped = */ true);
+
+ // SharedArrayBuffer intrinsics.
+ case InlinableNative::IntrinsicGuardToSharedArrayBuffer:
+ return tryAttachGuardToClass(callee, native);
+
+ // TypedArray intrinsics.
+ case InlinableNative::TypedArrayConstructor:
+ return AttachDecision::NoAction; // Not callable.
+ case InlinableNative::IntrinsicIsTypedArray:
+ return tryAttachIsTypedArray(callee, /* isPossiblyWrapped = */ false);
+ case InlinableNative::IntrinsicIsPossiblyWrappedTypedArray:
+ return tryAttachIsTypedArray(callee, /* isPossiblyWrapped = */ true);
+ case InlinableNative::IntrinsicIsTypedArrayConstructor:
+ return tryAttachIsTypedArrayConstructor(callee);
+ case InlinableNative::IntrinsicTypedArrayByteOffset:
+ return tryAttachTypedArrayByteOffset(callee);
+ case InlinableNative::IntrinsicTypedArrayElementShift:
+ return tryAttachTypedArrayElementShift(callee);
+ case InlinableNative::IntrinsicTypedArrayLength:
+ return tryAttachTypedArrayLength(callee, /* isPossiblyWrapped = */ false);
+ case InlinableNative::IntrinsicPossiblyWrappedTypedArrayLength:
+ return tryAttachTypedArrayLength(callee, /* isPossiblyWrapped = */ true);
+
+ // Reflect natives.
+ case InlinableNative::ReflectGetPrototypeOf:
+ return tryAttachReflectGetPrototypeOf(callee);
+
+ // Atomics intrinsics:
+ case InlinableNative::AtomicsCompareExchange:
+ return tryAttachAtomicsCompareExchange(callee);
+ case InlinableNative::AtomicsExchange:
+ return tryAttachAtomicsExchange(callee);
+ case InlinableNative::AtomicsAdd:
+ return tryAttachAtomicsAdd(callee);
+ case InlinableNative::AtomicsSub:
+ return tryAttachAtomicsSub(callee);
+ case InlinableNative::AtomicsAnd:
+ return tryAttachAtomicsAnd(callee);
+ case InlinableNative::AtomicsOr:
+ return tryAttachAtomicsOr(callee);
+ case InlinableNative::AtomicsXor:
+ return tryAttachAtomicsXor(callee);
+ case InlinableNative::AtomicsLoad:
+ return tryAttachAtomicsLoad(callee);
+ case InlinableNative::AtomicsStore:
+ return tryAttachAtomicsStore(callee);
+ case InlinableNative::AtomicsIsLockFree:
+ return tryAttachAtomicsIsLockFree(callee);
+
+ // BigInt natives.
+ case InlinableNative::BigIntAsIntN:
+ return tryAttachBigIntAsIntN(callee);
+ case InlinableNative::BigIntAsUintN:
+ return tryAttachBigIntAsUintN(callee);
+
+ // Boolean natives.
+ case InlinableNative::Boolean:
+ return tryAttachBoolean(callee);
+
+ // Testing functions.
+ case InlinableNative::TestBailout:
+ return tryAttachBailout(callee);
+ case InlinableNative::TestAssertFloat32:
+ return tryAttachAssertFloat32(callee);
+ case InlinableNative::TestAssertRecoveredOnBailout:
+ return tryAttachAssertRecoveredOnBailout(callee);
+
+ case InlinableNative::Limit:
+ break;
+ }
+
+ MOZ_CRASH("Shouldn't get here");
+}
+
+// Remember the template object associated with any script being called
+// as a constructor, for later use during Ion compilation.
+ScriptedThisResult CallIRGenerator::getThisForScripted(
+ HandleFunction calleeFunc, MutableHandleObject result) {
+ // Some constructors allocate their own |this| object.
+ if (calleeFunc->constructorNeedsUninitializedThis()) {
+ return ScriptedThisResult::UninitializedThis;
+ }
+
+ // Only attach a stub if the newTarget is a function with a
+ // nonconfigurable prototype.
+ RootedValue protov(cx_);
+ RootedObject newTarget(cx_, &newTarget_.toObject());
+ if (!newTarget->is<JSFunction>() ||
+ !newTarget->as<JSFunction>().hasNonConfigurablePrototypeDataProperty()) {
+ return ScriptedThisResult::NoAction;
+ }
+
+ if (!GetProperty(cx_, newTarget, newTarget, cx_->names().prototype,
+ &protov)) {
+ cx_->clearPendingException();
+ return ScriptedThisResult::NoAction;
+ }
+
+ if (!protov.isObject()) {
+ return ScriptedThisResult::NoAction;
+ }
+
+ AutoRealm ar(cx_, calleeFunc);
+ PlainObject* thisObject =
+ CreateThisForFunction(cx_, calleeFunc, newTarget, TenuredObject);
+ if (!thisObject) {
+ cx_->clearPendingException();
+ return ScriptedThisResult::NoAction;
+ }
+
+ MOZ_ASSERT(thisObject->nonCCWRealm() == calleeFunc->realm());
+ result.set(thisObject);
+ return ScriptedThisResult::TemplateObject;
+}
+
+AttachDecision CallIRGenerator::tryAttachCallScripted(
+ HandleFunction calleeFunc) {
+ MOZ_ASSERT(calleeFunc->hasJitEntry());
+
+ // Never attach optimized scripted call stubs for JSOp::FunApply.
+ // MagicArguments may escape the frame through them.
+ if (op_ == JSOp::FunApply) {
+ return AttachDecision::NoAction;
+ }
+
+ if (calleeFunc->isWasmWithJitEntry()) {
+ TRY_ATTACH(tryAttachWasmCall(calleeFunc));
+ }
+
+ bool isSpecialized = mode_ == ICState::Mode::Specialized;
+
+ bool isConstructing = IsConstructPC(pc_);
+ bool isSpread = IsSpreadPC(pc_);
+ bool isSameRealm = isSpecialized && cx_->realm() == calleeFunc->realm();
+ CallFlags flags(isConstructing, isSpread, isSameRealm);
+
+ // If callee is not an interpreted constructor, we have to throw.
+ if (isConstructing && !calleeFunc->isConstructor()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Likewise, if the callee is a class constructor, we have to throw.
+ if (!isConstructing && calleeFunc->isClassConstructor()) {
+ return AttachDecision::NoAction;
+ }
+
+ if (isConstructing && !calleeFunc->hasJitScript()) {
+ // If we're constructing, require the callee to have a JitScript. This isn't
+ // required for correctness but avoids allocating a template object below
+ // for constructors that aren't hot. See bug 1419758.
+ return AttachDecision::TemporarilyUnoptimizable;
+ }
+
+ // Verify that spread calls have a reasonable number of arguments.
+ if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedObject templateObj(cx_);
+ if (isConstructing && isSpecialized) {
+ switch (getThisForScripted(calleeFunc, &templateObj)) {
+ case ScriptedThisResult::TemplateObject:
+ break;
+ case ScriptedThisResult::UninitializedThis:
+ flags.setNeedsUninitializedThis();
+ break;
+ case ScriptedThisResult::NoAction:
+ return AttachDecision::NoAction;
+ }
+ }
+
+ // Load argc.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Load the callee and ensure it is an object
+ ValOperandId calleeValId =
+ writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
+ ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
+
+ if (isSpecialized) {
+ MOZ_ASSERT_IF(isConstructing,
+ templateObj || flags.needsUninitializedThis());
+
+ // Ensure callee matches this stub's callee
+ emitCalleeGuard(calleeObjId, calleeFunc);
+ if (templateObj) {
+ // Emit guards to ensure the newTarget's .prototype property is what we
+ // expect. Note that getThisForScripted checked newTarget is a function
+ // with a non-configurable .prototype data property.
+ JSFunction* newTarget = &newTarget_.toObject().as<JSFunction>();
+ Shape* shape = newTarget->lookupPure(cx_->names().prototype);
+ MOZ_ASSERT(shape);
+ MOZ_ASSERT(newTarget->numFixedSlots() == 0, "Stub code relies on this");
+ uint32_t slot = shape->slot();
+ JSObject* prototypeObject = &newTarget->getSlot(slot).toObject();
+
+ ValOperandId newTargetValId = writer.loadArgumentDynamicSlot(
+ ArgumentKind::NewTarget, argcId, flags);
+ ObjOperandId newTargetObjId = writer.guardToObject(newTargetValId);
+ writer.guardShape(newTargetObjId, newTarget->lastProperty());
+ ObjOperandId protoId = writer.loadObject(prototypeObject);
+ writer.guardDynamicSlotIsSpecificObject(newTargetObjId, protoId, slot);
+
+ // Call metaScriptedTemplateObject before emitting the call, so that Warp
+ // can use this template object before transpiling the call.
+ writer.metaScriptedTemplateObject(calleeFunc, templateObj);
+ }
+ } else {
+ // Guard that object is a scripted function
+ writer.guardClass(calleeObjId, GuardClassKind::JSFunction);
+ writer.guardFunctionHasJitEntry(calleeObjId, isConstructing);
+
+ if (isConstructing) {
+ // If callee is not a constructor, we have to throw.
+ writer.guardFunctionIsConstructor(calleeObjId);
+ } else {
+ // If callee is a class constructor, we have to throw.
+ writer.guardNotClassConstructor(calleeObjId);
+ }
+ }
+
+ writer.callScriptedFunction(calleeObjId, argcId, flags);
+ writer.returnFromIC();
+
+ if (isSpecialized) {
+ trackAttached("Call scripted func");
+ } else {
+ trackAttached("Call any scripted func");
+ }
+
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachCallNative(HandleFunction calleeFunc) {
+ MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
+
+ bool isSpecialized = mode_ == ICState::Mode::Specialized;
+
+ bool isSpread = IsSpreadPC(pc_);
+ bool isSameRealm = isSpecialized && cx_->realm() == calleeFunc->realm();
+ bool isConstructing = IsConstructPC(pc_);
+ CallFlags flags(isConstructing, isSpread, isSameRealm);
+
+ if (isConstructing && !calleeFunc->isConstructor()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Verify that spread calls have a reasonable number of arguments.
+ if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check for specific native-function optimizations.
+ if (isSpecialized) {
+ TRY_ATTACH(tryAttachInlinableNative(calleeFunc));
+ }
+
+ // Load argc.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Load the callee and ensure it is an object
+ ValOperandId calleeValId =
+ writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
+ ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
+
+ // DOM calls need an additional guard so only try optimizing the first stub.
+ // Can only optimize normal (non-spread) calls.
+ if (isFirstStub_ && !isSpread && thisval_.isObject() &&
+ CanAttachDOMCall(cx_, JSJitInfo::Method, &thisval_.toObject(), calleeFunc,
+ mode_)) {
+ MOZ_ASSERT(!isConstructing, "DOM functions are not constructors");
+
+ // Guard that |this| is an object.
+ ValOperandId thisValId =
+ writer.loadArgumentDynamicSlot(ArgumentKind::This, argcId, flags);
+ ObjOperandId thisObjId = writer.guardToObject(thisValId);
+
+ // Guard on the |this| class to make sure it's the right instance.
+ writer.guardAnyClass(thisObjId, thisval_.toObject().getClass());
+
+ // Ensure callee matches this stub's callee
+ writer.guardSpecificFunction(calleeObjId, calleeFunc);
+ writer.callDOMFunction(calleeObjId, argcId, thisObjId, calleeFunc, flags);
+
+ trackAttached("CallDOM");
+ } else if (isSpecialized) {
+ // Ensure callee matches this stub's callee
+ writer.guardSpecificFunction(calleeObjId, calleeFunc);
+ writer.callNativeFunction(calleeObjId, argcId, op_, calleeFunc, flags);
+
+ trackAttached("CallNative");
+ } else {
+ // Guard that object is a native function
+ writer.guardClass(calleeObjId, GuardClassKind::JSFunction);
+ writer.guardFunctionHasNoJitEntry(calleeObjId);
+
+ if (isConstructing) {
+ // If callee is not a constructor, we have to throw.
+ writer.guardFunctionIsConstructor(calleeObjId);
+ } else {
+ // If callee is a class constructor, we have to throw.
+ writer.guardNotClassConstructor(calleeObjId);
+ }
+ writer.callAnyNativeFunction(calleeObjId, argcId, flags);
+
+ trackAttached("CallAnyNative");
+ }
+
+ writer.returnFromIC();
+
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachCallHook(HandleObject calleeObj) {
+ if (op_ == JSOp::FunCall || op_ == JSOp::FunApply) {
+ return AttachDecision::NoAction;
+ }
+
+ if (mode_ != ICState::Mode::Specialized) {
+ // We do not have megamorphic call hook stubs.
+ // TODO: Should we attach specialized call hook stubs in
+ // megamorphic mode to avoid going generic?
+ return AttachDecision::NoAction;
+ }
+
+ bool isSpread = IsSpreadPC(pc_);
+ bool isConstructing = IsConstructPC(pc_);
+ CallFlags flags(isConstructing, isSpread);
+ JSNative hook =
+ isConstructing ? calleeObj->constructHook() : calleeObj->callHook();
+ if (!hook) {
+ return AttachDecision::NoAction;
+ }
+
+ // Verify that spread calls have a reasonable number of arguments.
+ if (isSpread && args_.length() > JIT_ARGS_LENGTH_MAX) {
+ return AttachDecision::NoAction;
+ }
+
+ // Load argc.
+ Int32OperandId argcId(writer.setInputOperandId(0));
+
+ // Load the callee and ensure it is an object
+ ValOperandId calleeValId =
+ writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags);
+ ObjOperandId calleeObjId = writer.guardToObject(calleeValId);
+
+ // Ensure the callee's class matches the one in this stub.
+ writer.guardAnyClass(calleeObjId, calleeObj->getClass());
+
+ writer.callClassHook(calleeObjId, argcId, hook, flags);
+ writer.returnFromIC();
+
+ trackAttached("Call native func");
+
+ return AttachDecision::Attach;
+}
+
+AttachDecision CallIRGenerator::tryAttachStub() {
+ AutoAssertNoPendingException aanpe(cx_);
+
+ // Some opcodes are not yet supported.
+ switch (op_) {
+ case JSOp::Call:
+ case JSOp::CallIgnoresRv:
+ case JSOp::CallIter:
+ case JSOp::SpreadCall:
+ case JSOp::New:
+ case JSOp::SpreadNew:
+ case JSOp::SuperCall:
+ case JSOp::SpreadSuperCall:
+ case JSOp::FunCall:
+ case JSOp::FunApply:
+ break;
+ default:
+ return AttachDecision::NoAction;
+ }
+
+ MOZ_ASSERT(mode_ != ICState::Mode::Generic);
+
+ // Ensure callee is a function.
+ if (!callee_.isObject()) {
+ return AttachDecision::NoAction;
+ }
+
+ RootedObject calleeObj(cx_, &callee_.toObject());
+ if (!calleeObj->is<JSFunction>()) {
+ return tryAttachCallHook(calleeObj);
+ }
+
+ HandleFunction calleeFunc = calleeObj.as<JSFunction>();
+
+ if (op_ == JSOp::FunCall) {
+ return tryAttachFunCall(calleeFunc);
+ }
+ if (op_ == JSOp::FunApply) {
+ return tryAttachFunApply(calleeFunc);
+ }
+
+ // Check for scripted optimizations.
+ if (calleeFunc->hasJitEntry()) {
+ return tryAttachCallScripted(calleeFunc);
+ }
+
+ // Check for native-function optimizations.
+ MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
+
+ return tryAttachCallNative(calleeFunc);
+}
+
+void CallIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("callee", callee_);
+ sp.valueProperty("thisval", thisval_);
+ sp.valueProperty("argc", Int32Value(argc_));
+
+ // Try to log the first two arguments.
+ if (args_.length() >= 1) {
+ sp.valueProperty("arg0", args_[0]);
+ }
+ if (args_.length() >= 2) {
+ sp.valueProperty("arg1", args_[1]);
+ }
+ }
+#endif
+}
+
+// Class which holds a shape pointer for use when caches might reference data in
+// other zones.
+static const JSClass shapeContainerClass = {"ShapeContainer",
+ JSCLASS_HAS_RESERVED_SLOTS(1)};
+
+static const size_t SHAPE_CONTAINER_SLOT = 0;
+
+JSObject* jit::NewWrapperWithObjectShape(JSContext* cx,
+ HandleNativeObject obj) {
+ MOZ_ASSERT(cx->compartment() != obj->compartment());
+
+ RootedObject wrapper(cx);
+ {
+ AutoRealm ar(cx, obj);
+ wrapper = NewBuiltinClassInstance(cx, &shapeContainerClass);
+ if (!wrapper) {
+ return nullptr;
+ }
+ wrapper->as<NativeObject>().setSlot(
+ SHAPE_CONTAINER_SLOT, PrivateGCThingValue(obj->lastProperty()));
+ }
+ if (!JS_WrapObject(cx, &wrapper)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(IsWrapper(wrapper));
+ return wrapper;
+}
+
+void jit::LoadShapeWrapperContents(MacroAssembler& masm, Register obj,
+ Register dst, Label* failure) {
+ masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), dst);
+ Address privateAddr(dst,
+ js::detail::ProxyReservedSlots::offsetOfPrivateSlot());
+ masm.fallibleUnboxObject(privateAddr, dst, failure);
+ masm.unboxNonDouble(
+ Address(dst, NativeObject::getFixedSlotOffset(SHAPE_CONTAINER_SLOT)), dst,
+ JSVAL_TYPE_PRIVATE_GCTHING);
+}
+
+CompareIRGenerator::CompareIRGenerator(JSContext* cx, HandleScript script,
+ jsbytecode* pc, ICState::Mode mode,
+ JSOp op, HandleValue lhsVal,
+ HandleValue rhsVal)
+ : IRGenerator(cx, script, pc, CacheKind::Compare, mode),
+ op_(op),
+ lhsVal_(lhsVal),
+ rhsVal_(rhsVal) {}
+
+AttachDecision CompareIRGenerator::tryAttachString(ValOperandId lhsId,
+ ValOperandId rhsId) {
+ if (!lhsVal_.isString() || !rhsVal_.isString()) {
+ return AttachDecision::NoAction;
+ }
+
+ StringOperandId lhsStrId = writer.guardToString(lhsId);
+ StringOperandId rhsStrId = writer.guardToString(rhsId);
+ writer.compareStringResult(op_, lhsStrId, rhsStrId);
+ writer.returnFromIC();
+
+ trackAttached("String");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachObject(ValOperandId lhsId,
+ ValOperandId rhsId) {
+ MOZ_ASSERT(IsEqualityOp(op_));
+
+ if (!lhsVal_.isObject() || !rhsVal_.isObject()) {
+ return AttachDecision::NoAction;
+ }
+
+ ObjOperandId lhsObjId = writer.guardToObject(lhsId);
+ ObjOperandId rhsObjId = writer.guardToObject(rhsId);
+ writer.compareObjectResult(op_, lhsObjId, rhsObjId);
+ writer.returnFromIC();
+
+ trackAttached("Object");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachSymbol(ValOperandId lhsId,
+ ValOperandId rhsId) {
+ MOZ_ASSERT(IsEqualityOp(op_));
+
+ if (!lhsVal_.isSymbol() || !rhsVal_.isSymbol()) {
+ return AttachDecision::NoAction;
+ }
+
+ SymbolOperandId lhsSymId = writer.guardToSymbol(lhsId);
+ SymbolOperandId rhsSymId = writer.guardToSymbol(rhsId);
+ writer.compareSymbolResult(op_, lhsSymId, rhsSymId);
+ writer.returnFromIC();
+
+ trackAttached("Symbol");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachStrictDifferentTypes(
+ ValOperandId lhsId, ValOperandId rhsId) {
+ MOZ_ASSERT(IsEqualityOp(op_));
+
+ if (op_ != JSOp::StrictEq && op_ != JSOp::StrictNe) {
+ return AttachDecision::NoAction;
+ }
+
+ // Probably can't hit some of these.
+ if (SameType(lhsVal_, rhsVal_) ||
+ (lhsVal_.isNumber() && rhsVal_.isNumber())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Compare tags
+ ValueTagOperandId lhsTypeId = writer.loadValueTag(lhsId);
+ ValueTagOperandId rhsTypeId = writer.loadValueTag(rhsId);
+ writer.guardTagNotEqual(lhsTypeId, rhsTypeId);
+
+ // Now that we've passed the guard, we know differing types, so return the
+ // bool result.
+ writer.loadBooleanResult(op_ == JSOp::StrictNe ? true : false);
+ writer.returnFromIC();
+
+ trackAttached("StrictDifferentTypes");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachInt32(ValOperandId lhsId,
+ ValOperandId rhsId) {
+ if ((!lhsVal_.isInt32() && !lhsVal_.isBoolean()) ||
+ (!rhsVal_.isInt32() && !rhsVal_.isBoolean())) {
+ return AttachDecision::NoAction;
+ }
+
+ Int32OperandId lhsIntId = lhsVal_.isBoolean()
+ ? writer.guardBooleanToInt32(lhsId)
+ : writer.guardToInt32(lhsId);
+ Int32OperandId rhsIntId = rhsVal_.isBoolean()
+ ? writer.guardBooleanToInt32(rhsId)
+ : writer.guardToInt32(rhsId);
+
+ // Strictly different types should have been handed by
+ // tryAttachStrictDifferentTypes
+ MOZ_ASSERT_IF(op_ == JSOp::StrictEq || op_ == JSOp::StrictNe,
+ lhsVal_.isInt32() == rhsVal_.isInt32());
+
+ writer.compareInt32Result(op_, lhsIntId, rhsIntId);
+ writer.returnFromIC();
+
+ trackAttached(lhsVal_.isBoolean() ? "Boolean" : "Int32");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachNumber(ValOperandId lhsId,
+ ValOperandId rhsId) {
+ if (!lhsVal_.isNumber() || !rhsVal_.isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ NumberOperandId lhs = writer.guardIsNumber(lhsId);
+ NumberOperandId rhs = writer.guardIsNumber(rhsId);
+ writer.compareDoubleResult(op_, lhs, rhs);
+ writer.returnFromIC();
+
+ trackAttached("Number");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachBigInt(ValOperandId lhsId,
+ ValOperandId rhsId) {
+ if (!lhsVal_.isBigInt() || !rhsVal_.isBigInt()) {
+ return AttachDecision::NoAction;
+ }
+
+ BigIntOperandId lhs = writer.guardToBigInt(lhsId);
+ BigIntOperandId rhs = writer.guardToBigInt(rhsId);
+
+ writer.compareBigIntResult(op_, lhs, rhs);
+ writer.returnFromIC();
+
+ trackAttached("BigInt");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachNumberUndefined(
+ ValOperandId lhsId, ValOperandId rhsId) {
+ if (!(lhsVal_.isUndefined() && rhsVal_.isNumber()) &&
+ !(rhsVal_.isUndefined() && lhsVal_.isNumber())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Should have been handled by tryAttachAnyNullUndefined.
+ MOZ_ASSERT(!IsEqualityOp(op_));
+
+ if (lhsVal_.isNumber()) {
+ writer.guardIsNumber(lhsId);
+ } else {
+ writer.guardIsUndefined(lhsId);
+ }
+
+ if (rhsVal_.isNumber()) {
+ writer.guardIsNumber(rhsId);
+ } else {
+ writer.guardIsUndefined(rhsId);
+ }
+
+ // Relational comparing a number with undefined will always be false.
+ writer.loadBooleanResult(false);
+ writer.returnFromIC();
+
+ trackAttached("NumberUndefined");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachAnyNullUndefined(
+ ValOperandId lhsId, ValOperandId rhsId) {
+ MOZ_ASSERT(IsEqualityOp(op_));
+
+ // Either RHS or LHS needs to be null/undefined.
+ if (!lhsVal_.isNullOrUndefined() && !rhsVal_.isNullOrUndefined()) {
+ return AttachDecision::NoAction;
+ }
+
+ // We assume that the side with null/undefined is usually constant, in
+ // code like `if (x === undefined) { x = {}; }`.
+ // That is why we don't attach when both sides are undefined/null,
+ // because we would basically need to decide by chance which side is
+ // the likely constant.
+ // The actual generated code however handles null/undefined of course.
+ if (lhsVal_.isNullOrUndefined() && rhsVal_.isNullOrUndefined()) {
+ return AttachDecision::NoAction;
+ }
+
+ if (rhsVal_.isNullOrUndefined()) {
+ if (rhsVal_.isNull()) {
+ writer.guardIsNull(rhsId);
+ writer.compareNullUndefinedResult(op_, /* isUndefined */ false, lhsId);
+ trackAttached("AnyNull");
+ } else {
+ writer.guardIsUndefined(rhsId);
+ writer.compareNullUndefinedResult(op_, /* isUndefined */ true, lhsId);
+ trackAttached("AnyUndefined");
+ }
+ } else {
+ if (lhsVal_.isNull()) {
+ writer.guardIsNull(lhsId);
+ writer.compareNullUndefinedResult(op_, /* isUndefined */ false, rhsId);
+ trackAttached("NullAny");
+ } else {
+ writer.guardIsUndefined(lhsId);
+ writer.compareNullUndefinedResult(op_, /* isUndefined */ true, rhsId);
+ trackAttached("UndefinedAny");
+ }
+ }
+
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+// Handle {null/undefined} x {null,undefined} equality comparisons
+AttachDecision CompareIRGenerator::tryAttachNullUndefined(ValOperandId lhsId,
+ ValOperandId rhsId) {
+ if (!lhsVal_.isNullOrUndefined() || !rhsVal_.isNullOrUndefined()) {
+ return AttachDecision::NoAction;
+ }
+
+ if (op_ == JSOp::Eq || op_ == JSOp::Ne) {
+ writer.guardIsNullOrUndefined(lhsId);
+ writer.guardIsNullOrUndefined(rhsId);
+ // Sloppy equality means we actually only care about the op:
+ writer.loadBooleanResult(op_ == JSOp::Eq);
+ trackAttached("SloppyNullUndefined");
+ } else {
+ // Strict equality only hits this branch, and only in the
+ // undef {!,=}== undef and null {!,=}== null cases.
+ // The other cases should have hit tryAttachStrictDifferentTypes.
+ MOZ_ASSERT(lhsVal_.isNull() == rhsVal_.isNull());
+ lhsVal_.isNull() ? writer.guardIsNull(lhsId)
+ : writer.guardIsUndefined(lhsId);
+ rhsVal_.isNull() ? writer.guardIsNull(rhsId)
+ : writer.guardIsUndefined(rhsId);
+ writer.loadBooleanResult(op_ == JSOp::StrictEq);
+ trackAttached("StrictNullUndefinedEquality");
+ }
+
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachStringNumber(ValOperandId lhsId,
+ ValOperandId rhsId) {
+ // Ensure String x Number
+ if (!(lhsVal_.isString() && rhsVal_.isNumber()) &&
+ !(rhsVal_.isString() && lhsVal_.isNumber())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Case should have been handled by tryAttachStrictDifferentTypes
+ MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
+
+ auto createGuards = [&](HandleValue v, ValOperandId vId) {
+ if (v.isString()) {
+ StringOperandId strId = writer.guardToString(vId);
+ return writer.guardStringToNumber(strId);
+ }
+ MOZ_ASSERT(v.isNumber());
+ NumberOperandId numId = writer.guardIsNumber(vId);
+ return numId;
+ };
+
+ NumberOperandId lhsGuardedId = createGuards(lhsVal_, lhsId);
+ NumberOperandId rhsGuardedId = createGuards(rhsVal_, rhsId);
+ writer.compareDoubleResult(op_, lhsGuardedId, rhsGuardedId);
+ writer.returnFromIC();
+
+ trackAttached("StringNumber");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachPrimitiveSymbol(
+ ValOperandId lhsId, ValOperandId rhsId) {
+ MOZ_ASSERT(IsEqualityOp(op_));
+
+ // The set of primitive cases we want to handle here (excluding null,
+ // undefined, and symbol)
+ auto isPrimitive = [](HandleValue x) {
+ return x.isString() || x.isBoolean() || x.isNumber() || x.isBigInt();
+ };
+
+ // Ensure Symbol x {String, Bool, Number, BigInt}.
+ if (!(lhsVal_.isSymbol() && isPrimitive(rhsVal_)) &&
+ !(rhsVal_.isSymbol() && isPrimitive(lhsVal_))) {
+ return AttachDecision::NoAction;
+ }
+
+ auto guardPrimitive = [&](HandleValue v, ValOperandId id) {
+ MOZ_ASSERT(isPrimitive(v));
+ if (v.isNumber()) {
+ writer.guardIsNumber(id);
+ return;
+ }
+ switch (v.extractNonDoubleType()) {
+ case JSVAL_TYPE_STRING:
+ writer.guardToString(id);
+ return;
+ case JSVAL_TYPE_BOOLEAN:
+ writer.guardToBoolean(id);
+ return;
+ case JSVAL_TYPE_BIGINT:
+ writer.guardToBigInt(id);
+ return;
+ default:
+ MOZ_CRASH("unexpected type");
+ return;
+ }
+ };
+
+ if (lhsVal_.isSymbol()) {
+ writer.guardToSymbol(lhsId);
+ guardPrimitive(rhsVal_, rhsId);
+ } else {
+ guardPrimitive(lhsVal_, lhsId);
+ writer.guardToSymbol(rhsId);
+ }
+
+ // Comparing a primitive with symbol will always be true for Ne/StrictNe, and
+ // always be false for other compare ops.
+ writer.loadBooleanResult(op_ == JSOp::Ne || op_ == JSOp::StrictNe);
+ writer.returnFromIC();
+
+ trackAttached("PrimitiveSymbol");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachBoolStringOrNumber(
+ ValOperandId lhsId, ValOperandId rhsId) {
+ // Ensure Boolean x {String, Number}.
+ if (!(lhsVal_.isBoolean() && (rhsVal_.isString() || rhsVal_.isNumber())) &&
+ !(rhsVal_.isBoolean() && (lhsVal_.isString() || lhsVal_.isNumber()))) {
+ return AttachDecision::NoAction;
+ }
+
+ // Case should have been handled by tryAttachStrictDifferentTypes
+ MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
+
+ // Case should have been handled by tryAttachInt32
+ MOZ_ASSERT(!lhsVal_.isInt32() && !rhsVal_.isInt32());
+
+ auto createGuards = [&](HandleValue v, ValOperandId vId) {
+ if (v.isBoolean()) {
+ BooleanOperandId boolId = writer.guardToBoolean(vId);
+ return writer.booleanToNumber(boolId);
+ }
+ if (v.isString()) {
+ StringOperandId strId = writer.guardToString(vId);
+ return writer.guardStringToNumber(strId);
+ }
+ MOZ_ASSERT(v.isNumber());
+ return writer.guardIsNumber(vId);
+ };
+
+ NumberOperandId lhsGuardedId = createGuards(lhsVal_, lhsId);
+ NumberOperandId rhsGuardedId = createGuards(rhsVal_, rhsId);
+ writer.compareDoubleResult(op_, lhsGuardedId, rhsGuardedId);
+ writer.returnFromIC();
+
+ trackAttached("BoolStringOrNumber");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachBigIntInt32(ValOperandId lhsId,
+ ValOperandId rhsId) {
+ // Ensure BigInt x {Int32, Boolean}.
+ if (!(lhsVal_.isBigInt() && (rhsVal_.isInt32() || rhsVal_.isBoolean())) &&
+ !(rhsVal_.isBigInt() && (lhsVal_.isInt32() || lhsVal_.isBoolean()))) {
+ return AttachDecision::NoAction;
+ }
+
+ // Case should have been handled by tryAttachStrictDifferentTypes
+ MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
+
+ auto createGuards = [&](HandleValue v, ValOperandId vId) {
+ if (v.isBoolean()) {
+ return writer.guardBooleanToInt32(vId);
+ }
+ MOZ_ASSERT(v.isInt32());
+ return writer.guardToInt32(vId);
+ };
+
+ if (lhsVal_.isBigInt()) {
+ BigIntOperandId bigIntId = writer.guardToBigInt(lhsId);
+ Int32OperandId intId = createGuards(rhsVal_, rhsId);
+
+ writer.compareBigIntInt32Result(op_, bigIntId, intId);
+ } else {
+ Int32OperandId intId = createGuards(lhsVal_, lhsId);
+ BigIntOperandId bigIntId = writer.guardToBigInt(rhsId);
+
+ writer.compareBigIntInt32Result(ReverseCompareOp(op_), bigIntId, intId);
+ }
+ writer.returnFromIC();
+
+ trackAttached("BigIntInt32");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachBigIntNumber(ValOperandId lhsId,
+ ValOperandId rhsId) {
+ // Ensure BigInt x Number.
+ if (!(lhsVal_.isBigInt() && rhsVal_.isNumber()) &&
+ !(rhsVal_.isBigInt() && lhsVal_.isNumber())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Case should have been handled by tryAttachStrictDifferentTypes
+ MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
+
+ if (lhsVal_.isBigInt()) {
+ BigIntOperandId bigIntId = writer.guardToBigInt(lhsId);
+ NumberOperandId numId = writer.guardIsNumber(rhsId);
+
+ writer.compareBigIntNumberResult(op_, bigIntId, numId);
+ } else {
+ NumberOperandId numId = writer.guardIsNumber(lhsId);
+ BigIntOperandId bigIntId = writer.guardToBigInt(rhsId);
+
+ writer.compareBigIntNumberResult(ReverseCompareOp(op_), bigIntId, numId);
+ }
+ writer.returnFromIC();
+
+ trackAttached("BigIntNumber");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachBigIntString(ValOperandId lhsId,
+ ValOperandId rhsId) {
+ // Ensure BigInt x String.
+ if (!(lhsVal_.isBigInt() && rhsVal_.isString()) &&
+ !(rhsVal_.isBigInt() && lhsVal_.isString())) {
+ return AttachDecision::NoAction;
+ }
+
+ // Case should have been handled by tryAttachStrictDifferentTypes
+ MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe);
+
+ if (lhsVal_.isBigInt()) {
+ BigIntOperandId bigIntId = writer.guardToBigInt(lhsId);
+ StringOperandId strId = writer.guardToString(rhsId);
+
+ writer.compareBigIntStringResult(op_, bigIntId, strId);
+ } else {
+ StringOperandId strId = writer.guardToString(lhsId);
+ BigIntOperandId bigIntId = writer.guardToBigInt(rhsId);
+
+ writer.compareBigIntStringResult(ReverseCompareOp(op_), bigIntId, strId);
+ }
+ writer.returnFromIC();
+
+ trackAttached("BigIntString");
+ return AttachDecision::Attach;
+}
+
+AttachDecision CompareIRGenerator::tryAttachStub() {
+ MOZ_ASSERT(cacheKind_ == CacheKind::Compare);
+ MOZ_ASSERT(IsEqualityOp(op_) || IsRelationalOp(op_));
+
+ AutoAssertNoPendingException aanpe(cx_);
+
+ constexpr uint8_t lhsIndex = 0;
+ constexpr uint8_t rhsIndex = 1;
+
+ static_assert(lhsIndex == 0 && rhsIndex == 1,
+ "Indexes relied upon by baseline inspector");
+
+ ValOperandId lhsId(writer.setInputOperandId(lhsIndex));
+ ValOperandId rhsId(writer.setInputOperandId(rhsIndex));
+
+ // For sloppy equality ops, there are cases this IC does not handle:
+ // - {Object} x {String, Symbol, Bool, Number, BigInt}.
+ //
+ // (The above lists omits the equivalent case {B} x {A} when {A} x {B} is
+ // already present.)
+
+ if (IsEqualityOp(op_)) {
+ TRY_ATTACH(tryAttachObject(lhsId, rhsId));
+ TRY_ATTACH(tryAttachSymbol(lhsId, rhsId));
+
+ // Handles any (non null or undefined) comparison with null/undefined.
+ TRY_ATTACH(tryAttachAnyNullUndefined(lhsId, rhsId));
+
+ // This covers -strict- equality/inequality using a type tag check, so
+ // catches all different type pairs outside of Numbers, which cannot be
+ // checked on tags alone.
+ TRY_ATTACH(tryAttachStrictDifferentTypes(lhsId, rhsId));
+
+ TRY_ATTACH(tryAttachNullUndefined(lhsId, rhsId));
+
+ TRY_ATTACH(tryAttachPrimitiveSymbol(lhsId, rhsId));
+ }
+
+ // This should preceed the Int32/Number cases to allow
+ // them to not concern themselves with handling undefined
+ // or null.
+ TRY_ATTACH(tryAttachNumberUndefined(lhsId, rhsId));
+
+ // We want these to be last, to allow us to bypass the
+ // strictly-different-types cases in the below attachment code
+ TRY_ATTACH(tryAttachInt32(lhsId, rhsId));
+ TRY_ATTACH(tryAttachNumber(lhsId, rhsId));
+ TRY_ATTACH(tryAttachBigInt(lhsId, rhsId));
+ TRY_ATTACH(tryAttachString(lhsId, rhsId));
+
+ TRY_ATTACH(tryAttachStringNumber(lhsId, rhsId));
+ TRY_ATTACH(tryAttachBoolStringOrNumber(lhsId, rhsId));
+
+ TRY_ATTACH(tryAttachBigIntInt32(lhsId, rhsId));
+ TRY_ATTACH(tryAttachBigIntNumber(lhsId, rhsId));
+ TRY_ATTACH(tryAttachBigIntString(lhsId, rhsId));
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+}
+
+void CompareIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("lhs", lhsVal_);
+ sp.valueProperty("rhs", rhsVal_);
+ sp.opcodeProperty("op", op_);
+ }
+#endif
+}
+
+ToBoolIRGenerator::ToBoolIRGenerator(JSContext* cx, HandleScript script,
+ jsbytecode* pc, ICState::Mode mode,
+ HandleValue val)
+ : IRGenerator(cx, script, pc, CacheKind::ToBool, mode), val_(val) {}
+
+void ToBoolIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("val", val_);
+ }
+#endif
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachStub() {
+ AutoAssertNoPendingException aanpe(cx_);
+
+ TRY_ATTACH(tryAttachInt32());
+ TRY_ATTACH(tryAttachNumber());
+ TRY_ATTACH(tryAttachString());
+ TRY_ATTACH(tryAttachNullOrUndefined());
+ TRY_ATTACH(tryAttachObject());
+ TRY_ATTACH(tryAttachSymbol());
+ TRY_ATTACH(tryAttachBigInt());
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachInt32() {
+ if (!val_.isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ writer.guardNonDoubleType(valId, ValueType::Int32);
+ writer.loadInt32TruthyResult(valId);
+ writer.returnFromIC();
+ trackAttached("ToBoolInt32");
+ return AttachDecision::Attach;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachNumber() {
+ if (!val_.isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ NumberOperandId numId = writer.guardIsNumber(valId);
+ writer.loadDoubleTruthyResult(numId);
+ writer.returnFromIC();
+ trackAttached("ToBoolNumber");
+ return AttachDecision::Attach;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachSymbol() {
+ if (!val_.isSymbol()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ writer.guardNonDoubleType(valId, ValueType::Symbol);
+ writer.loadBooleanResult(true);
+ writer.returnFromIC();
+ trackAttached("ToBoolSymbol");
+ return AttachDecision::Attach;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachString() {
+ if (!val_.isString()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ StringOperandId strId = writer.guardToString(valId);
+ writer.loadStringTruthyResult(strId);
+ writer.returnFromIC();
+ trackAttached("ToBoolString");
+ return AttachDecision::Attach;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachNullOrUndefined() {
+ if (!val_.isNullOrUndefined()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ writer.guardIsNullOrUndefined(valId);
+ writer.loadBooleanResult(false);
+ writer.returnFromIC();
+ trackAttached("ToBoolNullOrUndefined");
+ return AttachDecision::Attach;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachObject() {
+ if (!val_.isObject()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ ObjOperandId objId = writer.guardToObject(valId);
+ writer.loadObjectTruthyResult(objId);
+ writer.returnFromIC();
+ trackAttached("ToBoolObject");
+ return AttachDecision::Attach;
+}
+
+AttachDecision ToBoolIRGenerator::tryAttachBigInt() {
+ if (!val_.isBigInt()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ BigIntOperandId bigIntId = writer.guardToBigInt(valId);
+ writer.loadBigIntTruthyResult(bigIntId);
+ writer.returnFromIC();
+ trackAttached("ToBoolBigInt");
+ return AttachDecision::Attach;
+}
+
+GetIntrinsicIRGenerator::GetIntrinsicIRGenerator(JSContext* cx,
+ HandleScript script,
+ jsbytecode* pc,
+ ICState::Mode mode,
+ HandleValue val)
+ : IRGenerator(cx, script, pc, CacheKind::GetIntrinsic, mode), val_(val) {}
+
+void GetIntrinsicIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("val", val_);
+ }
+#endif
+}
+
+AttachDecision GetIntrinsicIRGenerator::tryAttachStub() {
+ AutoAssertNoPendingException aanpe(cx_);
+ writer.loadValueResult(val_);
+ writer.returnFromIC();
+ trackAttached("GetIntrinsic");
+ return AttachDecision::Attach;
+}
+
+UnaryArithIRGenerator::UnaryArithIRGenerator(JSContext* cx, HandleScript script,
+ jsbytecode* pc, ICState::Mode mode,
+ JSOp op, HandleValue val,
+ HandleValue res)
+ : IRGenerator(cx, script, pc, CacheKind::UnaryArith, mode),
+ op_(op),
+ val_(val),
+ res_(res) {}
+
+void UnaryArithIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("val", val_);
+ sp.valueProperty("res", res_);
+ }
+#endif
+}
+
+AttachDecision UnaryArithIRGenerator::tryAttachStub() {
+ AutoAssertNoPendingException aanpe(cx_);
+ TRY_ATTACH(tryAttachInt32());
+ TRY_ATTACH(tryAttachNumber());
+ TRY_ATTACH(tryAttachBitwise());
+ TRY_ATTACH(tryAttachBigInt());
+ TRY_ATTACH(tryAttachStringInt32());
+ TRY_ATTACH(tryAttachStringNumber());
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+}
+
+AttachDecision UnaryArithIRGenerator::tryAttachInt32() {
+ if (op_ == JSOp::BitNot) {
+ return AttachDecision::NoAction;
+ }
+ if (!val_.isInt32() || !res_.isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+
+ Int32OperandId intId = writer.guardToInt32(valId);
+ switch (op_) {
+ case JSOp::Pos:
+ writer.loadInt32Result(intId);
+ trackAttached("UnaryArith.Int32Pos");
+ break;
+ case JSOp::Neg:
+ writer.int32NegationResult(intId);
+ trackAttached("UnaryArith.Int32Neg");
+ break;
+ case JSOp::Inc:
+ writer.int32IncResult(intId);
+ trackAttached("UnaryArith.Int32Inc");
+ break;
+ case JSOp::Dec:
+ writer.int32DecResult(intId);
+ trackAttached("UnaryArith.Int32Dec");
+ break;
+ case JSOp::ToNumeric:
+ writer.loadInt32Result(intId);
+ trackAttached("UnaryArith.Int32ToNumeric");
+ break;
+ default:
+ MOZ_CRASH("unexpected OP");
+ }
+
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+AttachDecision UnaryArithIRGenerator::tryAttachNumber() {
+ if (op_ == JSOp::BitNot) {
+ return AttachDecision::NoAction;
+ }
+ if (!val_.isNumber()) {
+ return AttachDecision::NoAction;
+ }
+ MOZ_ASSERT(res_.isNumber());
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ NumberOperandId numId = writer.guardIsNumber(valId);
+ Int32OperandId truncatedId;
+ switch (op_) {
+ case JSOp::Pos:
+ writer.loadDoubleResult(numId);
+ trackAttached("UnaryArith.DoublePos");
+ break;
+ case JSOp::Neg:
+ writer.doubleNegationResult(numId);
+ trackAttached("UnaryArith.DoubleNeg");
+ break;
+ case JSOp::Inc:
+ writer.doubleIncResult(numId);
+ trackAttached("UnaryArith.DoubleInc");
+ break;
+ case JSOp::Dec:
+ writer.doubleDecResult(numId);
+ trackAttached("UnaryArith.DoubleDec");
+ break;
+ case JSOp::ToNumeric:
+ writer.loadDoubleResult(numId);
+ trackAttached("UnaryArith.DoubleToNumeric");
+ break;
+ default:
+ MOZ_CRASH("Unexpected OP");
+ }
+
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+static bool CanTruncateToInt32(const Value& val) {
+ return val.isNumber() || val.isBoolean() || val.isNullOrUndefined() ||
+ val.isString();
+}
+
+// Convert type into int32 for the bitwise/shift operands.
+static Int32OperandId EmitTruncateToInt32Guard(CacheIRWriter& writer,
+ ValOperandId id,
+ HandleValue val) {
+ MOZ_ASSERT(CanTruncateToInt32(val));
+ if (val.isInt32()) {
+ return writer.guardToInt32(id);
+ }
+ if (val.isBoolean()) {
+ return writer.guardBooleanToInt32(id);
+ }
+ if (val.isNullOrUndefined()) {
+ writer.guardIsNullOrUndefined(id);
+ return writer.loadInt32Constant(0);
+ }
+ NumberOperandId numId;
+ if (val.isString()) {
+ StringOperandId strId = writer.guardToString(id);
+ numId = writer.guardStringToNumber(strId);
+ } else {
+ MOZ_ASSERT(val.isDouble());
+ numId = writer.guardIsNumber(id);
+ }
+ return writer.truncateDoubleToUInt32(numId);
+}
+
+AttachDecision UnaryArithIRGenerator::tryAttachBitwise() {
+ // Only bitwise operators.
+ if (op_ != JSOp::BitNot) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check guard conditions
+ if (!CanTruncateToInt32(val_)) {
+ return AttachDecision::NoAction;
+ }
+
+ // Bitwise operators always produce Int32 values.
+ MOZ_ASSERT(res_.isInt32());
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ Int32OperandId intId = EmitTruncateToInt32Guard(writer, valId, val_);
+ writer.int32NotResult(intId);
+ trackAttached("BinaryArith.Bitwise.BitNot");
+
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+AttachDecision UnaryArithIRGenerator::tryAttachBigInt() {
+ if (!val_.isBigInt()) {
+ return AttachDecision::NoAction;
+ }
+ MOZ_ASSERT(res_.isBigInt());
+
+ MOZ_ASSERT(op_ != JSOp::Pos,
+ "Applying the unary + operator on BigInt values throws an error");
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ BigIntOperandId bigIntId = writer.guardToBigInt(valId);
+ switch (op_) {
+ case JSOp::BitNot:
+ writer.bigIntNotResult(bigIntId);
+ trackAttached("UnaryArith.BigIntNot");
+ break;
+ case JSOp::Neg:
+ writer.bigIntNegationResult(bigIntId);
+ trackAttached("UnaryArith.BigIntNeg");
+ break;
+ case JSOp::Inc:
+ writer.bigIntIncResult(bigIntId);
+ trackAttached("UnaryArith.BigIntInc");
+ break;
+ case JSOp::Dec:
+ writer.bigIntDecResult(bigIntId);
+ trackAttached("UnaryArith.BigIntDec");
+ break;
+ case JSOp::ToNumeric:
+ writer.loadBigIntResult(bigIntId);
+ trackAttached("UnaryArith.BigIntToNumeric");
+ break;
+ default:
+ MOZ_CRASH("Unexpected OP");
+ }
+
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+AttachDecision UnaryArithIRGenerator::tryAttachStringInt32() {
+ if (!val_.isString()) {
+ return AttachDecision::NoAction;
+ }
+ MOZ_ASSERT(res_.isNumber());
+
+ // Case should have been handled by tryAttachBitwise.
+ MOZ_ASSERT(op_ != JSOp::BitNot);
+
+ if (!res_.isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ StringOperandId stringId = writer.guardToString(valId);
+ Int32OperandId intId = writer.guardStringToInt32(stringId);
+
+ switch (op_) {
+ case JSOp::Pos:
+ writer.loadInt32Result(intId);
+ trackAttached("UnaryArith.StringInt32Pos");
+ break;
+ case JSOp::Neg:
+ writer.int32NegationResult(intId);
+ trackAttached("UnaryArith.StringInt32Neg");
+ break;
+ case JSOp::Inc:
+ writer.int32IncResult(intId);
+ trackAttached("UnaryArith.StringInt32Inc");
+ break;
+ case JSOp::Dec:
+ writer.int32DecResult(intId);
+ trackAttached("UnaryArith.StringInt32Dec");
+ break;
+ case JSOp::ToNumeric:
+ writer.loadInt32Result(intId);
+ trackAttached("UnaryArith.StringInt32ToNumeric");
+ break;
+ default:
+ MOZ_CRASH("Unexpected OP");
+ }
+
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+AttachDecision UnaryArithIRGenerator::tryAttachStringNumber() {
+ if (!val_.isString()) {
+ return AttachDecision::NoAction;
+ }
+ MOZ_ASSERT(res_.isNumber());
+
+ // Case should have been handled by tryAttachBitwise.
+ MOZ_ASSERT(op_ != JSOp::BitNot);
+
+ ValOperandId valId(writer.setInputOperandId(0));
+ StringOperandId stringId = writer.guardToString(valId);
+ NumberOperandId numId = writer.guardStringToNumber(stringId);
+
+ Int32OperandId truncatedId;
+ switch (op_) {
+ case JSOp::Pos:
+ writer.loadDoubleResult(numId);
+ trackAttached("UnaryArith.StringNumberPos");
+ break;
+ case JSOp::Neg:
+ writer.doubleNegationResult(numId);
+ trackAttached("UnaryArith.StringNumberNeg");
+ break;
+ case JSOp::Inc:
+ writer.doubleIncResult(numId);
+ trackAttached("UnaryArith.StringNumberInc");
+ break;
+ case JSOp::Dec:
+ writer.doubleDecResult(numId);
+ trackAttached("UnaryArith.StringNumberDec");
+ break;
+ case JSOp::ToNumeric:
+ writer.loadDoubleResult(numId);
+ trackAttached("UnaryArith.StringNumberToNumeric");
+ break;
+ default:
+ MOZ_CRASH("Unexpected OP");
+ }
+
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+ToPropertyKeyIRGenerator::ToPropertyKeyIRGenerator(JSContext* cx,
+ HandleScript script,
+ jsbytecode* pc,
+ ICState::Mode mode,
+ HandleValue val)
+ : IRGenerator(cx, script, pc, CacheKind::ToPropertyKey, mode), val_(val) {}
+
+void ToPropertyKeyIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.valueProperty("val", val_);
+ }
+#endif
+}
+
+AttachDecision ToPropertyKeyIRGenerator::tryAttachStub() {
+ AutoAssertNoPendingException aanpe(cx_);
+ TRY_ATTACH(tryAttachInt32());
+ TRY_ATTACH(tryAttachNumber());
+ TRY_ATTACH(tryAttachString());
+ TRY_ATTACH(tryAttachSymbol());
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+}
+
+AttachDecision ToPropertyKeyIRGenerator::tryAttachInt32() {
+ if (!val_.isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+
+ Int32OperandId intId = writer.guardToInt32(valId);
+ writer.loadInt32Result(intId);
+ writer.returnFromIC();
+
+ trackAttached("ToPropertyKey.Int32");
+ return AttachDecision::Attach;
+}
+
+AttachDecision ToPropertyKeyIRGenerator::tryAttachNumber() {
+ if (!val_.isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ // We allow negative zero here because ToPropertyKey(-0.0) is 0.
+ int32_t unused;
+ if (!mozilla::NumberEqualsInt32(val_.toNumber(), &unused)) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+
+ Int32OperandId intId = writer.guardToInt32Index(valId);
+ writer.loadInt32Result(intId);
+ writer.returnFromIC();
+
+ trackAttached("ToPropertyKey.Number");
+ return AttachDecision::Attach;
+}
+
+AttachDecision ToPropertyKeyIRGenerator::tryAttachString() {
+ if (!val_.isString()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+
+ StringOperandId strId = writer.guardToString(valId);
+ writer.loadStringResult(strId);
+ writer.returnFromIC();
+
+ trackAttached("ToPropertyKey.String");
+ return AttachDecision::Attach;
+}
+
+AttachDecision ToPropertyKeyIRGenerator::tryAttachSymbol() {
+ if (!val_.isSymbol()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId valId(writer.setInputOperandId(0));
+
+ SymbolOperandId strId = writer.guardToSymbol(valId);
+ writer.loadSymbolResult(strId);
+ writer.returnFromIC();
+
+ trackAttached("ToPropertyKey.Symbol");
+ return AttachDecision::Attach;
+}
+
+BinaryArithIRGenerator::BinaryArithIRGenerator(
+ JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode,
+ JSOp op, HandleValue lhs, HandleValue rhs, HandleValue res)
+ : IRGenerator(cx, script, pc, CacheKind::BinaryArith, mode),
+ op_(op),
+ lhs_(lhs),
+ rhs_(rhs),
+ res_(res) {}
+
+void BinaryArithIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.opcodeProperty("op", op_);
+ sp.valueProperty("rhs", rhs_);
+ sp.valueProperty("lhs", lhs_);
+ }
+#endif
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachStub() {
+ AutoAssertNoPendingException aanpe(cx_);
+ // Arithmetic operations with Int32 operands
+ TRY_ATTACH(tryAttachInt32());
+
+ // Bitwise operations with Int32/Double/Boolean/Null/Undefined/String
+ // operands.
+ TRY_ATTACH(tryAttachBitwise());
+
+ // Arithmetic operations with Double operands. This needs to come after
+ // tryAttachInt32, as the guards overlap, and we'd prefer to attach the
+ // more specialized Int32 IC if it is possible.
+ TRY_ATTACH(tryAttachDouble());
+
+ // String x String
+ TRY_ATTACH(tryAttachStringConcat());
+
+ // String x Object
+ TRY_ATTACH(tryAttachStringObjectConcat());
+
+ TRY_ATTACH(tryAttachStringNumberConcat());
+
+ // String + Boolean
+ TRY_ATTACH(tryAttachStringBooleanConcat());
+
+ // Arithmetic operations or bitwise operations with BigInt operands
+ TRY_ATTACH(tryAttachBigInt());
+
+ // Arithmetic operations (without addition) with String x Int32.
+ TRY_ATTACH(tryAttachStringInt32Arith());
+
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachBitwise() {
+ // Only bit-wise and shifts.
+ if (op_ != JSOp::BitOr && op_ != JSOp::BitXor && op_ != JSOp::BitAnd &&
+ op_ != JSOp::Lsh && op_ != JSOp::Rsh && op_ != JSOp::Ursh) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check guard conditions
+ if (!CanTruncateToInt32(lhs_) || !CanTruncateToInt32(rhs_)) {
+ return AttachDecision::NoAction;
+ }
+
+ // All ops, with the exception of Ursh, produce Int32 values.
+ MOZ_ASSERT_IF(op_ != JSOp::Ursh, res_.isInt32());
+
+ ValOperandId lhsId(writer.setInputOperandId(0));
+ ValOperandId rhsId(writer.setInputOperandId(1));
+
+ Int32OperandId lhsIntId = EmitTruncateToInt32Guard(writer, lhsId, lhs_);
+ Int32OperandId rhsIntId = EmitTruncateToInt32Guard(writer, rhsId, rhs_);
+
+ switch (op_) {
+ case JSOp::BitOr:
+ writer.int32BitOrResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.Bitwise.BitOr");
+ break;
+ case JSOp::BitXor:
+ writer.int32BitXorResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.Bitwise.BitXor");
+ break;
+ case JSOp::BitAnd:
+ writer.int32BitAndResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.Bitwise.BitAnd");
+ break;
+ case JSOp::Lsh:
+ writer.int32LeftShiftResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.Bitwise.LeftShift");
+ break;
+ case JSOp::Rsh:
+ writer.int32RightShiftResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.Bitwise.RightShift");
+ break;
+ case JSOp::Ursh:
+ writer.int32URightShiftResult(lhsIntId, rhsIntId, res_.isDouble());
+ trackAttached("BinaryArith.Bitwise.UnsignedRightShift");
+ break;
+ default:
+ MOZ_CRASH("Unhandled op in tryAttachBitwise");
+ }
+
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachDouble() {
+ // Check valid opcodes
+ if (op_ != JSOp::Add && op_ != JSOp::Sub && op_ != JSOp::Mul &&
+ op_ != JSOp::Div && op_ != JSOp::Mod && op_ != JSOp::Pow) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check guard conditions
+ if (!lhs_.isNumber() || !rhs_.isNumber()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId lhsId(writer.setInputOperandId(0));
+ ValOperandId rhsId(writer.setInputOperandId(1));
+
+ NumberOperandId lhs = writer.guardIsNumber(lhsId);
+ NumberOperandId rhs = writer.guardIsNumber(rhsId);
+
+ switch (op_) {
+ case JSOp::Add:
+ writer.doubleAddResult(lhs, rhs);
+ trackAttached("BinaryArith.Double.Add");
+ break;
+ case JSOp::Sub:
+ writer.doubleSubResult(lhs, rhs);
+ trackAttached("BinaryArith.Double.Sub");
+ break;
+ case JSOp::Mul:
+ writer.doubleMulResult(lhs, rhs);
+ trackAttached("BinaryArith.Double.Mul");
+ break;
+ case JSOp::Div:
+ writer.doubleDivResult(lhs, rhs);
+ trackAttached("BinaryArith.Double.Div");
+ break;
+ case JSOp::Mod:
+ writer.doubleModResult(lhs, rhs);
+ trackAttached("BinaryArith.Double.Mod");
+ break;
+ case JSOp::Pow:
+ writer.doublePowResult(lhs, rhs);
+ trackAttached("BinaryArith.Double.Pow");
+ break;
+ default:
+ MOZ_CRASH("Unhandled Op");
+ }
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachInt32() {
+ // Check guard conditions
+ if (!(lhs_.isInt32() || lhs_.isBoolean()) ||
+ !(rhs_.isInt32() || rhs_.isBoolean())) {
+ return AttachDecision::NoAction;
+ }
+
+ // These ICs will failure() if result can't be encoded in an Int32:
+ // If sample result is not Int32, we should avoid IC.
+ if (!res_.isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ if (op_ != JSOp::Add && op_ != JSOp::Sub && op_ != JSOp::Mul &&
+ op_ != JSOp::Div && op_ != JSOp::Mod && op_ != JSOp::Pow) {
+ return AttachDecision::NoAction;
+ }
+
+ if (op_ == JSOp::Pow && !CanAttachInt32Pow(lhs_, rhs_)) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId lhsId(writer.setInputOperandId(0));
+ ValOperandId rhsId(writer.setInputOperandId(1));
+
+ auto guardToInt32 = [&](ValOperandId id, HandleValue v) {
+ if (v.isInt32()) {
+ return writer.guardToInt32(id);
+ }
+ MOZ_ASSERT(v.isBoolean());
+ return writer.guardBooleanToInt32(id);
+ };
+
+ Int32OperandId lhsIntId = guardToInt32(lhsId, lhs_);
+ Int32OperandId rhsIntId = guardToInt32(rhsId, rhs_);
+
+ switch (op_) {
+ case JSOp::Add:
+ writer.int32AddResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.Int32.Add");
+ break;
+ case JSOp::Sub:
+ writer.int32SubResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.Int32.Sub");
+ break;
+ case JSOp::Mul:
+ writer.int32MulResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.Int32.Mul");
+ break;
+ case JSOp::Div:
+ writer.int32DivResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.Int32.Div");
+ break;
+ case JSOp::Mod:
+ writer.int32ModResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.Int32.Mod");
+ break;
+ case JSOp::Pow:
+ writer.int32PowResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.Int32.Pow");
+ break;
+ default:
+ MOZ_CRASH("Unhandled op in tryAttachInt32");
+ }
+
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachStringNumberConcat() {
+ // Only Addition
+ if (op_ != JSOp::Add) {
+ return AttachDecision::NoAction;
+ }
+
+ if (!(lhs_.isString() && rhs_.isNumber()) &&
+ !(lhs_.isNumber() && rhs_.isString())) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId lhsId(writer.setInputOperandId(0));
+ ValOperandId rhsId(writer.setInputOperandId(1));
+
+ StringOperandId lhsStrId = EmitToStringGuard(writer, lhsId, lhs_);
+ StringOperandId rhsStrId = EmitToStringGuard(writer, rhsId, rhs_);
+
+ writer.callStringConcatResult(lhsStrId, rhsStrId);
+
+ writer.returnFromIC();
+ trackAttached("BinaryArith.StringNumberConcat");
+ return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachStringBooleanConcat() {
+ // Only Addition
+ if (op_ != JSOp::Add) {
+ return AttachDecision::NoAction;
+ }
+
+ if ((!lhs_.isString() || !rhs_.isBoolean()) &&
+ (!lhs_.isBoolean() || !rhs_.isString())) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId lhsId(writer.setInputOperandId(0));
+ ValOperandId rhsId(writer.setInputOperandId(1));
+
+ auto guardToString = [&](ValOperandId id, HandleValue v) {
+ if (v.isString()) {
+ return writer.guardToString(id);
+ }
+ MOZ_ASSERT(v.isBoolean());
+ BooleanOperandId boolId = writer.guardToBoolean(id);
+ return writer.booleanToString(boolId);
+ };
+
+ StringOperandId lhsStrId = guardToString(lhsId, lhs_);
+ StringOperandId rhsStrId = guardToString(rhsId, rhs_);
+
+ writer.callStringConcatResult(lhsStrId, rhsStrId);
+
+ writer.returnFromIC();
+ trackAttached("BinaryArith.StringBooleanConcat");
+ return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachStringConcat() {
+ // Only Addition
+ if (op_ != JSOp::Add) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check guards
+ if (!lhs_.isString() || !rhs_.isString()) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId lhsId(writer.setInputOperandId(0));
+ ValOperandId rhsId(writer.setInputOperandId(1));
+
+ StringOperandId lhsStrId = writer.guardToString(lhsId);
+ StringOperandId rhsStrId = writer.guardToString(rhsId);
+
+ writer.callStringConcatResult(lhsStrId, rhsStrId);
+
+ writer.returnFromIC();
+ trackAttached("BinaryArith.StringConcat");
+ return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachStringObjectConcat() {
+ // Only Addition
+ if (op_ != JSOp::Add) {
+ return AttachDecision::NoAction;
+ }
+
+ // Check Guards
+ if (!(lhs_.isObject() && rhs_.isString()) &&
+ !(lhs_.isString() && rhs_.isObject()))
+ return AttachDecision::NoAction;
+
+ ValOperandId lhsId(writer.setInputOperandId(0));
+ ValOperandId rhsId(writer.setInputOperandId(1));
+
+ // This guard is actually overly tight, as the runtime
+ // helper can handle lhs or rhs being a string, so long
+ // as the other is an object.
+ if (lhs_.isString()) {
+ writer.guardToString(lhsId);
+ writer.guardToObject(rhsId);
+ } else {
+ writer.guardToObject(lhsId);
+ writer.guardToString(rhsId);
+ }
+
+ writer.callStringObjectConcatResult(lhsId, rhsId);
+
+ writer.returnFromIC();
+ trackAttached("BinaryArith.StringObjectConcat");
+ return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachBigInt() {
+ // Check Guards
+ if (!lhs_.isBigInt() || !rhs_.isBigInt()) {
+ return AttachDecision::NoAction;
+ }
+
+ switch (op_) {
+ case JSOp::Add:
+ case JSOp::Sub:
+ case JSOp::Mul:
+ case JSOp::Div:
+ case JSOp::Mod:
+ case JSOp::Pow:
+ // Arithmetic operations.
+ break;
+
+ case JSOp::BitOr:
+ case JSOp::BitXor:
+ case JSOp::BitAnd:
+ case JSOp::Lsh:
+ case JSOp::Rsh:
+ // Bitwise operations.
+ break;
+
+ default:
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId lhsId(writer.setInputOperandId(0));
+ ValOperandId rhsId(writer.setInputOperandId(1));
+
+ BigIntOperandId lhsBigIntId = writer.guardToBigInt(lhsId);
+ BigIntOperandId rhsBigIntId = writer.guardToBigInt(rhsId);
+
+ switch (op_) {
+ case JSOp::Add:
+ writer.bigIntAddResult(lhsBigIntId, rhsBigIntId);
+ trackAttached("BinaryArith.BigInt.Add");
+ break;
+ case JSOp::Sub:
+ writer.bigIntSubResult(lhsBigIntId, rhsBigIntId);
+ trackAttached("BinaryArith.BigInt.Sub");
+ break;
+ case JSOp::Mul:
+ writer.bigIntMulResult(lhsBigIntId, rhsBigIntId);
+ trackAttached("BinaryArith.BigInt.Mul");
+ break;
+ case JSOp::Div:
+ writer.bigIntDivResult(lhsBigIntId, rhsBigIntId);
+ trackAttached("BinaryArith.BigInt.Div");
+ break;
+ case JSOp::Mod:
+ writer.bigIntModResult(lhsBigIntId, rhsBigIntId);
+ trackAttached("BinaryArith.BigInt.Mod");
+ break;
+ case JSOp::Pow:
+ writer.bigIntPowResult(lhsBigIntId, rhsBigIntId);
+ trackAttached("BinaryArith.BigInt.Pow");
+ break;
+ case JSOp::BitOr:
+ writer.bigIntBitOrResult(lhsBigIntId, rhsBigIntId);
+ trackAttached("BinaryArith.BigInt.BitOr");
+ break;
+ case JSOp::BitXor:
+ writer.bigIntBitXorResult(lhsBigIntId, rhsBigIntId);
+ trackAttached("BinaryArith.BigInt.BitXor");
+ break;
+ case JSOp::BitAnd:
+ writer.bigIntBitAndResult(lhsBigIntId, rhsBigIntId);
+ trackAttached("BinaryArith.BigInt.BitAnd");
+ break;
+ case JSOp::Lsh:
+ writer.bigIntLeftShiftResult(lhsBigIntId, rhsBigIntId);
+ trackAttached("BinaryArith.BigInt.LeftShift");
+ break;
+ case JSOp::Rsh:
+ writer.bigIntRightShiftResult(lhsBigIntId, rhsBigIntId);
+ trackAttached("BinaryArith.BigInt.RightShift");
+ break;
+ default:
+ MOZ_CRASH("Unhandled op in tryAttachBigInt");
+ }
+
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+AttachDecision BinaryArithIRGenerator::tryAttachStringInt32Arith() {
+ // Check for either int32 x string or string x int32.
+ if (!(lhs_.isInt32() && rhs_.isString()) &&
+ !(lhs_.isString() && rhs_.isInt32())) {
+ return AttachDecision::NoAction;
+ }
+
+ // The created ICs will fail if the result can't be encoded as as int32.
+ // Thus skip this IC, if the sample result is not an int32.
+ if (!res_.isInt32()) {
+ return AttachDecision::NoAction;
+ }
+
+ // Must _not_ support Add, because it would be string concatenation instead.
+ // For Pow we can't easily determine the CanAttachInt32Pow conditions so we
+ // reject that as well.
+ if (op_ != JSOp::Sub && op_ != JSOp::Mul && op_ != JSOp::Div &&
+ op_ != JSOp::Mod) {
+ return AttachDecision::NoAction;
+ }
+
+ ValOperandId lhsId(writer.setInputOperandId(0));
+ ValOperandId rhsId(writer.setInputOperandId(1));
+
+ auto guardToInt32 = [&](ValOperandId id, HandleValue v) {
+ if (v.isInt32()) {
+ return writer.guardToInt32(id);
+ }
+
+ MOZ_ASSERT(v.isString());
+ StringOperandId strId = writer.guardToString(id);
+ return writer.guardStringToInt32(strId);
+ };
+
+ Int32OperandId lhsIntId = guardToInt32(lhsId, lhs_);
+ Int32OperandId rhsIntId = guardToInt32(rhsId, rhs_);
+
+ switch (op_) {
+ case JSOp::Sub:
+ writer.int32SubResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.StringInt32.Sub");
+ break;
+ case JSOp::Mul:
+ writer.int32MulResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.StringInt32.Mul");
+ break;
+ case JSOp::Div:
+ writer.int32DivResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.StringInt32.Div");
+ break;
+ case JSOp::Mod:
+ writer.int32ModResult(lhsIntId, rhsIntId);
+ trackAttached("BinaryArith.StringInt32.Mod");
+ break;
+ default:
+ MOZ_CRASH("Unhandled op in tryAttachStringInt32Arith");
+ }
+
+ writer.returnFromIC();
+ return AttachDecision::Attach;
+}
+
+NewObjectIRGenerator::NewObjectIRGenerator(JSContext* cx, HandleScript script,
+ jsbytecode* pc, ICState::Mode mode,
+ JSOp op, HandleObject templateObj)
+ : IRGenerator(cx, script, pc, CacheKind::NewObject, mode),
+#ifdef JS_CACHEIR_SPEW
+ op_(op),
+#endif
+ templateObject_(templateObj) {
+ MOZ_ASSERT(templateObject_);
+}
+
+void NewObjectIRGenerator::trackAttached(const char* name) {
+#ifdef JS_CACHEIR_SPEW
+ if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
+ sp.opcodeProperty("op", op_);
+ }
+#endif
+}
+
+AttachDecision NewObjectIRGenerator::tryAttachStub() {
+ AutoAssertNoPendingException aanpe(cx_);
+ if (templateObject_->as<NativeObject>().hasDynamicSlots()) {
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ // Stub doesn't support metadata builder
+ if (cx_->realm()->hasAllocationMetadataBuilder()) {
+ trackAttached(IRGenerator::NotAttached);
+ return AttachDecision::NoAction;
+ }
+
+ writer.guardNoAllocationMetadataBuilder();
+
+ // Bake in a monotonically increasing number to ensure we differentiate
+ // between different baseline stubs that otherwise might share stub code.
+ uint64_t id = cx_->runtime()->jitRuntime()->nextDisambiguationId();
+ uint32_t idHi = id >> 32;
+ uint32_t idLo = id & UINT32_MAX;
+ writer.loadNewObjectFromTemplateResult(templateObject_, idHi, idLo);
+
+ writer.returnFromIC();
+
+ trackAttached("NewObjectWithTemplate");
+ return AttachDecision::Attach;
+}
+
+#ifdef JS_SIMULATOR
+bool js::jit::CallAnyNative(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ JSObject* calleeObj = &args.callee();
+
+ MOZ_ASSERT(calleeObj->is<JSFunction>());
+ auto* calleeFunc = &calleeObj->as<JSFunction>();
+ MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry());
+
+ JSNative native = calleeFunc->native();
+ return native(cx, args.length(), args.base());
+}
+#endif