From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/jit/CacheIR.cpp | 14238 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 14238 insertions(+) create mode 100644 js/src/jit/CacheIR.cpp (limited to 'js/src/jit/CacheIR.cpp') diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp new file mode 100644 index 0000000000..c2245e38b5 --- /dev/null +++ b/js/src/jit/CacheIR.cpp @@ -0,0 +1,14238 @@ +/* -*- 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 "jsapi.h" +#include "jsmath.h" +#include "jsnum.h" + +#include "builtin/DataViewObject.h" +#include "builtin/MapObject.h" +#include "builtin/ModuleObject.h" +#include "builtin/Object.h" +#include "jit/BaselineIC.h" +#include "jit/CacheIRCloner.h" +#include "jit/CacheIRCompiler.h" +#include "jit/CacheIRGenerator.h" +#include "jit/CacheIRSpewer.h" +#include "jit/CacheIRWriter.h" +#include "jit/InlinableNatives.h" +#include "jit/JitContext.h" +#include "jit/JitZone.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/GCAPI.h" // JS::AutoSuppressGCAnalysis +#include "js/Prefs.h" // JS::Prefs +#include "js/RegExpFlags.h" // JS::RegExpFlags +#include "js/ScalarType.h" // js::Scalar::Type +#include "js/Utility.h" // JS::AutoEnterOOMUnsafeRegion +#include "js/Wrapper.h" +#include "proxy/DOMProxy.h" // js::GetDOMProxyHandlerFamily +#include "proxy/ScriptedProxyHandler.h" +#include "util/DifferentialTesting.h" +#include "util/Unicode.h" +#include "vm/ArrayBufferObject.h" +#include "vm/BoundFunctionObject.h" +#include "vm/BytecodeUtil.h" +#include "vm/Compartment.h" +#include "vm/Iteration.h" +#include "vm/PlainObject.h" // js::PlainObject +#include "vm/ProxyObject.h" +#include "vm/RegExpObject.h" +#include "vm/SelfHosting.h" +#include "vm/ThrowMsgKind.h" // ThrowCondition +#include "vm/Watchtower.h" +#include "wasm/WasmInstance.h" + +#include "jit/BaselineFrame-inl.h" +#include "jit/MacroAssembler-inl.h" +#include "vm/ArrayBufferObject-inl.h" +#include "vm/BytecodeUtil-inl.h" +#include "vm/EnvironmentObject-inl.h" +#include "vm/JSContext-inl.h" +#include "vm/JSFunction-inl.h" +#include "vm/JSObject-inl.h" +#include "vm/JSScript-inl.h" +#include "vm/List-inl.h" +#include "vm/NativeObject-inl.h" +#include "vm/PlainObject-inl.h" +#include "vm/StringObject-inl.h" +#include "wasm/WasmInstance-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 +}; + +size_t js::jit::NumInputsForCacheKind(CacheKind kind) { + switch (kind) { + case CacheKind::NewArray: + 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: + case CacheKind::CloseIter: + case CacheKind::OptimizeGetIterator: + 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"); +} + +#ifdef DEBUG +void CacheIRWriter::assertSameCompartment(JSObject* obj) { + MOZ_ASSERT(cx_->compartment() == obj->compartment()); +} +void CacheIRWriter::assertSameZone(Shape* shape) { + MOZ_ASSERT(cx_->zone() == shape->zone()); +} +#endif + +StubField CacheIRWriter::readStubField(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(readStubWord(stubOffset)); +} +Shape* CacheIRCloner::getWeakShapeField(uint32_t stubOffset) { + // No barrier is required to clone a weak pointer. + return reinterpret_cast(readStubWord(stubOffset)); +} +GetterSetter* CacheIRCloner::getWeakGetterSetterField(uint32_t stubOffset) { + // No barrier is required to clone a weak pointer. + return reinterpret_cast(readStubWord(stubOffset)); +} +JSObject* CacheIRCloner::getObjectField(uint32_t stubOffset) { + return reinterpret_cast(readStubWord(stubOffset)); +} +JSObject* CacheIRCloner::getWeakObjectField(uint32_t stubOffset) { + // No barrier is required to clone a weak pointer. + return reinterpret_cast(readStubWord(stubOffset)); +} +JSString* CacheIRCloner::getStringField(uint32_t stubOffset) { + return reinterpret_cast(readStubWord(stubOffset)); +} +JSAtom* CacheIRCloner::getAtomField(uint32_t stubOffset) { + return reinterpret_cast(readStubWord(stubOffset)); +} +JS::Symbol* CacheIRCloner::getSymbolField(uint32_t stubOffset) { + return reinterpret_cast(readStubWord(stubOffset)); +} +BaseScript* CacheIRCloner::getWeakBaseScriptField(uint32_t stubOffset) { + // No barrier is required to clone a weak pointer. + return reinterpret_cast(readStubWord(stubOffset)); +} +JitCode* CacheIRCloner::getJitCodeField(uint32_t stubOffset) { + return reinterpret_cast(readStubWord(stubOffset)); +} +uint32_t CacheIRCloner::getRawInt32Field(uint32_t stubOffset) { + return uint32_t(reinterpret_cast(readStubWord(stubOffset))); +} +const void* CacheIRCloner::getRawPointerField(uint32_t stubOffset) { + return reinterpret_cast(readStubWord(stubOffset)); +} +uint64_t CacheIRCloner::getRawInt64Field(uint32_t stubOffset) { + return static_cast(readStubInt64(stubOffset)); +} +gc::AllocSite* CacheIRCloner::getAllocSiteField(uint32_t stubOffset) { + return reinterpret_cast(readStubWord(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))); +} +double CacheIRCloner::getDoubleField(uint32_t stubOffset) { + uint64_t bits = uint64_t(readStubInt64(stubOffset)); + return mozilla::BitwiseCast(bits); +} + +IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, + CacheKind cacheKind, ICState state) + : writer(cx), + cx_(cx), + script_(script), + pc_(pc), + cacheKind_(cacheKind), + mode_(state.mode()), + isFirstStub_(state.newStubIsFirstStub()), + numOptimizedStubs_(state.numOptimizedStubs()) {} + +GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script, + jsbytecode* pc, ICState state, + CacheKind cacheKind, HandleValue val, + HandleValue idVal) + : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {} + +static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderId, + NativeObject* holder, PropertyInfo prop) { + if (holder->isFixedSlot(prop.slot())) { + writer.loadFixedSlotResult(holderId, + NativeObject::getFixedSlotOffset(prop.slot())); + } else { + size_t dynamicSlotOffset = + holder->dynamicSlotIndex(prop.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(ProxyObject* obj) { + const BaseProxyHandler* handler = obj->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()) { + return ProxyStubType::None; + } + auto proxy = obj.as(); + + if (!IsCacheableDOMProxy(proxy)) { + return ProxyStubType::Generic; + } + + // Private fields are defined on a separate expando object. + if (id.isPrivateName()) { + return ProxyStubType::Generic; + } + + DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, proxy, 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() && !idVal.isUndefined() && + !idVal.isNull()) { + return true; + } + + if (!PrimitiveValueToId(cx, idVal, id)) { + return false; + } + + if (!id.isAtom() && !id.isSymbol()) { + id.set(JS::PropertyKey::Void()); + return true; + } + + if (id.isAtom() && id.toAtom()->isIndex()) { + id.set(JS::PropertyKey::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(tryAttachTypedArray(obj, objId, id)); + TRY_ATTACH(tryAttachDataView(obj, objId, id)); + TRY_ATTACH(tryAttachArrayBufferMaybeShared(obj, objId, id)); + TRY_ATTACH(tryAttachRegExp(obj, objId, id)); + TRY_ATTACH(tryAttachMap(obj, objId, id)); + TRY_ATTACH(tryAttachSet(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(tryAttachArgumentsObjectCallee(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)); + TRY_ATTACH(tryAttachTypedArrayElement(obj, objId)); + + uint32_t index; + Int32OperandId indexId; + if (maybeGuardInt32Index(idVal_, getElemKeyValueId(), &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(tryAttachArgumentsObjectArgHole(obj, objId, index, indexId)); + TRY_ATTACH( + tryAttachGenericElement(obj, objId, index, indexId, receiverId)); + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; + } + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; + } + + if (nameOrSymbol) { + TRY_ATTACH(tryAttachPrimitive(valId, id)); + TRY_ATTACH(tryAttachStringLength(valId, id)); + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; + } + + if (idVal_.isInt32()) { + ValOperandId indexId = getElemKeyValueId(); + TRY_ATTACH(tryAttachStringChar(valId, indexId)); + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; + } + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; +} + +#ifdef DEBUG +// Any property lookups performed when trying to attach ICs must be pure, i.e. +// must use LookupPropertyPure() or similar functions. Pure lookups are +// guaranteed to never modify the prototype chain. This ensures that the holder +// object can always be found on the prototype chain. +static bool IsCacheableProtoChain(NativeObject* obj, NativeObject* holder) { + while (obj != holder) { + JSObject* proto = obj->staticPrototype(); + if (!proto || !proto->is()) { + return false; + } + obj = &proto->as(); + } + return true; +} +#endif + +static bool IsCacheableGetPropSlot(NativeObject* obj, NativeObject* holder, + PropertyInfo prop) { + MOZ_ASSERT(IsCacheableProtoChain(obj, holder)); + + return prop.isDataProperty(); +} + +enum class NativeGetPropKind { + None, + Missing, + Slot, + NativeGetter, + ScriptedGetter, +}; + +static NativeGetPropKind IsCacheableGetPropCall(NativeObject* obj, + NativeObject* holder, + PropertyInfo prop) { + MOZ_ASSERT(IsCacheableProtoChain(obj, holder)); + + if (!prop.isAccessorProperty()) { + return NativeGetPropKind::None; + } + + JSObject* getterObject = holder->getGetter(prop); + if (!getterObject || !getterObject->is()) { + return NativeGetPropKind::None; + } + + JSFunction& getter = getterObject->as(); + + if (getter.isClassConstructor()) { + return NativeGetPropKind::None; + } + + // Scripted functions and natives with JIT entry can use the scripted path. + if (getter.hasJitEntry()) { + return NativeGetPropKind::ScriptedGetter; + } + + MOZ_ASSERT(getter.isNativeWithoutJitEntry()); + return NativeGetPropKind::NativeGetter; +} + +static bool CheckHasNoSuchOwnProperty(JSContext* cx, JSObject* obj, jsid id) { + if (!obj->is()) { + return false; + } + // Don't handle objects with resolve hooks. + if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj)) { + return false; + } + if (obj->as().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, NativeObject* obj, + NativeObject* holder, jsid id, + jsbytecode* pc) { + 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 NativeGetPropKind CanAttachNativeGetProp(JSContext* cx, JSObject* obj, + PropertyKey id, + NativeObject** holder, + Maybe* propInfo, + jsbytecode* pc) { + MOZ_ASSERT(id.isString() || id.isSymbol()); + MOZ_ASSERT(!*holder); + + // 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. + NativeObject* baseHolder = nullptr; + PropertyResult prop; + if (!LookupPropertyPure(cx, obj, id, &baseHolder, &prop)) { + return NativeGetPropKind::None; + } + auto* nobj = &obj->as(); + + if (prop.isNativeProperty()) { + MOZ_ASSERT(baseHolder); + *holder = baseHolder; + *propInfo = mozilla::Some(prop.propertyInfo()); + + if (IsCacheableGetPropSlot(nobj, *holder, propInfo->ref())) { + return NativeGetPropKind::Slot; + } + + return IsCacheableGetPropCall(nobj, *holder, propInfo->ref()); + } + + if (!prop.isFound()) { + if (IsCacheableNoProperty(cx, nobj, *holder, id, pc)) { + return NativeGetPropKind::Missing; + } + } + + return NativeGetPropKind::None; +} + +static void GuardReceiverProto(CacheIRWriter& writer, NativeObject* obj, + ObjOperandId objId) { + // Note: we guard on the actual prototype and not on the shape because this is + // used for sparse elements where we expect shape changes. + + if (JSObject* proto = obj->staticPrototype()) { + writer.guardProto(objId, proto); + } else { + writer.guardNullProto(objId); + } +} + +// Guard that a given object has same class and same OwnProperties (excluding +// dense elements and dynamic properties). +static void TestMatchingNativeReceiver(CacheIRWriter& writer, NativeObject* obj, + ObjOperandId objId) { + writer.guardShapeForOwnProperties(objId, obj->shape()); +} + +// Similar to |TestMatchingNativeReceiver|, but specialized for ProxyObject. +static void TestMatchingProxyReceiver(CacheIRWriter& writer, ProxyObject* obj, + ObjOperandId objId) { + writer.guardShapeForClass(objId, obj->shape()); +} + +static void GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj, + NativeObject* 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 whenever C starts + // shadowing a property on its proto chain, we invalidate (and opt out of) the + // teleporting optimization by setting the InvalidatedTeleporting flag on the + // object we're shadowing, triggering a shape change of that object. 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 by setting the InvalidatedTeleporting flag on them. This means + // the same two shape checks as above are sufficient. + // + // Once the InvalidatedTeleporting flag is set, it means the shape will no + // longer be changed by ReshapeForProtoMutation and ReshapeForShadowedProp. + // In this case we can no longer apply the optimization. + // + // See: + // - ReshapeForProtoMutation + // - ReshapeForShadowedProp + + MOZ_ASSERT(holder); + MOZ_ASSERT(obj != holder); + + // Receiver guards (see TestMatchingReceiver) ensure the receiver's proto is + // unchanged so peel off the receiver. + JSObject* pobj = obj->staticPrototype(); + MOZ_ASSERT(pobj->isUsedAsPrototype()); + + // If teleporting is supported for this holder, we are done. + if (!holder->hasInvalidatedTeleporting()) { + return; + } + + // If already at the holder, no further proto checks are needed. + if (pobj == holder) { + return; + } + + // Synchronize pobj and protoId. + MOZ_ASSERT(pobj == obj->staticPrototype()); + ObjOperandId protoId = writer.loadProto(objId); + + // Shape guard each prototype object between receiver and holder. This guards + // against both proto changes and shadowing properties. + while (pobj != holder) { + writer.guardShape(protoId, pobj->shape()); + + pobj = pobj->staticPrototype(); + protoId = writer.loadProto(protoId); + } +} + +static void GeneratePrototypeHoleGuards(CacheIRWriter& writer, + NativeObject* obj, ObjOperandId objId, + bool alwaysGuardFirstProto) { + if (alwaysGuardFirstProto) { + GuardReceiverProto(writer, obj, objId); + } + + JSObject* pobj = obj->staticPrototype(); + while (pobj) { + ObjOperandId protoId = writer.loadObject(pobj); + + // Make sure the shape matches, to ensure the proto is unchanged and to + // avoid non-dense elements or anything else that is being checked by + // CanAttachDenseElementHole. + MOZ_ASSERT(pobj->is()); + writer.guardShape(protoId, pobj->shape()); + + // 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, NativeObject* obj, + ObjOperandId objId) { + // The GeneratePrototypeGuards + TestMatchingHolder checks only support + // prototype chains composed of NativeObject (excluding the receiver + // itself). + writer.guardShapeForOwnProperties(objId, obj->shape()); +} + +enum class IsCrossCompartment { No, Yes }; + +// Emit a shape guard for all objects on the proto chain. This does NOT include +// the receiver; callers must ensure the receiver's proto is the first proto by +// either emitting a shape guard or a prototype guard for |objId|. +// +// Note: this relies on shape implying proto. +template +static void ShapeGuardProtoChain(CacheIRWriter& writer, NativeObject* obj, + ObjOperandId objId) { + uint32_t depth = 0; + static const uint32_t MAX_CACHED_LOADS = 4; + ObjOperandId receiverObjId = objId; + + while (true) { + JSObject* proto = obj->staticPrototype(); + if (!proto) { + return; + } + + obj = &proto->as(); + + // After guarding the shape of an object, we can safely bake that + // object's proto into the stub data. Compared to LoadProto, this + // takes one load instead of three (object -> shape -> baseshape + // -> proto). We cap the depth to avoid bloating the size of the + // stub data. To avoid compartment mismatch, we skip this optimization + // in the cross-compartment case. + if (depth < MAX_CACHED_LOADS && + MaybeCrossCompartment == IsCrossCompartment::No) { + objId = writer.loadProtoObject(obj, receiverObjId); + } else { + objId = writer.loadProto(objId); + } + depth++; + + writer.guardShape(objId, obj->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. +// +// Returns the holder's OperandId. +static ObjOperandId ShapeGuardProtoChainForCrossCompartmentHolder( + CacheIRWriter& writer, NativeObject* obj, ObjOperandId objId, + NativeObject* holder) { + MOZ_ASSERT(obj != holder); + MOZ_ASSERT(holder); + while (true) { + MOZ_ASSERT(obj->staticPrototype()); + obj = &obj->staticPrototype()->as(); + + objId = writer.loadProto(objId); + if (obj == holder) { + TestMatchingHolder(writer, obj, objId); + return objId; + } + writer.guardShapeForOwnProperties(objId, obj->shape()); + } +} + +// Emit guards for reading a data property on |holder|. Returns the holder's +// OperandId. +template +static ObjOperandId EmitReadSlotGuard(CacheIRWriter& writer, NativeObject* obj, + NativeObject* holder, + ObjOperandId objId) { + MOZ_ASSERT(holder); + TestMatchingNativeReceiver(writer, obj, objId); + + if (obj == holder) { + return objId; + } + + if (MaybeCrossCompartment == IsCrossCompartment::Yes) { + // Guard proto chain integrity. + // We use a variant of guards that avoid baking in any cross-compartment + // object pointers. + return ShapeGuardProtoChainForCrossCompartmentHolder(writer, obj, objId, + holder); + } + + // Guard proto chain integrity. + GeneratePrototypeGuards(writer, obj, holder, objId); + + // Guard on the holder's shape. + ObjOperandId holderId = writer.loadObject(holder); + TestMatchingHolder(writer, holder, holderId); + return holderId; +} + +template +static void EmitMissingPropGuard(CacheIRWriter& writer, NativeObject* obj, + ObjOperandId objId) { + TestMatchingNativeReceiver(writer, obj, objId); + + // 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); +} + +template +static void EmitReadSlotResult(CacheIRWriter& writer, NativeObject* obj, + NativeObject* holder, PropertyInfo prop, + ObjOperandId objId) { + MOZ_ASSERT(holder); + + ObjOperandId holderId = + EmitReadSlotGuard(writer, obj, holder, objId); + + MOZ_ASSERT(holderId.valid()); + EmitLoadSlotResult(writer, holderId, holder, prop); +} + +template +static void EmitMissingPropResult(CacheIRWriter& writer, NativeObject* obj, + ObjOperandId objId) { + EmitMissingPropGuard(writer, obj, objId); + writer.loadUndefinedResult(); +} + +static void EmitCallGetterResultNoGuards(JSContext* cx, CacheIRWriter& writer, + NativeGetPropKind kind, + NativeObject* obj, + NativeObject* holder, + PropertyInfo prop, + ValOperandId receiverId) { + MOZ_ASSERT(IsCacheableGetPropCall(obj, holder, prop) == kind); + + JSFunction* target = &holder->getGetter(prop)->as(); + bool sameRealm = cx->realm() == target->realm(); + + switch (kind) { + case NativeGetPropKind::NativeGetter: { + writer.callNativeGetterResult(receiverId, target, sameRealm); + writer.returnFromIC(); + break; + } + case NativeGetPropKind::ScriptedGetter: { + 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; + } +} + +// See the SMDOC comment in vm/GetterSetter.h for more info on Getter/Setter +// properties +static void EmitGuardGetterSetterSlot(CacheIRWriter& writer, + NativeObject* holder, PropertyInfo prop, + ObjOperandId holderId, + bool holderIsConstant = false) { + // If the holder is guaranteed to be the same object, and it never had a + // slot holding a GetterSetter mutated or deleted, its Shape will change when + // that does happen so we don't need to guard on the GetterSetter. + if (holderIsConstant && !holder->hadGetterSetterChange()) { + return; + } + + size_t slot = prop.slot(); + Value slotVal = holder->getSlot(slot); + MOZ_ASSERT(slotVal.isPrivateGCThing()); + + if (holder->isFixedSlot(slot)) { + size_t offset = NativeObject::getFixedSlotOffset(slot); + writer.guardFixedSlotValue(holderId, offset, slotVal); + } else { + size_t offset = holder->dynamicSlotIndex(slot) * sizeof(Value); + writer.guardDynamicSlotValue(holderId, offset, slotVal); + } +} + +static void EmitCallGetterResultGuards(CacheIRWriter& writer, NativeObject* obj, + NativeObject* holder, HandleId id, + PropertyInfo prop, 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). + + MOZ_ASSERT(holder->containsPure(id, prop)); + + if (mode == ICState::Mode::Specialized || IsWindow(obj)) { + TestMatchingNativeReceiver(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); + + EmitGuardGetterSetterSlot(writer, holder, prop, holderId, + /* holderIsConstant = */ true); + } else { + EmitGuardGetterSetterSlot(writer, holder, prop, objId); + } + } else { + GetterSetter* gs = holder->getGetterSetter(prop); + writer.guardHasGetterSetter(objId, id, gs); + } +} + +static void EmitCallGetterResult(JSContext* cx, CacheIRWriter& writer, + NativeGetPropKind kind, NativeObject* obj, + NativeObject* holder, HandleId id, + PropertyInfo prop, ObjOperandId objId, + ValOperandId receiverId, ICState::Mode mode) { + EmitCallGetterResultGuards(writer, obj, holder, id, prop, objId, mode); + EmitCallGetterResultNoGuards(cx, writer, kind, obj, holder, prop, 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(); + if (jitInfo->type() != type) { + return false; + } + + MOZ_ASSERT_IF(IsWindow(obj), !jitInfo->needsOuterizedThisObject()); + + const JSClass* clasp = obj->getClass(); + if (!clasp->isDOMClass()) { + return false; + } + + if (type != JSJitInfo::Method && clasp->isProxyObject()) { + return false; + } + + // Ion codegen expects DOM_OBJECT_SLOT to be a fixed slot in LoadDOMPrivate. + // It can be a dynamic slot if we transplanted this reflector object with a + // proxy. + if (obj->is() && obj->as().numFixedSlots() == 0) { + return false; + } + + // Tell the analysis the |DOMInstanceClassHasProtoAtDepth| hook can't GC. + JS::AutoSuppressGCAnalysis nogc; + + DOMInstanceClassHasProtoAtDepth instanceChecker = + cx->runtime()->DOMcallbacks->instanceClassMatchesProto; + return instanceChecker(clasp, jitInfo->protoID, jitInfo->depth); +} + +static bool CanAttachDOMGetterSetter(JSContext* cx, JSJitInfo::OpType type, + NativeObject* obj, NativeObject* holder, + PropertyInfo prop, ICState::Mode mode) { + MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter); + + JSObject* accessor = type == JSJitInfo::Getter ? holder->getGetter(prop) + : holder->getSetter(prop); + JSFunction* fun = &accessor->as(); + + return CanAttachDOMCall(cx, type, obj, fun, mode); +} + +static void EmitCallDOMGetterResultNoGuards(CacheIRWriter& writer, + NativeObject* holder, + PropertyInfo prop, + ObjOperandId objId) { + JSFunction* getter = &holder->getGetter(prop)->as(); + writer.callDOMGetterResult(objId, getter->jitInfo()); + writer.returnFromIC(); +} + +static void EmitCallDOMGetterResult(JSContext* cx, CacheIRWriter& writer, + NativeObject* obj, NativeObject* holder, + HandleId id, PropertyInfo prop, + 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, id, prop, objId, + ICState::Mode::Specialized); + EmitCallDOMGetterResultNoGuards(writer, holder, prop, objId); +} + +static ValOperandId EmitLoadSlot(CacheIRWriter& writer, NativeObject* holder, + ObjOperandId holderId, uint32_t slot) { + if (holder->isFixedSlot(slot)) { + return writer.loadFixedSlot(holderId, + NativeObject::getFixedSlotOffset(slot)); + } + size_t dynamicSlotIndex = holder->dynamicSlotIndex(slot); + return writer.loadDynamicSlot(holderId, dynamicSlotIndex); +} + +void GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId, + jsid id) { + MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic); + + // We don't support GetBoundName because environment objects have + // lookupProperty hooks and GetBoundName is usually not megamorphic. + MOZ_ASSERT(JSOp(*pc_) != JSOp::GetBoundName); + + if (cacheKind_ == CacheKind::GetProp || + cacheKind_ == CacheKind::GetPropSuper) { + writer.megamorphicLoadSlotResult(objId, id); + } else { + MOZ_ASSERT(cacheKind_ == CacheKind::GetElem || + cacheKind_ == CacheKind::GetElemSuper); + writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId()); + } + writer.returnFromIC(); + + trackAttached("GetProp.MegamorphicNativeSlot"); +} + +AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj, + ObjOperandId objId, + HandleId id, + ValOperandId receiverId) { + Maybe prop; + NativeObject* holder = nullptr; + + NativeGetPropKind kind = + CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_); + switch (kind) { + case NativeGetPropKind::None: + return AttachDecision::NoAction; + case NativeGetPropKind::Missing: + case NativeGetPropKind::Slot: { + auto* nobj = &obj->as(); + + if (mode_ == ICState::Mode::Megamorphic && + JSOp(*pc_) != JSOp::GetBoundName) { + attachMegamorphicNativeSlot(objId, id); + return AttachDecision::Attach; + } + + maybeEmitIdGuard(id); + if (kind == NativeGetPropKind::Slot) { + EmitReadSlotResult(writer, nobj, holder, *prop, objId); + writer.returnFromIC(); + trackAttached("GetProp.NativeSlot"); + } else { + EmitMissingPropResult(writer, nobj, objId); + writer.returnFromIC(); + trackAttached("GetProp.Missing"); + } + return AttachDecision::Attach; + } + case NativeGetPropKind::ScriptedGetter: + case NativeGetPropKind::NativeGetter: { + auto* nobj = &obj->as(); + + maybeEmitIdGuard(id); + + if (!isSuper() && CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, nobj, + holder, *prop, mode_)) { + EmitCallDOMGetterResult(cx_, writer, nobj, holder, id, *prop, objId); + + trackAttached("GetProp.DOMGetter"); + return AttachDecision::Attach; + } + + EmitCallGetterResult(cx_, writer, kind, nobj, holder, id, *prop, objId, + receiverId, mode_); + + trackAttached("GetProp.NativeGetter"); + return AttachDecision::Attach; + } + } + + MOZ_CRASH("Bad NativeGetPropKind"); +} + +// Returns whether obj is a WindowProxy wrapping the script's global. +static bool 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) { + writer.guardClass(objId, GuardClassKind::WindowProxy); + ObjOperandId windowObjId = writer.loadWrapperTarget(objId); + writer.guardSpecificObject(windowObjId, windowObj); + return windowObjId; +} + +// Whether a getter/setter on the global should have the WindowProxy as |this| +// value instead of the Window (the global object). This always returns true for +// scripted functions. +static bool GetterNeedsWindowProxyThis(NativeObject* holder, + PropertyInfo prop) { + JSFunction* callee = &holder->getGetter(prop)->as(); + return !callee->hasJitInfo() || callee->jitInfo()->needsOuterizedThisObject(); +} +static bool SetterNeedsWindowProxyThis(NativeObject* holder, + PropertyInfo prop) { + JSFunction* callee = &holder->getSetter(prop)->as(); + return !callee->hasJitInfo() || callee->jitInfo()->needsOuterizedThisObject(); +} + +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). + GlobalObject* windowObj = cx_->global(); + NativeObject* holder = nullptr; + Maybe prop; + NativeGetPropKind kind = + CanAttachNativeGetProp(cx_, windowObj, id, &holder, &prop, pc_); + switch (kind) { + case NativeGetPropKind::None: + return AttachDecision::NoAction; + + case NativeGetPropKind::Slot: { + maybeEmitIdGuard(id); + ObjOperandId windowObjId = + GuardAndLoadWindowProxyWindow(writer, objId, windowObj); + EmitReadSlotResult(writer, windowObj, holder, *prop, windowObjId); + writer.returnFromIC(); + + trackAttached("GetProp.WindowProxySlot"); + return AttachDecision::Attach; + } + + case NativeGetPropKind::Missing: { + maybeEmitIdGuard(id); + ObjOperandId windowObjId = + GuardAndLoadWindowProxyWindow(writer, objId, windowObj); + EmitMissingPropResult(writer, windowObj, windowObjId); + writer.returnFromIC(); + + trackAttached("GetProp.WindowProxyMissing"); + return AttachDecision::Attach; + } + + case NativeGetPropKind::NativeGetter: + case NativeGetPropKind::ScriptedGetter: { + // If a |super| access, it is not worth the complexity to attach an IC. + if (isSuper()) { + return AttachDecision::NoAction; + } + + bool needsWindowProxy = GetterNeedsWindowProxyThis(holder, *prop); + + // 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); + + if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, windowObj, holder, + *prop, mode_)) { + MOZ_ASSERT(!needsWindowProxy); + EmitCallDOMGetterResult(cx_, writer, windowObj, holder, id, *prop, + windowObjId); + trackAttached("GetProp.WindowProxyDOMGetter"); + } else { + ValOperandId receiverId = + writer.boxObject(needsWindowProxy ? objId : windowObjId); + EmitCallGetterResult(cx_, writer, kind, windowObj, holder, id, *prop, + windowObjId, receiverId, mode_); + trackAttached("GetProp.WindowProxyGetter"); + } + + return AttachDecision::Attach; + } + } + + 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; + } + + NativeObject* holder = nullptr; + Maybe prop; + + // Enter realm of target to prevent failing compartment assertions when doing + // the lookup. + { + AutoRealm ar(cx_, unwrapped); + + NativeGetPropKind kind = + CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &prop, pc_); + if (kind != NativeGetPropKind::Slot && kind != NativeGetPropKind::Missing) { + return AttachDecision::NoAction; + } + } + auto* unwrappedNative = &unwrapped->as(); + + 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, + unwrappedNative->compartment()); + + ObjOperandId unwrappedId = wrapperTargetId; + if (holder) { + EmitReadSlotResult(writer, unwrappedNative, holder, + *prop, unwrappedId); + writer.wrapResult(); + writer.returnFromIC(); + trackAttached("GetProp.CCWSlot"); + } else { + EmitMissingPropResult(writer, unwrappedNative, + unwrappedId); + writer.returnFromIC(); + trackAttached("GetProp.CCWMissing"); + } + return AttachDecision::Attach; +} + +static JSObject* NewWrapperWithObjectShape(JSContext* cx, + Handle obj); + +static bool GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray, + MutableHandleObject wrapper) { + Value v = GetProxyReservedSlot(xray, GetXrayJitInfo()->xrayHolderSlot); + if (v.isObject()) { + NativeObject* holder = &v.toObject().as(); + v = holder->getFixedSlot(GetXrayJitInfo()->holderExpandoSlot); + if (v.isObject()) { + Rooted expando( + cx, &UncheckedUnwrap(&v.toObject())->as()); + 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 (!obj->is()) { + 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> 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.isSome()) { + break; + } + if (!GetPrototype(cx_, holder, &holder)) { + cx_->clearPendingException(); + return AttachDecision::NoAction; + } + if (!holder || !holder->is() || + !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->getter()); + if (!getter || !getter->is() || + !getter->as().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().realm(); + writer.callNativeGetterResult(receiverId, &getter->as(), + sameRealm); + writer.returnFromIC(); + + trackAttached("GetProp.XrayCCW"); + return AttachDecision::Attach; +} + +#ifdef JS_PUNBOX64 +AttachDecision GetPropIRGenerator::tryAttachScriptedProxy( + Handle obj, ObjOperandId objId, HandleId id) { + if (cacheKind_ != CacheKind::GetProp && cacheKind_ != CacheKind::GetElem) { + return AttachDecision::NoAction; + } + if (cacheKind_ == CacheKind::GetElem) { + if (!idVal_.isString() && !idVal_.isInt32() && !idVal_.isSymbol()) { + return AttachDecision::NoAction; + } + } + + JSObject* handlerObj = ScriptedProxyHandler::handlerObject(obj); + if (!handlerObj) { + return AttachDecision::NoAction; + } + + NativeObject* trapHolder = nullptr; + Maybe trapProp; + // We call with pc_ even though that's not the actual corresponding pc. It + // should, however, be fine, because it's just used to check if this is a + // GetBoundName, which it's not. + NativeGetPropKind trapKind = CanAttachNativeGetProp( + cx_, handlerObj, NameToId(cx_->names().get), &trapHolder, &trapProp, pc_); + + if (trapKind != NativeGetPropKind::Missing && + trapKind != NativeGetPropKind::Slot) { + return AttachDecision::NoAction; + } + + if (trapKind != NativeGetPropKind::Missing) { + uint32_t trapSlot = trapProp->slot(); + const Value& trapVal = trapHolder->getSlot(trapSlot); + if (!trapVal.isObject()) { + return AttachDecision::NoAction; + } + + JSObject* trapObj = &trapVal.toObject(); + if (!trapObj->is()) { + return AttachDecision::NoAction; + } + + JSFunction* trapFn = &trapObj->as(); + if (trapFn->isClassConstructor()) { + return AttachDecision::NoAction; + } + + if (!trapFn->hasJitEntry()) { + return AttachDecision::NoAction; + } + + if (cx_->realm() != trapFn->realm()) { + return AttachDecision::NoAction; + } + } + + NativeObject* nHandlerObj = &handlerObj->as(); + JSObject* targetObj = obj->target(); + MOZ_ASSERT(targetObj, "Guaranteed by the scripted Proxy constructor"); + + // We just require that the target is a NativeObject to make our lives + // easier. There's too much nonsense we might have to handle otherwise and + // we're not set up to recursively call GetPropIRGenerator::tryAttachStub + // for the target object. + if (!targetObj->is()) { + return AttachDecision::NoAction; + } + + writer.guardIsProxy(objId); + writer.guardHasProxyHandler(objId, &ScriptedProxyHandler::singleton); + ValOperandId handlerValId = writer.loadScriptedProxyHandler(objId); + ObjOperandId handlerObjId = writer.guardToObject(handlerValId); + ObjOperandId targetObjId = writer.loadWrapperTarget(objId); + + writer.guardIsNativeObject(targetObjId); + + if (trapKind == NativeGetPropKind::Missing) { + EmitMissingPropGuard(writer, nHandlerObj, handlerObjId); + if (cacheKind_ == CacheKind::GetProp) { + writer.megamorphicLoadSlotResult(targetObjId, id); + } else { + writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId()); + } + } else { + uint32_t trapSlot = trapProp->slot(); + const Value& trapVal = trapHolder->getSlot(trapSlot); + JSObject* trapObj = &trapVal.toObject(); + JSFunction* trapFn = &trapObj->as(); + ObjOperandId trapHolderId = + EmitReadSlotGuard(writer, nHandlerObj, trapHolder, handlerObjId); + + ValOperandId fnValId = + EmitLoadSlot(writer, trapHolder, trapHolderId, trapSlot); + ObjOperandId fnObjId = writer.guardToObject(fnValId); + writer.guardSpecificFunction(fnObjId, trapFn); + ValOperandId targetValId = writer.boxObject(targetObjId); + if (cacheKind_ == CacheKind::GetProp) { + writer.callScriptedProxyGetResult(targetValId, objId, handlerObjId, + trapFn, id); + } else { + ValOperandId idId = getElemKeyValueId(); + ValOperandId stringIdId = writer.idToStringOrSymbol(idId); + writer.callScriptedProxyGetByValueResult(targetValId, objId, handlerObjId, + stringIdId, trapFn); + } + } + writer.returnFromIC(); + + trackAttached("GetScriptedProxy"); + return AttachDecision::Attach; +} +#endif + +AttachDecision GetPropIRGenerator::tryAttachGenericProxy( + Handle obj, ObjOperandId objId, HandleId id, + bool handleDOMProxies) { + 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("GetProp.GenericProxy"); + return AttachDecision::Attach; +} + +static bool ValueIsInt64Index(const Value& val, int64_t* index) { + // Try to convert the Value to a TypedArray index or DataView offset. + + if (val.isInt32()) { + *index = val.toInt32(); + return true; + } + + if (val.isDouble()) { + // Use NumberEqualsInt64 because ToPropertyKey(-0) is 0. + return mozilla::NumberEqualsInt64(val.toDouble(), index); + } + + return false; +} + +IntPtrOperandId IRGenerator::guardToIntPtrIndex(const Value& index, + ValOperandId indexId, + bool supportOOB) { +#ifdef DEBUG + int64_t indexInt64; + MOZ_ASSERT_IF(!supportOOB, ValueIsInt64Index(index, &indexInt64)); +#endif + + if (index.isInt32()) { + Int32OperandId int32IndexId = writer.guardToInt32(indexId); + return writer.int32ToIntPtr(int32IndexId); + } + + MOZ_ASSERT(index.isNumber()); + NumberOperandId numberIndexId = writer.guardIsNumber(indexId); + return writer.guardNumberToIntPtrIndex(numberIndexId, supportOOB); +} + +ObjOperandId IRGenerator::guardDOMProxyExpandoObjectAndShape( + ProxyObject* obj, ObjOperandId objId, const Value& expandoVal, + NativeObject* expandoObj) { + MOZ_ASSERT(IsCacheableDOMProxy(obj)); + + TestMatchingProxyReceiver(writer, obj, 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( + Handle obj, ObjOperandId objId, HandleId id, + ValOperandId receiverId) { + MOZ_ASSERT(IsCacheableDOMProxy(obj)); + + Value expandoVal = GetProxyPrivate(obj); + JSObject* expandoObj; + if (expandoVal.isObject()) { + expandoObj = &expandoVal.toObject(); + } else { + MOZ_ASSERT(!expandoVal.isUndefined(), + "How did a missing expando manage to shadow things?"); + auto expandoAndGeneration = + static_cast(expandoVal.toPrivate()); + MOZ_ASSERT(expandoAndGeneration); + expandoObj = &expandoAndGeneration->expando.toObject(); + } + + // Try to do the lookup on the expando object. + NativeObject* holder = nullptr; + Maybe prop; + NativeGetPropKind kind = + CanAttachNativeGetProp(cx_, expandoObj, id, &holder, &prop, pc_); + if (kind == NativeGetPropKind::None) { + return AttachDecision::NoAction; + } + if (!holder) { + return AttachDecision::NoAction; + } + auto* nativeExpandoObj = &expandoObj->as(); + + MOZ_ASSERT(holder == nativeExpandoObj); + + maybeEmitIdGuard(id); + ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape( + obj, objId, expandoVal, nativeExpandoObj); + + if (kind == NativeGetPropKind::Slot) { + // Load from the expando's slots. + EmitLoadSlotResult(writer, expandoObjId, nativeExpandoObj, *prop); + writer.returnFromIC(); + } else { + // Call the getter. Note that we pass objId, the DOM proxy, as |this| + // and not the expando object. + MOZ_ASSERT(kind == NativeGetPropKind::NativeGetter || + kind == NativeGetPropKind::ScriptedGetter); + EmitGuardGetterSetterSlot(writer, nativeExpandoObj, *prop, expandoObjId); + EmitCallGetterResultNoGuards(cx_, writer, kind, nativeExpandoObj, + nativeExpandoObj, *prop, receiverId); + } + + trackAttached("GetProp.DOMProxyExpando"); + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachDOMProxyShadowed( + Handle obj, ObjOperandId objId, HandleId id) { + MOZ_ASSERT(!isSuper()); + MOZ_ASSERT(IsCacheableDOMProxy(obj)); + + maybeEmitIdGuard(id); + TestMatchingProxyReceiver(writer, obj, objId); + writer.proxyGetResult(objId, id); + writer.returnFromIC(); + + trackAttached("GetProp.DOMProxyShadowed"); + return AttachDecision::Attach; +} + +// Emit CacheIR to guard the DOM proxy doesn't shadow |id|. There are two types +// of DOM proxies: +// +// (a) DOM proxies marked LegacyOverrideBuiltIns in WebIDL, for example +// HTMLDocument or HTMLFormElement. These proxies look up properties in this +// order: +// +// (1) The expando object. +// (2) The proxy's named-property handler. +// (3) The prototype chain. +// +// To optimize properties on the prototype chain, we have to guard that (1) +// and (2) don't shadow (3). We handle (1) by either emitting a shape guard +// for the expando object or by guarding the proxy has no expando object. To +// efficiently handle (2), the proxy must have an ExpandoAndGeneration* +// stored as PrivateValue. We guard on its generation field to ensure the +// set of names hasn't changed. +// +// Missing properties can be optimized in a similar way by emitting shape +// guards for the prototype chain. +// +// (b) Other DOM proxies. These proxies look up properties in this +// order: +// +// (1) The expando object. +// (2) The prototype chain. +// (3) The proxy's named-property handler. +// +// To optimize properties on the prototype chain, we only have to guard the +// expando object doesn't shadow it. +// +// Missing properties can't be optimized in this case because we don't have +// an efficient way to guard against the proxy handler shadowing the +// property (there's no ExpandoAndGeneration*). +// +// See also: +// * DOMProxyShadows in DOMJSProxyHandler.cpp +// * https://webidl.spec.whatwg.org/#dfn-named-property-visibility (the Note at +// the end) +// +// Callers are expected to have already guarded on the shape of the +// object, which guarantees the object is a DOM proxy. +static void CheckDOMProxyDoesNotShadow(CacheIRWriter& writer, ProxyObject* obj, + jsid id, ObjOperandId objId, + bool* canOptimizeMissing) { + MOZ_ASSERT(IsCacheableDOMProxy(obj)); + + Value expandoVal = GetProxyPrivate(obj); + + ValOperandId expandoId; + if (!expandoVal.isObject() && !expandoVal.isUndefined()) { + // Case (a). + auto expandoAndGeneration = + static_cast(expandoVal.toPrivate()); + uint64_t generation = expandoAndGeneration->generation; + expandoId = writer.loadDOMExpandoValueGuardGeneration( + objId, expandoAndGeneration, generation); + expandoVal = expandoAndGeneration->expando; + *canOptimizeMissing = true; + } else { + // Case (b). + expandoId = writer.loadDOMExpandoValue(objId); + *canOptimizeMissing = false; + } + + 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(); + MOZ_ASSERT(!expandoObj.containsPure(id)); + writer.guardDOMExpandoMissingOrGuardShape(expandoId, expandoObj.shape()); + } else { + MOZ_CRASH("Invalid expando value"); + } +} + +AttachDecision GetPropIRGenerator::tryAttachDOMProxyUnshadowed( + Handle obj, ObjOperandId objId, HandleId id, + ValOperandId receiverId) { + MOZ_ASSERT(IsCacheableDOMProxy(obj)); + + JSObject* protoObj = obj->staticPrototype(); + if (!protoObj) { + return AttachDecision::NoAction; + } + + NativeObject* holder = nullptr; + Maybe prop; + NativeGetPropKind kind = + CanAttachNativeGetProp(cx_, protoObj, id, &holder, &prop, pc_); + if (kind == NativeGetPropKind::None) { + return AttachDecision::NoAction; + } + auto* nativeProtoObj = &protoObj->as(); + + maybeEmitIdGuard(id); + + // Guard that our proxy (expando) object hasn't started shadowing this + // property. + TestMatchingProxyReceiver(writer, obj, objId); + bool canOptimizeMissing = false; + CheckDOMProxyDoesNotShadow(writer, obj, id, objId, &canOptimizeMissing); + + 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 (kind == NativeGetPropKind::Slot) { + EmitLoadSlotResult(writer, holderId, holder, *prop); + 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(kind == NativeGetPropKind::NativeGetter || + kind == NativeGetPropKind::ScriptedGetter); + MOZ_ASSERT(!isSuper()); + EmitGuardGetterSetterSlot(writer, holder, *prop, holderId, + /* holderIsConstant = */ true); + EmitCallGetterResultNoGuards(cx_, writer, kind, nativeProtoObj, holder, + *prop, receiverId); + } + } else { + // Property was not found on the prototype chain. + MOZ_ASSERT(kind == NativeGetPropKind::Missing); + if (canOptimizeMissing) { + // We already guarded on the proxy's shape, so now shape guard the proto + // chain. + ObjOperandId protoId = writer.loadObject(nativeProtoObj); + EmitMissingPropResult(writer, nativeProtoObj, protoId); + } else { + MOZ_ASSERT(!isSuper()); + writer.proxyGetResult(objId, id); + } + writer.returnFromIC(); + } + + trackAttached("GetProp.DOMProxyUnshadowed"); + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachProxy(HandleObject obj, + ObjOperandId objId, + HandleId id, + ValOperandId receiverId) { + // The proxy stubs don't currently support |super| access. + if (isSuper()) { + return AttachDecision::NoAction; + } + + // Always try to attach scripted proxy get even if we're megamorphic. + // In Speedometer 3 we'll often run into cases where we're megamorphic + // overall, but monomorphic for the proxy case. This is because there + // are functions which lazily turn various differently-shaped objects + // into proxies. So the un-proxified objects are megamorphic, but the + // proxy handlers are actually monomorphic. There is room for a bit + // more sophistication here, but this should do for now. + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto proxy = obj.as(); +#ifdef JS_PUNBOX64 + if (proxy->handler()->isScripted()) { + TRY_ATTACH(tryAttachScriptedProxy(proxy, objId, id)); + } +#endif + + ProxyStubType type = GetProxyStubType(cx_, obj, id); + if (type == ProxyStubType::None) { + return AttachDecision::NoAction; + } + + if (mode_ == ICState::Mode::Megamorphic) { + return tryAttachGenericProxy(proxy, objId, id, + /* handleDOMProxies = */ true); + } + + switch (type) { + case ProxyStubType::None: + break; + case ProxyStubType::DOMExpando: + TRY_ATTACH(tryAttachDOMProxyExpando(proxy, objId, id, receiverId)); + [[fallthrough]]; // Fall through to the generic shadowed case. + case ProxyStubType::DOMShadowed: + return tryAttachDOMProxyShadowed(proxy, objId, id); + case ProxyStubType::DOMUnshadowed: + TRY_ATTACH(tryAttachDOMProxyUnshadowed(proxy, objId, id, receiverId)); + return tryAttachGenericProxy(proxy, objId, id, + /* handleDOMProxies = */ true); + case ProxyStubType::Generic: + return tryAttachGenericProxy(proxy, objId, id, + /* handleDOMProxies = */ false); + } + + MOZ_CRASH("Unexpected ProxyStubType"); +} + +// Guards the class of an object. Because shape implies class, and a shape guard +// is faster than a class guard, if this is our first time attaching a stub, we +// instead generate a shape guard. +void IRGenerator::emitOptimisticClassGuard(ObjOperandId objId, JSObject* obj, + GuardClassKind kind) { +#ifdef DEBUG + switch (kind) { + case GuardClassKind::Array: + MOZ_ASSERT(obj->is()); + break; + case GuardClassKind::PlainObject: + MOZ_ASSERT(obj->is()); + break; + case GuardClassKind::FixedLengthArrayBuffer: + MOZ_ASSERT(obj->is()); + break; + case GuardClassKind::FixedLengthSharedArrayBuffer: + MOZ_ASSERT(obj->is()); + break; + case GuardClassKind::FixedLengthDataView: + MOZ_ASSERT(obj->is()); + break; + case GuardClassKind::Set: + MOZ_ASSERT(obj->is()); + break; + case GuardClassKind::Map: + MOZ_ASSERT(obj->is()); + break; + + case GuardClassKind::MappedArguments: + case GuardClassKind::UnmappedArguments: + case GuardClassKind::JSFunction: + case GuardClassKind::BoundFunction: + case GuardClassKind::WindowProxy: + // Arguments, functions, and the global object have + // less consistent shapes. + MOZ_CRASH("GuardClassKind not supported"); + } +#endif + + if (isFirstStub_) { + writer.guardShapeForClass(objId, obj->shape()); + } else { + writer.guardClass(objId, kind); + } +} + +static void AssertArgumentsCustomDataProp(ArgumentsObject* obj, + PropertyKey key) { +#ifdef DEBUG + // The property must still be a custom data property if it has been resolved. + // If this assertion fails, we're probably missing a call to mark this + // property overridden. + Maybe prop = obj->lookupPure(key); + MOZ_ASSERT_IF(prop, prop->isCustomDataProperty()); +#endif +} + +AttachDecision GetPropIRGenerator::tryAttachObjectLength(HandleObject obj, + ObjOperandId objId, + HandleId id) { + if (!id.isAtom(cx_->names().length)) { + return AttachDecision::NoAction; + } + + if (obj->is()) { + if (obj->as().length() > INT32_MAX) { + return AttachDecision::NoAction; + } + + maybeEmitIdGuard(id); + emitOptimisticClassGuard(objId, obj, GuardClassKind::Array); + writer.loadInt32ArrayLengthResult(objId); + writer.returnFromIC(); + + trackAttached("GetProp.ArrayLength"); + return AttachDecision::Attach; + } + + if (obj->is() && + !obj->as().hasOverriddenLength()) { + AssertArgumentsCustomDataProp(&obj->as(), id); + maybeEmitIdGuard(id); + if (obj->is()) { + writer.guardClass(objId, GuardClassKind::MappedArguments); + } else { + MOZ_ASSERT(obj->is()); + writer.guardClass(objId, GuardClassKind::UnmappedArguments); + } + writer.loadArgumentsObjectLengthResult(objId); + writer.returnFromIC(); + + trackAttached("GetProp.ArgumentsObjectLength"); + return AttachDecision::Attach; + } + + return AttachDecision::NoAction; +} + +AttachDecision GetPropIRGenerator::tryAttachTypedArray(HandleObject obj, + ObjOperandId objId, + HandleId id) { + // TODO: Support resizable typed arrays. (bug 1842999) + if (!obj->is()) { + return AttachDecision::NoAction; + } + + if (mode_ != ICState::Mode::Specialized) { + return AttachDecision::NoAction; + } + + // Receiver should be the object. + if (isSuper()) { + return AttachDecision::NoAction; + } + + bool isLength = id.isAtom(cx_->names().length); + bool isByteOffset = id.isAtom(cx_->names().byteOffset); + if (!isLength && !isByteOffset && !id.isAtom(cx_->names().byteLength)) { + return AttachDecision::NoAction; + } + + NativeObject* holder = nullptr; + Maybe prop; + NativeGetPropKind kind = + CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_); + if (kind != NativeGetPropKind::NativeGetter) { + return AttachDecision::NoAction; + } + + JSFunction& fun = holder->getGetter(*prop)->as(); + if (isLength) { + if (!TypedArrayObject::isOriginalLengthGetter(fun.native())) { + return AttachDecision::NoAction; + } + } else if (isByteOffset) { + if (!TypedArrayObject::isOriginalByteOffsetGetter(fun.native())) { + return AttachDecision::NoAction; + } + } else { + if (!TypedArrayObject::isOriginalByteLengthGetter(fun.native())) { + return AttachDecision::NoAction; + } + } + + auto* tarr = &obj->as(); + + maybeEmitIdGuard(id); + // Emit all the normal guards for calling this native, but specialize + // callNativeGetterResult. + EmitCallGetterResultGuards(writer, tarr, holder, id, *prop, objId, mode_); + if (isLength) { + if (tarr->length() <= INT32_MAX) { + writer.loadArrayBufferViewLengthInt32Result(objId); + } else { + writer.loadArrayBufferViewLengthDoubleResult(objId); + } + trackAttached("GetProp.TypedArrayLength"); + } else if (isByteOffset) { + if (tarr->byteOffset() <= INT32_MAX) { + writer.arrayBufferViewByteOffsetInt32Result(objId); + } else { + writer.arrayBufferViewByteOffsetDoubleResult(objId); + } + trackAttached("GetProp.TypedArrayByteOffset"); + } else { + if (tarr->byteLength() <= INT32_MAX) { + writer.typedArrayByteLengthInt32Result(objId); + } else { + writer.typedArrayByteLengthDoubleResult(objId); + } + trackAttached("GetProp.TypedArrayByteLength"); + } + writer.returnFromIC(); + + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachDataView(HandleObject obj, + ObjOperandId objId, + HandleId id) { + // TODO: Support resizable dataviews. (bug 1842999) + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto* dv = &obj->as(); + + if (mode_ != ICState::Mode::Specialized) { + return AttachDecision::NoAction; + } + + // Receiver should be the object. + if (isSuper()) { + return AttachDecision::NoAction; + } + + bool isByteOffset = id.isAtom(cx_->names().byteOffset); + if (!isByteOffset && !id.isAtom(cx_->names().byteLength)) { + return AttachDecision::NoAction; + } + + // byteOffset and byteLength both throw when the ArrayBuffer is detached. + if (dv->hasDetachedBuffer()) { + return AttachDecision::NoAction; + } + + NativeObject* holder = nullptr; + Maybe prop; + NativeGetPropKind kind = + CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_); + if (kind != NativeGetPropKind::NativeGetter) { + return AttachDecision::NoAction; + } + + auto& fun = holder->getGetter(*prop)->as(); + if (isByteOffset) { + if (!DataViewObject::isOriginalByteOffsetGetter(fun.native())) { + return AttachDecision::NoAction; + } + } else { + if (!DataViewObject::isOriginalByteLengthGetter(fun.native())) { + return AttachDecision::NoAction; + } + } + + maybeEmitIdGuard(id); + // Emit all the normal guards for calling this native, but specialize + // callNativeGetterResult. + EmitCallGetterResultGuards(writer, dv, holder, id, *prop, objId, mode_); + writer.guardHasAttachedArrayBuffer(objId); + if (isByteOffset) { + if (dv->byteOffset() <= INT32_MAX) { + writer.arrayBufferViewByteOffsetInt32Result(objId); + } else { + writer.arrayBufferViewByteOffsetDoubleResult(objId); + } + trackAttached("GetProp.DataViewByteOffset"); + } else { + if (dv->byteLength() <= INT32_MAX) { + writer.loadArrayBufferViewLengthInt32Result(objId); + } else { + writer.loadArrayBufferViewLengthDoubleResult(objId); + } + trackAttached("GetProp.DataViewByteLength"); + } + writer.returnFromIC(); + + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachArrayBufferMaybeShared( + HandleObject obj, ObjOperandId objId, HandleId id) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto* buf = &obj->as(); + + // TODO: Support resizable buffers. (bug 1842999) + if (buf->isResizable()) { + return AttachDecision::NoAction; + } + + if (mode_ != ICState::Mode::Specialized) { + return AttachDecision::NoAction; + } + + // Receiver should be the object. + if (isSuper()) { + return AttachDecision::NoAction; + } + + if (!id.isAtom(cx_->names().byteLength)) { + return AttachDecision::NoAction; + } + + NativeObject* holder = nullptr; + Maybe prop; + NativeGetPropKind kind = + CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_); + if (kind != NativeGetPropKind::NativeGetter) { + return AttachDecision::NoAction; + } + + auto& fun = holder->getGetter(*prop)->as(); + if (buf->is()) { + if (!ArrayBufferObject::isOriginalByteLengthGetter(fun.native())) { + return AttachDecision::NoAction; + } + } else { + if (!SharedArrayBufferObject::isOriginalByteLengthGetter(fun.native())) { + return AttachDecision::NoAction; + } + } + + maybeEmitIdGuard(id); + // Emit all the normal guards for calling this native, but specialize + // callNativeGetterResult. + EmitCallGetterResultGuards(writer, buf, holder, id, *prop, objId, mode_); + if (buf->byteLength() <= INT32_MAX) { + writer.loadArrayBufferByteLengthInt32Result(objId); + } else { + writer.loadArrayBufferByteLengthDoubleResult(objId); + } + writer.returnFromIC(); + + trackAttached("GetProp.ArrayBufferMaybeSharedByteLength"); + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachRegExp(HandleObject obj, + ObjOperandId objId, + HandleId id) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto* regExp = &obj->as(); + + if (mode_ != ICState::Mode::Specialized) { + return AttachDecision::NoAction; + } + + // Receiver should be the object. + if (isSuper()) { + return AttachDecision::NoAction; + } + + NativeObject* holder = nullptr; + Maybe prop; + NativeGetPropKind kind = + CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_); + if (kind != NativeGetPropKind::NativeGetter) { + return AttachDecision::NoAction; + } + + auto& fun = holder->getGetter(*prop)->as(); + JS::RegExpFlags flags = JS::RegExpFlag::NoFlags; + if (!RegExpObject::isOriginalFlagGetter(fun.native(), &flags)) { + return AttachDecision::NoAction; + } + + maybeEmitIdGuard(id); + // Emit all the normal guards for calling this native, but specialize + // callNativeGetterResult. + EmitCallGetterResultGuards(writer, regExp, holder, id, *prop, objId, mode_); + + writer.regExpFlagResult(objId, flags.value()); + writer.returnFromIC(); + + trackAttached("GetProp.RegExpFlag"); + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachMap(HandleObject obj, + ObjOperandId objId, + HandleId id) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto* mapObj = &obj->as(); + + if (mode_ != ICState::Mode::Specialized) { + return AttachDecision::NoAction; + } + + // Receiver should be the object. + if (isSuper()) { + return AttachDecision::NoAction; + } + + if (!id.isAtom(cx_->names().size)) { + return AttachDecision::NoAction; + } + + NativeObject* holder = nullptr; + Maybe prop; + NativeGetPropKind kind = + CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_); + if (kind != NativeGetPropKind::NativeGetter) { + return AttachDecision::NoAction; + } + + auto& fun = holder->getGetter(*prop)->as(); + if (!MapObject::isOriginalSizeGetter(fun.native())) { + return AttachDecision::NoAction; + } + + maybeEmitIdGuard(id); + + // Emit all the normal guards for calling this native, but specialize + // callNativeGetterResult. + EmitCallGetterResultGuards(writer, mapObj, holder, id, *prop, objId, mode_); + + writer.mapSizeResult(objId); + writer.returnFromIC(); + + trackAttached("GetProp.MapSize"); + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachSet(HandleObject obj, + ObjOperandId objId, + HandleId id) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto* setObj = &obj->as(); + + if (mode_ != ICState::Mode::Specialized) { + return AttachDecision::NoAction; + } + + // Receiver should be the object. + if (isSuper()) { + return AttachDecision::NoAction; + } + + if (!id.isAtom(cx_->names().size)) { + return AttachDecision::NoAction; + } + + NativeObject* holder = nullptr; + Maybe prop; + NativeGetPropKind kind = + CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_); + if (kind != NativeGetPropKind::NativeGetter) { + return AttachDecision::NoAction; + } + + auto& fun = holder->getGetter(*prop)->as(); + if (!SetObject::isOriginalSizeGetter(fun.native())) { + return AttachDecision::NoAction; + } + + maybeEmitIdGuard(id); + + // Emit all the normal guards for calling this native, but specialize + // callNativeGetterResult. + EmitCallGetterResultGuards(writer, setObj, holder, id, *prop, objId, mode_); + + writer.setSizeResult(objId); + writer.returnFromIC(); + + trackAttached("GetProp.SetSize"); + 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()) { + return AttachDecision::NoAction; + } + + bool isLength = id.isAtom(cx_->names().length); + if (!isLength && !id.isAtom(cx_->names().name)) { + return AttachDecision::NoAction; + } + + NativeObject* 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(); + + 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; + } + } else { + // name was probably deleted from the function. + if (fun->hasResolvedName()) { + return AttachDecision::NoAction; + } + } + + maybeEmitIdGuard(id); + writer.guardClass(objId, GuardClassKind::JSFunction); + if (isLength) { + writer.loadFunctionLengthResult(objId); + writer.returnFromIC(); + trackAttached("GetProp.FunctionLength"); + } else { + writer.loadFunctionNameResult(objId); + writer.returnFromIC(); + trackAttached("GetProp.FunctionName"); + } + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectIterator( + HandleObject obj, ObjOperandId objId, HandleId id) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + + if (!id.isWellKnownSymbol(JS::SymbolCode::iterator)) { + return AttachDecision::NoAction; + } + + Handle args = obj.as(); + if (args->hasOverriddenIterator()) { + return AttachDecision::NoAction; + } + + AssertArgumentsCustomDataProp(args, id); + + RootedValue iterator(cx_); + if (!ArgumentsObject::getArgumentsIterator(cx_, &iterator)) { + cx_->recoverFromOutOfMemory(); + return AttachDecision::NoAction; + } + MOZ_ASSERT(iterator.isObject()); + + maybeEmitIdGuard(id); + if (args->is()) { + writer.guardClass(objId, GuardClassKind::MappedArguments); + } else { + MOZ_ASSERT(args->is()); + writer.guardClass(objId, GuardClassKind::UnmappedArguments); + } + uint32_t flags = ArgumentsObject::ITERATOR_OVERRIDDEN_BIT; + writer.guardArgumentsObjectFlags(objId, flags); + + ObjOperandId iterId = writer.loadObject(&iterator.toObject()); + writer.loadObjectResult(iterId); + writer.returnFromIC(); + + trackAttached("GetProp.ArgumentsObjectIterator"); + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachModuleNamespace(HandleObject obj, + ObjOperandId objId, + HandleId id) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + + auto* ns = &obj->as(); + ModuleEnvironmentObject* env = nullptr; + Maybe prop; + if (!ns->bindings().lookup(id, &env, &prop)) { + return AttachDecision::NoAction; + } + + // Don't emit a stub until the target binding has been initialized. + if (env->getSlot(prop->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, *prop); + writer.returnFromIC(); + + trackAttached("GetProp.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 (id.isAtom(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; +#ifdef ENABLE_RECORD_TUPLE + case ValueType::ExtendedPrimitive: +#endif + case ValueType::Object: + case ValueType::PrivateGCThing: + MOZ_CRASH("unexpected type"); + } + + JSObject* proto = GlobalObject::getOrCreatePrototype(cx_, protoKey); + if (!proto) { + cx_->recoverFromOutOfMemory(); + return AttachDecision::NoAction; + } + + NativeObject* holder = nullptr; + Maybe prop; + NativeGetPropKind kind = + CanAttachNativeGetProp(cx_, proto, id, &holder, &prop, pc_); + switch (kind) { + case NativeGetPropKind::None: + return AttachDecision::NoAction; + case NativeGetPropKind::Missing: + case NativeGetPropKind::Slot: { + auto* nproto = &proto->as(); + + if (val_.isNumber()) { + writer.guardIsNumber(valId); + } else { + writer.guardNonDoubleType(valId, val_.type()); + } + maybeEmitIdGuard(id); + + ObjOperandId protoId = writer.loadObject(nproto); + if (kind == NativeGetPropKind::Slot) { + EmitReadSlotResult(writer, nproto, holder, *prop, protoId); + writer.returnFromIC(); + trackAttached("GetProp.PrimitiveSlot"); + } else { + EmitMissingPropResult(writer, nproto, protoId); + writer.returnFromIC(); + trackAttached("GetProp.PrimitiveMissing"); + } + return AttachDecision::Attach; + } + case NativeGetPropKind::ScriptedGetter: + case NativeGetPropKind::NativeGetter: { + auto* nproto = &proto->as(); + + if (val_.isNumber()) { + writer.guardIsNumber(valId); + } else { + writer.guardNonDoubleType(valId, val_.type()); + } + maybeEmitIdGuard(id); + + ObjOperandId protoId = writer.loadObject(nproto); + EmitCallGetterResult(cx_, writer, kind, nproto, holder, id, *prop, + protoId, valId, mode_); + + trackAttached("GetProp.PrimitiveGetter"); + return AttachDecision::Attach; + } + } + + MOZ_CRASH("Bad NativeGetPropKind"); +} + +AttachDecision GetPropIRGenerator::tryAttachStringLength(ValOperandId valId, + HandleId id) { + if (!val_.isString() || !id.isAtom(cx_->names().length)) { + return AttachDecision::NoAction; + } + + StringOperandId strId = writer.guardToString(valId); + maybeEmitIdGuard(id); + writer.loadStringLengthResult(strId); + writer.returnFromIC(); + + trackAttached("GetProp.StringLength"); + return AttachDecision::Attach; +} + +enum class AttachStringChar { No, Yes, Linearize, OutOfBounds }; + +static AttachStringChar CanAttachStringChar(const Value& val, + const Value& idVal, + StringChar kind) { + if (!val.isString() || !idVal.isInt32()) { + return AttachStringChar::No; + } + + JSString* str = val.toString(); + int32_t index = idVal.toInt32(); + + if (index < 0 && kind == StringChar::At) { + static_assert(JSString::MAX_LENGTH <= INT32_MAX, + "string length fits in int32"); + index += int32_t(str->length()); + } + + if (index < 0 || size_t(index) >= str->length()) { + return AttachStringChar::OutOfBounds; + } + + // This follows JSString::getChar and MacroAssembler::loadStringChar. + if (str->isRope()) { + JSRope* rope = &str->asRope(); + if (size_t(index) < rope->leftChild()->length()) { + str = rope->leftChild(); + + // MacroAssembler::loadStringChar doesn't support surrogate pairs which + // are split between the left and right child of a rope. + if (kind == StringChar::CodePointAt && + size_t(index) + 1 == str->length() && str->isLinear()) { + // Linearize the string when the last character of the left child is a + // a lead surrogate. + char16_t ch = str->asLinear().latin1OrTwoByteChar(index); + if (unicode::IsLeadSurrogate(ch)) { + return AttachStringChar::Linearize; + } + } + } else { + str = rope->rightChild(); + } + } + + if (!str->isLinear()) { + return AttachStringChar::Linearize; + } + + return AttachStringChar::Yes; +} + +AttachDecision GetPropIRGenerator::tryAttachStringChar(ValOperandId valId, + ValOperandId indexId) { + MOZ_ASSERT(idVal_.isInt32()); + + auto attach = CanAttachStringChar(val_, idVal_, StringChar::CharAt); + if (attach == AttachStringChar::No) { + return AttachDecision::NoAction; + } + + // Can't attach for out-of-bounds access without guarding that indexed + // properties aren't present along the prototype chain of |String.prototype|. + if (attach == AttachStringChar::OutOfBounds) { + return AttachDecision::NoAction; + } + + StringOperandId strId = writer.guardToString(valId); + Int32OperandId int32IndexId = writer.guardToInt32Index(indexId); + if (attach == AttachStringChar::Linearize) { + strId = writer.linearizeForCharAccess(strId, int32IndexId); + } + writer.loadStringCharResult(strId, int32IndexId, /* handleOOB = */ false); + writer.returnFromIC(); + + trackAttached("GetProp.StringChar"); + return AttachDecision::Attach; +} + +static bool ClassCanHaveExtraProperties(const JSClass* clasp) { + return clasp->getResolve() || clasp->getOpsLookupProperty() || + clasp->getOpsGetProperty() || IsTypedArrayClass(clasp); +} + +enum class OwnProperty : bool { No, Yes }; +enum class AllowIndexedReceiver : bool { No, Yes }; +enum class AllowExtraReceiverProperties : bool { No, Yes }; + +static bool CanAttachDenseElementHole( + NativeObject* obj, OwnProperty ownProp, + AllowIndexedReceiver allowIndexedReceiver = AllowIndexedReceiver::No, + AllowExtraReceiverProperties allowExtraReceiverProperties = + AllowExtraReceiverProperties::No) { + // 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 == AllowIndexedReceiver::No && obj->isIndexed()) { + return false; + } + allowIndexedReceiver = AllowIndexedReceiver::No; + + if (allowExtraReceiverProperties == AllowExtraReceiverProperties::No && + ClassCanHaveExtraProperties(obj->getClass())) { + return false; + } + allowExtraReceiverProperties = AllowExtraReceiverProperties::No; + + // Don't need to check prototype for OwnProperty checks + if (ownProp == OwnProperty::Yes) { + return true; + } + + JSObject* proto = obj->staticPrototype(); + if (!proto) { + break; + } + + if (!proto->is()) { + return false; + } + + // Make sure objects on the prototype don't have dense elements. + if (proto->as().getDenseInitializedLength() != 0) { + return false; + } + + obj = &proto->as(); + } while (true); + + return true; +} + +AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectArg( + HandleObject obj, ObjOperandId objId, uint32_t index, + Int32OperandId indexId) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto* args = &obj->as(); + + // No elements must have been overridden or deleted. + if (args->hasOverriddenElement()) { + return AttachDecision::NoAction; + } + + // Check bounds. + if (index >= args->initialLength()) { + return AttachDecision::NoAction; + } + + AssertArgumentsCustomDataProp(args, PropertyKey::Int(index)); + + // And finally also check that the argument isn't forwarded. + if (args->argIsForwarded(index)) { + return AttachDecision::NoAction; + } + + if (args->is()) { + writer.guardClass(objId, GuardClassKind::MappedArguments); + } else { + MOZ_ASSERT(args->is()); + writer.guardClass(objId, GuardClassKind::UnmappedArguments); + } + + writer.loadArgumentsObjectArgResult(objId, indexId); + writer.returnFromIC(); + + trackAttached("GetProp.ArgumentsObjectArg"); + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectArgHole( + HandleObject obj, ObjOperandId objId, uint32_t index, + Int32OperandId indexId) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto* args = &obj->as(); + + // No elements must have been overridden or deleted. + if (args->hasOverriddenElement()) { + return AttachDecision::NoAction; + } + + // And also check that the argument isn't forwarded. + if (index < args->initialLength() && args->argIsForwarded(index)) { + return AttachDecision::NoAction; + } + + if (!CanAttachDenseElementHole(args, OwnProperty::No, + AllowIndexedReceiver::Yes, + AllowExtraReceiverProperties::Yes)) { + return AttachDecision::NoAction; + } + + // We don't need to guard on the shape, because we check if any element is + // overridden. Elements are marked as overridden iff any element is defined, + // irrespective of whether the element is in-bounds or out-of-bounds. So when + // that flag isn't set, we can guarantee that the arguments object doesn't + // have any additional own elements. + + if (args->is()) { + writer.guardClass(objId, GuardClassKind::MappedArguments); + } else { + MOZ_ASSERT(args->is()); + writer.guardClass(objId, GuardClassKind::UnmappedArguments); + } + + GeneratePrototypeHoleGuards(writer, args, objId, + /* alwaysGuardFirstProto = */ true); + + writer.loadArgumentsObjectArgHoleResult(objId, indexId); + writer.returnFromIC(); + + trackAttached("GetProp.ArgumentsObjectArgHole"); + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectCallee( + HandleObject obj, ObjOperandId objId, HandleId id) { + // Only mapped arguments objects have a `callee` property. + if (!obj->is()) { + return AttachDecision::NoAction; + } + + if (!id.isAtom(cx_->names().callee)) { + return AttachDecision::NoAction; + } + + // The callee must not have been overridden or deleted. + MappedArgumentsObject* args = &obj->as(); + if (args->hasOverriddenCallee()) { + return AttachDecision::NoAction; + } + + AssertArgumentsCustomDataProp(args, id); + + maybeEmitIdGuard(id); + writer.guardClass(objId, GuardClassKind::MappedArguments); + + uint32_t flags = ArgumentsObject::CALLEE_OVERRIDDEN_BIT; + writer.guardArgumentsObjectFlags(objId, flags); + + writer.loadFixedSlotResult(objId, + MappedArgumentsObject::getCalleeSlotOffset()); + writer.returnFromIC(); + + trackAttached("GetProp.ArgumentsObjectCallee"); + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachDenseElement( + HandleObject obj, ObjOperandId objId, uint32_t index, + Int32OperandId indexId) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + + NativeObject* nobj = &obj->as(); + if (!nobj->containsDenseElement(index)) { + return AttachDecision::NoAction; + } + + if (mode_ == ICState::Mode::Megamorphic) { + writer.guardIsNativeObject(objId); + } else { + TestMatchingNativeReceiver(writer, nobj, objId); + } + writer.loadDenseElementResult(objId, indexId); + writer.returnFromIC(); + + trackAttached("GetProp.DenseElement"); + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachDenseElementHole( + HandleObject obj, ObjOperandId objId, uint32_t index, + Int32OperandId indexId) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + + NativeObject* nobj = &obj->as(); + if (nobj->containsDenseElement(index)) { + return AttachDecision::NoAction; + } + if (!CanAttachDenseElementHole(nobj, OwnProperty::No)) { + 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("GetProp.DenseElementHole"); + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachSparseElement( + HandleObject obj, ObjOperandId objId, uint32_t index, + Int32OperandId indexId) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + NativeObject* nobj = &obj->as(); + + // Stub doesn't handle negative indices. + if (index > INT32_MAX) { + return AttachDecision::NoAction; + } + + // The object must have sparse elements. + if (!nobj->isIndexed()) { + return AttachDecision::NoAction; + } + + // The index must not be for a dense element. + if (nobj->containsDenseElement(index)) { + return AttachDecision::NoAction; + } + + // Only handle ArrayObject and PlainObject in this stub. + if (!nobj->is() && !nobj->is()) { + return AttachDecision::NoAction; + } + + // GetSparseElementHelper assumes that the target and the receiver + // are the same. + if (isSuper()) { + 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 receiver object. + if (PrototypeMayHaveIndexedProperties(nobj)) { + return AttachDecision::NoAction; + } + + // Ensure that obj is an ArrayObject or PlainObject. + if (nobj->is()) { + writer.guardClass(objId, GuardClassKind::Array); + } else { + MOZ_ASSERT(nobj->is()); + writer.guardClass(objId, GuardClassKind::PlainObject); + } + + // The helper we are going to call only applies to non-dense elements. + writer.guardIndexIsNotDenseElement(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("GetProp.SparseElement"); + return AttachDecision::Attach; +} + +// For Uint32Array we let the stub return an Int32 if we have not seen a +// double, to allow better codegen in Warp while avoiding bailout loops. +static bool ForceDoubleForUint32Array(FixedLengthTypedArrayObject* tarr, + uint64_t index) { + MOZ_ASSERT(index < tarr->length()); + + if (tarr->type() != Scalar::Type::Uint32) { + // Return value is only relevant for Uint32Array. + return false; + } + + Value res; + MOZ_ALWAYS_TRUE(tarr->getElementPure(index, &res)); + MOZ_ASSERT(res.isNumber()); + return res.isDouble(); +} + +AttachDecision GetPropIRGenerator::tryAttachTypedArrayElement( + HandleObject obj, ObjOperandId objId) { + // TODO: Support resizable typed arrays. (bug 1842999) + if (!obj->is()) { + return AttachDecision::NoAction; + } + + if (!idVal_.isNumber()) { + return AttachDecision::NoAction; + } + + auto* tarr = &obj->as(); + + bool handleOOB = false; + int64_t indexInt64; + if (!ValueIsInt64Index(idVal_, &indexInt64) || indexInt64 < 0 || + uint64_t(indexInt64) >= tarr->length()) { + handleOOB = true; + } + + // If the number is not representable as an integer the result will be + // |undefined| so we leave |forceDoubleForUint32| as false. + bool forceDoubleForUint32 = false; + if (!handleOOB) { + uint64_t index = uint64_t(indexInt64); + forceDoubleForUint32 = ForceDoubleForUint32Array(tarr, index); + } + + writer.guardShapeForClass(objId, tarr->shape()); + + ValOperandId keyId = getElemKeyValueId(); + IntPtrOperandId intPtrIndexId = guardToIntPtrIndex(idVal_, keyId, handleOOB); + + writer.loadTypedArrayElementResult(objId, intPtrIndexId, tarr->type(), + handleOOB, forceDoubleForUint32); + writer.returnFromIC(); + + trackAttached("GetProp.TypedElement"); + return AttachDecision::Attach; +} + +AttachDecision GetPropIRGenerator::tryAttachGenericElement( + HandleObject obj, ObjOperandId objId, uint32_t index, + Int32OperandId indexId, ValOperandId receiverId) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + +#ifdef JS_CODEGEN_X86 + if (isSuper()) { + // There aren't enough registers available on x86. + return AttachDecision::NoAction; + } +#endif + + // 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(); + TestMatchingNativeReceiver(writer, nobj, objId); + } + writer.guardIndexIsNotDenseElement(objId, indexId); + if (isSuper()) { + writer.callNativeGetElementSuperResult(objId, indexId, receiverId); + } else { + 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()) { + return AttachDecision::NoAction; + } + + // The proxy stubs don't currently support |super| access. + if (isSuper()) { + return AttachDecision::NoAction; + } + +#ifdef JS_PUNBOX64 + auto proxy = obj.as(); + if (proxy->handler()->isScripted()) { + TRY_ATTACH(tryAttachScriptedProxy(proxy, objId, JS::VoidHandlePropertyKey)); + } +#endif + + 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("GetProp.ProxyElement"); + return AttachDecision::Attach; +} + +void GetPropIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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, const Value& idVal, jsid id) { + if (id.isSymbol()) { + MOZ_ASSERT(idVal.toSymbol() == id.toSymbol()); + SymbolOperandId symId = writer.guardToSymbol(valId); + writer.guardSpecificSymbol(symId, id.toSymbol()); + } else { + MOZ_ASSERT(id.isAtom()); + if (idVal.isUndefined()) { + MOZ_ASSERT(id.isAtom(cx_->names().undefined)); + writer.guardIsUndefined(valId); + } else if (idVal.isNull()) { + MOZ_ASSERT(id.isAtom(cx_->names().null)); + writer.guardIsNull(valId); + } else { + MOZ_ASSERT(idVal.isString()); + StringOperandId strId = writer.guardToString(valId); + writer.guardSpecificAtom(strId, id.toAtom()); + } + } +} + +void GetPropIRGenerator::maybeEmitIdGuard(jsid id) { + if (cacheKind_ == CacheKind::GetProp || + cacheKind_ == CacheKind::GetPropSuper) { + // Constant PropertyName, no guards necessary. + MOZ_ASSERT(&idVal_.toString()->asAtom() == id.toAtom()); + return; + } + + MOZ_ASSERT(cacheKind_ == CacheKind::GetElem || + cacheKind_ == CacheKind::GetElemSuper); + emitIdGuard(getElemKeyValueId(), idVal_, id); +} + +void SetPropIRGenerator::maybeEmitIdGuard(jsid id) { + if (cacheKind_ == CacheKind::SetProp) { + // Constant PropertyName, no guards necessary. + MOZ_ASSERT(&idVal_.toString()->asAtom() == id.toAtom()); + return; + } + + MOZ_ASSERT(cacheKind_ == CacheKind::SetElem); + emitIdGuard(setElemKeyValueId(), idVal_, id); +} + +GetNameIRGenerator::GetNameIRGenerator(JSContext* cx, HandleScript script, + jsbytecode* pc, ICState state, + HandleObject env, + Handle name) + : IRGenerator(cx, script, pc, CacheKind::GetName, state), + 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; +} + +static bool CanAttachGlobalName(JSContext* cx, + GlobalLexicalEnvironmentObject* globalLexical, + PropertyKey id, NativeObject** holder, + Maybe* prop) { + // The property must be found, and it must be found as a normal data property. + NativeObject* current = globalLexical; + while (true) { + *prop = current->lookup(cx, id); + if (prop->isSome()) { + 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()) { + return false; + } + + current = &proto->as(); + } + } + + *holder = current; + return true; +} + +AttachDecision GetNameIRGenerator::tryAttachGlobalNameValue(ObjOperandId objId, + HandleId id) { + if (!IsGlobalOp(JSOp(*pc_))) { + return AttachDecision::NoAction; + } + MOZ_ASSERT(!script_->hasNonSyntacticScope()); + + auto* globalLexical = &env_->as(); + + NativeObject* holder = nullptr; + Maybe prop; + if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &prop)) { + return AttachDecision::NoAction; + } + + // The property must be found, and it must be found as a normal data property. + if (!prop->isDataProperty()) { + return AttachDecision::NoAction; + } + + // This might still be an uninitialized lexical. + if (holder->getSlot(prop->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(prop->slot()) * sizeof(Value); + writer.loadDynamicSlotResult(objId, dynamicSlotOffset); + } else if (holder == &globalLexical->global()) { + MOZ_ASSERT(globalLexical->global().isGenerationCountedGlobal()); + writer.guardGlobalGeneration( + globalLexical->global().generationCount(), + globalLexical->global().addressOfGenerationCount()); + ObjOperandId holderId = writer.loadObject(holder); +#ifdef DEBUG + writer.assertPropertyLookup(holderId, id, prop->slot()); +#endif + EmitLoadSlotResult(writer, holderId, holder, *prop); + } 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 (!IsCacheableGetPropSlot(&globalLexical->global(), holder, *prop)) { + return AttachDecision::NoAction; + } + + // Shape guard for global lexical. + writer.guardShape(objId, globalLexical->shape()); + + // Guard on the shape of the GlobalObject. + ObjOperandId globalId = writer.loadObject(&globalLexical->global()); + writer.guardShape(globalId, globalLexical->global().shape()); + + // Shape guard holder. + ObjOperandId holderId = writer.loadObject(holder); + writer.guardShape(holderId, holder->shape()); + + EmitLoadSlotResult(writer, holderId, holder, *prop); + } + + writer.returnFromIC(); + + trackAttached("GetName.GlobalNameValue"); + return AttachDecision::Attach; +} + +AttachDecision GetNameIRGenerator::tryAttachGlobalNameGetter(ObjOperandId objId, + HandleId id) { + if (!IsGlobalOp(JSOp(*pc_))) { + return AttachDecision::NoAction; + } + MOZ_ASSERT(!script_->hasNonSyntacticScope()); + + Handle globalLexical = + env_.as(); + MOZ_ASSERT(globalLexical->isGlobal()); + + NativeObject* holder = nullptr; + Maybe prop; + if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &prop)) { + return AttachDecision::NoAction; + } + + if (holder == globalLexical) { + return AttachDecision::NoAction; + } + + GlobalObject* global = &globalLexical->global(); + + NativeGetPropKind kind = IsCacheableGetPropCall(global, holder, *prop); + if (kind != NativeGetPropKind::NativeGetter && + kind != NativeGetPropKind::ScriptedGetter) { + return AttachDecision::NoAction; + } + + bool needsWindowProxy = + IsWindow(global) && GetterNeedsWindowProxyThis(holder, *prop); + + // Shape guard for global lexical. + writer.guardShape(objId, globalLexical->shape()); + + // Guard on the shape of the GlobalObject. + ObjOperandId globalId = writer.loadEnclosingEnvironment(objId); + writer.guardShape(globalId, global->shape()); + + if (holder != global) { + // Shape guard holder. + ObjOperandId holderId = writer.loadObject(holder); + writer.guardShape(holderId, holder->shape()); + EmitGuardGetterSetterSlot(writer, holder, *prop, holderId, + /* holderIsConstant = */ true); + } else { + // Note: pass true for |holderIsConstant| because the holder must be the + // current global object. + EmitGuardGetterSetterSlot(writer, holder, *prop, globalId, + /* holderIsConstant = */ true); + } + + if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, global, holder, *prop, + mode_)) { + // The global shape guard above ensures the instance JSClass is correct. + MOZ_ASSERT(!needsWindowProxy); + EmitCallDOMGetterResultNoGuards(writer, holder, *prop, globalId); + trackAttached("GetName.GlobalNameDOMGetter"); + } else { + ObjOperandId receiverObjId; + if (needsWindowProxy) { + MOZ_ASSERT(cx_->global()->maybeWindowProxy()); + receiverObjId = writer.loadObject(cx_->global()->maybeWindowProxy()); + } else { + receiverObjId = globalId; + } + ValOperandId receiverId = writer.boxObject(receiverObjId); + EmitCallGetterResultNoGuards(cx_, writer, kind, global, holder, *prop, + receiverId); + trackAttached("GetName.GlobalNameGetter"); + } + + return AttachDecision::Attach; +} + +static bool NeedEnvironmentShapeGuard(JSContext* cx, JSObject* envObj) { + if (!envObj->is()) { + 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(); + JSFunction* fun = &callObj->callee(); + if (!fun->hasBaseScript() || fun->baseScript()->funHasExtensibleScope() || + DebugEnvironments::hasDebugEnvironment(cx, *callObj)) { + return true; + } + + return false; +} + +AttachDecision GetNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId, + HandleId id) { + if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) { + return AttachDecision::NoAction; + } + + JSObject* env = env_; + Maybe prop; + NativeObject* holder = nullptr; + + while (env) { + if (env->is()) { + prop = env->as().lookup(cx_, id); + if (prop.isSome()) { + break; + } + return AttachDecision::NoAction; + } + + if (!env->is() || env->is()) { + 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. + prop = env->as().lookup(cx_, id); + if (prop.isSome()) { + break; + } + + env = env->enclosingEnvironment(); + } + + holder = &env->as(); + if (!IsCacheableGetPropSlot(holder, holder, *prop)) { + return AttachDecision::NoAction; + } + if (holder->getSlot(prop->slot()).isMagic()) { + MOZ_ASSERT(holder->is()); + return AttachDecision::NoAction; + } + + ObjOperandId lastObjId = objId; + env = env_; + while (env) { + if (NeedEnvironmentShapeGuard(cx_, env)) { + writer.guardShape(lastObjId, env->shape()); + } + + if (env == holder) { + break; + } + + lastObjId = writer.loadEnclosingEnvironment(lastObjId); + env = env->enclosingEnvironment(); + } + + ValOperandId resId = EmitLoadSlot(writer, holder, lastObjId, prop->slot()); + if (holder->is()) { + writer.guardIsNotUninitializedLexical(resId); + } + writer.loadOperandResult(resId); + writer.returnFromIC(); + + trackAttached("GetName.EnvironmentName"); + return AttachDecision::Attach; +} + +void GetNameIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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 state, + HandleObject env, + Handle name) + : IRGenerator(cx, script, pc, CacheKind::BindName, state), + 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_))) { + return AttachDecision::NoAction; + } + MOZ_ASSERT(!script_->hasNonSyntacticScope()); + + Handle globalLexical = + env_.as(); + MOZ_ASSERT(globalLexical->isGlobal()); + + JSObject* result = nullptr; + if (Maybe prop = globalLexical->lookup(cx_, id)) { + // If this is an uninitialized lexical or a const, we need to return a + // RuntimeLexicalErrorObject. + if (globalLexical->getSlot(prop->slot()).isMagic() || !prop->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. + Maybe prop = result->as().lookup(cx_, id); + if (prop.isNothing() || prop->configurable()) { + writer.guardShape(objId, globalLexical->shape()); + } + ObjOperandId globalId = writer.loadEnclosingEnvironment(objId); + writer.loadObjectResult(globalId); + } + writer.returnFromIC(); + + trackAttached("BindName.GlobalName"); + return AttachDecision::Attach; +} + +AttachDecision BindNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId, + HandleId id) { + if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) { + return AttachDecision::NoAction; + } + + JSObject* env = env_; + Maybe prop; + while (true) { + if (!env->is() && !env->is()) { + return AttachDecision::NoAction; + } + if (env->is()) { + 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. + prop = env->as().lookup(cx_, id); + if (prop.isSome()) { + break; + } + + env = env->enclosingEnvironment(); + } + + // If this is an uninitialized lexical or a const, we need to return a + // RuntimeLexicalErrorObject. + auto* holder = &env->as(); + if (prop.isSome() && holder->is() && + (holder->getSlot(prop->slot()).isMagic() || !prop->writable())) { + return AttachDecision::NoAction; + } + + ObjOperandId lastObjId = objId; + env = env_; + while (env) { + if (NeedEnvironmentShapeGuard(cx_, env) && !env->is()) { + writer.guardShape(lastObjId, env->shape()); + } + + if (env == holder) { + break; + } + + lastObjId = writer.loadEnclosingEnvironment(lastObjId); + env = env->enclosingEnvironment(); + } + + if (prop.isSome() && holder->is()) { + ValOperandId valId = EmitLoadSlot(writer, holder, lastObjId, prop->slot()); + writer.guardIsNotUninitializedLexical(valId); + } + + writer.loadObjectResult(lastObjId); + writer.returnFromIC(); + + trackAttached("BindName.EnvironmentName"); + return AttachDecision::Attach; +} + +void BindNameIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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 state, + CacheKind cacheKind, HandleValue idVal, + HandleValue val) + : IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {} + +AttachDecision HasPropIRGenerator::tryAttachDense(HandleObject obj, + ObjOperandId objId, + uint32_t index, + Int32OperandId indexId) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + + NativeObject* nobj = &obj->as(); + if (!nobj->containsDenseElement(index)) { + return AttachDecision::NoAction; + } + + if (mode_ == ICState::Mode::Megamorphic) { + writer.guardIsNativeObject(objId); + } else { + // Guard shape to ensure object class is NativeObject. + TestMatchingNativeReceiver(writer, nobj, objId); + } + writer.loadDenseElementExistsResult(objId, indexId); + writer.returnFromIC(); + + trackAttached("HasProp.Dense"); + return AttachDecision::Attach; +} + +AttachDecision HasPropIRGenerator::tryAttachDenseHole(HandleObject obj, + ObjOperandId objId, + uint32_t index, + Int32OperandId indexId) { + bool hasOwn = (cacheKind_ == CacheKind::HasOwn); + OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No; + + if (!obj->is()) { + return AttachDecision::NoAction; + } + + NativeObject* nobj = &obj->as(); + if (nobj->containsDenseElement(index)) { + return AttachDecision::NoAction; + } + if (!CanAttachDenseElementHole(nobj, ownProp)) { + 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("HasProp.DenseHole"); + return AttachDecision::Attach; +} + +AttachDecision HasPropIRGenerator::tryAttachSparse(HandleObject obj, + ObjOperandId objId, + Int32OperandId indexId) { + bool hasOwn = (cacheKind_ == CacheKind::HasOwn); + OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No; + + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto* nobj = &obj->as(); + + if (!nobj->isIndexed()) { + return AttachDecision::NoAction; + } + if (!CanAttachDenseElementHole(nobj, ownProp, AllowIndexedReceiver::Yes)) { + 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, nobj, 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("HasProp.Sparse"); + return AttachDecision::Attach; +} + +AttachDecision HasPropIRGenerator::tryAttachArgumentsObjectArg( + HandleObject obj, ObjOperandId objId, Int32OperandId indexId) { + bool hasOwn = (cacheKind_ == CacheKind::HasOwn); + OwnProperty ownProp = hasOwn ? OwnProperty::Yes : OwnProperty::No; + + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto* args = &obj->as(); + + // No elements must have been overridden or deleted. + if (args->hasOverriddenElement()) { + return AttachDecision::NoAction; + } + + if (!CanAttachDenseElementHole(args, ownProp, AllowIndexedReceiver::Yes, + AllowExtraReceiverProperties::Yes)) { + return AttachDecision::NoAction; + } + + if (args->is()) { + writer.guardClass(objId, GuardClassKind::MappedArguments); + } else { + MOZ_ASSERT(args->is()); + writer.guardClass(objId, GuardClassKind::UnmappedArguments); + } + + if (!hasOwn) { + GeneratePrototypeHoleGuards(writer, args, objId, + /* alwaysGuardFirstProto = */ true); + } + + writer.loadArgumentsObjectArgExistsResult(objId, indexId); + writer.returnFromIC(); + + trackAttached("HasProp.ArgumentsObjectArg"); + return AttachDecision::Attach; +} + +AttachDecision HasPropIRGenerator::tryAttachNamedProp(HandleObject obj, + ObjOperandId objId, + HandleId key, + ValOperandId keyId) { + bool hasOwn = (cacheKind_ == CacheKind::HasOwn); + + Rooted holder(cx_); + PropertyResult prop; + + if (hasOwn) { + if (!LookupOwnPropertyPure(cx_, obj, key, &prop)) { + return AttachDecision::NoAction; + } + + holder.set(&obj->as()); + } else { + NativeObject* nHolder = nullptr; + if (!LookupPropertyPure(cx_, obj, key, &nHolder, &prop)) { + return AttachDecision::NoAction; + } + holder.set(nHolder); + } + if (prop.isNotFound()) { + return AttachDecision::NoAction; + } + + TRY_ATTACH(tryAttachSmallObjectVariableKey(obj, objId, key, keyId)); + TRY_ATTACH(tryAttachMegamorphic(objId, keyId)); + TRY_ATTACH(tryAttachNative(&obj->as(), objId, key, keyId, prop, + holder.get())); + + return AttachDecision::NoAction; +} + +AttachDecision HasPropIRGenerator::tryAttachSmallObjectVariableKey( + HandleObject obj, ObjOperandId objId, jsid key, ValOperandId keyId) { + MOZ_ASSERT(obj->is()); + + if (cacheKind_ != CacheKind::HasOwn) { + return AttachDecision::NoAction; + } + + if (mode_ != ICState::Mode::Megamorphic) { + return AttachDecision::NoAction; + } + + if (numOptimizedStubs_ != 0) { + return AttachDecision::NoAction; + } + + if (!key.isString()) { + return AttachDecision::NoAction; + } + + if (!obj->as().hasEmptyElements()) { + return AttachDecision::NoAction; + } + + if (obj->getClass()->getResolve()) { + return AttachDecision::NoAction; + } + + if (!obj->shape()->isShared()) { + return AttachDecision::NoAction; + } + + static constexpr size_t SMALL_OBJECT_SIZE = 5; + + if (obj->shape()->asShared().slotSpan() > SMALL_OBJECT_SIZE) { + return AttachDecision::NoAction; + } + + Rooted keyListObj(cx_, ListObject::create(cx_)); + if (!keyListObj) { + cx_->recoverFromOutOfMemory(); + return AttachDecision::NoAction; + } + + for (SharedShapePropertyIter iter(cx_, &obj->shape()->asShared()); + !iter.done(); iter++) { + if (!iter->key().isAtom()) { + return AttachDecision::NoAction; + } + + if (keyListObj->length() == SMALL_OBJECT_SIZE) { + return AttachDecision::NoAction; + } + + RootedValue key(cx_, StringValue(iter->key().toAtom())); + if (!keyListObj->append(cx_, key)) { + cx_->recoverFromOutOfMemory(); + return AttachDecision::NoAction; + } + } + + writer.guardShape(objId, obj->shape()); + writer.guardNoDenseElements(objId); + StringOperandId keyStrId = writer.guardToString(keyId); + StringOperandId keyAtomId = writer.stringToAtom(keyStrId); + writer.smallObjectVariableKeyHasOwnResult(keyAtomId, keyListObj, + obj->shape()); + writer.returnFromIC(); + trackAttached("HasProp.SmallObjectVariableKey"); + return AttachDecision::Attach; +} + +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("HasProp.Megamorphic"); + return AttachDecision::Attach; +} + +AttachDecision HasPropIRGenerator::tryAttachNative(NativeObject* obj, + ObjOperandId objId, jsid key, + ValOperandId keyId, + PropertyResult prop, + NativeObject* holder) { + MOZ_ASSERT(IsCacheableProtoChain(obj, holder)); + + if (!prop.isNativeProperty()) { + return AttachDecision::NoAction; + } + + emitIdGuard(keyId, idVal_, key); + EmitReadSlotGuard(writer, obj, holder, objId); + writer.loadBooleanResult(true); + writer.returnFromIC(); + + trackAttached("HasProp.Native"); + return AttachDecision::Attach; +} + +AttachDecision HasPropIRGenerator::tryAttachTypedArray(HandleObject obj, + ObjOperandId objId, + ValOperandId keyId) { + // TODO: Support resizable typed arrays. (bug 1842999) + if (!obj->is()) { + return AttachDecision::NoAction; + } + + int64_t index; + if (!ValueIsInt64Index(idVal_, &index)) { + return AttachDecision::NoAction; + } + + writer.guardIsFixedLengthTypedArray(objId); + IntPtrOperandId intPtrIndexId = + guardToIntPtrIndex(idVal_, keyId, /* supportOOB = */ true); + writer.loadTypedArrayElementExistsResult(objId, intPtrIndexId); + writer.returnFromIC(); + + trackAttached("HasProp.TypedArrayObject"); + return AttachDecision::Attach; +} + +AttachDecision HasPropIRGenerator::tryAttachSlotDoesNotExist( + NativeObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId) { + bool hasOwn = (cacheKind_ == CacheKind::HasOwn); + + emitIdGuard(keyId, idVal_, key); + if (hasOwn) { + TestMatchingNativeReceiver(writer, obj, objId); + } else { + EmitMissingPropGuard(writer, obj, objId); + } + writer.loadBooleanResult(false); + writer.returnFromIC(); + + trackAttached("HasProp.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(tryAttachSmallObjectVariableKey(obj, objId, key, keyId)); + TRY_ATTACH(tryAttachMegamorphic(objId, keyId)); + TRY_ATTACH( + tryAttachSlotDoesNotExist(&obj->as(), objId, key, keyId)); + + return AttachDecision::NoAction; +} + +AttachDecision HasPropIRGenerator::tryAttachProxyElement(HandleObject obj, + ObjOperandId objId, + ValOperandId keyId) { + bool hasOwn = (cacheKind_ == CacheKind::HasOwn); + + if (!obj->is()) { + return AttachDecision::NoAction; + } + + writer.guardIsProxy(objId); + writer.proxyHasPropResult(objId, keyId, hasOwn); + writer.returnFromIC(); + + trackAttached("HasProp.ProxyElement"); + 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; + } + + TRY_ATTACH(tryAttachTypedArray(obj, objId, keyId)); + + 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(tryAttachSparse(obj, objId, indexId)); + TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, indexId)); + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; + } + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; +} + +void HasPropIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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 state, + CacheKind cacheKind, HandleValue idVal, HandleValue val) + : IRGenerator(cx, script, pc, cacheKind, state), 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; + } + JSObject* obj = &val_.toObject(); + ObjOperandId objId = writer.guardToObject(valId); + PropertyKey key = PropertyKey::Symbol(idVal_.toSymbol()); + + ThrowCondition condition; + ThrowMsgKind msgKind; + GetCheckPrivateFieldOperands(pc_, &condition, &msgKind); + + PropertyResult prop; + if (!LookupOwnPropertyPure(cx_, obj, key, &prop)) { + return AttachDecision::NoAction; + } + + if (CheckPrivateFieldWillThrow(condition, prop.isFound())) { + // Don't attach a stub if the operation will throw. + return AttachDecision::NoAction; + } + + auto* nobj = &obj->as(); + + TRY_ATTACH(tryAttachNative(nobj, objId, key, keyId, prop)); + + return AttachDecision::NoAction; +} + +AttachDecision CheckPrivateFieldIRGenerator::tryAttachNative( + NativeObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId, + PropertyResult prop) { + MOZ_ASSERT(prop.isNativeProperty() || prop.isNotFound()); + + emitIdGuard(keyId, idVal_, key); + TestMatchingNativeReceiver(writer, obj, objId); + writer.loadBooleanResult(prop.isFound()); + writer.returnFromIC(); + + trackAttached("CheckPrivateField.Native"); + return AttachDecision::Attach; +} + +void CheckPrivateFieldIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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 state, HandleValue lhsVal, + HandleValue idVal, HandleValue rhsVal) + : IRGenerator(cx, script, pc, cacheKind, state), + 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)); + TRY_ATTACH(tryAttachMegamorphicSetSlot(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)); + } + + TRY_ATTACH(tryAttachSetTypedArrayElement(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(tryAttachAddOrUpdateSparseElement(obj, objId, index, indexId, + rhsValId)); + return AttachDecision::NoAction; + } + } + return AttachDecision::NoAction; +} + +static void EmitStoreSlotAndReturn(CacheIRWriter& writer, ObjOperandId objId, + NativeObject* nobj, PropertyInfo prop, + ValOperandId rhsId) { + if (nobj->isFixedSlot(prop.slot())) { + size_t offset = NativeObject::getFixedSlotOffset(prop.slot()); + writer.storeFixedSlot(objId, offset, rhsId); + } else { + size_t offset = nobj->dynamicSlotIndex(prop.slot()) * sizeof(Value); + writer.storeDynamicSlot(objId, offset, rhsId); + } + writer.returnFromIC(); +} + +static Maybe LookupShapeForSetSlot(JSOp op, NativeObject* obj, + jsid id) { + Maybe prop = obj->lookupPure(id); + if (prop.isNothing() || !prop->isDataProperty() || !prop->writable()) { + return mozilla::Nothing(); + } + + // If this is a property init operation, the property's attributes may have to + // be changed too, so make sure the current flags match. + if (IsPropertyInitOp(op)) { + // Don't support locked init operations. + if (IsLockedInitOp(op)) { + return mozilla::Nothing(); + } + + // Can't redefine a non-configurable property. + if (!prop->configurable()) { + return mozilla::Nothing(); + } + + // Make sure the enumerable flag matches the init operation. + if (IsHiddenInitOp(op) == prop->enumerable()) { + return mozilla::Nothing(); + } + } + + return prop; +} + +static bool CanAttachNativeSetSlot(JSOp op, JSObject* obj, PropertyKey id, + Maybe* prop) { + if (!obj->is()) { + return false; + } + + if (Watchtower::watchesPropertyModification(&obj->as())) { + return false; + } + + *prop = LookupShapeForSetSlot(op, &obj->as(), id); + return prop->isSome(); +} + +// 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, + PropertyInfo prop) { + // Ensure that the env can't change. + if (op != JSOp::SetGName && op != JSOp::StrictSetGName) { + return false; + } + + if (!obj->is()) { + return false; + } + + // Uninitialized let bindings use a RuntimeLexicalErrorObject. + MOZ_ASSERT(!obj->getSlot(prop.slot()).isMagic()); + MOZ_ASSERT(prop.writable()); + MOZ_ASSERT(!prop.configurable()); + return true; +} + +AttachDecision SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj, + ObjOperandId objId, + HandleId id, + ValOperandId rhsId) { + Maybe prop; + if (!CanAttachNativeSetSlot(JSOp(*pc_), obj, id, &prop)) { + return AttachDecision::NoAction; + } + + if (mode_ == ICState::Mode::Megamorphic && cacheKind_ == CacheKind::SetProp && + IsPropertySetOp(JSOp(*pc_))) { + return AttachDecision::NoAction; + } + + maybeEmitIdGuard(id); + + NativeObject* nobj = &obj->as(); + if (!IsGlobalLexicalSetGName(JSOp(*pc_), nobj, *prop)) { + TestMatchingNativeReceiver(writer, nobj, objId); + } + EmitStoreSlotAndReturn(writer, objId, nobj, *prop, rhsId); + + trackAttached("SetProp.NativeSlot"); + return AttachDecision::Attach; +} + +static bool ValueCanConvertToNumeric(Scalar::Type type, const Value& val) { + if (Scalar::isBigIntType(type)) { + return val.isBigInt(); + } + return val.isNumber() || val.isNullOrUndefined() || val.isBoolean() || + val.isString(); +} + +OperandId IRGenerator::emitNumericGuard(ValOperandId valId, const Value& v, + Scalar::Type type) { + MOZ_ASSERT(ValueCanConvertToNumeric(type, v)); + switch (type) { + case Scalar::Int8: + case Scalar::Uint8: + case Scalar::Int16: + case Scalar::Uint16: + case Scalar::Int32: + case Scalar::Uint32: { + if (v.isNumber()) { + return writer.guardToInt32ModUint32(valId); + } + if (v.isNullOrUndefined()) { + writer.guardIsNullOrUndefined(valId); + return writer.loadInt32Constant(0); + } + if (v.isBoolean()) { + return writer.guardBooleanToInt32(valId); + } + MOZ_ASSERT(v.isString()); + StringOperandId strId = writer.guardToString(valId); + NumberOperandId numId = writer.guardStringToNumber(strId); + return writer.truncateDoubleToUInt32(numId); + } + + case Scalar::Float32: + case Scalar::Float64: { + if (v.isNumber()) { + return writer.guardIsNumber(valId); + } + if (v.isNull()) { + writer.guardIsNull(valId); + return writer.loadDoubleConstant(0.0); + } + if (v.isUndefined()) { + writer.guardIsUndefined(valId); + return writer.loadDoubleConstant(JS::GenericNaN()); + } + if (v.isBoolean()) { + BooleanOperandId boolId = writer.guardToBoolean(valId); + return writer.booleanToNumber(boolId); + } + MOZ_ASSERT(v.isString()); + StringOperandId strId = writer.guardToString(valId); + return writer.guardStringToNumber(strId); + } + + case Scalar::Uint8Clamped: { + if (v.isNumber()) { + return writer.guardToUint8Clamped(valId); + } + if (v.isNullOrUndefined()) { + writer.guardIsNullOrUndefined(valId); + return writer.loadInt32Constant(0); + } + if (v.isBoolean()) { + return writer.guardBooleanToInt32(valId); + } + MOZ_ASSERT(v.isString()); + StringOperandId strId = writer.guardToString(valId); + NumberOperandId numId = writer.guardStringToNumber(strId); + return writer.doubleToUint8Clamped(numId); + } + + case Scalar::BigInt64: + case Scalar::BigUint64: + MOZ_ASSERT(v.isBigInt()); + return writer.guardToBigInt(valId); + + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + case Scalar::Simd128: + break; + } + MOZ_CRASH("Unsupported TypedArray type"); +} + +void SetPropIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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(NativeObject* obj, + NativeObject* holder, + PropertyInfo prop) { + MOZ_ASSERT(IsCacheableProtoChain(obj, holder)); + + if (!prop.isAccessorProperty()) { + return false; + } + + JSObject* setterObject = holder->getSetter(prop); + if (!setterObject || !setterObject->is()) { + return false; + } + + JSFunction& setter = setterObject->as(); + if (!setter.isNativeWithoutJitEntry()) { + return false; + } + + if (setter.isClassConstructor()) { + return false; + } + + return true; +} + +static bool IsCacheableSetPropCallScripted(NativeObject* obj, + NativeObject* holder, + PropertyInfo prop) { + MOZ_ASSERT(IsCacheableProtoChain(obj, holder)); + + if (!prop.isAccessorProperty()) { + return false; + } + + JSObject* setterObject = holder->getSetter(prop); + if (!setterObject || !setterObject->is()) { + return false; + } + + JSFunction& setter = setterObject->as(); + 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, JSObject* obj, + PropertyKey id, NativeObject** holder, + Maybe* propInfo) { + // Don't attach a setter stub for ops like JSOp::InitElem. + MOZ_ASSERT(IsPropertySetOp(JSOp(*pc))); + + PropertyResult prop; + if (!LookupPropertyPure(cx, obj, id, holder, &prop)) { + return false; + } + auto* nobj = &obj->as(); + + if (!prop.isNativeProperty()) { + return false; + } + + if (!IsCacheableSetPropCallScripted(nobj, *holder, prop.propertyInfo()) && + !IsCacheableSetPropCallNative(nobj, *holder, prop.propertyInfo())) { + return false; + } + + *propInfo = mozilla::Some(prop.propertyInfo()); + return true; +} + +static void EmitCallSetterNoGuards(JSContext* cx, CacheIRWriter& writer, + NativeObject* obj, NativeObject* holder, + PropertyInfo prop, ObjOperandId receiverId, + ValOperandId rhsId) { + JSFunction* target = &holder->getSetter(prop)->as(); + bool sameRealm = cx->realm() == target->realm(); + + if (target->isNativeWithoutJitEntry()) { + MOZ_ASSERT(IsCacheableSetPropCallNative(obj, holder, prop)); + writer.callNativeSetter(receiverId, target, rhsId, sameRealm); + writer.returnFromIC(); + return; + } + + MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, prop)); + writer.callScriptedSetter(receiverId, target, rhsId, sameRealm); + writer.returnFromIC(); +} + +static void EmitCallDOMSetterNoGuards(JSContext* cx, CacheIRWriter& writer, + NativeObject* holder, PropertyInfo prop, + ObjOperandId objId, ValOperandId rhsId) { + JSFunction* setter = &holder->getSetter(prop)->as(); + 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) { + // Don't attach a setter stub for ops like JSOp::InitElem. + MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); + + NativeObject* holder = nullptr; + Maybe prop; + if (!CanAttachSetter(cx_, pc_, obj, id, &holder, &prop)) { + return AttachDecision::NoAction; + } + auto* nobj = &obj->as(); + + bool needsWindowProxy = + IsWindow(nobj) && SetterNeedsWindowProxyThis(holder, *prop); + + 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(nobj)) { + TestMatchingNativeReceiver(writer, nobj, objId); + + if (nobj != holder) { + GeneratePrototypeGuards(writer, nobj, holder, objId); + + // Guard on the holder's shape. + ObjOperandId holderId = writer.loadObject(holder); + TestMatchingHolder(writer, holder, holderId); + + EmitGuardGetterSetterSlot(writer, holder, *prop, holderId, + /* holderIsConstant = */ true); + } else { + EmitGuardGetterSetterSlot(writer, holder, *prop, objId); + } + } else { + GetterSetter* gs = holder->getGetterSetter(*prop); + writer.guardHasGetterSetter(objId, id, gs); + } + + if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Setter, nobj, holder, *prop, + mode_)) { + MOZ_ASSERT(!needsWindowProxy); + EmitCallDOMSetterNoGuards(cx_, writer, holder, *prop, objId, rhsId); + + trackAttached("SetProp.DOMSetter"); + return AttachDecision::Attach; + } + + ObjOperandId receiverId; + if (needsWindowProxy) { + MOZ_ASSERT(cx_->global()->maybeWindowProxy()); + receiverId = writer.loadObject(cx_->global()->maybeWindowProxy()); + } else { + receiverId = objId; + } + EmitCallSetterNoGuards(cx_, writer, nobj, holder, *prop, receiverId, rhsId); + + trackAttached("SetProp.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() || !id.isAtom(cx_->names().length) || + !obj->as().lengthIsWritable()) { + return AttachDecision::NoAction; + } + + maybeEmitIdGuard(id); + emitOptimisticClassGuard(objId, obj, GuardClassKind::Array); + writer.callSetArrayLength(objId, IsStrictSetPC(pc_), rhsId); + writer.returnFromIC(); + + trackAttached("SetProp.ArrayLength"); + return AttachDecision::Attach; +} + +AttachDecision SetPropIRGenerator::tryAttachSetDenseElement( + HandleObject obj, ObjOperandId objId, uint32_t index, + Int32OperandId indexId, ValOperandId rhsId) { + if (!obj->is()) { + return AttachDecision::NoAction; + } + + NativeObject* nobj = &obj->as(); + 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)); + + JSOp op = JSOp(*pc_); + + // We don't currently emit locked init for any indexed properties. + MOZ_ASSERT(!IsLockedInitOp(op)); + + // We don't currently emit hidden init for any existing indexed properties. + MOZ_ASSERT(!IsHiddenInitOp(op)); + + // 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(op) && !nobj->isExtensible()) { + return AttachDecision::NoAction; + } + + TestMatchingNativeReceiver(writer, nobj, objId); + + writer.storeDenseElement(objId, indexId, rhsId); + writer.returnFromIC(); + + trackAttached("SetProp.DenseElement"); + return AttachDecision::Attach; +} + +static bool CanAttachAddElement(NativeObject* obj, bool isInit, + AllowIndexedReceiver allowIndexedReceiver) { + // Make sure the receiver doesn't have any indexed properties and that such + // properties can't appear without a shape change. + if (allowIndexedReceiver == AllowIndexedReceiver::No && obj->isIndexed()) { + return false; + } + + do { + // This check is also relevant for the receiver object. + 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->is()) { + return false; + } + + NativeObject* nproto = &proto->as(); + if (nproto->isIndexed()) { + return false; + } + + // We have to make sure the proto has no non-writable (frozen) elements + // because we're not allowed to shadow them. + 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->is()) { + 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)); + + // We don't currently emit locked init for any indexed properties. + MOZ_ASSERT(!IsLockedInitOp(op)); + + // Hidden init can be emitted for absent indexed properties. + if (IsHiddenInitOp(op)) { + MOZ_ASSERT(op == JSOp::InitHiddenElem); + return AttachDecision::NoAction; + } + + NativeObject* nobj = &obj->as(); + 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. + // + // TODO(post-Warp): noteHasDenseAdd (nee: noteArrayWriteHole) no longer exists + 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() && + !nobj->as().lengthIsWritable()) { + return AttachDecision::NoAction; + } + + // Typed arrays don't have dense elements. + if (nobj->is()) { + return AttachDecision::NoAction; + } + + // Check for other indexed properties or class hooks. + if (!CanAttachAddElement(nobj, IsPropertyInitOp(op), + AllowIndexedReceiver::No)) { + return AttachDecision::NoAction; + } + + TestMatchingNativeReceiver(writer, nobj, objId); + + // Also shape guard the proto chain, unless this is an InitElem. + if (IsPropertySetOp(op)) { + ShapeGuardProtoChain(writer, nobj, 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 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->is()) { + return AttachDecision::NoAction; + } + NativeObject* nobj = &obj->as(); + + // We cannot attach a stub to a non-extensible object + if (!nobj->isExtensible()) { + return AttachDecision::NoAction; + } + + // Stub doesn't handle negative indices. + if (index > INT32_MAX) { + return AttachDecision::NoAction; + } + + // The index must not be for a dense element. + if (nobj->containsDenseElement(index)) { + return AttachDecision::NoAction; + } + + // Only handle ArrayObject and PlainObject in this stub. + if (!nobj->is() && !nobj->is()) { + return AttachDecision::NoAction; + } + + // Don't attach if we're adding to an array with non-writable length. + if (nobj->is()) { + ArrayObject* aobj = &nobj->as(); + bool isAdd = (index >= aobj->length()); + if (isAdd && !aobj->lengthIsWritable()) { + return AttachDecision::NoAction; + } + } + + // Check for class hooks or indexed properties on the prototype chain that + // we're not allowed to shadow. + if (!CanAttachAddElement(nobj, /* isInit = */ false, + AllowIndexedReceiver::Yes)) { + return AttachDecision::NoAction; + } + + // Ensure that obj is an ArrayObject or PlainObject. + if (nobj->is()) { + writer.guardClass(objId, GuardClassKind::Array); + } else { + MOZ_ASSERT(nobj->is()); + writer.guardClass(objId, GuardClassKind::PlainObject); + } + + // The helper we are going to call only applies to non-dense elements. + writer.guardIndexIsNotDenseElement(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. + GuardReceiverProto(writer, nobj, 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. + if (IsPropertySetOp(op)) { + ShapeGuardProtoChain(writer, nobj, objId); + } + + // Ensure that if we're adding an element to the object, the object's + // length is writable. + if (nobj->is()) { + writer.guardIndexIsValidUpdateOrAdd(objId, indexId); + } + + writer.callAddOrUpdateSparseElementHelper( + objId, indexId, rhsId, + /* strict = */ op == JSOp::StrictSetElem); + writer.returnFromIC(); + + trackAttached("SetProp.AddOrUpdateSparseElement"); + return AttachDecision::Attach; +} + +AttachDecision SetPropIRGenerator::tryAttachSetTypedArrayElement( + HandleObject obj, ObjOperandId objId, ValOperandId rhsId) { + // TODO: Support resizable typed arrays. (bug 1842999) + if (!obj->is()) { + return AttachDecision::NoAction; + } + if (!idVal_.isNumber()) { + return AttachDecision::NoAction; + } + + auto* tarr = &obj->as(); + Scalar::Type elementType = tarr->type(); + + // Don't attach if the input type doesn't match the guard added below. + if (!ValueCanConvertToNumeric(elementType, rhsVal_)) { + return AttachDecision::NoAction; + } + + bool handleOOB = false; + int64_t indexInt64; + if (!ValueIsInt64Index(idVal_, &indexInt64) || indexInt64 < 0 || + uint64_t(indexInt64) >= tarr->length()) { + handleOOB = true; + } + + JSOp op = JSOp(*pc_); + + // The only expected property init operation is InitElem. + MOZ_ASSERT_IF(IsPropertyInitOp(op), op == JSOp::InitElem); + + // InitElem (DefineProperty) has to throw an exception on out-of-bounds. + if (handleOOB && IsPropertyInitOp(op)) { + return AttachDecision::NoAction; + } + + writer.guardShapeForClass(objId, tarr->shape()); + + OperandId rhsValId = emitNumericGuard(rhsId, rhsVal_, elementType); + + ValOperandId keyId = setElemKeyValueId(); + IntPtrOperandId indexId = guardToIntPtrIndex(idVal_, keyId, handleOOB); + + writer.storeTypedArrayElement(objId, elementType, indexId, rhsValId, + handleOOB); + writer.returnFromIC(); + + trackAttached(handleOOB ? "SetTypedElementOOB" : "SetTypedElement"); + return AttachDecision::Attach; +} + +AttachDecision SetPropIRGenerator::tryAttachGenericProxy( + Handle obj, ObjOperandId objId, HandleId id, + ValOperandId rhsId, bool handleDOMProxies) { + // Don't attach a proxy stub for ops like JSOp::InitElem. + MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); + + 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("SetProp.GenericProxy"); + return AttachDecision::Attach; +} + +AttachDecision SetPropIRGenerator::tryAttachDOMProxyShadowed( + Handle obj, ObjOperandId objId, HandleId id, + ValOperandId rhsId) { + // Don't attach a proxy stub for ops like JSOp::InitElem. + MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); + + MOZ_ASSERT(IsCacheableDOMProxy(obj)); + + maybeEmitIdGuard(id); + TestMatchingProxyReceiver(writer, obj, objId); + writer.proxySet(objId, id, rhsId, IsStrictSetPC(pc_)); + writer.returnFromIC(); + + trackAttached("SetProp.DOMProxyShadowed"); + return AttachDecision::Attach; +} + +AttachDecision SetPropIRGenerator::tryAttachDOMProxyUnshadowed( + Handle obj, ObjOperandId objId, HandleId id, + ValOperandId rhsId) { + // Don't attach a proxy stub for ops like JSOp::InitElem. + MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); + + MOZ_ASSERT(IsCacheableDOMProxy(obj)); + + JSObject* proto = obj->staticPrototype(); + if (!proto) { + return AttachDecision::NoAction; + } + + NativeObject* holder = nullptr; + Maybe prop; + if (!CanAttachSetter(cx_, pc_, proto, id, &holder, &prop)) { + return AttachDecision::NoAction; + } + auto* nproto = &proto->as(); + + maybeEmitIdGuard(id); + + // Guard that our proxy (expando) object hasn't started shadowing this + // property. + TestMatchingProxyReceiver(writer, obj, objId); + bool canOptimizeMissing = false; + CheckDOMProxyDoesNotShadow(writer, obj, id, objId, &canOptimizeMissing); + + GeneratePrototypeGuards(writer, obj, holder, objId); + + // Guard on the holder of the property. + ObjOperandId holderId = writer.loadObject(holder); + TestMatchingHolder(writer, holder, holderId); + + EmitGuardGetterSetterSlot(writer, holder, *prop, holderId, + /* holderIsConstant = */ true); + + // 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, nproto, holder, *prop, objId, rhsId); + + trackAttached("SetProp.DOMProxyUnshadowed"); + return AttachDecision::Attach; +} + +AttachDecision SetPropIRGenerator::tryAttachDOMProxyExpando( + Handle obj, ObjOperandId objId, HandleId id, + ValOperandId rhsId) { + // Don't attach a proxy stub for ops like JSOp::InitElem. + MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); + + MOZ_ASSERT(IsCacheableDOMProxy(obj)); + + Value expandoVal = GetProxyPrivate(obj); + JSObject* expandoObj; + if (expandoVal.isObject()) { + expandoObj = &expandoVal.toObject(); + } else { + MOZ_ASSERT(!expandoVal.isUndefined(), + "How did a missing expando manage to shadow things?"); + auto expandoAndGeneration = + static_cast(expandoVal.toPrivate()); + MOZ_ASSERT(expandoAndGeneration); + expandoObj = &expandoAndGeneration->expando.toObject(); + } + + Maybe prop; + if (CanAttachNativeSetSlot(JSOp(*pc_), expandoObj, id, &prop)) { + auto* nativeExpandoObj = &expandoObj->as(); + + maybeEmitIdGuard(id); + ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape( + obj, objId, expandoVal, nativeExpandoObj); + + EmitStoreSlotAndReturn(writer, expandoObjId, nativeExpandoObj, *prop, + rhsId); + trackAttached("SetProp.DOMProxyExpandoSlot"); + return AttachDecision::Attach; + } + + NativeObject* holder = nullptr; + if (CanAttachSetter(cx_, pc_, expandoObj, id, &holder, &prop)) { + auto* nativeExpandoObj = &expandoObj->as(); + + // Call the setter. Note that we pass objId, the DOM proxy, as |this| + // and not the expando object. + maybeEmitIdGuard(id); + ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape( + obj, objId, expandoVal, nativeExpandoObj); + + MOZ_ASSERT(holder == nativeExpandoObj); + EmitGuardGetterSetterSlot(writer, nativeExpandoObj, *prop, expandoObjId); + EmitCallSetterNoGuards(cx_, writer, nativeExpandoObj, nativeExpandoObj, + *prop, objId, rhsId); + trackAttached("SetProp.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; + } + auto proxy = obj.as(); + + if (mode_ == ICState::Mode::Megamorphic) { + return tryAttachGenericProxy(proxy, objId, id, rhsId, + /* handleDOMProxies = */ true); + } + + switch (type) { + case ProxyStubType::None: + break; + case ProxyStubType::DOMExpando: + TRY_ATTACH(tryAttachDOMProxyExpando(proxy, objId, id, rhsId)); + [[fallthrough]]; // Fall through to the generic shadowed case. + case ProxyStubType::DOMShadowed: + return tryAttachDOMProxyShadowed(proxy, objId, id, rhsId); + case ProxyStubType::DOMUnshadowed: + TRY_ATTACH(tryAttachDOMProxyUnshadowed(proxy, objId, id, rhsId)); + return tryAttachGenericProxy(proxy, objId, id, rhsId, + /* handleDOMProxies = */ true); + case ProxyStubType::Generic: + return tryAttachGenericProxy(proxy, 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()) { + 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("SetProp.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()) { + return AttachDecision::NoAction; + } + + writer.megamorphicSetElement(objId, setElemKeyValueId(), rhsId, + IsStrictSetPC(pc_)); + writer.returnFromIC(); + + trackAttached("SetProp.MegamorphicSetElement"); + return AttachDecision::Attach; +} + +AttachDecision SetPropIRGenerator::tryAttachMegamorphicSetSlot( + HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { + if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetProp) { + return AttachDecision::NoAction; + } + + writer.megamorphicStoreSlot(objId, id, rhsId, IsStrictSetPC(pc_)); + writer.returnFromIC(); + trackAttached("SetProp.MegamorphicNativeSlot"); + return AttachDecision::Attach; +} + +AttachDecision SetPropIRGenerator::tryAttachWindowProxy(HandleObject obj, + ObjOperandId objId, + HandleId id, + ValOperandId rhsId) { + // Don't attach a window proxy stub for ops like JSOp::InitElem. + MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); + + // 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). + GlobalObject* windowObj = cx_->global(); + + Maybe prop; + if (!CanAttachNativeSetSlot(JSOp(*pc_), windowObj, id, &prop)) { + return AttachDecision::NoAction; + } + + maybeEmitIdGuard(id); + + ObjOperandId windowObjId = + GuardAndLoadWindowProxyWindow(writer, objId, windowObj); + writer.guardShape(windowObjId, windowObj->shape()); + + EmitStoreSlotAndReturn(writer, windowObjId, windowObj, *prop, rhsId); + + trackAttached("SetProp.WindowProxySlot"); + return AttachDecision::Attach; +} + +// Detect if |id| refers to the 'prototype' property of a function object. This +// property is special-cased in canAttachAddSlotStub(). +static bool IsFunctionPrototype(const JSAtomState& names, JSObject* obj, + PropertyKey id) { + return obj->is() && id.isAtom(names.prototype); +} + +bool SetPropIRGenerator::canAttachAddSlotStub(HandleObject obj, HandleId id) { + if (!obj->is()) { + return false; + } + auto* nobj = &obj->as(); + + // Special-case JSFunction resolve hook to allow redefining the 'prototype' + // property without triggering lazy expansion of property and object + // allocation. + if (IsFunctionPrototype(cx_->names(), nobj, id)) { + MOZ_ASSERT(ClassMayResolveId(cx_->names(), nobj->getClass(), id, nobj)); + + // 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 = &nobj->as(); + 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_, nobj, id, &prop)) { + return false; + } + if (prop.isFound()) { + return false; + } + } + + // For now we don't optimize Watchtower-monitored objects. + if (Watchtower::watchesPropertyAdd(nobj)) { + return false; + } + + // Object must be extensible, or we must be initializing a private + // elem. + bool canAddNewProperty = nobj->isExtensible() || id.isPrivateName(); + if (!canAddNewProperty) { + return false; + } + + JSOp op = JSOp(*pc_); + if (IsPropertyInitOp(op)) { + return true; + } + + MOZ_ASSERT(IsPropertySetOp(op)); + + // 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 = nobj->staticPrototype(); proto; + proto = proto->staticPrototype()) { + if (!proto->is()) { + return false; + } + + // If prototype defines this property in a non-plain way, don't optimize. + Maybe protoProp = proto->as().lookup(cx_, id); + if (protoProp.isSome() && !protoProp->isDataProperty()) { + 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()) { + return false; + } + } + + return true; +} + +static PropertyFlags SetPropertyFlags(JSOp op, bool isFunctionPrototype) { + // Locked properties are non-writable, non-enumerable, and non-configurable. + if (IsLockedInitOp(op)) { + return {}; + } + + // Hidden properties are writable, non-enumerable, and configurable. + if (IsHiddenInitOp(op)) { + return { + PropertyFlag::Writable, + PropertyFlag::Configurable, + }; + } + + // This is a special case to overwrite an unresolved function.prototype + // property. The initial property flags of this property are writable, + // non-enumerable, and non-configurable. See canAttachAddSlotStub. + if (isFunctionPrototype) { + return { + PropertyFlag::Writable, + }; + } + + // Other properties are writable, enumerable, and configurable. + return PropertyFlags::defaultDataPropFlags; +} + +AttachDecision SetPropIRGenerator::tryAttachAddSlotStub( + Handle 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; + } + + JSObject* obj = &lhsVal_.toObject(); + + PropertyResult prop; + if (!LookupOwnPropertyPure(cx_, obj, id, &prop)) { + return AttachDecision::NoAction; + } + if (prop.isNotFound()) { + return AttachDecision::NoAction; + } + + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto* nobj = &obj->as(); + + PropertyInfo propInfo = prop.propertyInfo(); + NativeObject* holder = nobj; + + if (holder->inDictionaryMode()) { + return AttachDecision::NoAction; + } + + SharedShape* oldSharedShape = &oldShape->asShared(); + + // The property must be the last added property of the object. + SharedShape* newShape = holder->sharedShape(); + MOZ_RELEASE_ASSERT(newShape->lastProperty() == propInfo); + +#ifdef DEBUG + // Verify exactly one property was added by comparing the property map + // lengths. + if (oldSharedShape->propMapLength() == PropMap::Capacity) { + MOZ_ASSERT(newShape->propMapLength() == 1); + } else { + MOZ_ASSERT(newShape->propMapLength() == + oldSharedShape->propMapLength() + 1); + } +#endif + + bool isFunctionPrototype = IsFunctionPrototype(cx_->names(), nobj, id); + + JSOp op = JSOp(*pc_); + PropertyFlags flags = SetPropertyFlags(op, isFunctionPrototype); + + // Basic property checks. + if (!propInfo.isDataProperty() || propInfo.flags() != flags) { + 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 (isFunctionPrototype) { + MOZ_ASSERT(nobj->as().isNonBuiltinConstructor()); + writer.guardFunctionIsNonBuiltinCtor(objId); + } + + // Also shape guard the proto chain, unless this is an InitElem. + if (IsPropertySetOp(op)) { + ShapeGuardProtoChain(writer, nobj, objId); + } + + // If the JSClass has an addProperty hook, we need to call a VM function to + // invoke this hook. Ignore the Array addProperty hook, because it doesn't do + // anything for non-index properties. + DebugOnly index; + MOZ_ASSERT_IF(obj->is(), !IdIsIndex(id, &index)); + bool mustCallAddPropertyHook = + obj->getClass()->getAddProperty() && !obj->is(); + + if (mustCallAddPropertyHook) { + writer.addSlotAndCallAddPropHook(objId, rhsValId, newShape); + trackAttached("SetProp.AddSlotWithAddPropertyHook"); + } else if (holder->isFixedSlot(propInfo.slot())) { + size_t offset = NativeObject::getFixedSlotOffset(propInfo.slot()); + writer.addAndStoreFixedSlot(objId, offset, rhsValId, newShape); + trackAttached("SetProp.AddSlotFixed"); + } else { + size_t offset = holder->dynamicSlotIndex(propInfo.slot()) * sizeof(Value); + uint32_t numOldSlots = NativeObject::calculateDynamicSlots(oldSharedShape); + uint32_t numNewSlots = holder->numDynamicSlots(); + if (numOldSlots == numNewSlots) { + writer.addAndStoreDynamicSlot(objId, offset, rhsValId, newShape); + trackAttached("SetProp.AddSlotDynamic"); + } else { + MOZ_ASSERT(numNewSlots > numOldSlots); + writer.allocateAndStoreDynamicSlot(objId, offset, rhsValId, newShape, + numNewSlots); + trackAttached("SetProp.AllocateSlot"); + } + } + writer.returnFromIC(); + + return AttachDecision::Attach; +} + +InstanceOfIRGenerator::InstanceOfIRGenerator(JSContext* cx, HandleScript script, + jsbytecode* pc, ICState state, + HandleValue lhs, HandleObject rhs) + : IRGenerator(cx, script, pc, CacheKind::InstanceOf, state), + 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()) { + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; + } + + HandleFunction fun = rhsObj_.as(); + + // 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; + NativeObject* hasInstanceHolder = nullptr; + jsid hasInstanceID = PropertyKey::Symbol(cx_->wellKnownSymbols().hasInstance); + if (!LookupPropertyPure(cx_, fun, hasInstanceID, &hasInstanceHolder, + &hasInstanceProp) || + !hasInstanceProp.isNativeProperty()) { + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; + } + + JSObject& funProto = cx_->global()->getPrototype(JSProto_Function); + if (hasInstanceHolder != &funProto) { + 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.propertyInfo().isDataProperty()); + MOZ_ASSERT(!hasInstanceProp.propertyInfo().configurable()); + MOZ_ASSERT(!hasInstanceProp.propertyInfo().writable()); + + MOZ_ASSERT(IsCacheableProtoChain(fun, hasInstanceHolder)); + + // Ensure that the function's prototype slot is the same. + Maybe prop = fun->lookupPure(cx_->names().prototype); + if (prop.isNothing() || !prop->isDataProperty()) { + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; + } + + uint32_t slot = prop->slot(); + MOZ_ASSERT(slot >= fun->numFixedSlots(), "Stub code relies on this"); + if (!fun->getSlot(slot).isObject()) { + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; + } + + // Abstract Objects + ValOperandId lhs(writer.setInputOperandId(0)); + ValOperandId rhs(writer.setInputOperandId(1)); + + ObjOperandId rhsId = writer.guardToObject(rhs); + writer.guardShape(rhsId, fun->shape()); + + // 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 the .prototype value and ensure it's an object. + ValOperandId protoValId = + writer.loadDynamicSlot(rhsId, slot - fun->numFixedSlots()); + ObjOperandId protoId = writer.guardToObject(protoValId); + + // 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) { + stubName_ = name ? name : "NotAttached"; +#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. + (void)lhsVal_; +#endif +} + +TypeOfIRGenerator::TypeOfIRGenerator(JSContext* cx, HandleScript script, + jsbytecode* pc, ICState state, + HandleValue value) + : IRGenerator(cx, script, pc, CacheKind::TypeOf, state), val_(value) {} + +void TypeOfIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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(); + writer.setTypeData(TypeData(JSValueType(val_.type()))); + trackAttached("TypeOf.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(); + writer.setTypeData(TypeData(JSValueType(val_.type()))); + trackAttached("TypeOf.Object"); + return AttachDecision::Attach; +} + +GetIteratorIRGenerator::GetIteratorIRGenerator(JSContext* cx, + HandleScript script, + jsbytecode* pc, ICState state, + HandleValue value) + : IRGenerator(cx, script, pc, CacheKind::GetIterator, state), val_(value) {} + +AttachDecision GetIteratorIRGenerator::tryAttachStub() { + MOZ_ASSERT(cacheKind_ == CacheKind::GetIterator); + + AutoAssertNoPendingException aanpe(cx_); + + ValOperandId valId(writer.setInputOperandId(0)); + + TRY_ATTACH(tryAttachObject(valId)); + TRY_ATTACH(tryAttachNullOrUndefined(valId)); + TRY_ATTACH(tryAttachGeneric(valId)); + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; +} + +AttachDecision GetIteratorIRGenerator::tryAttachObject(ValOperandId valId) { + if (!val_.isObject()) { + return AttachDecision::NoAction; + } + + MOZ_ASSERT(val_.toObject().compartment() == cx_->compartment()); + + ObjOperandId objId = writer.guardToObject(valId); + writer.objectToIteratorResult(objId, cx_->compartment()->enumeratorsAddr()); + writer.returnFromIC(); + + trackAttached("GetIterator.Object"); + return AttachDecision::Attach; +} + +AttachDecision GetIteratorIRGenerator::tryAttachNullOrUndefined( + ValOperandId valId) { + MOZ_ASSERT(JSOp(*pc_) == JSOp::Iter); + + // For null/undefined we can simply return the empty iterator singleton. This + // works because this iterator is unlinked and immutable. + + if (!val_.isNullOrUndefined()) { + return AttachDecision::NoAction; + } + + PropertyIteratorObject* emptyIter = + GlobalObject::getOrCreateEmptyIterator(cx_); + if (!emptyIter) { + cx_->recoverFromOutOfMemory(); + return AttachDecision::NoAction; + } + + writer.guardIsNullOrUndefined(valId); + + ObjOperandId iterId = writer.loadObject(emptyIter); + writer.loadObjectResult(iterId); + writer.returnFromIC(); + + trackAttached("GetIterator.NullOrUndefined"); + return AttachDecision::Attach; +} + +AttachDecision GetIteratorIRGenerator::tryAttachGeneric(ValOperandId valId) { + writer.valueToIteratorResult(valId); + writer.returnFromIC(); + + trackAttached("GetIterator.Generic"); + return AttachDecision::Attach; +} + +void GetIteratorIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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 state, + HandleValue value) + : IRGenerator(cx, script, pc, CacheKind::OptimizeSpreadCall, state), + val_(value) {} + +AttachDecision OptimizeSpreadCallIRGenerator::tryAttachStub() { + MOZ_ASSERT(cacheKind_ == CacheKind::OptimizeSpreadCall); + + AutoAssertNoPendingException aanpe(cx_); + + TRY_ATTACH(tryAttachArray()); + TRY_ATTACH(tryAttachArguments()); + TRY_ATTACH(tryAttachNotOptimizable()); + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; +} + +static bool IsArrayInstanceOptimizable(JSContext* cx, Handle arr, + MutableHandle arrProto) { + // Prototype must be Array.prototype. + auto* proto = cx->global()->maybeGetArrayPrototype(); + if (!proto || arr->staticPrototype() != proto) { + return false; + } + arrProto.set(proto); + + // The object must not have an own @@iterator property. + PropertyKey iteratorKey = + PropertyKey::Symbol(cx->wellKnownSymbols().iterator); + return !arr->lookupPure(iteratorKey); +} + +static bool IsArrayPrototypeOptimizable(JSContext* cx, Handle arr, + Handle arrProto, + uint32_t* slot, + MutableHandle iterFun) { + PropertyKey iteratorKey = + PropertyKey::Symbol(cx->wellKnownSymbols().iterator); + // Ensure that Array.prototype's @@iterator slot is unchanged. + Maybe prop = arrProto->lookupPure(iteratorKey); + if (prop.isNothing() || !prop->isDataProperty()) { + return false; + } + + *slot = prop->slot(); + MOZ_ASSERT(arrProto->numFixedSlots() == 0, "Stub code relies on this"); + + const Value& iterVal = arrProto->getSlot(*slot); + if (!iterVal.isObject() || !iterVal.toObject().is()) { + return false; + } + + iterFun.set(&iterVal.toObject().as()); + return IsSelfHostedFunctionWithName(iterFun, cx->names().dollar_ArrayValues_); +} + +enum class AllowIteratorReturn : bool { + No, + Yes, +}; +static bool IsArrayIteratorPrototypeOptimizable( + JSContext* cx, AllowIteratorReturn allowReturn, + MutableHandle arrIterProto, uint32_t* slot, + MutableHandle nextFun) { + NativeObject* proto = nullptr; + { + AutoEnterOOMUnsafeRegion oom; + proto = GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global()); + if (!proto) { + oom.crash("failed to allocate Array iterator prototype"); + } + } + arrIterProto.set(proto); + + // Ensure that %ArrayIteratorPrototype%'s "next" slot is unchanged. + Maybe prop = proto->lookupPure(cx->names().next); + if (prop.isNothing() || !prop->isDataProperty()) { + return false; + } + + *slot = prop->slot(); + MOZ_ASSERT(proto->numFixedSlots() == 0, "Stub code relies on this"); + + const Value& nextVal = proto->getSlot(*slot); + if (!nextVal.isObject() || !nextVal.toObject().is()) { + return false; + } + + nextFun.set(&nextVal.toObject().as()); + if (!IsSelfHostedFunctionWithName(nextFun, cx->names().ArrayIteratorNext)) { + return false; + } + + if (allowReturn == AllowIteratorReturn::No) { + // Ensure that %ArrayIteratorPrototype% doesn't define "return". + if (!CheckHasNoSuchProperty(cx, proto, NameToId(cx->names().return_))) { + return false; + } + } + + return true; +} + +AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArray() { + if (!isFirstStub_) { + return AttachDecision::NoAction; + } + + // The value must be a packed array. + if (!val_.isObject()) { + return AttachDecision::NoAction; + } + Rooted obj(cx_, &val_.toObject()); + if (!IsPackedArray(obj)) { + return AttachDecision::NoAction; + } + + // Prototype must be Array.prototype and Array.prototype[@@iterator] must not + // be modified. + Rooted arrProto(cx_); + uint32_t arrProtoIterSlot; + Rooted iterFun(cx_); + if (!IsArrayInstanceOptimizable(cx_, obj.as(), &arrProto)) { + return AttachDecision::NoAction; + } + + if (!IsArrayPrototypeOptimizable(cx_, obj.as(), arrProto, + &arrProtoIterSlot, &iterFun)) { + return AttachDecision::NoAction; + } + + // %ArrayIteratorPrototype%.next must not be modified. + Rooted arrayIteratorProto(cx_); + uint32_t iterNextSlot; + Rooted nextFun(cx_); + if (!IsArrayIteratorPrototypeOptimizable(cx_, AllowIteratorReturn::Yes, + &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. + MOZ_ASSERT(obj->is()); + writer.guardShape(objId, obj->shape()); + writer.guardArrayIsPacked(objId); + + // Guard on Array.prototype[@@iterator]. + ObjOperandId arrProtoId = writer.loadObject(arrProto); + ObjOperandId iterId = writer.loadObject(iterFun); + writer.guardShape(arrProtoId, arrProto->shape()); + writer.guardDynamicSlotIsSpecificObject(arrProtoId, iterId, arrProtoIterSlot); + + // Guard on %ArrayIteratorPrototype%.next. + ObjOperandId iterProtoId = writer.loadObject(arrayIteratorProto); + ObjOperandId nextId = writer.loadObject(nextFun); + writer.guardShape(iterProtoId, arrayIteratorProto->shape()); + writer.guardDynamicSlotIsSpecificObject(iterProtoId, nextId, iterNextSlot); + + writer.loadObjectResult(objId); + writer.returnFromIC(); + + trackAttached("OptimizeSpreadCall.Array"); + return AttachDecision::Attach; +} + +AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArguments() { + // The value must be an arguments object. + if (!val_.isObject()) { + return AttachDecision::NoAction; + } + RootedObject obj(cx_, &val_.toObject()); + if (!obj->is()) { + return AttachDecision::NoAction; + } + auto args = obj.as(); + + // Ensure neither elements, nor the length, nor the iterator has been + // overridden. Also ensure no args are forwarded to allow reading them + // directly from the frame. + if (args->hasOverriddenElement() || args->hasOverriddenLength() || + args->hasOverriddenIterator() || args->anyArgIsForwarded()) { + return AttachDecision::NoAction; + } + + Rooted shape(cx_, GlobalObject::getArrayShapeWithDefaultProto(cx_)); + if (!shape) { + cx_->clearPendingException(); + return AttachDecision::NoAction; + } + + Rooted arrayIteratorProto(cx_); + uint32_t slot; + Rooted nextFun(cx_); + if (!IsArrayIteratorPrototypeOptimizable(cx_, AllowIteratorReturn::Yes, + &arrayIteratorProto, &slot, + &nextFun)) { + return AttachDecision::NoAction; + } + + ValOperandId valId(writer.setInputOperandId(0)); + ObjOperandId objId = writer.guardToObject(valId); + + if (args->is()) { + writer.guardClass(objId, GuardClassKind::MappedArguments); + } else { + MOZ_ASSERT(args->is()); + writer.guardClass(objId, GuardClassKind::UnmappedArguments); + } + uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT | + ArgumentsObject::LENGTH_OVERRIDDEN_BIT | + ArgumentsObject::ITERATOR_OVERRIDDEN_BIT | + ArgumentsObject::FORWARDED_ARGUMENTS_BIT; + writer.guardArgumentsObjectFlags(objId, flags); + + ObjOperandId protoId = writer.loadObject(arrayIteratorProto); + ObjOperandId nextId = writer.loadObject(nextFun); + + writer.guardShape(protoId, arrayIteratorProto->shape()); + + // Ensure that proto[slot] == nextFun. + writer.guardDynamicSlotIsSpecificObject(protoId, nextId, slot); + + writer.arrayFromArgumentsObjectResult(objId, shape); + writer.returnFromIC(); + + trackAttached("OptimizeSpreadCall.Arguments"); + return AttachDecision::Attach; +} + +AttachDecision OptimizeSpreadCallIRGenerator::tryAttachNotOptimizable() { + ValOperandId valId(writer.setInputOperandId(0)); + + writer.loadUndefinedResult(); + writer.returnFromIC(); + + trackAttached("OptimizeSpreadCall.NotOptimizable"); + return AttachDecision::Attach; +} + +void OptimizeSpreadCallIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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 state, + uint32_t argc, HandleValue callee, + HandleValue thisval, HandleValue newTarget, + HandleValueArray args) + : IRGenerator(cx, script, pc, CacheKind::Call, state), + op_(op), + argc_(argc), + callee_(callee), + thisval_(thisval), + newTarget_(newTarget), + args_(args) {} + +void InlinableNativeIRGenerator::emitNativeCalleeGuard() { + // Note: we rely on GuardSpecificFunction to also guard against the same + // native from a different realm. + MOZ_ASSERT(callee_->isNativeWithoutJitEntry()); + + ObjOperandId calleeObjId; + if (flags_.getArgFormat() == CallFlags::Standard) { + ValOperandId calleeValId = + writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags_); + calleeObjId = writer.guardToObject(calleeValId); + } else if (flags_.getArgFormat() == CallFlags::Spread) { + ValOperandId calleeValId = + writer.loadArgumentFixedSlot(ArgumentKind::Callee, argc_, flags_); + calleeObjId = writer.guardToObject(calleeValId); + } else if (flags_.getArgFormat() == CallFlags::FunCall) { + MOZ_ASSERT(generator_.writer.numOperandIds() > 0, "argcId is initialized"); + + Int32OperandId argcId(0); + calleeObjId = generator_.emitFunCallOrApplyGuard(argcId); + } else { + MOZ_ASSERT(flags_.getArgFormat() == CallFlags::FunApplyArray); + MOZ_ASSERT(generator_.writer.numOperandIds() > 0, "argcId is initialized"); + + Int32OperandId argcId(0); + calleeObjId = generator_.emitFunApplyGuard(argcId); + } + + writer.guardSpecificFunction(calleeObjId, callee_); + + // If we're constructing we also need to guard newTarget == callee. + if (flags_.isConstructing()) { + MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard); + MOZ_ASSERT(&newTarget_.toObject() == callee_); + + ValOperandId newTargetValId = + writer.loadArgumentFixedSlot(ArgumentKind::NewTarget, argc_, flags_); + ObjOperandId newTargetObjId = writer.guardToObject(newTargetValId); + writer.guardSpecificFunction(newTargetObjId, callee_); + } +} + +ObjOperandId InlinableNativeIRGenerator::emitLoadArgsArray() { + if (flags_.getArgFormat() == CallFlags::Spread) { + return writer.loadSpreadArgs(); + } + + MOZ_ASSERT(flags_.getArgFormat() == CallFlags::FunApplyArray); + return generator_.emitFunApplyArgsGuard(flags_.getArgFormat()).ref(); +} + +void IRGenerator::emitCalleeGuard(ObjOperandId calleeId, JSFunction* 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. + // + // Self-hosted functions are more complicated: top-level functions can be + // relazified using SelfHostedLazyScript and this means they don't have a + // stable BaseScript pointer. These functions are never lambda clones, though, + // so we can just always guard on the JSFunction*. Self-hosted lambdas are + // never relazified so there we use the normal heuristics. + if (isFirstStub_ || !callee->hasBaseScript() || + (callee->isSelfHostedBuiltin() && !callee->isLambda())) { + writer.guardSpecificFunction(calleeId, callee); + } else { + MOZ_ASSERT_IF(callee->isSelfHostedBuiltin(), + !callee->baseScript()->allowRelazify()); + writer.guardClass(calleeId, GuardClassKind::JSFunction); + writer.guardFunctionScript(calleeId, callee->baseScript()); + } +} + +ObjOperandId CallIRGenerator::emitFunCallOrApplyGuard(Int32OperandId argcId) { + JSFunction* callee = &callee_.toObject().as(); + MOZ_ASSERT(callee->native() == fun_call || callee->native() == fun_apply); + + // Guard that callee is the |fun_call| or |fun_apply| 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); + return writer.guardToObject(thisValId); +} + +ObjOperandId CallIRGenerator::emitFunCallGuard(Int32OperandId argcId) { + MOZ_ASSERT(callee_.toObject().as().native() == fun_call); + + return emitFunCallOrApplyGuard(argcId); +} + +ObjOperandId CallIRGenerator::emitFunApplyGuard(Int32OperandId argcId) { + MOZ_ASSERT(callee_.toObject().as().native() == fun_apply); + + return emitFunCallOrApplyGuard(argcId); +} + +Maybe CallIRGenerator::emitFunApplyArgsGuard( + CallFlags::ArgFormat format) { + MOZ_ASSERT(argc_ == 2); + + ValOperandId argValId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + + if (format == CallFlags::FunApplyArgsObj) { + ObjOperandId argObjId = writer.guardToObject(argValId); + if (args_[1].toObject().is()) { + writer.guardClass(argObjId, GuardClassKind::MappedArguments); + } else { + MOZ_ASSERT(args_[1].toObject().is()); + writer.guardClass(argObjId, GuardClassKind::UnmappedArguments); + } + uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT | + ArgumentsObject::FORWARDED_ARGUMENTS_BIT; + writer.guardArgumentsObjectFlags(argObjId, flags); + return mozilla::Some(argObjId); + } + + if (format == CallFlags::FunApplyArray) { + ObjOperandId argObjId = writer.guardToObject(argValId); + emitOptimisticClassGuard(argObjId, &args_[1].toObject(), + GuardClassKind::Array); + writer.guardArrayIsPacked(argObjId); + return mozilla::Some(argObjId); + } + + MOZ_ASSERT(format == CallFlags::FunApplyNullUndefined); + writer.guardIsNullOrUndefined(argValId); + return mozilla::Nothing(); +} + +AttachDecision InlinableNativeIRGenerator::tryAttachArrayPush() { + // 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()) { + return AttachDecision::NoAction; + } + + auto* thisarray = &thisobj->as(); + + // Check for other indexed properties or class hooks. + if (!CanAttachAddElement(thisarray, /* isInit = */ false, + AllowIndexedReceiver::No)) { + 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. + initializeInputOperand(); + + // Guard callee is the 'push' native function. + emitNativeCalleeGuard(); + + // 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, thisarray, 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 InlinableNativeIRGenerator::tryAttachArrayPopShift( + 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(); + if (!arr->lengthIsWritable() || !arr->isExtensible() || + arr->denseElementsHaveMaybeInIterationFlag()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'pop' or 'shift' native function. + emitNativeCalleeGuard(); + + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId objId = writer.guardToObject(thisValId); + emitOptimisticClassGuard(objId, arr, 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 InlinableNativeIRGenerator::tryAttachArrayJoin() { + // Only handle argc <= 1. + if (argc_ > 1) { + return AttachDecision::NoAction; + } + + // Only optimize if |this| is an array. + if (!thisval_.isObject() || !thisval_.toObject().is()) { + 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. + initializeInputOperand(); + + // Guard callee is the 'join' native function. + emitNativeCalleeGuard(); + + // Guard this is an array object. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId thisObjId = writer.guardToObject(thisValId); + emitOptimisticClassGuard(thisObjId, &thisval_.toObject(), + 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 InlinableNativeIRGenerator::tryAttachArraySlice() { + // Only handle argc <= 2. + if (argc_ > 2) { + return AttachDecision::NoAction; + } + + // Only optimize if |this| is a packed array or an arguments object. + if (!thisval_.isObject()) { + return AttachDecision::NoAction; + } + + bool isPackedArray = IsPackedArray(&thisval_.toObject()); + if (!isPackedArray) { + if (!thisval_.toObject().is()) { + return AttachDecision::NoAction; + } + auto* args = &thisval_.toObject().as(); + + // No elements must have been overridden or deleted. + if (args->hasOverriddenElement()) { + return AttachDecision::NoAction; + } + + // The length property mustn't be overridden. + if (args->hasOverriddenLength()) { + return AttachDecision::NoAction; + } + + // And finally also check that no argument is forwarded. + if (args->anyArgIsForwarded()) { + 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; + } + + JSObject* templateObj = NewDenseFullyAllocatedArray(cx_, 0, TenuredObject); + if (!templateObj) { + cx_->recoverFromOutOfMemory(); + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'slice' native function. + emitNativeCalleeGuard(); + + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId objId = writer.guardToObject(thisValId); + + if (isPackedArray) { + emitOptimisticClassGuard(objId, &thisval_.toObject(), + GuardClassKind::Array); + } else { + auto* args = &thisval_.toObject().as(); + + if (args->is()) { + writer.guardClass(objId, GuardClassKind::MappedArguments); + } else { + MOZ_ASSERT(args->is()); + writer.guardClass(objId, GuardClassKind::UnmappedArguments); + } + + uint8_t flags = ArgumentsObject::ELEMENT_OVERRIDDEN_BIT | + ArgumentsObject::LENGTH_OVERRIDDEN_BIT | + ArgumentsObject::FORWARDED_ARGUMENTS_BIT; + writer.guardArgumentsObjectFlags(objId, flags); + } + + 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 if (isPackedArray) { + int32EndId = writer.loadInt32ArrayLength(objId); + } else { + int32EndId = writer.loadArgumentsObjectLength(objId); + } + + if (isPackedArray) { + writer.packedArraySliceResult(templateObj, objId, int32BeginId, int32EndId); + } else { + writer.argumentsSliceResult(templateObj, objId, int32BeginId, int32EndId); + } + writer.returnFromIC(); + + trackAttached(isPackedArray ? "ArraySlice" : "ArgumentsSlice"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachArrayIsArray() { + // Need a single argument. + if (argc_ != 1) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'isArray' native function. + emitNativeCalleeGuard(); + + // 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 InlinableNativeIRGenerator::tryAttachDataViewGet( + Scalar::Type type) { + // Ensure |this| is a DataViewObject. + // TODO: Support resizable dataviews. (bug 1842999) + if (!thisval_.isObject() || + !thisval_.toObject().is()) { + return AttachDecision::NoAction; + } + + // Expected arguments: offset (number), optional littleEndian (boolean). + if (argc_ < 1 || argc_ > 2) { + return AttachDecision::NoAction; + } + int64_t offsetInt64; + if (!ValueIsInt64Index(args_[0], &offsetInt64)) { + return AttachDecision::NoAction; + } + if (argc_ > 1 && !args_[1].isBoolean()) { + return AttachDecision::NoAction; + } + + auto* dv = &thisval_.toObject().as(); + + // Bounds check the offset. + if (offsetInt64 < 0 || + !dv->offsetIsInBounds(Scalar::byteSize(type), offsetInt64)) { + return AttachDecision::NoAction; + } + + // For getUint32 we let the stub return an Int32 if we have not seen a + // double, to allow better codegen in Warp while avoiding bailout loops. + bool forceDoubleForUint32 = false; + if (type == Scalar::Uint32) { + bool isLittleEndian = argc_ > 1 && args_[1].toBoolean(); + uint32_t res = dv->read(offsetInt64, isLittleEndian); + forceDoubleForUint32 = res >= INT32_MAX; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is this DataView native function. + emitNativeCalleeGuard(); + + // Guard |this| is a DataViewObject. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId objId = writer.guardToObject(thisValId); + emitOptimisticClassGuard(objId, &thisval_.toObject(), + GuardClassKind::FixedLengthDataView); + + // Convert offset to intPtr. + ValOperandId offsetId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + IntPtrOperandId intPtrOffsetId = + guardToIntPtrIndex(args_[0], offsetId, /* supportOOB = */ false); + + BooleanOperandId boolLittleEndianId; + if (argc_ > 1) { + ValOperandId littleEndianId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + boolLittleEndianId = writer.guardToBoolean(littleEndianId); + } else { + boolLittleEndianId = writer.loadBooleanConstant(false); + } + + writer.loadDataViewValueResult(objId, intPtrOffsetId, boolLittleEndianId, + type, forceDoubleForUint32); + writer.returnFromIC(); + + trackAttached("DataViewGet"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachDataViewSet( + Scalar::Type type) { + // Ensure |this| is a DataViewObject. + // TODO: Support resizable dataviews. (bug 1842999) + if (!thisval_.isObject() || + !thisval_.toObject().is()) { + return AttachDecision::NoAction; + } + + // Expected arguments: offset (number), value, optional littleEndian (boolean) + if (argc_ < 2 || argc_ > 3) { + return AttachDecision::NoAction; + } + int64_t offsetInt64; + if (!ValueIsInt64Index(args_[0], &offsetInt64)) { + return AttachDecision::NoAction; + } + if (!ValueCanConvertToNumeric(type, args_[1])) { + return AttachDecision::NoAction; + } + if (argc_ > 2 && !args_[2].isBoolean()) { + return AttachDecision::NoAction; + } + + auto* dv = &thisval_.toObject().as(); + + // Bounds check the offset. + if (offsetInt64 < 0 || + !dv->offsetIsInBounds(Scalar::byteSize(type), offsetInt64)) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is this DataView native function. + emitNativeCalleeGuard(); + + // Guard |this| is a DataViewObject. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId objId = writer.guardToObject(thisValId); + emitOptimisticClassGuard(objId, &thisval_.toObject(), + GuardClassKind::FixedLengthDataView); + + // Convert offset to intPtr. + ValOperandId offsetId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + IntPtrOperandId intPtrOffsetId = + guardToIntPtrIndex(args_[0], offsetId, /* supportOOB = */ false); + + // Convert value to number or BigInt. + ValOperandId valueId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + OperandId numericValueId = emitNumericGuard(valueId, args_[1], 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, intPtrOffsetId, numericValueId, + boolLittleEndianId, type); + writer.returnFromIC(); + + trackAttached("DataViewSet"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachUnsafeGetReservedSlot( + 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. + initializeInputOperand(); + + // 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::assertSelfHostedUnsafeGetReservedSlot 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; + default: + MOZ_CRASH("unexpected native"); + } + + writer.returnFromIC(); + + trackAttached("UnsafeGetReservedSlot"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachUnsafeSetReservedSlot() { + // 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. + initializeInputOperand(); + + // 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::assertSelfHostedUnsafeSetReservedSlot 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 InlinableNativeIRGenerator::tryAttachIsSuspendedGenerator() { + // 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); + + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachToObject() { + // Self-hosted code calls this with a single argument. + MOZ_ASSERT(argc_ == 1); + + // Need a single object argument. + // TODO(Warp): Support all or more conversions to object. + if (!args_[0].isObject()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // 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); + + // Return the object. + writer.loadObjectResult(objId); + writer.returnFromIC(); + + trackAttached("ToObject"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachToInteger() { + // 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. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachToLength() { + // 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. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachIsObject() { + // Self-hosted code calls this with a single argument. + MOZ_ASSERT(argc_ == 1); + + // Initialize the input operand. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachIsPackedArray() { + // Self-hosted code calls this with a single object argument. + MOZ_ASSERT(argc_ == 1); + MOZ_ASSERT(args_[0].isObject()); + + // Initialize the input operand. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachIsCallable() { + // Self-hosted code calls this with a single argument. + MOZ_ASSERT(argc_ == 1); + + // Initialize the input operand. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachIsConstructor() { + // 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. + initializeInputOperand(); + + // 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 +InlinableNativeIRGenerator::tryAttachIsCrossRealmArrayConstructor() { + // Self-hosted code calls this with an object argument. + MOZ_ASSERT(argc_ == 1); + MOZ_ASSERT(args_[0].isObject()); + + if (args_[0].toObject().is()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachGuardToClass( + 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. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachGuardToArrayBuffer() { + // TODO: Support resizable ArrayBuffers (bug 1842999), for now simply + // pass through to tryAttachGuardToClass which guards on + // FixedLengthArrayBufferObject. + return tryAttachGuardToClass(InlinableNative::IntrinsicGuardToArrayBuffer); +} + +AttachDecision InlinableNativeIRGenerator::tryAttachGuardToSharedArrayBuffer() { + // TODO: Support resizable SharedArrayBuffers (bug 1842999), for now simply + // pass through to tryAttachGuardToClass which guards on + // FixedLengthSharedArrayBufferObject. + return tryAttachGuardToClass( + InlinableNative::IntrinsicGuardToSharedArrayBuffer); +} + +AttachDecision InlinableNativeIRGenerator::tryAttachHasClass( + 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 && args_[0].toObject().is()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // 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; +} + +// Returns whether the .lastIndex property is a non-negative int32 value and is +// still writable. +static bool HasOptimizableLastIndexSlot(RegExpObject* regexp, JSContext* cx) { + auto lastIndexProp = regexp->lookupPure(cx->names().lastIndex); + MOZ_ASSERT(lastIndexProp->isDataProperty()); + if (!lastIndexProp->writable()) { + return false; + } + Value lastIndex = regexp->getLastIndex(); + if (!lastIndex.isInt32() || lastIndex.toInt32() < 0) { + return false; + } + return true; +} + +// Returns the RegExp stub used by the optimized code path for this intrinsic. +// We store a pointer to this in the IC stub to ensure GC doesn't discard it. +static JitCode* GetOrCreateRegExpStub(JSContext* cx, InlinableNative native) { +#ifdef ENABLE_PORTABLE_BASELINE_INTERP + return nullptr; +#else + // The stubs assume the global has non-null RegExpStatics and match result + // shape. + if (!GlobalObject::getRegExpStatics(cx, cx->global()) || + !cx->global()->regExpRealm().getOrCreateMatchResultShape(cx)) { + MOZ_ASSERT(cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed()); + cx->clearPendingException(); + return nullptr; + } + JitCode* code; + switch (native) { + case InlinableNative::IntrinsicRegExpBuiltinExecForTest: + case InlinableNative::IntrinsicRegExpExecForTest: + code = cx->zone()->jitZone()->ensureRegExpExecTestStubExists(cx); + break; + case InlinableNative::IntrinsicRegExpBuiltinExec: + case InlinableNative::IntrinsicRegExpExec: + code = cx->zone()->jitZone()->ensureRegExpExecMatchStubExists(cx); + break; + case InlinableNative::RegExpMatcher: + code = cx->zone()->jitZone()->ensureRegExpMatcherStubExists(cx); + break; + case InlinableNative::RegExpSearcher: + code = cx->zone()->jitZone()->ensureRegExpSearcherStubExists(cx); + break; + default: + MOZ_CRASH("Unexpected native"); + } + if (!code) { + MOZ_ASSERT(cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed()); + cx->clearPendingException(); + return nullptr; + } + return code; +#endif +} + +static void EmitGuardLastIndexIsNonNegativeInt32(CacheIRWriter& writer, + ObjOperandId regExpId) { + size_t offset = + NativeObject::getFixedSlotOffset(RegExpObject::lastIndexSlot()); + ValOperandId lastIndexValId = writer.loadFixedSlot(regExpId, offset); + Int32OperandId lastIndexId = writer.guardToInt32(lastIndexValId); + writer.guardInt32IsNonNegative(lastIndexId); +} + +AttachDecision InlinableNativeIRGenerator::tryAttachIntrinsicRegExpBuiltinExec( + InlinableNative native) { + // Self-hosted code calls this with (regexp, string) arguments. + MOZ_ASSERT(argc_ == 2); + MOZ_ASSERT(args_[0].isObject()); + MOZ_ASSERT(args_[1].isString()); + + JitCode* stub = GetOrCreateRegExpStub(cx_, native); + if (!stub) { + return AttachDecision::NoAction; + } + + RegExpObject* re = &args_[0].toObject().as(); + if (!HasOptimizableLastIndexSlot(re, cx_)) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Note: we don't need to call emitNativeCalleeGuard for intrinsics. + + ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ObjOperandId regExpId = writer.guardToObject(arg0Id); + writer.guardShape(regExpId, re->shape()); + EmitGuardLastIndexIsNonNegativeInt32(writer, regExpId); + + ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + StringOperandId inputId = writer.guardToString(arg1Id); + + if (native == InlinableNative::IntrinsicRegExpBuiltinExecForTest) { + writer.regExpBuiltinExecTestResult(regExpId, inputId, stub); + } else { + writer.regExpBuiltinExecMatchResult(regExpId, inputId, stub); + } + writer.returnFromIC(); + + trackAttached("IntrinsicRegExpBuiltinExec"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachIntrinsicRegExpExec( + InlinableNative native) { + // Self-hosted code calls this with (object, string) arguments. + MOZ_ASSERT(argc_ == 2); + MOZ_ASSERT(args_[0].isObject()); + MOZ_ASSERT(args_[1].isString()); + + if (!args_[0].toObject().is()) { + return AttachDecision::NoAction; + } + + JitCode* stub = GetOrCreateRegExpStub(cx_, native); + if (!stub) { + return AttachDecision::NoAction; + } + + RegExpObject* re = &args_[0].toObject().as(); + if (!HasOptimizableLastIndexSlot(re, cx_)) { + return AttachDecision::NoAction; + } + + // Ensure regexp.exec is the original RegExp.prototype.exec function on the + // prototype. + if (re->containsPure(cx_->names().exec)) { + return AttachDecision::NoAction; + } + MOZ_ASSERT(cx_->global()->maybeGetRegExpPrototype()); + auto* regExpProto = + &cx_->global()->maybeGetRegExpPrototype()->as(); + if (re->staticPrototype() != regExpProto) { + return AttachDecision::NoAction; + } + auto execProp = regExpProto->as().lookupPure(cx_->names().exec); + if (!execProp || !execProp->isDataProperty()) { + return AttachDecision::NoAction; + } + // It should be stored in a dynamic slot. We assert this in + // FinishRegExpClassInit. + if (regExpProto->isFixedSlot(execProp->slot())) { + return AttachDecision::NoAction; + } + Value execVal = regExpProto->getSlot(execProp->slot()); + PropertyName* execName = cx_->names().RegExp_prototype_Exec; + if (!IsSelfHostedFunctionWithName(execVal, execName)) { + return AttachDecision::NoAction; + } + JSFunction* execFunction = &execVal.toObject().as(); + + // Initialize the input operand. + initializeInputOperand(); + + // Note: we don't need to call emitNativeCalleeGuard for intrinsics. + + ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ObjOperandId regExpId = writer.guardToObject(arg0Id); + writer.guardShape(regExpId, re->shape()); + EmitGuardLastIndexIsNonNegativeInt32(writer, regExpId); + + // Emit guards for the RegExp.prototype.exec property. + ObjOperandId regExpProtoId = writer.loadObject(regExpProto); + writer.guardShape(regExpProtoId, regExpProto->shape()); + size_t offset = + regExpProto->dynamicSlotIndex(execProp->slot()) * sizeof(Value); + writer.guardDynamicSlotValue(regExpProtoId, offset, + ObjectValue(*execFunction)); + + ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + StringOperandId inputId = writer.guardToString(arg1Id); + + if (native == InlinableNative::IntrinsicRegExpExecForTest) { + writer.regExpBuiltinExecTestResult(regExpId, inputId, stub); + } else { + writer.regExpBuiltinExecMatchResult(regExpId, inputId, stub); + } + writer.returnFromIC(); + + trackAttached("IntrinsicRegExpExec"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachRegExpMatcherSearcher( + 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; + } + + JitCode* stub = GetOrCreateRegExpStub(cx_, native); + if (!stub) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // 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, stub); + writer.returnFromIC(); + trackAttached("RegExpMatcher"); + break; + + case InlinableNative::RegExpSearcher: + writer.callRegExpSearcherResult(reId, inputId, lastIndexId, stub); + writer.returnFromIC(); + trackAttached("RegExpSearcher"); + break; + + default: + MOZ_CRASH("Unexpected native"); + } + + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachRegExpSearcherLastLimit() { + // Self-hosted code calls this with a string argument that's only used for an + // assertion. + MOZ_ASSERT(argc_ == 1); + MOZ_ASSERT(args_[0].isString()); + + // Initialize the input operand. + initializeInputOperand(); + + // Note: we don't need to call emitNativeCalleeGuard for intrinsics. + + writer.regExpSearcherLastLimitResult(); + writer.returnFromIC(); + + trackAttached("RegExpSearcherLastLimit"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachRegExpHasCaptureGroups() { + // Self-hosted code calls this with object and string arguments. + MOZ_ASSERT(argc_ == 2); + MOZ_ASSERT(args_[0].toObject().is()); + MOZ_ASSERT(args_[1].isString()); + + // Initialize the input operand. + initializeInputOperand(); + + // Note: we don't need to call emitNativeCalleeGuard for intrinsics. + + ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ObjOperandId objId = writer.guardToObject(arg0Id); + + ValOperandId arg1Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + StringOperandId inputId = writer.guardToString(arg1Id); + + writer.regExpHasCaptureGroupsResult(objId, inputId); + writer.returnFromIC(); + + trackAttached("RegExpHasCaptureGroups"); + return AttachDecision::Attach; +} + +AttachDecision +InlinableNativeIRGenerator::tryAttachRegExpPrototypeOptimizable() { + // Self-hosted code calls this with a single object argument. + MOZ_ASSERT(argc_ == 1); + MOZ_ASSERT(args_[0].isObject()); + + // Initialize the input operand. + initializeInputOperand(); + + // 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 +InlinableNativeIRGenerator::tryAttachRegExpInstanceOptimizable() { + // 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. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachGetFirstDollarIndex() { + // Self-hosted code calls this with a single string argument. + MOZ_ASSERT(argc_ == 1); + MOZ_ASSERT(args_[0].isString()); + + // Initialize the input operand. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachSubstringKernel() { + // 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. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachObjectHasPrototype() { + // 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(); + auto* proto = &args_[1].toObject().as(); + + // Only attach when obj.__proto__ is proto. + if (obj->staticPrototype() != proto) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // 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; +} + +static bool CanConvertToString(const Value& v) { + return v.isString() || v.isNumber() || v.isBoolean() || v.isNullOrUndefined(); +} + +AttachDecision InlinableNativeIRGenerator::tryAttachString() { + // Need a single argument that is or can be converted to a string. + if (argc_ != 1 || !CanConvertToString(args_[0])) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'String' function. + emitNativeCalleeGuard(); + + // Guard that the argument is a string or can be converted to one. + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + StringOperandId strId = emitToStringGuard(argId, args_[0]); + + // Return the string. + writer.loadStringResult(strId); + writer.returnFromIC(); + + trackAttached("String"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringConstructor() { + // Need a single argument that is or can be converted to a string. + if (argc_ != 1 || !CanConvertToString(args_[0])) { + 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. + initializeInputOperand(); + + // Guard callee is the 'String' function. + emitNativeCalleeGuard(); + + // Guard on number and convert to string. + ValOperandId argId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags_); + StringOperandId strId = emitToStringGuard(argId, args_[0]); + + writer.newStringObjectResult(templateObj, strId); + writer.returnFromIC(); + + trackAttached("StringConstructor"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringToStringValueOf() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'toString' OR 'valueOf' native function. + emitNativeCalleeGuard(); + + // 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 InlinableNativeIRGenerator::tryAttachStringReplaceString() { + // 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. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachStringSplitString() { + // 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. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachStringChar( + StringChar kind) { + // Need one argument. + if (argc_ != 1) { + return AttachDecision::NoAction; + } + + auto attach = CanAttachStringChar(thisval_, args_[0], kind); + if (attach == AttachStringChar::No) { + return AttachDecision::NoAction; + } + + bool handleOOB = attach == AttachStringChar::OutOfBounds; + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'charCodeAt', 'codePointAt', 'charAt', or 'at' native + // function. + emitNativeCalleeGuard(); + + // 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); + + // Handle relative string indices, if necessary. + if (kind == StringChar::At) { + int32IndexId = writer.toRelativeStringIndex(int32IndexId, strId); + } + + // Linearize the string. + // + // AttachStringChar doesn't have a separate state when OOB access happens on + // a string which needs to be linearized, so just linearize unconditionally + // for out-of-bounds accesses. + if (attach == AttachStringChar::Linearize || + attach == AttachStringChar::OutOfBounds) { + switch (kind) { + case StringChar::CharCodeAt: + case StringChar::CharAt: + case StringChar::At: + strId = writer.linearizeForCharAccess(strId, int32IndexId); + break; + case StringChar::CodePointAt: + strId = writer.linearizeForCodePointAccess(strId, int32IndexId); + break; + } + } + + // Load string char or code. + switch (kind) { + case StringChar::CharCodeAt: + writer.loadStringCharCodeResult(strId, int32IndexId, handleOOB); + break; + case StringChar::CodePointAt: + writer.loadStringCodePointResult(strId, int32IndexId, handleOOB); + break; + case StringChar::CharAt: + writer.loadStringCharResult(strId, int32IndexId, handleOOB); + break; + case StringChar::At: + writer.loadStringAtResult(strId, int32IndexId, handleOOB); + break; + } + + writer.returnFromIC(); + + switch (kind) { + case StringChar::CharCodeAt: + trackAttached("StringCharCodeAt"); + break; + case StringChar::CodePointAt: + trackAttached("StringCodePointAt"); + break; + case StringChar::CharAt: + trackAttached("StringCharAt"); + break; + case StringChar::At: + trackAttached("StringAt"); + break; + } + + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringCharCodeAt() { + return tryAttachStringChar(StringChar::CharCodeAt); +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringCodePointAt() { + return tryAttachStringChar(StringChar::CodePointAt); +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringCharAt() { + return tryAttachStringChar(StringChar::CharAt); +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringAt() { + return tryAttachStringChar(StringChar::At); +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringFromCharCode() { + // Need one number argument. + if (argc_ != 1 || !args_[0].isNumber()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'fromCharCode' native function. + emitNativeCalleeGuard(); + + // Guard int32 argument. + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + Int32OperandId codeId; + if (args_[0].isInt32()) { + codeId = writer.guardToInt32(argId); + } else { + // 'fromCharCode' performs ToUint16 on its input. We can use Uint32 + // semantics, because ToUint16(ToUint32(v)) == ToUint16(v). + codeId = writer.guardToInt32ModUint32(argId); + } + + // Return string created from code. + writer.stringFromCharCodeResult(codeId); + writer.returnFromIC(); + + trackAttached("StringFromCharCode"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringFromCodePoint() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'fromCodePoint' native function. + emitNativeCalleeGuard(); + + // 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 InlinableNativeIRGenerator::tryAttachStringIncludes() { + // Need one string argument. + if (argc_ != 1 || !args_[0].isString()) { + return AttachDecision::NoAction; + } + + // Ensure |this| is a primitive string value. + if (!thisval_.isString()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'includes' native function. + emitNativeCalleeGuard(); + + // Guard this is a string. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + StringOperandId strId = writer.guardToString(thisValId); + + // Guard string argument. + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + StringOperandId searchStrId = writer.guardToString(argId); + + writer.stringIncludesResult(strId, searchStrId); + writer.returnFromIC(); + + trackAttached("StringIncludes"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringIndexOf() { + // Need one string argument. + if (argc_ != 1 || !args_[0].isString()) { + return AttachDecision::NoAction; + } + + // Ensure |this| is a primitive string value. + if (!thisval_.isString()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'indexOf' native function. + emitNativeCalleeGuard(); + + // Guard this is a string. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + StringOperandId strId = writer.guardToString(thisValId); + + // Guard string argument. + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + StringOperandId searchStrId = writer.guardToString(argId); + + writer.stringIndexOfResult(strId, searchStrId); + writer.returnFromIC(); + + trackAttached("StringIndexOf"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringLastIndexOf() { + // Need one string argument. + if (argc_ != 1 || !args_[0].isString()) { + return AttachDecision::NoAction; + } + + // Ensure |this| is a primitive string value. + if (!thisval_.isString()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'lastIndexOf' native function. + emitNativeCalleeGuard(); + + // Guard this is a string. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + StringOperandId strId = writer.guardToString(thisValId); + + // Guard string argument. + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + StringOperandId searchStrId = writer.guardToString(argId); + + writer.stringLastIndexOfResult(strId, searchStrId); + writer.returnFromIC(); + + trackAttached("StringLastIndexOf"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringStartsWith() { + // Need one string argument. + if (argc_ != 1 || !args_[0].isString()) { + return AttachDecision::NoAction; + } + + // Ensure |this| is a primitive string value. + if (!thisval_.isString()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'startsWith' native function. + emitNativeCalleeGuard(); + + // Guard this is a string. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + StringOperandId strId = writer.guardToString(thisValId); + + // Guard string argument. + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + StringOperandId searchStrId = writer.guardToString(argId); + + writer.stringStartsWithResult(strId, searchStrId); + writer.returnFromIC(); + + trackAttached("StringStartsWith"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringEndsWith() { + // Need one string argument. + if (argc_ != 1 || !args_[0].isString()) { + return AttachDecision::NoAction; + } + + // Ensure |this| is a primitive string value. + if (!thisval_.isString()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'endsWith' native function. + emitNativeCalleeGuard(); + + // Guard this is a string. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + StringOperandId strId = writer.guardToString(thisValId); + + // Guard string argument. + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + StringOperandId searchStrId = writer.guardToString(argId); + + writer.stringEndsWithResult(strId, searchStrId); + writer.returnFromIC(); + + trackAttached("StringEndsWith"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringToLowerCase() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'toLowerCase' native function. + emitNativeCalleeGuard(); + + // 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 InlinableNativeIRGenerator::tryAttachStringToUpperCase() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'toUpperCase' native function. + emitNativeCalleeGuard(); + + // 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 InlinableNativeIRGenerator::tryAttachStringTrim() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'trim' native function. + emitNativeCalleeGuard(); + + // Guard this is a string. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + StringOperandId strId = writer.guardToString(thisValId); + + writer.stringTrimResult(strId); + writer.returnFromIC(); + + trackAttached("StringTrim"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringTrimStart() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'trimStart' native function. + emitNativeCalleeGuard(); + + // Guard this is a string. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + StringOperandId strId = writer.guardToString(thisValId); + + writer.stringTrimStartResult(strId); + writer.returnFromIC(); + + trackAttached("StringTrimStart"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachStringTrimEnd() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'trimEnd' native function. + emitNativeCalleeGuard(); + + // Guard this is a string. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + StringOperandId strId = writer.guardToString(thisValId); + + writer.stringTrimEndResult(strId); + writer.returnFromIC(); + + trackAttached("StringTrimEnd"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachMathRandom() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'random' native function. + emitNativeCalleeGuard(); + + mozilla::non_crypto::XorShift128PlusRNG* rng = + &cx_->realm()->getOrCreateRandomNumberGenerator(); + writer.mathRandomResult(rng); + + writer.returnFromIC(); + + trackAttached("MathRandom"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachMathAbs() { + // Need one argument. + if (argc_ != 1) { + return AttachDecision::NoAction; + } + + if (!args_[0].isNumber()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'abs' native function. + emitNativeCalleeGuard(); + + 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 InlinableNativeIRGenerator::tryAttachMathClz32() { + // Need one (number) argument. + if (argc_ != 1 || !args_[0].isNumber()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'clz32' native function. + emitNativeCalleeGuard(); + + 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 InlinableNativeIRGenerator::tryAttachMathSign() { + // Need one (number) argument. + if (argc_ != 1 || !args_[0].isNumber()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'sign' native function. + emitNativeCalleeGuard(); + + 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 InlinableNativeIRGenerator::tryAttachMathImul() { + // Need two (number) arguments. + if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'imul' native function. + emitNativeCalleeGuard(); + + 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 InlinableNativeIRGenerator::tryAttachMathFloor() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'floor' native function. + emitNativeCalleeGuard(); + + 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 InlinableNativeIRGenerator::tryAttachMathCeil() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'ceil' native function. + emitNativeCalleeGuard(); + + 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 InlinableNativeIRGenerator::tryAttachMathTrunc() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'trunc' native function. + emitNativeCalleeGuard(); + + 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 InlinableNativeIRGenerator::tryAttachMathRound() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'round' native function. + emitNativeCalleeGuard(); + + 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 InlinableNativeIRGenerator::tryAttachMathSqrt() { + // Need one (number) argument. + if (argc_ != 1 || !args_[0].isNumber()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'sqrt' native function. + emitNativeCalleeGuard(); + + ValOperandId argumentId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + NumberOperandId numberId = writer.guardIsNumber(argumentId); + writer.mathSqrtNumberResult(numberId); + writer.returnFromIC(); + + trackAttached("MathSqrt"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachMathFRound() { + // Need one (number) argument. + if (argc_ != 1 || !args_[0].isNumber()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'fround' native function. + emitNativeCalleeGuard(); + + 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) { + auto valToInt32 = [](const Value& v) { + if (v.isInt32()) { + return v.toInt32(); + } + if (v.isBoolean()) { + return int32_t(v.toBoolean()); + } + MOZ_ASSERT(v.isNull()); + return 0; + }; + int32_t base = valToInt32(baseVal); + int32_t power = valToInt32(powerVal); + + // 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 InlinableNativeIRGenerator::tryAttachMathPow() { + // Need two number arguments. + if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'pow' function. + emitNativeCalleeGuard(); + + 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 InlinableNativeIRGenerator::tryAttachMathHypot() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'hypot' native function. + emitNativeCalleeGuard(); + + ValOperandId firstId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ValOperandId secondId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg1, 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.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_); + thirdNumId = writer.guardIsNumber(thirdId); + writer.mathHypot3NumberResult(firstNumId, secondNumId, thirdNumId); + break; + case 4: + thirdId = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_); + fourthId = writer.loadArgumentFixedSlot(ArgumentKind::Arg3, 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 InlinableNativeIRGenerator::tryAttachMathATan2() { + // Requires two numbers as arguments. + if (argc_ != 2 || !args_[0].isNumber() || !args_[1].isNumber()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'atan2' native function. + emitNativeCalleeGuard(); + + 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 InlinableNativeIRGenerator::tryAttachMathMinMax(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. + initializeInputOperand(); + + // Guard callee is this Math function. + emitNativeCalleeGuard(); + + if (allInt32) { + ValOperandId valId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + Int32OperandId resId = writer.guardToInt32(valId); + for (size_t i = 1; i < argc_; i++) { + ValOperandId argId = + writer.loadArgumentFixedSlot(ArgumentKindForArgIndex(i), argc_); + Int32OperandId argInt32Id = writer.guardToInt32(argId); + resId = writer.int32MinMax(isMax, resId, argInt32Id); + } + writer.loadInt32Result(resId); + } else { + ValOperandId valId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + NumberOperandId resId = writer.guardIsNumber(valId); + for (size_t i = 1; i < argc_; i++) { + ValOperandId argId = + writer.loadArgumentFixedSlot(ArgumentKindForArgIndex(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 InlinableNativeIRGenerator::tryAttachSpreadMathMinMax( + bool isMax) { + MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Spread || + flags_.getArgFormat() == CallFlags::FunApplyArray); + + // The result will be an int32 if there is at least one argument, + // and all the arguments are int32. + bool int32Result = args_.length() > 0; + for (size_t i = 0; i < args_.length(); i++) { + if (!args_[i].isNumber()) { + return AttachDecision::NoAction; + } + if (!args_[i].isInt32()) { + int32Result = false; + } + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is this Math function. + emitNativeCalleeGuard(); + + // Load the argument array. + ObjOperandId argsId = emitLoadArgsArray(); + + if (int32Result) { + writer.int32MinMaxArrayResult(argsId, isMax); + } else { + writer.numberMinMaxArrayResult(argsId, isMax); + } + + writer.returnFromIC(); + + trackAttached(isMax ? "MathMaxArray" : "MathMinArray"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachMathFunction( + UnaryMathFunction fun) { + // Need one argument. + if (argc_ != 1) { + return AttachDecision::NoAction; + } + + if (!args_[0].isNumber()) { + return AttachDecision::NoAction; + } + + if (math_use_fdlibm_for_sin_cos_tan() || + callee_->realm()->creationOptions().alwaysUseFdlibm()) { + switch (fun) { + case UnaryMathFunction::SinNative: + fun = UnaryMathFunction::SinFdlibm; + break; + case UnaryMathFunction::CosNative: + fun = UnaryMathFunction::CosFdlibm; + break; + case UnaryMathFunction::TanNative: + fun = UnaryMathFunction::TanFdlibm; + break; + default: + break; + } + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is this Math function. + emitNativeCalleeGuard(); + + ValOperandId argumentId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + NumberOperandId numberId = writer.guardIsNumber(argumentId); + writer.mathFunctionNumberResult(numberId, fun); + writer.returnFromIC(); + + trackAttached("MathFunction"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachNumber() { + // Expect a single string argument. + if (argc_ != 1 || !args_[0].isString()) { + return AttachDecision::NoAction; + } + + double num; + if (!StringToNumber(cx_, args_[0].toString(), &num)) { + cx_->recoverFromOutOfMemory(); + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the `Number` function. + emitNativeCalleeGuard(); + + // Guard that the argument is a string. + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + StringOperandId strId = writer.guardToString(argId); + + // Return either an Int32 or Double result. + int32_t unused; + if (mozilla::NumberIsInt32(num, &unused)) { + Int32OperandId resultId = writer.guardStringToInt32(strId); + writer.loadInt32Result(resultId); + } else { + NumberOperandId resultId = writer.guardStringToNumber(strId); + writer.loadDoubleResult(resultId); + } + writer.returnFromIC(); + + trackAttached("Number"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachNumberParseInt() { + // Expected arguments: input (string or number), optional radix (int32). + if (argc_ < 1 || argc_ > 2) { + return AttachDecision::NoAction; + } + if (!args_[0].isString() && !args_[0].isNumber()) { + return AttachDecision::NoAction; + } + if (args_[0].isDouble()) { + double d = args_[0].toDouble(); + + // See num_parseInt for why we have to reject numbers smaller than 1.0e-6. + // Negative numbers in the exclusive range (-1, -0) return -0. + bool canTruncateToInt32 = + (DOUBLE_DECIMAL_IN_SHORTEST_LOW <= d && d <= double(INT32_MAX)) || + (double(INT32_MIN) <= d && d <= -1.0) || (d == 0.0); + if (!canTruncateToInt32) { + return AttachDecision::NoAction; + } + } + if (argc_ > 1 && !args_[1].isInt32(10)) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'parseInt' native function. + emitNativeCalleeGuard(); + + auto guardRadix = [&]() { + ValOperandId radixId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + Int32OperandId intRadixId = writer.guardToInt32(radixId); + writer.guardSpecificInt32(intRadixId, 10); + return intRadixId; + }; + + ValOperandId inputId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + + if (args_[0].isString()) { + StringOperandId strId = writer.guardToString(inputId); + + Int32OperandId intRadixId; + if (argc_ > 1) { + intRadixId = guardRadix(); + } else { + intRadixId = writer.loadInt32Constant(0); + } + + writer.numberParseIntResult(strId, intRadixId); + } else if (args_[0].isInt32()) { + Int32OperandId intId = writer.guardToInt32(inputId); + if (argc_ > 1) { + guardRadix(); + } + writer.loadInt32Result(intId); + } else { + MOZ_ASSERT(args_[0].isDouble()); + + NumberOperandId numId = writer.guardIsNumber(inputId); + if (argc_ > 1) { + guardRadix(); + } + writer.doubleParseIntResult(numId); + } + + writer.returnFromIC(); + + trackAttached("NumberParseInt"); + return AttachDecision::Attach; +} + +StringOperandId IRGenerator::emitToStringGuard(ValOperandId id, + const Value& v) { + MOZ_ASSERT(CanConvertToString(v)); + if (v.isString()) { + return writer.guardToString(id); + } + if (v.isBoolean()) { + BooleanOperandId boolId = writer.guardToBoolean(id); + return writer.booleanToString(boolId); + } + if (v.isNull()) { + writer.guardIsNull(id); + return writer.loadConstantString(cx_->names().null); + } + if (v.isUndefined()) { + writer.guardIsUndefined(id); + return writer.loadConstantString(cx_->names().undefined); + } + 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 InlinableNativeIRGenerator::tryAttachNumberToString() { + // Expecting no arguments or a single int32 argument. + if (argc_ > 1) { + return AttachDecision::NoAction; + } + if (argc_ == 1 && !args_[0].isInt32()) { + return AttachDecision::NoAction; + } + + // Ensure |this| is a primitive number value. + if (!thisval_.isNumber()) { + return AttachDecision::NoAction; + } + + // No arguments means base 10. + int32_t base = 10; + if (argc_ > 0) { + base = args_[0].toInt32(); + if (base < 2 || base > 36) { + return AttachDecision::NoAction; + } + + // Non-decimal bases currently only support int32 inputs. + if (base != 10 && !thisval_.isInt32()) { + return AttachDecision::NoAction; + } + } + MOZ_ASSERT(2 <= base && base <= 36); + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'toString' native function. + emitNativeCalleeGuard(); + + // Initialize the |this| operand. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + + // Guard on number and convert to string. + if (base == 10) { + // If an explicit base was passed, guard its value. + if (argc_ > 0) { + // Guard the `base` argument is an int32. + ValOperandId baseId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + Int32OperandId intBaseId = writer.guardToInt32(baseId); + + // Guard `base` is 10 for decimal toString representation. + writer.guardSpecificInt32(intBaseId, 10); + } + + StringOperandId strId = emitToStringGuard(thisValId, thisval_); + + // Return the string. + writer.loadStringResult(strId); + } else { + MOZ_ASSERT(argc_ > 0); + + // Guard the |this| value is an int32. + Int32OperandId thisIntId = writer.guardToInt32(thisValId); + + // Guard the `base` argument is an int32. + ValOperandId baseId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + Int32OperandId intBaseId = writer.guardToInt32(baseId); + + // Return the string. + writer.int32ToStringWithBaseResult(thisIntId, intBaseId); + } + + writer.returnFromIC(); + + trackAttached("NumberToString"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachReflectGetPrototypeOf() { + // Need one argument. + if (argc_ != 1) { + return AttachDecision::NoAction; + } + + if (!args_[0].isObject()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'getPrototypeOf' native function. + emitNativeCalleeGuard(); + + 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(FixedLengthTypedArrayObject* typedArray, + const Value& index) { + switch (typedArray->type()) { + case Scalar::Int8: + case Scalar::Uint8: + case Scalar::Int16: + case Scalar::Uint16: + case Scalar::Int32: + case Scalar::Uint32: + case Scalar::BigInt64: + case Scalar::BigUint64: + break; + + 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. + int64_t indexInt64; + if (!ValueIsInt64Index(index, &indexInt64)) { + return false; + } + if (indexInt64 < 0 || uint64_t(indexInt64) >= typedArray->length()) { + return false; + } + + return true; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsCompareExchange() { + if (!JitSupportsAtomics()) { + return AttachDecision::NoAction; + } + + // Need four arguments. + if (argc_ != 4) { + return AttachDecision::NoAction; + } + + // TODO: Support resizable typed arrays. (bug 1842999) + // Arguments: typedArray, index (number), expected, replacement. + if (!args_[0].isObject() || + !args_[0].toObject().is()) { + return AttachDecision::NoAction; + } + if (!args_[1].isNumber()) { + return AttachDecision::NoAction; + } + + auto* typedArray = &args_[0].toObject().as(); + if (!AtomicsMeetsPreconditions(typedArray, args_[1])) { + return AttachDecision::NoAction; + } + + Scalar::Type elementType = typedArray->type(); + if (!ValueCanConvertToNumeric(elementType, args_[2])) { + return AttachDecision::NoAction; + } + if (!ValueCanConvertToNumeric(elementType, args_[3])) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the `compareExchange` native function. + emitNativeCalleeGuard(); + + ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ObjOperandId objId = writer.guardToObject(arg0Id); + writer.guardShapeForClass(objId, typedArray->shape()); + + // Convert index to intPtr. + ValOperandId indexId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + IntPtrOperandId intPtrIndexId = + guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false); + + // Convert expected value to int32/BigInt. + ValOperandId expectedId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_); + OperandId numericExpectedId = + emitNumericGuard(expectedId, args_[2], elementType); + + // Convert replacement value to int32/BigInt. + ValOperandId replacementId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg3, argc_); + OperandId numericReplacementId = + emitNumericGuard(replacementId, args_[3], elementType); + + writer.atomicsCompareExchangeResult(objId, intPtrIndexId, numericExpectedId, + numericReplacementId, typedArray->type()); + writer.returnFromIC(); + + trackAttached("AtomicsCompareExchange"); + return AttachDecision::Attach; +} + +bool InlinableNativeIRGenerator::canAttachAtomicsReadWriteModify() { + if (!JitSupportsAtomics()) { + return false; + } + + // Need three arguments. + if (argc_ != 3) { + return false; + } + + // TODO: Support resizable typed arrays. (bug 1842999) + // Arguments: typedArray, index (number), value. + if (!args_[0].isObject() || + !args_[0].toObject().is()) { + return false; + } + if (!args_[1].isNumber()) { + return false; + } + + auto* typedArray = &args_[0].toObject().as(); + if (!AtomicsMeetsPreconditions(typedArray, args_[1])) { + return false; + } + if (!ValueCanConvertToNumeric(typedArray->type(), args_[2])) { + return false; + } + return true; +} + +InlinableNativeIRGenerator::AtomicsReadWriteModifyOperands +InlinableNativeIRGenerator::emitAtomicsReadWriteModifyOperands() { + MOZ_ASSERT(canAttachAtomicsReadWriteModify()); + + auto* typedArray = &args_[0].toObject().as(); + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is this Atomics function. + emitNativeCalleeGuard(); + + ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ObjOperandId objId = writer.guardToObject(arg0Id); + writer.guardShapeForClass(objId, typedArray->shape()); + + // Convert index to intPtr. + ValOperandId indexId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + IntPtrOperandId intPtrIndexId = + guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false); + + // Convert value to int32/BigInt. + ValOperandId valueId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_); + OperandId numericValueId = + emitNumericGuard(valueId, args_[2], typedArray->type()); + + return {objId, intPtrIndexId, numericValueId}; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsExchange() { + if (!canAttachAtomicsReadWriteModify()) { + return AttachDecision::NoAction; + } + + auto [objId, intPtrIndexId, numericValueId] = + emitAtomicsReadWriteModifyOperands(); + + auto* typedArray = &args_[0].toObject().as(); + + writer.atomicsExchangeResult(objId, intPtrIndexId, numericValueId, + typedArray->type()); + writer.returnFromIC(); + + trackAttached("AtomicsExchange"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsAdd() { + if (!canAttachAtomicsReadWriteModify()) { + return AttachDecision::NoAction; + } + + auto [objId, intPtrIndexId, numericValueId] = + emitAtomicsReadWriteModifyOperands(); + + auto* typedArray = &args_[0].toObject().as(); + bool forEffect = ignoresResult(); + + writer.atomicsAddResult(objId, intPtrIndexId, numericValueId, + typedArray->type(), forEffect); + writer.returnFromIC(); + + trackAttached("AtomicsAdd"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsSub() { + if (!canAttachAtomicsReadWriteModify()) { + return AttachDecision::NoAction; + } + + auto [objId, intPtrIndexId, numericValueId] = + emitAtomicsReadWriteModifyOperands(); + + auto* typedArray = &args_[0].toObject().as(); + bool forEffect = ignoresResult(); + + writer.atomicsSubResult(objId, intPtrIndexId, numericValueId, + typedArray->type(), forEffect); + writer.returnFromIC(); + + trackAttached("AtomicsSub"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsAnd() { + if (!canAttachAtomicsReadWriteModify()) { + return AttachDecision::NoAction; + } + + auto [objId, intPtrIndexId, numericValueId] = + emitAtomicsReadWriteModifyOperands(); + + auto* typedArray = &args_[0].toObject().as(); + bool forEffect = ignoresResult(); + + writer.atomicsAndResult(objId, intPtrIndexId, numericValueId, + typedArray->type(), forEffect); + writer.returnFromIC(); + + trackAttached("AtomicsAnd"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsOr() { + if (!canAttachAtomicsReadWriteModify()) { + return AttachDecision::NoAction; + } + + auto [objId, intPtrIndexId, numericValueId] = + emitAtomicsReadWriteModifyOperands(); + + auto* typedArray = &args_[0].toObject().as(); + bool forEffect = ignoresResult(); + + writer.atomicsOrResult(objId, intPtrIndexId, numericValueId, + typedArray->type(), forEffect); + writer.returnFromIC(); + + trackAttached("AtomicsOr"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsXor() { + if (!canAttachAtomicsReadWriteModify()) { + return AttachDecision::NoAction; + } + + auto [objId, intPtrIndexId, numericValueId] = + emitAtomicsReadWriteModifyOperands(); + + auto* typedArray = &args_[0].toObject().as(); + bool forEffect = ignoresResult(); + + writer.atomicsXorResult(objId, intPtrIndexId, numericValueId, + typedArray->type(), forEffect); + writer.returnFromIC(); + + trackAttached("AtomicsXor"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsLoad() { + if (!JitSupportsAtomics()) { + return AttachDecision::NoAction; + } + + // Need two arguments. + if (argc_ != 2) { + return AttachDecision::NoAction; + } + + // TODO: Support resizable typed arrays. (bug 1842999) + // Arguments: typedArray, index (number). + if (!args_[0].isObject() || + !args_[0].toObject().is()) { + return AttachDecision::NoAction; + } + if (!args_[1].isNumber()) { + return AttachDecision::NoAction; + } + + auto* typedArray = &args_[0].toObject().as(); + if (!AtomicsMeetsPreconditions(typedArray, args_[1])) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the `load` native function. + emitNativeCalleeGuard(); + + ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ObjOperandId objId = writer.guardToObject(arg0Id); + writer.guardShapeForClass(objId, typedArray->shape()); + + // Convert index to intPtr. + ValOperandId indexId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + IntPtrOperandId intPtrIndexId = + guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false); + + writer.atomicsLoadResult(objId, intPtrIndexId, typedArray->type()); + writer.returnFromIC(); + + trackAttached("AtomicsLoad"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsStore() { + 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. + + // TODO: Support resizable typed arrays. (bug 1842999) + // Arguments: typedArray, index (number), value. + if (!args_[0].isObject() || + !args_[0].toObject().is()) { + return AttachDecision::NoAction; + } + if (!args_[1].isNumber()) { + return AttachDecision::NoAction; + } + + auto* typedArray = &args_[0].toObject().as(); + if (!AtomicsMeetsPreconditions(typedArray, args_[1])) { + return AttachDecision::NoAction; + } + + Scalar::Type elementType = typedArray->type(); + if (!ValueCanConvertToNumeric(elementType, args_[2])) { + return AttachDecision::NoAction; + } + + bool guardIsInt32 = !Scalar::isBigIntType(elementType) && !ignoresResult(); + + if (guardIsInt32 && !args_[2].isInt32()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the `store` native function. + emitNativeCalleeGuard(); + + ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ObjOperandId objId = writer.guardToObject(arg0Id); + writer.guardShapeForClass(objId, typedArray->shape()); + + // Convert index to intPtr. + ValOperandId indexId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + IntPtrOperandId intPtrIndexId = + guardToIntPtrIndex(args_[1], indexId, /* supportOOB = */ false); + + // Ensure value is int32 or BigInt. + ValOperandId valueId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_); + OperandId numericValueId; + if (guardIsInt32) { + numericValueId = writer.guardToInt32(valueId); + } else { + numericValueId = emitNumericGuard(valueId, args_[2], elementType); + } + + writer.atomicsStoreResult(objId, intPtrIndexId, numericValueId, + typedArray->type()); + writer.returnFromIC(); + + trackAttached("AtomicsStore"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachAtomicsIsLockFree() { + // Need one argument. + if (argc_ != 1) { + return AttachDecision::NoAction; + } + + if (!args_[0].isInt32()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the `isLockFree` native function. + emitNativeCalleeGuard(); + + // 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 InlinableNativeIRGenerator::tryAttachBoolean() { + // Need zero or one argument. + if (argc_ > 1) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'Boolean' native function. + emitNativeCalleeGuard(); + + 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 InlinableNativeIRGenerator::tryAttachBailout() { + // Expecting no arguments. + if (argc_ != 0) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'bailout' native function. + emitNativeCalleeGuard(); + + writer.bailout(); + writer.loadUndefinedResult(); + writer.returnFromIC(); + + trackAttached("Bailout"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachAssertFloat32() { + // Expecting two arguments. + if (argc_ != 2) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'assertFloat32' native function. + emitNativeCalleeGuard(); + + // TODO: Warp doesn't yet optimize Float32 (bug 1655773). + + // NOP when not in IonMonkey. + writer.loadUndefinedResult(); + writer.returnFromIC(); + + trackAttached("AssertFloat32"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachAssertRecoveredOnBailout() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'assertRecoveredOnBailout' native function. + emitNativeCalleeGuard(); + + ValOperandId valId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + + writer.assertRecoveredOnBailoutResult(valId, mustBeRecovered); + writer.returnFromIC(); + + trackAttached("AssertRecoveredOnBailout"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachObjectIs() { + // Need two arguments. + if (argc_ != 2) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the `is` native function. + emitNativeCalleeGuard(); + + ValOperandId lhsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ValOperandId rhsId = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + + HandleValue lhs = args_[0]; + HandleValue rhs = args_[1]; + + if (!isFirstStub()) { + writer.sameValueResult(lhsId, rhsId); + } else 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; + } + +#ifdef ENABLE_RECORD_TUPLE + case ValueType::ExtendedPrimitive: +#endif + case JS::ValueType::Double: + case JS::ValueType::Magic: + case JS::ValueType::PrivateGCThing: + MOZ_CRASH("Unexpected type"); + } + } + + writer.returnFromIC(); + + trackAttached("ObjectIs"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachObjectIsPrototypeOf() { + // 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. + initializeInputOperand(); + + // Guard callee is the `isPrototypeOf` native function. + emitNativeCalleeGuard(); + + // Guard that |this| is an object. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId thisObjId = writer.guardToObject(thisValId); + + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + + writer.loadInstanceOfObjectResult(argId, thisObjId); + writer.returnFromIC(); + + trackAttached("ObjectIsPrototypeOf"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachObjectKeys() { + // Only handle argc <= 1. + if (argc_ != 1) { + return AttachDecision::NoAction; + } + + // Do not attach any IC if the argument is not an object. + if (!args_[0].isObject()) { + return AttachDecision::NoAction; + } + // Do not attach any IC if the argument is a Proxy. While implementation could + // work with proxies the goal of this implementation is to provide an + // optimization for calls of `Object.keys(obj)` where there is no side-effect, + // and where the computation of the array of property name can be moved. + const JSClass* clasp = args_[0].toObject().getClass(); + if (clasp->isProxyObject()) { + return AttachDecision::NoAction; + } + + // Generate cache IR code to attach a new inline cache which will delegate the + // call to Object.keys to the native function. + initializeInputOperand(); + + // Guard callee is the 'keys' native function. + emitNativeCalleeGuard(); + + // Implicit: Note `Object.keys` is a property of the `Object` global. The fact + // that we are in this function implies that we already identify the function + // as being the proper one. Thus there should not be any need to validate that + // this is the proper function. (test: ion/object-keys-05) + + // Guard `arg0` is an object. + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ObjOperandId argObjId = writer.guardToObject(argId); + + // Guard against proxies. + writer.guardIsNotProxy(argObjId); + + // Compute the keys array. + writer.objectKeysResult(argObjId); + + writer.returnFromIC(); + + trackAttached("ObjectKeys"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachObjectToString() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'toString' native function. + emitNativeCalleeGuard(); + + // Guard that |this| is an object. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId thisObjId = writer.guardToObject(thisValId); + + writer.objectToStringResult(thisObjId); + writer.returnFromIC(); + + trackAttached("ObjectToString"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachBigIntAsIntN() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'BigInt.asIntN' native function. + emitNativeCalleeGuard(); + + // 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 InlinableNativeIRGenerator::tryAttachBigIntAsUintN() { + // 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. + initializeInputOperand(); + + // Guard callee is the 'BigInt.asUintN' native function. + emitNativeCalleeGuard(); + + // 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 InlinableNativeIRGenerator::tryAttachSetHas() { + // Ensure |this| is a SetObject. + if (!thisval_.isObject() || !thisval_.toObject().is()) { + return AttachDecision::NoAction; + } + + // Need a single argument. + if (argc_ != 1) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'has' native function. + emitNativeCalleeGuard(); + + // Guard |this| is a SetObject. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId objId = writer.guardToObject(thisValId); + emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Set); + + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + +#ifndef JS_CODEGEN_X86 + // Assume the hash key will likely always have the same type when attaching + // the first stub. If the call is polymorphic on the hash key, attach a stub + // which handles any value. + if (isFirstStub()) { + switch (args_[0].type()) { + case ValueType::Double: + case ValueType::Int32: + case ValueType::Boolean: + case ValueType::Undefined: + case ValueType::Null: { + writer.guardToNonGCThing(argId); + writer.setHasNonGCThingResult(objId, argId); + break; + } + case ValueType::String: { + StringOperandId strId = writer.guardToString(argId); + writer.setHasStringResult(objId, strId); + break; + } + case ValueType::Symbol: { + SymbolOperandId symId = writer.guardToSymbol(argId); + writer.setHasSymbolResult(objId, symId); + break; + } + case ValueType::BigInt: { + BigIntOperandId bigIntId = writer.guardToBigInt(argId); + writer.setHasBigIntResult(objId, bigIntId); + break; + } + case ValueType::Object: { + // Currently only supported on 64-bit platforms. +# ifdef JS_PUNBOX64 + ObjOperandId valId = writer.guardToObject(argId); + writer.setHasObjectResult(objId, valId); +# else + writer.setHasResult(objId, argId); +# endif + break; + } + +# ifdef ENABLE_RECORD_TUPLE + case ValueType::ExtendedPrimitive: +# endif + case ValueType::Magic: + case ValueType::PrivateGCThing: + MOZ_CRASH("Unexpected type"); + } + } else { + writer.setHasResult(objId, argId); + } +#else + // The optimized versions require too many registers on x86. + writer.setHasResult(objId, argId); +#endif + + writer.returnFromIC(); + + trackAttached("SetHas"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachSetSize() { + // Ensure |this| is a SetObject. + if (!thisval_.isObject() || !thisval_.toObject().is()) { + return AttachDecision::NoAction; + } + + // Expecting no arguments. + if (argc_ != 0) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'size' native function. + emitNativeCalleeGuard(); + + // Guard |this| is a SetObject. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId objId = writer.guardToObject(thisValId); + writer.guardClass(objId, GuardClassKind::Set); + + writer.setSizeResult(objId); + writer.returnFromIC(); + + trackAttached("SetSize"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachMapHas() { + // Ensure |this| is a MapObject. + if (!thisval_.isObject() || !thisval_.toObject().is()) { + return AttachDecision::NoAction; + } + + // Need a single argument. + if (argc_ != 1) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'has' native function. + emitNativeCalleeGuard(); + + // Guard |this| is a MapObject. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId objId = writer.guardToObject(thisValId); + emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Map); + + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + +#ifndef JS_CODEGEN_X86 + // Assume the hash key will likely always have the same type when attaching + // the first stub. If the call is polymorphic on the hash key, attach a stub + // which handles any value. + if (isFirstStub()) { + switch (args_[0].type()) { + case ValueType::Double: + case ValueType::Int32: + case ValueType::Boolean: + case ValueType::Undefined: + case ValueType::Null: { + writer.guardToNonGCThing(argId); + writer.mapHasNonGCThingResult(objId, argId); + break; + } + case ValueType::String: { + StringOperandId strId = writer.guardToString(argId); + writer.mapHasStringResult(objId, strId); + break; + } + case ValueType::Symbol: { + SymbolOperandId symId = writer.guardToSymbol(argId); + writer.mapHasSymbolResult(objId, symId); + break; + } + case ValueType::BigInt: { + BigIntOperandId bigIntId = writer.guardToBigInt(argId); + writer.mapHasBigIntResult(objId, bigIntId); + break; + } + case ValueType::Object: { + // Currently only supported on 64-bit platforms. +# ifdef JS_PUNBOX64 + ObjOperandId valId = writer.guardToObject(argId); + writer.mapHasObjectResult(objId, valId); +# else + writer.mapHasResult(objId, argId); +# endif + break; + } + +# ifdef ENABLE_RECORD_TUPLE + case ValueType::ExtendedPrimitive: +# endif + case ValueType::Magic: + case ValueType::PrivateGCThing: + MOZ_CRASH("Unexpected type"); + } + } else { + writer.mapHasResult(objId, argId); + } +#else + // The optimized versions require too many registers on x86. + writer.mapHasResult(objId, argId); +#endif + + writer.returnFromIC(); + + trackAttached("MapHas"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachMapGet() { + // Ensure |this| is a MapObject. + if (!thisval_.isObject() || !thisval_.toObject().is()) { + return AttachDecision::NoAction; + } + + // Need a single argument. + if (argc_ != 1) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'get' native function. + emitNativeCalleeGuard(); + + // Guard |this| is a MapObject. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId objId = writer.guardToObject(thisValId); + emitOptimisticClassGuard(objId, &thisval_.toObject(), GuardClassKind::Map); + + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + +#ifndef JS_CODEGEN_X86 + // Assume the hash key will likely always have the same type when attaching + // the first stub. If the call is polymorphic on the hash key, attach a stub + // which handles any value. + if (isFirstStub()) { + switch (args_[0].type()) { + case ValueType::Double: + case ValueType::Int32: + case ValueType::Boolean: + case ValueType::Undefined: + case ValueType::Null: { + writer.guardToNonGCThing(argId); + writer.mapGetNonGCThingResult(objId, argId); + break; + } + case ValueType::String: { + StringOperandId strId = writer.guardToString(argId); + writer.mapGetStringResult(objId, strId); + break; + } + case ValueType::Symbol: { + SymbolOperandId symId = writer.guardToSymbol(argId); + writer.mapGetSymbolResult(objId, symId); + break; + } + case ValueType::BigInt: { + BigIntOperandId bigIntId = writer.guardToBigInt(argId); + writer.mapGetBigIntResult(objId, bigIntId); + break; + } + case ValueType::Object: { + // Currently only supported on 64-bit platforms. +# ifdef JS_PUNBOX64 + ObjOperandId valId = writer.guardToObject(argId); + writer.mapGetObjectResult(objId, valId); +# else + writer.mapGetResult(objId, argId); +# endif + break; + } + +# ifdef ENABLE_RECORD_TUPLE + case ValueType::ExtendedPrimitive: +# endif + case ValueType::Magic: + case ValueType::PrivateGCThing: + MOZ_CRASH("Unexpected type"); + } + } else { + writer.mapGetResult(objId, argId); + } +#else + // The optimized versions require too many registers on x86. + writer.mapGetResult(objId, argId); +#endif + + writer.returnFromIC(); + + trackAttached("MapGet"); + return AttachDecision::Attach; +} + +AttachDecision CallIRGenerator::tryAttachFunCall(HandleFunction callee) { + MOZ_ASSERT(callee->isNativeWithoutJitEntry()); + + if (callee->native() != fun_call) { + return AttachDecision::NoAction; + } + + if (!thisval_.isObject() || !thisval_.toObject().is()) { + return AttachDecision::NoAction; + } + RootedFunction target(cx_, &thisval_.toObject().as()); + + bool isScripted = target->hasJitEntry(); + MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry()); + + if (target->isClassConstructor()) { + return AttachDecision::NoAction; + } + Int32OperandId argcId(writer.setInputOperandId(0)); + + CallFlags targetFlags(CallFlags::FunCall); + if (mode_ == ICState::Mode::Specialized) { + if (cx_->realm() == target->realm()) { + targetFlags.setIsSameRealm(); + } + } + + if (mode_ == ICState::Mode::Specialized && !isScripted && argc_ > 0) { + // The stack layout is already in the correct form for calls with at least + // one argument. + // + // clang-format off + // + // *** STACK LAYOUT (bottom to top) *** *** INDEX *** + // Callee <-- argc+1 + // ThisValue <-- argc + // Args: | Arg0 | <-- argc-1 + // | Arg1 | <-- argc-2 + // | ... | <-- ... + // | ArgN | <-- 0 + // + // When passing |argc-1| as the number of arguments, we get: + // + // *** STACK LAYOUT (bottom to top) *** *** INDEX *** + // Callee <-- (argc-1)+1 = argc = ThisValue + // ThisValue <-- (argc-1) = argc-1 = Arg0 + // Args: | Arg0 | <-- (argc-1)-1 = argc-2 = Arg1 + // | Arg1 | <-- (argc-1)-2 = argc-3 = Arg2 + // | ... | <-- ... + // + // clang-format on + // + // This allows to call |loadArgumentFixedSlot(ArgumentKind::Arg0)| and we + // still load the correct argument index from |ArgumentKind::Arg1|. + // + // When no arguments are passed, i.e. |argc==0|, we have to replace + // |ArgumentKind::Arg0| with the undefined value. But we don't yet support + // this case. + HandleValue newTarget = NullHandleValue; + HandleValue thisValue = args_[0]; + HandleValueArray args = + HandleValueArray::subarray(args_, 1, args_.length() - 1); + + // Check for specific native-function optimizations. + InlinableNativeIRGenerator nativeGen(*this, target, newTarget, thisValue, + args, targetFlags); + TRY_ATTACH(nativeGen.tryAttachStub()); + } + + ObjOperandId thisObjId = emitFunCallGuard(argcId); + + if (mode_ == ICState::Mode::Specialized) { + // Ensure that |this| is the expected target function. + emitCalleeGuard(thisObjId, target); + + if (isScripted) { + writer.callScriptedFunction(thisObjId, argcId, targetFlags, + ClampFixedArgc(argc_)); + } else { + writer.callNativeFunction(thisObjId, argcId, op_, target, targetFlags, + ClampFixedArgc(argc_)); + } + } 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) { + writer.guardFunctionHasJitEntry(thisObjId, /*isConstructing =*/false); + writer.callScriptedFunction(thisObjId, argcId, targetFlags, + ClampFixedArgc(argc_)); + } else { + writer.guardFunctionHasNoJitEntry(thisObjId); + writer.callAnyNativeFunction(thisObjId, argcId, targetFlags, + ClampFixedArgc(argc_)); + } + } + + writer.returnFromIC(); + + if (isScripted) { + trackAttached("Scripted fun_call"); + } else { + trackAttached("Native fun_call"); + } + + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachIsTypedArray( + 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. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachIsTypedArrayConstructor() { + // Self-hosted code calls this with a single object argument. + MOZ_ASSERT(argc_ == 1); + MOZ_ASSERT(args_[0].isObject()); + + // Initialize the input operand. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachTypedArrayByteOffset() { + // 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()); + + // TODO: Support resizable typed arrays. (bug 1842999) + if (!args_[0].toObject().is()) { + return AttachDecision::NoAction; + } + + auto* tarr = &args_[0].toObject().as(); + + // Initialize the input operand. + initializeInputOperand(); + + // Note: we don't need to call emitNativeCalleeGuard for intrinsics. + + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ObjOperandId objArgId = writer.guardToObject(argId); + writer.guardIsFixedLengthTypedArray(objArgId); + if (tarr->byteOffset() <= INT32_MAX) { + writer.arrayBufferViewByteOffsetInt32Result(objArgId); + } else { + writer.arrayBufferViewByteOffsetDoubleResult(objArgId); + } + writer.returnFromIC(); + + trackAttached("IntrinsicTypedArrayByteOffset"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayElementSize() { + // 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()); + + // Initialize the input operand. + initializeInputOperand(); + + // Note: we don't need to call emitNativeCalleeGuard for intrinsics. + + ValOperandId argId = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ObjOperandId objArgId = writer.guardToObject(argId); + writer.typedArrayElementSizeResult(objArgId); + writer.returnFromIC(); + + trackAttached("TypedArrayElementSize"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachTypedArrayLength( + 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()); + + // TODO: Support resizable typed arrays. (bug 1842999) + if (!args_[0].toObject().is()) { + return AttachDecision::NoAction; + } + + auto* tarr = &args_[0].toObject().as(); + + // Initialize the input operand. + initializeInputOperand(); + + // 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.guardIsFixedLengthTypedArray(objArgId); + if (tarr->length() <= INT32_MAX) { + writer.loadArrayBufferViewLengthInt32Result(objArgId); + } else { + writer.loadArrayBufferViewLengthDoubleResult(objArgId); + } + writer.returnFromIC(); + + trackAttached("IntrinsicTypedArrayLength"); + return AttachDecision::Attach; +} + +AttachDecision +InlinableNativeIRGenerator::tryAttachTypedArrayLengthZeroOnOutOfBounds() { + // We don't yet inline resizable buffers, so this operation is equivalent to + // the inline code path for tryAttachTypedArrayLength(). + return tryAttachTypedArrayLength(/* isPossiblyWrapped = */ false); +} + +AttachDecision InlinableNativeIRGenerator::tryAttachArrayBufferByteLength( + 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()); + + auto* buffer = &args_[0].toObject().as(); + + // TODO: Support resizable buffers. (bug 1842999) + if (buffer->isResizable()) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // 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); + } + + if (buffer->byteLength() <= INT32_MAX) { + writer.loadArrayBufferByteLengthInt32Result(objArgId); + } else { + writer.loadArrayBufferByteLengthDoubleResult(objArgId); + } + writer.returnFromIC(); + + trackAttached("ArrayBufferByteLength"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachIsConstructing() { + // Self-hosted code calls this with no arguments in function scripts. + MOZ_ASSERT(argc_ == 0); + MOZ_ASSERT(script()->isFunction()); + + // Initialize the input operand. + initializeInputOperand(); + + // Note: we don't need to call emitNativeCalleeGuard for intrinsics. + + writer.frameIsConstructingResult(); + writer.returnFromIC(); + + trackAttached("IsConstructing"); + return AttachDecision::Attach; +} + +AttachDecision +InlinableNativeIRGenerator::tryAttachGetNextMapSetEntryForIterator(bool isMap) { + // Self-hosted code calls this with two objects. + MOZ_ASSERT(argc_ == 2); + if (isMap) { + MOZ_ASSERT(args_[0].toObject().is()); + } else { + MOZ_ASSERT(args_[0].toObject().is()); + } + MOZ_ASSERT(args_[1].toObject().is()); + + // Initialize the input operand. + initializeInputOperand(); + + // 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 InlinableNativeIRGenerator::tryAttachNewArrayIterator() { + // 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. + initializeInputOperand(); + + // Note: we don't need to call emitNativeCalleeGuard for intrinsics. + + writer.newArrayIteratorResult(templateObj); + writer.returnFromIC(); + + trackAttached("NewArrayIterator"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachNewStringIterator() { + // 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. + initializeInputOperand(); + + // Note: we don't need to call emitNativeCalleeGuard for intrinsics. + + writer.newStringIteratorResult(templateObj); + writer.returnFromIC(); + + trackAttached("NewStringIterator"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachNewRegExpStringIterator() { + // 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. + initializeInputOperand(); + + // Note: we don't need to call emitNativeCalleeGuard for intrinsics. + + writer.newRegExpStringIteratorResult(templateObj); + writer.returnFromIC(); + + trackAttached("NewRegExpStringIterator"); + return AttachDecision::Attach; +} + +AttachDecision +InlinableNativeIRGenerator::tryAttachArrayIteratorPrototypeOptimizable() { + // Self-hosted code calls this without any arguments + MOZ_ASSERT(argc_ == 0); + + if (!isFirstStub()) { + // Attach only once to prevent slowdowns for polymorphic calls. + return AttachDecision::NoAction; + } + + Rooted arrayIteratorProto(cx_); + uint32_t slot; + Rooted nextFun(cx_); + if (!IsArrayIteratorPrototypeOptimizable(cx_, AllowIteratorReturn::Yes, + &arrayIteratorProto, &slot, + &nextFun)) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Note: we don't need to call emitNativeCalleeGuard for intrinsics. + + ObjOperandId protoId = writer.loadObject(arrayIteratorProto); + ObjOperandId nextId = writer.loadObject(nextFun); + + writer.guardShape(protoId, arrayIteratorProto->shape()); + + // Ensure that proto[slot] == nextFun. + writer.guardDynamicSlotIsSpecificObject(protoId, nextId, slot); + writer.loadBooleanResult(true); + writer.returnFromIC(); + + trackAttached("ArrayIteratorPrototypeOptimizable"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachObjectCreate() { + // Need a single object-or-null argument. + if (argc_ != 1 || !args_[0].isObjectOrNull()) { + return AttachDecision::NoAction; + } + + if (!isFirstStub()) { + // Attach only once to prevent slowdowns for polymorphic calls. + return AttachDecision::NoAction; + } + + RootedObject proto(cx_, args_[0].toObjectOrNull()); + JSObject* templateObj = ObjectCreateImpl(cx_, proto, TenuredObject); + if (!templateObj) { + cx_->recoverFromOutOfMemory(); + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'create' native function. + emitNativeCalleeGuard(); + + // 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 InlinableNativeIRGenerator::tryAttachObjectConstructor() { + // Expecting no arguments or a single object argument. + // TODO(Warp): Support all or more conversions to object. + if (argc_ > 1) { + return AttachDecision::NoAction; + } + if (argc_ == 1 && !args_[0].isObject()) { + return AttachDecision::NoAction; + } + + PlainObject* templateObj = nullptr; + if (argc_ == 0) { + // Stub doesn't support metadata builder + if (cx_->realm()->hasAllocationMetadataBuilder()) { + return AttachDecision::NoAction; + } + + // Create a temporary object to act as the template object. + templateObj = NewPlainObjectWithAllocKind(cx_, NewObjectGCKind()); + if (!templateObj) { + cx_->recoverFromOutOfMemory(); + return AttachDecision::NoAction; + } + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee and newTarget (if constructing) are this Object constructor + // function. + emitNativeCalleeGuard(); + + if (argc_ == 0) { + // TODO: Support pre-tenuring. + gc::AllocSite* site = + script()->zone()->unknownAllocSite(JS::TraceKind::Object); + MOZ_ASSERT(site); + + uint32_t numFixedSlots = templateObj->numUsedFixedSlots(); + uint32_t numDynamicSlots = templateObj->numDynamicSlots(); + gc::AllocKind allocKind = templateObj->allocKindForTenure(); + Shape* shape = templateObj->shape(); + + writer.guardNoAllocationMetadataBuilder( + cx_->realm()->addressOfMetadataBuilder()); + writer.newPlainObjectResult(numFixedSlots, numDynamicSlots, allocKind, + shape, site); + } else { + // Use standard call flags when this is an inline Function.prototype.call(), + // because GetIndexOfArgument() doesn't yet support |CallFlags::FunCall|. + CallFlags flags = flags_; + if (flags.getArgFormat() == CallFlags::FunCall) { + flags = CallFlags(CallFlags::Standard); + } + + // Guard that the argument is an object. + ValOperandId argId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_, flags); + ObjOperandId objId = writer.guardToObject(argId); + + // Return the object. + writer.loadObjectResult(objId); + } + + writer.returnFromIC(); + + trackAttached("ObjectConstructor"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachArrayConstructor() { + // 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, TenuredObject); + if (!templateObj) { + cx_->clearPendingException(); + return AttachDecision::NoAction; + } + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee and newTarget (if constructing) are this Array constructor + // function. + emitNativeCalleeGuard(); + + Int32OperandId lengthId; + if (argc_ == 1) { + // Use standard call flags when this is an inline Function.prototype.call(), + // because GetIndexOfArgument() doesn't yet support |CallFlags::FunCall|. + CallFlags flags = flags_; + if (flags.getArgFormat() == CallFlags::FunCall) { + flags = CallFlags(CallFlags::Standard); + } + + 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 InlinableNativeIRGenerator::tryAttachTypedArrayConstructor() { + MOZ_ASSERT(flags_.isConstructing()); + + if (argc_ == 0 || argc_ > 3) { + return AttachDecision::NoAction; + } + + if (!isFirstStub()) { + // Attach only once to prevent slowdowns for polymorphic calls. + return AttachDecision::NoAction; + } + + // 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()) { + return AttachDecision::NoAction; + } + if (args_[0].isObject() && + args_[0].toObject().is()) { + return AttachDecision::NoAction; + } + if (args_[0].isObject() && + args_[0].toObject().is()) { + 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()) { + 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. + initializeInputOperand(); + + // Guard callee and newTarget are this TypedArray constructor function. + emitNativeCalleeGuard(); + + 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()) { + // From ArrayBuffer. + if (obj->is()) { + writer.guardClass(objId, GuardClassKind::FixedLengthArrayBuffer); + } else { + MOZ_ASSERT(obj->is()); + writer.guardClass(objId, GuardClassKind::FixedLengthSharedArrayBuffer); + } + 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 InlinableNativeIRGenerator::tryAttachSpecializedFunctionBind( + Handle target, Handle templateObj) { + // Try to attach a faster stub that's more specialized than what we emit in + // tryAttachFunctionBind. This lets us allocate and initialize a bound + // function object in Ion without calling into C++. + // + // We can do this if: + // + // * The target's prototype is Function.prototype, because that's the proto we + // use for the template object. + // * All bound arguments can be stored inline. + // * The `.name`, `.length`, and `IsConstructor` values match `target`. + // + // We initialize the template object with the bound function's name, length, + // and flags. At runtime we then only have to clone the template object and + // initialize the slots for the target, the bound `this` and the bound + // arguments. + + if (!isFirstStub()) { + return AttachDecision::NoAction; + } + if (!target->is() && !target->is()) { + return AttachDecision::NoAction; + } + if (target->staticPrototype() != &cx_->global()->getFunctionPrototype()) { + return AttachDecision::NoAction; + } + size_t numBoundArgs = argc_ > 0 ? argc_ - 1 : 0; + if (numBoundArgs > BoundFunctionObject::MaxInlineBoundArgs) { + return AttachDecision::NoAction; + } + + const bool targetIsConstructor = target->isConstructor(); + Rooted targetName(cx_); + uint32_t targetLength = 0; + + if (target->is()) { + Rooted fun(cx_, &target->as()); + if (fun->isNativeFun()) { + return AttachDecision::NoAction; + } + if (fun->hasResolvedLength() || fun->hasResolvedName()) { + return AttachDecision::NoAction; + } + uint16_t len; + if (!JSFunction::getUnresolvedLength(cx_, fun, &len)) { + cx_->clearPendingException(); + return AttachDecision::NoAction; + } + targetName = fun->getUnresolvedName(cx_); + if (!targetName) { + cx_->clearPendingException(); + return AttachDecision::NoAction; + } + + targetLength = len; + } else { + BoundFunctionObject* bound = &target->as(); + if (!targetIsConstructor) { + // Only support constructors for now. This lets us use + // GuardBoundFunctionIsConstructor. + return AttachDecision::NoAction; + } + Shape* initialShape = + cx_->global()->maybeBoundFunctionShapeWithDefaultProto(); + if (bound->shape() != initialShape) { + return AttachDecision::NoAction; + } + Value lenVal = bound->getLengthForInitialShape(); + Value nameVal = bound->getNameForInitialShape(); + if (!lenVal.isInt32() || lenVal.toInt32() < 0 || !nameVal.isString() || + !nameVal.toString()->isAtom()) { + return AttachDecision::NoAction; + } + targetName = &nameVal.toString()->asAtom(); + targetLength = uint32_t(lenVal.toInt32()); + } + + if (!templateObj->initTemplateSlotsForSpecializedBind( + cx_, numBoundArgs, targetIsConstructor, targetLength, targetName)) { + cx_->recoverFromOutOfMemory(); + return AttachDecision::NoAction; + } + + initializeInputOperand(); + emitNativeCalleeGuard(); + + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId targetId = writer.guardToObject(thisValId); + + // Ensure the JSClass and proto match, and that the `length` and `name` + // properties haven't been redefined. + writer.guardShape(targetId, target->shape()); + + // Emit guards for the `IsConstructor`, `.length`, and `.name` values. + if (target->is()) { + // Guard on: + // * The BaseScript (because that's what JSFunction uses for the `length`). + // Because MGuardFunctionScript doesn't support self-hosted functions yet, + // we use GuardSpecificFunction instead in this case. + // See assertion in MGuardFunctionScript::getAliasSet. + // * The flags slot (for the CONSTRUCTOR, RESOLVED_NAME, RESOLVED_LENGTH, + // HAS_INFERRED_NAME, and HAS_GUESSED_ATOM flags). + // * The atom slot. + JSFunction* fun = &target->as(); + if (fun->isSelfHostedBuiltin()) { + writer.guardSpecificFunction(targetId, fun); + } else { + writer.guardFunctionScript(targetId, fun->baseScript()); + } + writer.guardFixedSlotValue( + targetId, JSFunction::offsetOfFlagsAndArgCount(), + fun->getReservedSlot(JSFunction::FlagsAndArgCountSlot)); + writer.guardFixedSlotValue(targetId, JSFunction::offsetOfAtom(), + fun->getReservedSlot(JSFunction::AtomSlot)); + } else { + BoundFunctionObject* bound = &target->as(); + writer.guardBoundFunctionIsConstructor(targetId); + writer.guardFixedSlotValue(targetId, + BoundFunctionObject::offsetOfLengthSlot(), + bound->getLengthForInitialShape()); + writer.guardFixedSlotValue(targetId, + BoundFunctionObject::offsetOfNameSlot(), + bound->getNameForInitialShape()); + } + + writer.specializedBindFunctionResult(targetId, argc_, templateObj); + writer.returnFromIC(); + + trackAttached("SpecializedFunctionBind"); + return AttachDecision::Attach; +} + +AttachDecision InlinableNativeIRGenerator::tryAttachFunctionBind() { + // Ensure |this| (the target) is a function object or a bound function object. + // We could support other callables too, but note that we rely on the target + // having a static prototype in BoundFunctionObject::functionBindImpl. + if (!thisval_.isObject()) { + return AttachDecision::NoAction; + } + Rooted target(cx_, &thisval_.toObject()); + if (!target->is() && !target->is()) { + return AttachDecision::NoAction; + } + + // Only support standard, non-spread calls. + if (flags_.getArgFormat() != CallFlags::Standard) { + return AttachDecision::NoAction; + } + + // Only optimize if the number of arguments is small. This ensures we don't + // compile a lot of different stubs (because we bake in argc) and that we + // don't get anywhere near ARGS_LENGTH_MAX. + static constexpr size_t MaxArguments = 6; + if (argc_ > MaxArguments) { + return AttachDecision::NoAction; + } + + Rooted templateObj( + cx_, BoundFunctionObject::createTemplateObject(cx_)); + if (!templateObj) { + cx_->recoverFromOutOfMemory(); + return AttachDecision::NoAction; + } + + TRY_ATTACH(tryAttachSpecializedFunctionBind(target, templateObj)); + + initializeInputOperand(); + + emitNativeCalleeGuard(); + + // Guard |this| is a function object or a bound function object. + ValOperandId thisValId = + writer.loadArgumentFixedSlot(ArgumentKind::This, argc_); + ObjOperandId targetId = writer.guardToObject(thisValId); + if (target->is()) { + writer.guardClass(targetId, GuardClassKind::JSFunction); + } else { + MOZ_ASSERT(target->is()); + writer.guardClass(targetId, GuardClassKind::BoundFunction); + } + + writer.bindFunctionResult(targetId, argc_, templateObj); + writer.returnFromIC(); + + trackAttached("FunctionBind"); + return AttachDecision::Attach; +} + +AttachDecision CallIRGenerator::tryAttachFunApply(HandleFunction calleeFunc) { + MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry()); + + if (calleeFunc->native() != fun_apply) { + return AttachDecision::NoAction; + } + + if (argc_ > 2) { + return AttachDecision::NoAction; + } + + if (!thisval_.isObject() || !thisval_.toObject().is()) { + return AttachDecision::NoAction; + } + Rooted target(cx_, &thisval_.toObject().as()); + + bool isScripted = target->hasJitEntry(); + MOZ_ASSERT_IF(!isScripted, target->isNativeWithoutJitEntry()); + + if (target->isClassConstructor()) { + return AttachDecision::NoAction; + } + + CallFlags::ArgFormat format = CallFlags::Standard; + if (argc_ < 2) { + // |fun.apply()| and |fun.apply(thisValue)| are equivalent to |fun.call()| + // resp. |fun.call(thisValue)|. + format = CallFlags::FunCall; + } else if (args_[1].isNullOrUndefined()) { + // |fun.apply(thisValue, null)| and |fun.apply(thisValue, undefined)| are + // also equivalent to |fun.call(thisValue)|, but we can't use FunCall + // because we have to discard the second argument. + format = CallFlags::FunApplyNullUndefined; + } else if (args_[1].isObject() && args_[1].toObject().is()) { + auto* argsObj = &args_[1].toObject().as(); + if (argsObj->hasOverriddenElement() || argsObj->anyArgIsForwarded() || + argsObj->hasOverriddenLength() || + argsObj->initialLength() > JIT_ARGS_LENGTH_MAX) { + return AttachDecision::NoAction; + } + format = CallFlags::FunApplyArgsObj; + } else if (args_[1].isObject() && args_[1].toObject().is() && + args_[1].toObject().as().length() <= + JIT_ARGS_LENGTH_MAX && + IsPackedArray(&args_[1].toObject())) { + format = CallFlags::FunApplyArray; + } else { + return AttachDecision::NoAction; + } + + Int32OperandId argcId(writer.setInputOperandId(0)); + + CallFlags targetFlags(format); + if (mode_ == ICState::Mode::Specialized) { + if (cx_->realm() == target->realm()) { + targetFlags.setIsSameRealm(); + } + } + + if (mode_ == ICState::Mode::Specialized && !isScripted && + format == CallFlags::FunApplyArray) { + HandleValue newTarget = NullHandleValue; + HandleValue thisValue = args_[0]; + Rooted aobj(cx_, &args_[1].toObject().as()); + HandleValueArray args = HandleValueArray::fromMarkedLocation( + aobj->length(), aobj->getDenseElements()); + + // Check for specific native-function optimizations. + InlinableNativeIRGenerator nativeGen(*this, target, newTarget, thisValue, + args, targetFlags); + TRY_ATTACH(nativeGen.tryAttachStub()); + } + + // Don't inline when no arguments are passed, cf. |tryAttachFunCall()|. + if (mode_ == ICState::Mode::Specialized && !isScripted && + format == CallFlags::FunCall && argc_ > 0) { + MOZ_ASSERT(argc_ == 1); + + HandleValue newTarget = NullHandleValue; + HandleValue thisValue = args_[0]; + HandleValueArray args = HandleValueArray::empty(); + + // Check for specific native-function optimizations. + InlinableNativeIRGenerator nativeGen(*this, target, newTarget, thisValue, + args, targetFlags); + TRY_ATTACH(nativeGen.tryAttachStub()); + } + + ObjOperandId thisObjId = emitFunApplyGuard(argcId); + + uint32_t fixedArgc; + if (format == CallFlags::FunApplyArray || + format == CallFlags::FunApplyArgsObj || + format == CallFlags::FunApplyNullUndefined) { + emitFunApplyArgsGuard(format); + + // We always use MaxUnrolledArgCopy here because the fixed argc is + // meaningless in a FunApply case. + fixedArgc = MaxUnrolledArgCopy; + } else { + MOZ_ASSERT(format == CallFlags::FunCall); + + // Whereas for the FunCall case we need to use the actual fixed argc value. + fixedArgc = ClampFixedArgc(argc_); + } + + if (mode_ == ICState::Mode::Specialized) { + // Ensure that |this| is the expected target function. + emitCalleeGuard(thisObjId, target); + + if (isScripted) { + writer.callScriptedFunction(thisObjId, argcId, targetFlags, fixedArgc); + } else { + writer.callNativeFunction(thisObjId, argcId, op_, target, targetFlags, + fixedArgc); + } + } 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, fixedArgc); + } else { + // Guard that function is native. + writer.guardFunctionHasNoJitEntry(thisObjId); + writer.callAnyNativeFunction(thisObjId, argcId, targetFlags, fixedArgc); + } + } + + writer.returnFromIC(); + + if (isScripted) { + trackAttached("Call.ScriptedFunApply"); + } else { + trackAttached("Call.NativeFunApply"); + } + + 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; + } + JSOp op = JSOp(*pc_); + if (op != JSOp::Call && op != JSOp::CallContent && + op != 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 = inst.metadata().getFuncExportType(funcExport); + + MOZ_ASSERT(!IsInsideNursery(inst.object())); + MOZ_ASSERT(sig.canHaveJitEntry(), "Function should allow 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 || + argc_ > ArgumentKindArgIndexLimit) { + 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 = valType.toMIRType(); + 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: + // canHaveJitEntry restricts args to externref, where all JS values are + // valid and can be boxed. + MOZ_ASSERT(sig.args()[i].refType().isExtern(), + "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(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, ClampFixedArgc(argc_), + &funcExport, inst.object()); + writer.returnFromIC(); + + trackAttached("Call.WasmCall"); + + return AttachDecision::Attach; +} + +AttachDecision CallIRGenerator::tryAttachInlinableNative(HandleFunction callee, + CallFlags flags) { + MOZ_ASSERT(mode_ == ICState::Mode::Specialized); + MOZ_ASSERT(callee->isNativeWithoutJitEntry()); + MOZ_ASSERT(flags.getArgFormat() == CallFlags::Standard || + flags.getArgFormat() == CallFlags::Spread); + + // Special case functions are only optimized for normal calls. + if (op_ != JSOp::Call && op_ != JSOp::CallContent && op_ != JSOp::New && + op_ != JSOp::NewContent && op_ != JSOp::CallIgnoresRv && + op_ != JSOp::SpreadCall) { + return AttachDecision::NoAction; + } + + InlinableNativeIRGenerator nativeGen(*this, callee, newTarget_, thisval_, + args_, flags); + return nativeGen.tryAttachStub(); +} + +#ifdef FUZZING_JS_FUZZILLI +AttachDecision InlinableNativeIRGenerator::tryAttachFuzzilliHash() { + if (argc_ != 1) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + initializeInputOperand(); + + // Guard callee is the 'fuzzilli_hash' native function. + emitNativeCalleeGuard(); + + ValOperandId argValId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + + writer.fuzzilliHashResult(argValId); + writer.returnFromIC(); + + trackAttached("FuzzilliHash"); + return AttachDecision::Attach; +} +#endif + +AttachDecision InlinableNativeIRGenerator::tryAttachStub() { + 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 (flags_.isConstructing()) { + MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard); + + // newTarget must match the callee. CacheIR for this is emitted in + // emitNativeCalleeGuard. + if (ObjectValue(*callee_) != newTarget_) { + return AttachDecision::NoAction; + } + switch (native) { + case InlinableNative::Array: + return tryAttachArrayConstructor(); + case InlinableNative::TypedArrayConstructor: + return tryAttachTypedArrayConstructor(); + case InlinableNative::String: + return tryAttachStringConstructor(); + case InlinableNative::Object: + return tryAttachObjectConstructor(); + default: + break; + } + return AttachDecision::NoAction; + } + + // Check for special-cased native spread calls. + if (flags_.getArgFormat() == CallFlags::Spread || + flags_.getArgFormat() == CallFlags::FunApplyArray) { + switch (native) { + case InlinableNative::MathMin: + return tryAttachSpreadMathMinMax(/*isMax = */ false); + case InlinableNative::MathMax: + return tryAttachSpreadMathMinMax(/*isMax = */ true); + default: + break; + } + return AttachDecision::NoAction; + } + + MOZ_ASSERT(flags_.getArgFormat() == CallFlags::Standard || + flags_.getArgFormat() == CallFlags::FunCall); + + // Check for special-cased native functions. + switch (native) { + // Array natives. + case InlinableNative::Array: + return tryAttachArrayConstructor(); + case InlinableNative::ArrayPush: + return tryAttachArrayPush(); + case InlinableNative::ArrayPop: + case InlinableNative::ArrayShift: + return tryAttachArrayPopShift(native); + case InlinableNative::ArrayJoin: + return tryAttachArrayJoin(); + case InlinableNative::ArraySlice: + return tryAttachArraySlice(); + case InlinableNative::ArrayIsArray: + return tryAttachArrayIsArray(); + + // DataView natives. + case InlinableNative::DataViewGetInt8: + return tryAttachDataViewGet(Scalar::Int8); + case InlinableNative::DataViewGetUint8: + return tryAttachDataViewGet(Scalar::Uint8); + case InlinableNative::DataViewGetInt16: + return tryAttachDataViewGet(Scalar::Int16); + case InlinableNative::DataViewGetUint16: + return tryAttachDataViewGet(Scalar::Uint16); + case InlinableNative::DataViewGetInt32: + return tryAttachDataViewGet(Scalar::Int32); + case InlinableNative::DataViewGetUint32: + return tryAttachDataViewGet(Scalar::Uint32); + case InlinableNative::DataViewGetFloat32: + return tryAttachDataViewGet(Scalar::Float32); + case InlinableNative::DataViewGetFloat64: + return tryAttachDataViewGet(Scalar::Float64); + case InlinableNative::DataViewGetBigInt64: + return tryAttachDataViewGet(Scalar::BigInt64); + case InlinableNative::DataViewGetBigUint64: + return tryAttachDataViewGet(Scalar::BigUint64); + case InlinableNative::DataViewSetInt8: + return tryAttachDataViewSet(Scalar::Int8); + case InlinableNative::DataViewSetUint8: + return tryAttachDataViewSet(Scalar::Uint8); + case InlinableNative::DataViewSetInt16: + return tryAttachDataViewSet(Scalar::Int16); + case InlinableNative::DataViewSetUint16: + return tryAttachDataViewSet(Scalar::Uint16); + case InlinableNative::DataViewSetInt32: + return tryAttachDataViewSet(Scalar::Int32); + case InlinableNative::DataViewSetUint32: + return tryAttachDataViewSet(Scalar::Uint32); + case InlinableNative::DataViewSetFloat32: + return tryAttachDataViewSet(Scalar::Float32); + case InlinableNative::DataViewSetFloat64: + return tryAttachDataViewSet(Scalar::Float64); + case InlinableNative::DataViewSetBigInt64: + return tryAttachDataViewSet(Scalar::BigInt64); + case InlinableNative::DataViewSetBigUint64: + return tryAttachDataViewSet(Scalar::BigUint64); + + // Function natives. + case InlinableNative::FunctionBind: + return tryAttachFunctionBind(); + + // Intl natives. + case InlinableNative::IntlGuardToCollator: + case InlinableNative::IntlGuardToDateTimeFormat: + case InlinableNative::IntlGuardToDisplayNames: + case InlinableNative::IntlGuardToListFormat: + case InlinableNative::IntlGuardToNumberFormat: + case InlinableNative::IntlGuardToPluralRules: + case InlinableNative::IntlGuardToRelativeTimeFormat: + case InlinableNative::IntlGuardToSegmenter: + case InlinableNative::IntlGuardToSegments: + case InlinableNative::IntlGuardToSegmentIterator: + return tryAttachGuardToClass(native); + + // Slot intrinsics. + case InlinableNative::IntrinsicUnsafeGetReservedSlot: + case InlinableNative::IntrinsicUnsafeGetObjectFromReservedSlot: + case InlinableNative::IntrinsicUnsafeGetInt32FromReservedSlot: + case InlinableNative::IntrinsicUnsafeGetStringFromReservedSlot: + return tryAttachUnsafeGetReservedSlot(native); + case InlinableNative::IntrinsicUnsafeSetReservedSlot: + return tryAttachUnsafeSetReservedSlot(); + + // Intrinsics. + case InlinableNative::IntrinsicIsSuspendedGenerator: + return tryAttachIsSuspendedGenerator(); + case InlinableNative::IntrinsicToObject: + return tryAttachToObject(); + case InlinableNative::IntrinsicToInteger: + return tryAttachToInteger(); + case InlinableNative::IntrinsicToLength: + return tryAttachToLength(); + case InlinableNative::IntrinsicIsObject: + return tryAttachIsObject(); + case InlinableNative::IntrinsicIsPackedArray: + return tryAttachIsPackedArray(); + case InlinableNative::IntrinsicIsCallable: + return tryAttachIsCallable(); + case InlinableNative::IntrinsicIsConstructor: + return tryAttachIsConstructor(); + case InlinableNative::IntrinsicIsCrossRealmArrayConstructor: + return tryAttachIsCrossRealmArrayConstructor(); + 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(native); + case InlinableNative::IntrinsicSubstringKernel: + return tryAttachSubstringKernel(); + case InlinableNative::IntrinsicIsConstructing: + return tryAttachIsConstructing(); + case InlinableNative::IntrinsicNewArrayIterator: + return tryAttachNewArrayIterator(); + case InlinableNative::IntrinsicNewStringIterator: + return tryAttachNewStringIterator(); + case InlinableNative::IntrinsicNewRegExpStringIterator: + return tryAttachNewRegExpStringIterator(); + case InlinableNative::IntrinsicArrayIteratorPrototypeOptimizable: + return tryAttachArrayIteratorPrototypeOptimizable(); + case InlinableNative::IntrinsicObjectHasPrototype: + return tryAttachObjectHasPrototype(); + + // RegExp natives. + case InlinableNative::IsRegExpObject: + return tryAttachHasClass(&RegExpObject::class_, + /* isPossiblyWrapped = */ false); + case InlinableNative::IsPossiblyWrappedRegExpObject: + return tryAttachHasClass(&RegExpObject::class_, + /* isPossiblyWrapped = */ true); + case InlinableNative::RegExpMatcher: + case InlinableNative::RegExpSearcher: + return tryAttachRegExpMatcherSearcher(native); + case InlinableNative::RegExpSearcherLastLimit: + return tryAttachRegExpSearcherLastLimit(); + case InlinableNative::RegExpHasCaptureGroups: + return tryAttachRegExpHasCaptureGroups(); + case InlinableNative::RegExpPrototypeOptimizable: + return tryAttachRegExpPrototypeOptimizable(); + case InlinableNative::RegExpInstanceOptimizable: + return tryAttachRegExpInstanceOptimizable(); + case InlinableNative::GetFirstDollarIndex: + return tryAttachGetFirstDollarIndex(); + case InlinableNative::IntrinsicRegExpBuiltinExec: + case InlinableNative::IntrinsicRegExpBuiltinExecForTest: + return tryAttachIntrinsicRegExpBuiltinExec(native); + case InlinableNative::IntrinsicRegExpExec: + case InlinableNative::IntrinsicRegExpExecForTest: + return tryAttachIntrinsicRegExpExec(native); + + // String natives. + case InlinableNative::String: + return tryAttachString(); + case InlinableNative::StringToString: + case InlinableNative::StringValueOf: + return tryAttachStringToStringValueOf(); + case InlinableNative::StringCharCodeAt: + return tryAttachStringCharCodeAt(); + case InlinableNative::StringCodePointAt: + return tryAttachStringCodePointAt(); + case InlinableNative::StringCharAt: + return tryAttachStringCharAt(); + case InlinableNative::StringAt: + return tryAttachStringAt(); + case InlinableNative::StringFromCharCode: + return tryAttachStringFromCharCode(); + case InlinableNative::StringFromCodePoint: + return tryAttachStringFromCodePoint(); + case InlinableNative::StringIncludes: + return tryAttachStringIncludes(); + case InlinableNative::StringIndexOf: + return tryAttachStringIndexOf(); + case InlinableNative::StringLastIndexOf: + return tryAttachStringLastIndexOf(); + case InlinableNative::StringStartsWith: + return tryAttachStringStartsWith(); + case InlinableNative::StringEndsWith: + return tryAttachStringEndsWith(); + case InlinableNative::StringToLowerCase: + return tryAttachStringToLowerCase(); + case InlinableNative::StringToUpperCase: + return tryAttachStringToUpperCase(); + case InlinableNative::StringTrim: + return tryAttachStringTrim(); + case InlinableNative::StringTrimStart: + return tryAttachStringTrimStart(); + case InlinableNative::StringTrimEnd: + return tryAttachStringTrimEnd(); + case InlinableNative::IntrinsicStringReplaceString: + return tryAttachStringReplaceString(); + case InlinableNative::IntrinsicStringSplitString: + return tryAttachStringSplitString(); + + // Math natives. + case InlinableNative::MathRandom: + return tryAttachMathRandom(); + case InlinableNative::MathAbs: + return tryAttachMathAbs(); + case InlinableNative::MathClz32: + return tryAttachMathClz32(); + case InlinableNative::MathSign: + return tryAttachMathSign(); + case InlinableNative::MathImul: + return tryAttachMathImul(); + case InlinableNative::MathFloor: + return tryAttachMathFloor(); + case InlinableNative::MathCeil: + return tryAttachMathCeil(); + case InlinableNative::MathTrunc: + return tryAttachMathTrunc(); + case InlinableNative::MathRound: + return tryAttachMathRound(); + case InlinableNative::MathSqrt: + return tryAttachMathSqrt(); + case InlinableNative::MathFRound: + return tryAttachMathFRound(); + case InlinableNative::MathHypot: + return tryAttachMathHypot(); + case InlinableNative::MathATan2: + return tryAttachMathATan2(); + case InlinableNative::MathSin: + return tryAttachMathFunction(UnaryMathFunction::SinNative); + case InlinableNative::MathTan: + return tryAttachMathFunction(UnaryMathFunction::TanNative); + case InlinableNative::MathCos: + return tryAttachMathFunction(UnaryMathFunction::CosNative); + case InlinableNative::MathExp: + return tryAttachMathFunction(UnaryMathFunction::Exp); + case InlinableNative::MathLog: + return tryAttachMathFunction(UnaryMathFunction::Log); + case InlinableNative::MathASin: + return tryAttachMathFunction(UnaryMathFunction::ASin); + case InlinableNative::MathATan: + return tryAttachMathFunction(UnaryMathFunction::ATan); + case InlinableNative::MathACos: + return tryAttachMathFunction(UnaryMathFunction::ACos); + case InlinableNative::MathLog10: + return tryAttachMathFunction(UnaryMathFunction::Log10); + case InlinableNative::MathLog2: + return tryAttachMathFunction(UnaryMathFunction::Log2); + case InlinableNative::MathLog1P: + return tryAttachMathFunction(UnaryMathFunction::Log1P); + case InlinableNative::MathExpM1: + return tryAttachMathFunction(UnaryMathFunction::ExpM1); + case InlinableNative::MathCosH: + return tryAttachMathFunction(UnaryMathFunction::CosH); + case InlinableNative::MathSinH: + return tryAttachMathFunction(UnaryMathFunction::SinH); + case InlinableNative::MathTanH: + return tryAttachMathFunction(UnaryMathFunction::TanH); + case InlinableNative::MathACosH: + return tryAttachMathFunction(UnaryMathFunction::ACosH); + case InlinableNative::MathASinH: + return tryAttachMathFunction(UnaryMathFunction::ASinH); + case InlinableNative::MathATanH: + return tryAttachMathFunction(UnaryMathFunction::ATanH); + case InlinableNative::MathCbrt: + return tryAttachMathFunction(UnaryMathFunction::Cbrt); + case InlinableNative::MathPow: + return tryAttachMathPow(); + case InlinableNative::MathMin: + return tryAttachMathMinMax(/* isMax = */ false); + case InlinableNative::MathMax: + return tryAttachMathMinMax(/* isMax = */ true); + + // Map intrinsics. + case InlinableNative::IntrinsicGuardToMapObject: + return tryAttachGuardToClass(native); + case InlinableNative::IntrinsicGetNextMapEntryForIterator: + return tryAttachGetNextMapSetEntryForIterator(/* isMap = */ true); + + // Number natives. + case InlinableNative::Number: + return tryAttachNumber(); + case InlinableNative::NumberParseInt: + return tryAttachNumberParseInt(); + case InlinableNative::NumberToString: + return tryAttachNumberToString(); + + // Object natives. + case InlinableNative::Object: + return tryAttachObjectConstructor(); + case InlinableNative::ObjectCreate: + return tryAttachObjectCreate(); + case InlinableNative::ObjectIs: + return tryAttachObjectIs(); + case InlinableNative::ObjectIsPrototypeOf: + return tryAttachObjectIsPrototypeOf(); + case InlinableNative::ObjectKeys: + return tryAttachObjectKeys(); + case InlinableNative::ObjectToString: + return tryAttachObjectToString(); + + // Set intrinsics. + case InlinableNative::IntrinsicGuardToSetObject: + return tryAttachGuardToClass(native); + case InlinableNative::IntrinsicGetNextSetEntryForIterator: + return tryAttachGetNextMapSetEntryForIterator(/* isMap = */ false); + + // ArrayBuffer intrinsics. + case InlinableNative::IntrinsicGuardToArrayBuffer: + return tryAttachGuardToArrayBuffer(); + case InlinableNative::IntrinsicArrayBufferByteLength: + return tryAttachArrayBufferByteLength(/* isPossiblyWrapped = */ false); + case InlinableNative::IntrinsicPossiblyWrappedArrayBufferByteLength: + return tryAttachArrayBufferByteLength(/* isPossiblyWrapped = */ true); + + // SharedArrayBuffer intrinsics. + case InlinableNative::IntrinsicGuardToSharedArrayBuffer: + return tryAttachGuardToSharedArrayBuffer(); + + // TypedArray intrinsics. + case InlinableNative::TypedArrayConstructor: + return AttachDecision::NoAction; // Not callable. + case InlinableNative::IntrinsicIsTypedArray: + return tryAttachIsTypedArray(/* isPossiblyWrapped = */ false); + case InlinableNative::IntrinsicIsPossiblyWrappedTypedArray: + return tryAttachIsTypedArray(/* isPossiblyWrapped = */ true); + case InlinableNative::IntrinsicIsTypedArrayConstructor: + return tryAttachIsTypedArrayConstructor(); + case InlinableNative::IntrinsicTypedArrayByteOffset: + return tryAttachTypedArrayByteOffset(); + case InlinableNative::IntrinsicTypedArrayElementSize: + return tryAttachTypedArrayElementSize(); + case InlinableNative::IntrinsicTypedArrayLength: + return tryAttachTypedArrayLength(/* isPossiblyWrapped = */ false); + case InlinableNative::IntrinsicTypedArrayLengthZeroOnOutOfBounds: + return tryAttachTypedArrayLengthZeroOnOutOfBounds(); + case InlinableNative::IntrinsicPossiblyWrappedTypedArrayLength: + return tryAttachTypedArrayLength(/* isPossiblyWrapped = */ true); + + // Reflect natives. + case InlinableNative::ReflectGetPrototypeOf: + return tryAttachReflectGetPrototypeOf(); + + // Atomics intrinsics: + case InlinableNative::AtomicsCompareExchange: + return tryAttachAtomicsCompareExchange(); + case InlinableNative::AtomicsExchange: + return tryAttachAtomicsExchange(); + case InlinableNative::AtomicsAdd: + return tryAttachAtomicsAdd(); + case InlinableNative::AtomicsSub: + return tryAttachAtomicsSub(); + case InlinableNative::AtomicsAnd: + return tryAttachAtomicsAnd(); + case InlinableNative::AtomicsOr: + return tryAttachAtomicsOr(); + case InlinableNative::AtomicsXor: + return tryAttachAtomicsXor(); + case InlinableNative::AtomicsLoad: + return tryAttachAtomicsLoad(); + case InlinableNative::AtomicsStore: + return tryAttachAtomicsStore(); + case InlinableNative::AtomicsIsLockFree: + return tryAttachAtomicsIsLockFree(); + + // BigInt natives. + case InlinableNative::BigIntAsIntN: + return tryAttachBigIntAsIntN(); + case InlinableNative::BigIntAsUintN: + return tryAttachBigIntAsUintN(); + + // Boolean natives. + case InlinableNative::Boolean: + return tryAttachBoolean(); + + // Set natives. + case InlinableNative::SetHas: + return tryAttachSetHas(); + case InlinableNative::SetSize: + return tryAttachSetSize(); + + // Map natives. + case InlinableNative::MapHas: + return tryAttachMapHas(); + case InlinableNative::MapGet: + return tryAttachMapGet(); + + // Testing functions. + case InlinableNative::TestBailout: + if (js::SupportDifferentialTesting()) { + return AttachDecision::NoAction; + } + return tryAttachBailout(); + case InlinableNative::TestAssertFloat32: + return tryAttachAssertFloat32(); + case InlinableNative::TestAssertRecoveredOnBailout: + if (js::SupportDifferentialTesting()) { + return AttachDecision::NoAction; + } + return tryAttachAssertRecoveredOnBailout(); + +#ifdef FUZZING_JS_FUZZILLI + // Fuzzilli function + case InlinableNative::FuzzilliHash: + return tryAttachFuzzilliHash(); +#endif + + case InlinableNative::Limit: + break; + } + + MOZ_CRASH("Shouldn't get here"); +} + +// Remember the shape of the this object for any script being called as a +// constructor, for later use during Ion compilation. +ScriptedThisResult CallIRGenerator::getThisShapeForScripted( + HandleFunction calleeFunc, Handle newTarget, + MutableHandle 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. + if (!newTarget->is() || + !newTarget->as().hasNonConfigurablePrototypeDataProperty()) { + return ScriptedThisResult::NoAction; + } + + AutoRealm ar(cx_, calleeFunc); + Shape* thisShape = ThisShapeForFunction(cx_, calleeFunc, newTarget); + if (!thisShape) { + cx_->clearPendingException(); + return ScriptedThisResult::NoAction; + } + + MOZ_ASSERT(thisShape->realm() == calleeFunc->realm()); + result.set(thisShape); + return ScriptedThisResult::PlainObjectShape; +} + +static bool CanOptimizeScriptedCall(JSFunction* callee, bool isConstructing) { + if (!callee->hasJitEntry()) { + return false; + } + + // If callee is not an interpreted constructor, we have to throw. + if (isConstructing && !callee->isConstructor()) { + return false; + } + + // Likewise, if the callee is a class constructor, we have to throw. + if (!isConstructing && callee->isClassConstructor()) { + return false; + } + + return true; +} + +void CallIRGenerator::emitCallScriptedGuards(ObjOperandId calleeObjId, + JSFunction* calleeFunc, + Int32OperandId argcId, + CallFlags flags, Shape* thisShape, + bool isBoundFunction) { + bool isConstructing = flags.isConstructing(); + + if (mode_ == ICState::Mode::Specialized) { + MOZ_ASSERT_IF(isConstructing, thisShape || flags.needsUninitializedThis()); + + // Ensure callee matches this stub's callee + emitCalleeGuard(calleeObjId, calleeFunc); + if (thisShape) { + // 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; + ObjOperandId newTargetObjId; + if (isBoundFunction) { + newTarget = calleeFunc; + newTargetObjId = calleeObjId; + } else { + newTarget = &newTarget_.toObject().as(); + ValOperandId newTargetValId = writer.loadArgumentDynamicSlot( + ArgumentKind::NewTarget, argcId, flags); + newTargetObjId = writer.guardToObject(newTargetValId); + } + + Maybe prop = newTarget->lookupPure(cx_->names().prototype); + MOZ_ASSERT(prop.isSome()); + uint32_t slot = prop->slot(); + MOZ_ASSERT(slot >= newTarget->numFixedSlots(), + "Stub code relies on this"); + + writer.guardShape(newTargetObjId, newTarget->shape()); + + const Value& value = newTarget->getSlot(slot); + if (value.isObject()) { + JSObject* prototypeObject = &value.toObject(); + + ObjOperandId protoId = writer.loadObject(prototypeObject); + writer.guardDynamicSlotIsSpecificObject( + newTargetObjId, protoId, slot - newTarget->numFixedSlots()); + } else { + writer.guardDynamicSlotIsNotObject(newTargetObjId, + slot - newTarget->numFixedSlots()); + } + + // Call metaScriptedThisShape before emitting the call, so that Warp can + // use the shape to create the |this| object before transpiling the call. + writer.metaScriptedThisShape(thisShape); + } + } 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); + } + } +} + +AttachDecision CallIRGenerator::tryAttachCallScripted( + HandleFunction calleeFunc) { + MOZ_ASSERT(calleeFunc->hasJitEntry()); + + 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 (!CanOptimizeScriptedCall(calleeFunc, isConstructing)) { + 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; + } + + Rooted thisShape(cx_); + if (isConstructing && isSpecialized) { + Rooted newTarget(cx_, &newTarget_.toObject()); + switch (getThisShapeForScripted(calleeFunc, newTarget, &thisShape)) { + case ScriptedThisResult::PlainObjectShape: + 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); + + emitCallScriptedGuards(calleeObjId, calleeFunc, argcId, flags, thisShape, + /* isBoundFunction = */ false); + + writer.callScriptedFunction(calleeObjId, argcId, flags, + ClampFixedArgc(argc_)); + writer.returnFromIC(); + + if (isSpecialized) { + trackAttached("Call.CallScripted"); + } else { + trackAttached("Call.CallAnyScripted"); + } + + 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, flags)); + } + + // 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| shape to make sure it's the right instance. This also + // ensures DOM_OBJECT_SLOT is stored in a fixed slot. See CanAttachDOMCall. + writer.guardShape(thisObjId, thisval_.toObject().shape()); + + // Ensure callee matches this stub's callee + writer.guardSpecificFunction(calleeObjId, calleeFunc); + writer.callDOMFunction(calleeObjId, argcId, thisObjId, calleeFunc, flags, + ClampFixedArgc(argc_)); + + trackAttached("Call.CallDOM"); + } else if (isSpecialized) { + // Ensure callee matches this stub's callee + writer.guardSpecificFunction(calleeObjId, calleeFunc); + writer.callNativeFunction(calleeObjId, argcId, op_, calleeFunc, flags, + ClampFixedArgc(argc_)); + + trackAttached("Call.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, + ClampFixedArgc(argc_)); + + trackAttached("Call.CallAnyNative"); + } + + writer.returnFromIC(); + + return AttachDecision::Attach; +} + +AttachDecision CallIRGenerator::tryAttachCallHook(HandleObject calleeObj) { + 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; + } + + // Bound functions have a JSClass construct hook but are not always + // constructors. + if (isConstructing && !calleeObj->isConstructor()) { + return AttachDecision::NoAction; + } + + // We don't support spread calls in the transpiler yet. + if (isSpread) { + 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()); + + if (isConstructing && calleeObj->is()) { + writer.guardBoundFunctionIsConstructor(calleeObjId); + } + + writer.callClassHook(calleeObjId, argcId, hook, flags, ClampFixedArgc(argc_)); + writer.returnFromIC(); + + trackAttached("Call.CallHook"); + + return AttachDecision::Attach; +} + +AttachDecision CallIRGenerator::tryAttachBoundFunction( + Handle calleeObj) { + // The target must be a JSFunction with a JitEntry. + if (!calleeObj->getTarget()->is()) { + return AttachDecision::NoAction; + } + + bool isSpread = IsSpreadPC(pc_); + bool isConstructing = IsConstructPC(pc_); + + // Spread calls are not supported yet. + if (isSpread) { + return AttachDecision::NoAction; + } + + Rooted target(cx_, &calleeObj->getTarget()->as()); + if (!CanOptimizeScriptedCall(target, isConstructing)) { + return AttachDecision::NoAction; + } + + // Limit the number of bound arguments to prevent us from compiling many + // different stubs (we bake in numBoundArgs and it's usually very small). + static constexpr size_t MaxBoundArgs = 10; + size_t numBoundArgs = calleeObj->numBoundArgs(); + if (numBoundArgs > MaxBoundArgs) { + return AttachDecision::NoAction; + } + + // Ensure we don't exceed JIT_ARGS_LENGTH_MAX. + if (numBoundArgs + argc_ > JIT_ARGS_LENGTH_MAX) { + return AttachDecision::NoAction; + } + + CallFlags flags(isConstructing, isSpread); + + if (mode_ == ICState::Mode::Specialized) { + if (cx_->realm() == target->realm()) { + flags.setIsSameRealm(); + } + } + + Rooted thisShape(cx_); + if (isConstructing) { + // Only optimize if newTarget == callee. This is the common case and ensures + // we can always pass the bound function's target as newTarget. + if (newTarget_ != ObjectValue(*calleeObj)) { + return AttachDecision::NoAction; + } + + if (mode_ == ICState::Mode::Specialized) { + Handle newTarget = target; + switch (getThisShapeForScripted(target, newTarget, &thisShape)) { + case ScriptedThisResult::PlainObjectShape: + 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's a bound function. + ValOperandId calleeValId = + writer.loadArgumentDynamicSlot(ArgumentKind::Callee, argcId, flags); + ObjOperandId calleeObjId = writer.guardToObject(calleeValId); + writer.guardClass(calleeObjId, GuardClassKind::BoundFunction); + + // Ensure numBoundArgs matches. + Int32OperandId numBoundArgsId = writer.loadBoundFunctionNumArgs(calleeObjId); + writer.guardSpecificInt32(numBoundArgsId, numBoundArgs); + + if (isConstructing) { + // Guard newTarget == callee. We depend on this in CallBoundScriptedFunction + // and in emitCallScriptedGuards by using boundTarget as newTarget. + ValOperandId newTargetValId = + writer.loadArgumentDynamicSlot(ArgumentKind::NewTarget, argcId, flags); + ObjOperandId newTargetObjId = writer.guardToObject(newTargetValId); + writer.guardObjectIdentity(newTargetObjId, calleeObjId); + } + + ObjOperandId targetId = writer.loadBoundFunctionTarget(calleeObjId); + + emitCallScriptedGuards(targetId, target, argcId, flags, thisShape, + /* isBoundFunction = */ true); + + writer.callBoundScriptedFunction(calleeObjId, targetId, argcId, flags, + numBoundArgs); + writer.returnFromIC(); + + trackAttached("Call.BoundFunction"); + return AttachDecision::Attach; +} + +AttachDecision CallIRGenerator::tryAttachStub() { + AutoAssertNoPendingException aanpe(cx_); + + // Some opcodes are not yet supported. + switch (op_) { + case JSOp::Call: + case JSOp::CallContent: + case JSOp::CallIgnoresRv: + case JSOp::CallIter: + case JSOp::CallContentIter: + case JSOp::SpreadCall: + case JSOp::New: + case JSOp::NewContent: + case JSOp::SpreadNew: + case JSOp::SuperCall: + case JSOp::SpreadSuperCall: + 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()) { + TRY_ATTACH(tryAttachBoundFunction(calleeObj.as())); + } + if (!calleeObj->is()) { + return tryAttachCallHook(calleeObj); + } + + HandleFunction calleeFunc = calleeObj.as(); + + // Check for scripted optimizations. + if (calleeFunc->hasJitEntry()) { + return tryAttachCallScripted(calleeFunc); + } + + // Check for native-function optimizations. + MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry()); + + // Try inlining Function.prototype.{call,apply}. We don't use the + // InlinableNative mechanism for this because we want to optimize these more + // aggressively than other natives. + if (op_ == JSOp::Call || op_ == JSOp::CallContent || + op_ == JSOp::CallIgnoresRv) { + TRY_ATTACH(tryAttachFunCall(calleeFunc)); + TRY_ATTACH(tryAttachFunApply(calleeFunc)); + } + + return tryAttachCallNative(calleeFunc); +} + +void CallIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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; + +static JSObject* NewWrapperWithObjectShape(JSContext* cx, + Handle obj) { + MOZ_ASSERT(cx->compartment() != obj->compartment()); + + RootedObject wrapper(cx); + { + AutoRealm ar(cx, obj); + wrapper = NewBuiltinClassInstance(cx, &shapeContainerClass); + if (!wrapper) { + return nullptr; + } + wrapper->as().setReservedSlot( + SHAPE_CONTAINER_SLOT, PrivateGCThingValue(obj->shape())); + } + 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); +} + +static bool CanConvertToInt32ForToNumber(const Value& v) { + return v.isInt32() || v.isBoolean() || v.isNull(); +} + +static Int32OperandId EmitGuardToInt32ForToNumber(CacheIRWriter& writer, + ValOperandId id, + const Value& v) { + if (v.isInt32()) { + return writer.guardToInt32(id); + } + if (v.isNull()) { + writer.guardIsNull(id); + return writer.loadInt32Constant(0); + } + MOZ_ASSERT(v.isBoolean()); + return writer.guardBooleanToInt32(id); +} + +static bool CanConvertToDoubleForToNumber(const Value& v) { + return v.isNumber() || v.isBoolean() || v.isNullOrUndefined(); +} + +static NumberOperandId EmitGuardToDoubleForToNumber(CacheIRWriter& writer, + ValOperandId id, + const Value& v) { + if (v.isNumber()) { + return writer.guardIsNumber(id); + } + if (v.isBoolean()) { + BooleanOperandId boolId = writer.guardToBoolean(id); + return writer.booleanToNumber(boolId); + } + if (v.isNull()) { + writer.guardIsNull(id); + return writer.loadDoubleConstant(0.0); + } + MOZ_ASSERT(v.isUndefined()); + writer.guardIsUndefined(id); + return writer.loadDoubleConstant(JS::GenericNaN()); +} + +CompareIRGenerator::CompareIRGenerator(JSContext* cx, HandleScript script, + jsbytecode* pc, ICState state, JSOp op, + HandleValue lhsVal, HandleValue rhsVal) + : IRGenerator(cx, script, pc, CacheKind::Compare, state), + 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("Compare.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("Compare.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("Compare.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("Compare.StrictDifferentTypes"); + return AttachDecision::Attach; +} + +AttachDecision CompareIRGenerator::tryAttachInt32(ValOperandId lhsId, + ValOperandId rhsId) { + if (!CanConvertToInt32ForToNumber(lhsVal_) || + !CanConvertToInt32ForToNumber(rhsVal_)) { + return AttachDecision::NoAction; + } + + // Strictly different types should have been handed by + // tryAttachStrictDifferentTypes. + MOZ_ASSERT_IF(op_ == JSOp::StrictEq || op_ == JSOp::StrictNe, + lhsVal_.type() == rhsVal_.type()); + + // Should have been handled by tryAttachAnyNullUndefined. + MOZ_ASSERT_IF(lhsVal_.isNull() || rhsVal_.isNull(), !IsEqualityOp(op_)); + + Int32OperandId lhsIntId = EmitGuardToInt32ForToNumber(writer, lhsId, lhsVal_); + Int32OperandId rhsIntId = EmitGuardToInt32ForToNumber(writer, rhsId, rhsVal_); + + writer.compareInt32Result(op_, lhsIntId, rhsIntId); + writer.returnFromIC(); + + trackAttached("Compare.Int32"); + return AttachDecision::Attach; +} + +AttachDecision CompareIRGenerator::tryAttachNumber(ValOperandId lhsId, + ValOperandId rhsId) { + if (!CanConvertToDoubleForToNumber(lhsVal_) || + !CanConvertToDoubleForToNumber(rhsVal_)) { + return AttachDecision::NoAction; + } + + // Strictly different types should have been handed by + // tryAttachStrictDifferentTypes. + MOZ_ASSERT_IF(op_ == JSOp::StrictEq || op_ == JSOp::StrictNe, + lhsVal_.type() == rhsVal_.type() || + (lhsVal_.isNumber() && rhsVal_.isNumber())); + + // Should have been handled by tryAttachAnyNullUndefined. + MOZ_ASSERT_IF(lhsVal_.isNullOrUndefined() || rhsVal_.isNullOrUndefined(), + !IsEqualityOp(op_)); + + NumberOperandId lhs = EmitGuardToDoubleForToNumber(writer, lhsId, lhsVal_); + NumberOperandId rhs = EmitGuardToDoubleForToNumber(writer, rhsId, rhsVal_); + writer.compareDoubleResult(op_, lhs, rhs); + writer.returnFromIC(); + + trackAttached("Compare.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("Compare.BigInt"); + 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("Compare.AnyNull"); + } else { + writer.guardIsUndefined(rhsId); + writer.compareNullUndefinedResult(op_, /* isUndefined */ true, lhsId); + trackAttached("Compare.AnyUndefined"); + } + } else { + if (lhsVal_.isNull()) { + writer.guardIsNull(lhsId); + writer.compareNullUndefinedResult(op_, /* isUndefined */ false, rhsId); + trackAttached("Compare.NullAny"); + } else { + writer.guardIsUndefined(lhsId); + writer.compareNullUndefinedResult(op_, /* isUndefined */ true, rhsId); + trackAttached("Compare.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("Compare.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("Compare.StrictNullUndefinedEquality"); + } + + writer.returnFromIC(); + return AttachDecision::Attach; +} + +AttachDecision CompareIRGenerator::tryAttachStringNumber(ValOperandId lhsId, + ValOperandId rhsId) { + // Ensure String x {Number, Boolean, Null, Undefined} + if (!(lhsVal_.isString() && CanConvertToDoubleForToNumber(rhsVal_)) && + !(rhsVal_.isString() && CanConvertToDoubleForToNumber(lhsVal_))) { + return AttachDecision::NoAction; + } + + // Case should have been handled by tryAttachStrictDifferentTypes + MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe); + + auto createGuards = [&](const Value& v, ValOperandId vId) { + if (v.isString()) { + StringOperandId strId = writer.guardToString(vId); + return writer.guardStringToNumber(strId); + } + return EmitGuardToDoubleForToNumber(writer, vId, v); + }; + + NumberOperandId lhsGuardedId = createGuards(lhsVal_, lhsId); + NumberOperandId rhsGuardedId = createGuards(rhsVal_, rhsId); + writer.compareDoubleResult(op_, lhsGuardedId, rhsGuardedId); + writer.returnFromIC(); + + trackAttached("Compare.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 = [](const Value& 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 = [&](const Value& 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("Compare.PrimitiveSymbol"); + return AttachDecision::Attach; +} + +AttachDecision CompareIRGenerator::tryAttachBigIntInt32(ValOperandId lhsId, + ValOperandId rhsId) { + // Ensure BigInt x {Int32, Boolean, Null}. + if (!(lhsVal_.isBigInt() && CanConvertToInt32ForToNumber(rhsVal_)) && + !(rhsVal_.isBigInt() && CanConvertToInt32ForToNumber(lhsVal_))) { + 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); + Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, rhsId, rhsVal_); + + writer.compareBigIntInt32Result(op_, bigIntId, intId); + } else { + Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, lhsId, lhsVal_); + BigIntOperandId bigIntId = writer.guardToBigInt(rhsId); + + writer.compareBigIntInt32Result(ReverseCompareOp(op_), bigIntId, intId); + } + writer.returnFromIC(); + + trackAttached("Compare.BigIntInt32"); + return AttachDecision::Attach; +} + +AttachDecision CompareIRGenerator::tryAttachBigIntNumber(ValOperandId lhsId, + ValOperandId rhsId) { + // Ensure BigInt x {Number, Undefined}. + if (!(lhsVal_.isBigInt() && CanConvertToDoubleForToNumber(rhsVal_)) && + !(rhsVal_.isBigInt() && CanConvertToDoubleForToNumber(lhsVal_))) { + return AttachDecision::NoAction; + } + + // Case should have been handled by tryAttachStrictDifferentTypes + MOZ_ASSERT(op_ != JSOp::StrictEq && op_ != JSOp::StrictNe); + + // Case should have been handled by tryAttachBigIntInt32. + MOZ_ASSERT(!CanConvertToInt32ForToNumber(lhsVal_)); + MOZ_ASSERT(!CanConvertToInt32ForToNumber(rhsVal_)); + + if (lhsVal_.isBigInt()) { + BigIntOperandId bigIntId = writer.guardToBigInt(lhsId); + NumberOperandId numId = + EmitGuardToDoubleForToNumber(writer, rhsId, rhsVal_); + + writer.compareBigIntNumberResult(op_, bigIntId, numId); + } else { + NumberOperandId numId = + EmitGuardToDoubleForToNumber(writer, lhsId, lhsVal_); + BigIntOperandId bigIntId = writer.guardToBigInt(rhsId); + + writer.compareBigIntNumberResult(ReverseCompareOp(op_), bigIntId, numId); + } + writer.returnFromIC(); + + trackAttached("Compare.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("Compare.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; + + 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}. + // + // For relational comparison ops, these cases aren't handled: + // - Object x {String, Bool, Number, BigInt, Object, Null, Undefined}. + // Note: |Symbol x any| always throws, so it doesn't need to be handled. + // + // (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)); + } + + // 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(tryAttachBigIntInt32(lhsId, rhsId)); + TRY_ATTACH(tryAttachBigIntNumber(lhsId, rhsId)); + TRY_ATTACH(tryAttachBigIntString(lhsId, rhsId)); + + // Strict equality is always supported. + MOZ_ASSERT(!IsStrictEqualityOp(op_)); + + // Other operations are unsupported iff at least one operand is an object. + MOZ_ASSERT(lhsVal_.isObject() || rhsVal_.isObject()); + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; +} + +void CompareIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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 state, + HandleValue val) + : IRGenerator(cx, script, pc, CacheKind::ToBool, state), val_(val) {} + +void ToBoolIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#ifdef JS_CACHEIR_SPEW + if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { + sp.valueProperty("val", val_); + } +#endif +} + +AttachDecision ToBoolIRGenerator::tryAttachStub() { + AutoAssertNoPendingException aanpe(cx_); + writer.setTypeData(TypeData(JSValueType(val_.type()))); + + TRY_ATTACH(tryAttachBool()); + 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::tryAttachBool() { + if (!val_.isBoolean()) { + return AttachDecision::NoAction; + } + + ValOperandId valId(writer.setInputOperandId(0)); + writer.guardNonDoubleType(valId, ValueType::Boolean); + writer.loadOperandResult(valId); + writer.returnFromIC(); + trackAttached("ToBool.Bool"); + return AttachDecision::Attach; +} + +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("ToBool.Int32"); + 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("ToBool.Number"); + 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("ToBool.Symbol"); + 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("ToBool.String"); + 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("ToBool.NullOrUndefined"); + 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("ToBool.Object"); + 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("ToBool.BigInt"); + return AttachDecision::Attach; +} + +GetIntrinsicIRGenerator::GetIntrinsicIRGenerator(JSContext* cx, + HandleScript script, + jsbytecode* pc, ICState state, + HandleValue val) + : IRGenerator(cx, script, pc, CacheKind::GetIntrinsic, state), val_(val) {} + +void GetIntrinsicIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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 state, + JSOp op, HandleValue val, + HandleValue res) + : IRGenerator(cx, script, pc, CacheKind::UnaryArith, state), + op_(op), + val_(val), + res_(res) {} + +void UnaryArithIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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 (!CanConvertToInt32ForToNumber(val_) || !res_.isInt32()) { + return AttachDecision::NoAction; + } + + ValOperandId valId(writer.setInputOperandId(0)); + + Int32OperandId intId = EmitGuardToInt32ForToNumber(writer, valId, val_); + 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 (!CanConvertToDoubleForToNumber(val_)) { + return AttachDecision::NoAction; + } + MOZ_ASSERT(res_.isNumber()); + + ValOperandId valId(writer.setInputOperandId(0)); + NumberOperandId numId = EmitGuardToDoubleForToNumber(writer, valId, val_); + + 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, + const Value& 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("UnaryArith.BitwiseBitNot"); + + 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 state, + HandleValue val) + : IRGenerator(cx, script, pc, CacheKind::ToPropertyKey, state), val_(val) {} + +void ToPropertyKeyIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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 state, + JSOp op, HandleValue lhs, + HandleValue rhs, HandleValue res) + : IRGenerator(cx, script, pc, CacheKind::BinaryArith, state), + op_(op), + lhs_(lhs), + rhs_(rhs), + res_(res) {} + +void BinaryArithIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#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,Number,Boolean,Null,Undefined} + TRY_ATTACH(tryAttachStringConcat()); + + // String x Object + TRY_ATTACH(tryAttachStringObjectConcat()); + + // Arithmetic operations or bitwise operations with BigInt operands + TRY_ATTACH(tryAttachBigInt()); + + // Arithmetic operations (without addition) with String x Int32. + TRY_ATTACH(tryAttachStringInt32Arith()); + + // Arithmetic operations (without addition) with String x Number. This needs + // to come after tryAttachStringInt32Arith, as the guards overlap, and we'd + // prefer to attach the more specialized Int32 IC if it is possible. + TRY_ATTACH(tryAttachStringNumberArith()); + + 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.BitwiseBitOr"); + break; + case JSOp::BitXor: + writer.int32BitXorResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.BitwiseBitXor"); + break; + case JSOp::BitAnd: + writer.int32BitAndResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.BitwiseBitAnd"); + break; + case JSOp::Lsh: + writer.int32LeftShiftResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.BitwiseLeftShift"); + break; + case JSOp::Rsh: + writer.int32RightShiftResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.BitwiseRightShift"); + break; + case JSOp::Ursh: + writer.int32URightShiftResult(lhsIntId, rhsIntId, res_.isDouble()); + trackAttached("BinaryArith.BitwiseUnsignedRightShift"); + 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 (!CanConvertToDoubleForToNumber(lhs_) || + !CanConvertToDoubleForToNumber(rhs_)) { + return AttachDecision::NoAction; + } + + ValOperandId lhsId(writer.setInputOperandId(0)); + ValOperandId rhsId(writer.setInputOperandId(1)); + + NumberOperandId lhs = EmitGuardToDoubleForToNumber(writer, lhsId, lhs_); + NumberOperandId rhs = EmitGuardToDoubleForToNumber(writer, rhsId, rhs_); + + switch (op_) { + case JSOp::Add: + writer.doubleAddResult(lhs, rhs); + trackAttached("BinaryArith.DoubleAdd"); + break; + case JSOp::Sub: + writer.doubleSubResult(lhs, rhs); + trackAttached("BinaryArith.DoubleSub"); + break; + case JSOp::Mul: + writer.doubleMulResult(lhs, rhs); + trackAttached("BinaryArith.DoubleMul"); + break; + case JSOp::Div: + writer.doubleDivResult(lhs, rhs); + trackAttached("BinaryArith.DoubleDiv"); + break; + case JSOp::Mod: + writer.doubleModResult(lhs, rhs); + trackAttached("BinaryArith.DoubleMod"); + break; + case JSOp::Pow: + writer.doublePowResult(lhs, rhs); + trackAttached("BinaryArith.DoublePow"); + break; + default: + MOZ_CRASH("Unhandled Op"); + } + writer.returnFromIC(); + return AttachDecision::Attach; +} + +AttachDecision BinaryArithIRGenerator::tryAttachInt32() { + // Check guard conditions. + if (!CanConvertToInt32ForToNumber(lhs_) || + !CanConvertToInt32ForToNumber(rhs_)) { + 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)); + + Int32OperandId lhsIntId = EmitGuardToInt32ForToNumber(writer, lhsId, lhs_); + Int32OperandId rhsIntId = EmitGuardToInt32ForToNumber(writer, rhsId, rhs_); + + switch (op_) { + case JSOp::Add: + writer.int32AddResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.Int32Add"); + break; + case JSOp::Sub: + writer.int32SubResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.Int32Sub"); + break; + case JSOp::Mul: + writer.int32MulResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.Int32Mul"); + break; + case JSOp::Div: + writer.int32DivResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.Int32Div"); + break; + case JSOp::Mod: + writer.int32ModResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.Int32Mod"); + break; + case JSOp::Pow: + writer.int32PowResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.Int32Pow"); + break; + default: + MOZ_CRASH("Unhandled op in tryAttachInt32"); + } + + writer.returnFromIC(); + return AttachDecision::Attach; +} + +AttachDecision BinaryArithIRGenerator::tryAttachStringConcat() { + // Only Addition + if (op_ != JSOp::Add) { + return AttachDecision::NoAction; + } + + // One side must be a string, the other side a primitive value we can easily + // convert to a string. + if (!(lhs_.isString() && CanConvertToString(rhs_)) && + !(CanConvertToString(lhs_) && rhs_.isString())) { + return AttachDecision::NoAction; + } + + ValOperandId lhsId(writer.setInputOperandId(0)); + ValOperandId rhsId(writer.setInputOperandId(1)); + + StringOperandId lhsStrId = emitToStringGuard(lhsId, lhs_); + StringOperandId rhsStrId = emitToStringGuard(rhsId, rhs_); + + 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.BigIntAdd"); + break; + case JSOp::Sub: + writer.bigIntSubResult(lhsBigIntId, rhsBigIntId); + trackAttached("BinaryArith.BigIntSub"); + break; + case JSOp::Mul: + writer.bigIntMulResult(lhsBigIntId, rhsBigIntId); + trackAttached("BinaryArith.BigIntMul"); + break; + case JSOp::Div: + writer.bigIntDivResult(lhsBigIntId, rhsBigIntId); + trackAttached("BinaryArith.BigIntDiv"); + break; + case JSOp::Mod: + writer.bigIntModResult(lhsBigIntId, rhsBigIntId); + trackAttached("BinaryArith.BigIntMod"); + break; + case JSOp::Pow: + writer.bigIntPowResult(lhsBigIntId, rhsBigIntId); + trackAttached("BinaryArith.BigIntPow"); + break; + case JSOp::BitOr: + writer.bigIntBitOrResult(lhsBigIntId, rhsBigIntId); + trackAttached("BinaryArith.BigIntBitOr"); + break; + case JSOp::BitXor: + writer.bigIntBitXorResult(lhsBigIntId, rhsBigIntId); + trackAttached("BinaryArith.BigIntBitXor"); + break; + case JSOp::BitAnd: + writer.bigIntBitAndResult(lhsBigIntId, rhsBigIntId); + trackAttached("BinaryArith.BigIntBitAnd"); + break; + case JSOp::Lsh: + writer.bigIntLeftShiftResult(lhsBigIntId, rhsBigIntId); + trackAttached("BinaryArith.BigIntLeftShift"); + break; + case JSOp::Rsh: + writer.bigIntRightShiftResult(lhsBigIntId, rhsBigIntId); + trackAttached("BinaryArith.BigIntRightShift"); + 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; + } + + // The string operand must be convertable to an int32 value. + JSString* str = lhs_.isString() ? lhs_.toString() : rhs_.toString(); + + double num; + if (!StringToNumber(cx_, str, &num)) { + cx_->recoverFromOutOfMemory(); + return AttachDecision::NoAction; + } + + int32_t unused; + if (!mozilla::NumberIsInt32(num, &unused)) { + return AttachDecision::NoAction; + } + + ValOperandId lhsId(writer.setInputOperandId(0)); + ValOperandId rhsId(writer.setInputOperandId(1)); + + auto guardToInt32 = [&](ValOperandId id, const Value& 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.StringInt32Sub"); + break; + case JSOp::Mul: + writer.int32MulResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.StringInt32Mul"); + break; + case JSOp::Div: + writer.int32DivResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.StringInt32Div"); + break; + case JSOp::Mod: + writer.int32ModResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.StringInt32Mod"); + break; + default: + MOZ_CRASH("Unhandled op in tryAttachStringInt32Arith"); + } + + writer.returnFromIC(); + return AttachDecision::Attach; +} + +AttachDecision BinaryArithIRGenerator::tryAttachStringNumberArith() { + // Check for either number x string or string x number. + if (!(lhs_.isNumber() && rhs_.isString()) && + !(lhs_.isString() && rhs_.isNumber())) { + return AttachDecision::NoAction; + } + + // Must _not_ support Add, because it would be string concatenation instead. + if (op_ != JSOp::Sub && op_ != JSOp::Mul && op_ != JSOp::Div && + op_ != JSOp::Mod && op_ != JSOp::Pow) { + return AttachDecision::NoAction; + } + + ValOperandId lhsId(writer.setInputOperandId(0)); + ValOperandId rhsId(writer.setInputOperandId(1)); + + auto guardToNumber = [&](ValOperandId id, const Value& v) { + if (v.isNumber()) { + return writer.guardIsNumber(id); + } + + MOZ_ASSERT(v.isString()); + StringOperandId strId = writer.guardToString(id); + return writer.guardStringToNumber(strId); + }; + + NumberOperandId lhsIntId = guardToNumber(lhsId, lhs_); + NumberOperandId rhsIntId = guardToNumber(rhsId, rhs_); + + switch (op_) { + case JSOp::Sub: + writer.doubleSubResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.StringNumberSub"); + break; + case JSOp::Mul: + writer.doubleMulResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.StringNumberMul"); + break; + case JSOp::Div: + writer.doubleDivResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.StringNumberDiv"); + break; + case JSOp::Mod: + writer.doubleModResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.StringNumberMod"); + break; + case JSOp::Pow: + writer.doublePowResult(lhsIntId, rhsIntId); + trackAttached("BinaryArith.StringNumberPow"); + break; + default: + MOZ_CRASH("Unhandled op in tryAttachStringNumberArith"); + } + + writer.returnFromIC(); + return AttachDecision::Attach; +} + +NewArrayIRGenerator::NewArrayIRGenerator(JSContext* cx, HandleScript script, + jsbytecode* pc, ICState state, JSOp op, + HandleObject templateObj, + BaselineFrame* frame) + : IRGenerator(cx, script, pc, CacheKind::NewArray, state), +#ifdef JS_CACHEIR_SPEW + op_(op), +#endif + templateObject_(templateObj), + frame_(frame) { + MOZ_ASSERT(templateObject_); +} + +void NewArrayIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#ifdef JS_CACHEIR_SPEW + if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { + sp.opcodeProperty("op", op_); + } +#endif +} + +// Allocation sites are usually created during baseline compilation, but we also +// need to create them when an IC stub is added to a baseline compiled script +// and when trial inlining. +static gc::AllocSite* MaybeCreateAllocSite(jsbytecode* pc, + BaselineFrame* frame) { + MOZ_ASSERT(BytecodeOpCanHaveAllocSite(JSOp(*pc))); + + JSScript* outerScript = frame->outerScript(); + bool hasBaselineScript = outerScript->hasBaselineScript(); + bool isInlined = frame->icScript()->isInlined(); + + if (!hasBaselineScript && !isInlined) { + MOZ_ASSERT(frame->runningInInterpreter()); + return outerScript->zone()->unknownAllocSite(JS::TraceKind::Object); + } + + uint32_t pcOffset = frame->script()->pcToOffset(pc); + return frame->icScript()->getOrCreateAllocSite(outerScript, pcOffset); +} + +AttachDecision NewArrayIRGenerator::tryAttachArrayObject() { + ArrayObject* arrayObj = &templateObject_->as(); + + MOZ_ASSERT(arrayObj->numUsedFixedSlots() == 0); + MOZ_ASSERT(arrayObj->numDynamicSlots() == 0); + MOZ_ASSERT(!arrayObj->isSharedMemory()); + + // The macro assembler only supports creating arrays with fixed elements. + if (arrayObj->hasDynamicElements()) { + return AttachDecision::NoAction; + } + + // Stub doesn't support metadata builder + if (cx_->realm()->hasAllocationMetadataBuilder()) { + return AttachDecision::NoAction; + } + + writer.guardNoAllocationMetadataBuilder( + cx_->realm()->addressOfMetadataBuilder()); + + gc::AllocSite* site = MaybeCreateAllocSite(pc_, frame_); + if (!site) { + return AttachDecision::NoAction; + } + + Shape* shape = arrayObj->shape(); + uint32_t length = arrayObj->length(); + + writer.newArrayObjectResult(length, shape, site); + + writer.returnFromIC(); + + trackAttached("NewArray.Object"); + return AttachDecision::Attach; +} + +AttachDecision NewArrayIRGenerator::tryAttachStub() { + AutoAssertNoPendingException aanpe(cx_); + + TRY_ATTACH(tryAttachArrayObject()); + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; +} + +NewObjectIRGenerator::NewObjectIRGenerator(JSContext* cx, HandleScript script, + jsbytecode* pc, ICState state, + JSOp op, HandleObject templateObj, + BaselineFrame* frame) + : IRGenerator(cx, script, pc, CacheKind::NewObject, state), +#ifdef JS_CACHEIR_SPEW + op_(op), +#endif + templateObject_(templateObj), + frame_(frame) { + MOZ_ASSERT(templateObject_); +} + +void NewObjectIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; +#ifdef JS_CACHEIR_SPEW + if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { + sp.opcodeProperty("op", op_); + } +#endif +} + +AttachDecision NewObjectIRGenerator::tryAttachPlainObject() { + // Don't optimize allocations with too many dynamic slots. We use an unrolled + // loop when initializing slots and this avoids generating too much code. + static const uint32_t MaxDynamicSlotsToOptimize = 64; + + NativeObject* nativeObj = &templateObject_->as(); + MOZ_ASSERT(nativeObj->is()); + + // Stub doesn't support metadata builder + if (cx_->realm()->hasAllocationMetadataBuilder()) { + return AttachDecision::NoAction; + } + + if (nativeObj->numDynamicSlots() > MaxDynamicSlotsToOptimize) { + return AttachDecision::NoAction; + } + + MOZ_ASSERT(!nativeObj->hasDynamicElements()); + MOZ_ASSERT(!nativeObj->isSharedMemory()); + + gc::AllocSite* site = MaybeCreateAllocSite(pc_, frame_); + if (!site) { + return AttachDecision::NoAction; + } + + uint32_t numFixedSlots = nativeObj->numUsedFixedSlots(); + uint32_t numDynamicSlots = nativeObj->numDynamicSlots(); + gc::AllocKind allocKind = nativeObj->allocKindForTenure(); + Shape* shape = nativeObj->shape(); + + writer.guardNoAllocationMetadataBuilder( + cx_->realm()->addressOfMetadataBuilder()); + writer.newPlainObjectResult(numFixedSlots, numDynamicSlots, allocKind, shape, + site); + + writer.returnFromIC(); + + trackAttached("NewObject.PlainObject"); + return AttachDecision::Attach; +} + +AttachDecision NewObjectIRGenerator::tryAttachStub() { + AutoAssertNoPendingException aanpe(cx_); + + TRY_ATTACH(tryAttachPlainObject()); + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; +} + +CloseIterIRGenerator::CloseIterIRGenerator(JSContext* cx, HandleScript script, + jsbytecode* pc, ICState state, + HandleObject iter, + CompletionKind kind) + : IRGenerator(cx, script, pc, CacheKind::CloseIter, state), + iter_(iter), + kind_(kind) {} + +void CloseIterIRGenerator::trackAttached(const char* name) { +#ifdef JS_CACHEIR_SPEW + if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { + sp.valueProperty("iter", ObjectValue(*iter_)); + } +#endif +} + +AttachDecision CloseIterIRGenerator::tryAttachNoReturnMethod() { + Maybe prop; + NativeObject* holder = nullptr; + + // If we can guard that the iterator does not have a |return| method, + // then this CloseIter is a no-op. + NativeGetPropKind kind = CanAttachNativeGetProp( + cx_, iter_, NameToId(cx_->names().return_), &holder, &prop, pc_); + if (kind != NativeGetPropKind::Missing) { + return AttachDecision::NoAction; + } + MOZ_ASSERT(!holder); + + ObjOperandId objId(writer.setInputOperandId(0)); + + EmitMissingPropGuard(writer, &iter_->as(), objId); + + // There is no return method, so we don't have to do anything. + writer.returnFromIC(); + + trackAttached("CloseIter.NoReturn"); + return AttachDecision::Attach; +} + +AttachDecision CloseIterIRGenerator::tryAttachScriptedReturn() { + Maybe prop; + NativeObject* holder = nullptr; + + NativeGetPropKind kind = CanAttachNativeGetProp( + cx_, iter_, NameToId(cx_->names().return_), &holder, &prop, pc_); + if (kind != NativeGetPropKind::Slot) { + return AttachDecision::NoAction; + } + MOZ_ASSERT(holder); + MOZ_ASSERT(prop->isDataProperty()); + + size_t slot = prop->slot(); + Value calleeVal = holder->getSlot(slot); + if (!calleeVal.isObject() || !calleeVal.toObject().is()) { + return AttachDecision::NoAction; + } + + JSFunction* callee = &calleeVal.toObject().as(); + if (!callee->hasJitEntry()) { + return AttachDecision::NoAction; + } + if (callee->isClassConstructor()) { + return AttachDecision::NoAction; + } + + // We don't support cross-realm |return|. + if (cx_->realm() != callee->realm()) { + return AttachDecision::NoAction; + } + + ObjOperandId objId(writer.setInputOperandId(0)); + + ObjOperandId holderId = + EmitReadSlotGuard(writer, &iter_->as(), holder, objId); + + ValOperandId calleeValId = EmitLoadSlot(writer, holder, holderId, slot); + ObjOperandId calleeId = writer.guardToObject(calleeValId); + emitCalleeGuard(calleeId, callee); + + writer.closeIterScriptedResult(objId, calleeId, kind_, callee->nargs()); + + writer.returnFromIC(); + trackAttached("CloseIter.ScriptedReturn"); + + return AttachDecision::Attach; +} + +AttachDecision CloseIterIRGenerator::tryAttachStub() { + AutoAssertNoPendingException aanpe(cx_); + + TRY_ATTACH(tryAttachNoReturnMethod()); + TRY_ATTACH(tryAttachScriptedReturn()); + + trackAttached(IRGenerator::NotAttached); + return AttachDecision::NoAction; +} + +OptimizeGetIteratorIRGenerator::OptimizeGetIteratorIRGenerator( + JSContext* cx, HandleScript script, jsbytecode* pc, ICState state, + HandleValue value) + : IRGenerator(cx, script, pc, CacheKind::OptimizeGetIterator, state), + val_(value) {} + +AttachDecision OptimizeGetIteratorIRGenerator::tryAttachStub() { + MOZ_ASSERT(cacheKind_ == CacheKind::OptimizeGetIterator); + + AutoAssertNoPendingException aanpe(cx_); + + TRY_ATTACH(tryAttachArray()); + TRY_ATTACH(tryAttachNotOptimizable()); + + MOZ_CRASH("Failed to attach unoptimizable case."); +} + +AttachDecision OptimizeGetIteratorIRGenerator::tryAttachArray() { + if (!isFirstStub_) { + return AttachDecision::NoAction; + } + + // The value must be a packed array. + if (!val_.isObject()) { + return AttachDecision::NoAction; + } + Rooted obj(cx_, &val_.toObject()); + if (!IsPackedArray(obj)) { + return AttachDecision::NoAction; + } + + // Prototype must be Array.prototype and Array.prototype[@@iterator] must not + // be modified. + Rooted arrProto(cx_); + uint32_t arrProtoIterSlot; + Rooted iterFun(cx_); + if (!IsArrayInstanceOptimizable(cx_, obj.as(), &arrProto)) { + return AttachDecision::NoAction; + } + + if (!IsArrayPrototypeOptimizable(cx_, obj.as(), arrProto, + &arrProtoIterSlot, &iterFun)) { + // Fuse should be popped. + MOZ_ASSERT( + !obj->nonCCWRealm()->realmFuses.optimizeGetIteratorFuse.intact()); + return AttachDecision::NoAction; + } + + // %ArrayIteratorPrototype%.next must not be modified and + // %ArrayIteratorPrototype%.return must not be present. + Rooted arrayIteratorProto(cx_); + uint32_t slot; + Rooted nextFun(cx_); + if (!IsArrayIteratorPrototypeOptimizable( + cx_, AllowIteratorReturn::No, &arrayIteratorProto, &slot, &nextFun)) { + // Fuse should be popped. + MOZ_ASSERT( + !obj->nonCCWRealm()->realmFuses.optimizeGetIteratorFuse.intact()); + 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. + MOZ_ASSERT(obj->is()); + writer.guardShape(objId, obj->shape()); + writer.guardArrayIsPacked(objId); + bool intact = obj->nonCCWRealm()->realmFuses.optimizeGetIteratorFuse.intact(); + + // If the fuse isn't intact but we've still passed all these dynamic checks + // then we can attach a version of the IC that dynamically checks to ensure + // the required invariants still hold. + // + // As an example of how this could be the case, consider an assignment + // + // Array.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator] + // + // This assignment pops the fuse, however we can still use the dynamic check + // version of this IC, as the actual -value- is still correct. + bool useDynamicCheck = !intact || !JS::Prefs::destructuring_fuse(); + if (useDynamicCheck) { + // Guard on Array.prototype[@@iterator]. + ObjOperandId arrProtoId = writer.loadObject(arrProto); + ObjOperandId iterId = writer.loadObject(iterFun); + writer.guardShape(arrProtoId, arrProto->shape()); + writer.guardDynamicSlotIsSpecificObject(arrProtoId, iterId, + arrProtoIterSlot); + + // Guard on %ArrayIteratorPrototype%.next. + ObjOperandId iterProtoId = writer.loadObject(arrayIteratorProto); + ObjOperandId nextId = writer.loadObject(nextFun); + writer.guardShape(iterProtoId, arrayIteratorProto->shape()); + writer.guardDynamicSlotIsSpecificObject(iterProtoId, nextId, slot); + + // Guard on the prototype chain to ensure no "return" method is present. + ShapeGuardProtoChain(writer, arrayIteratorProto, iterProtoId); + } else { + // Guard on Array.prototype[@@iterator] and %ArrayIteratorPrototype%.next. + // This fuse also ensures the prototype chain for Array Iterator is + // maintained and that no return method is added. + writer.guardFuse(RealmFuses::FuseIndex::OptimizeGetIteratorFuse); + } + + writer.loadBooleanResult(true); + writer.returnFromIC(); + + if (useDynamicCheck) { + trackAttached("OptimizeGetIterator.Array.Dynamic"); + } else { + trackAttached("OptimizeGetIterator.Array.Fuse"); + } + return AttachDecision::Attach; +} + +AttachDecision OptimizeGetIteratorIRGenerator::tryAttachNotOptimizable() { + ValOperandId valId(writer.setInputOperandId(0)); + + writer.loadBooleanResult(false); + writer.returnFromIC(); + + trackAttached("OptimizeGetIterator.NotOptimizable"); + return AttachDecision::Attach; +} + +void OptimizeGetIteratorIRGenerator::trackAttached(const char* name) { + stubName_ = name ? name : "NotAttached"; + +#ifdef JS_CACHEIR_SPEW + if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { + sp.valueProperty("val", val_); + } +#endif +} + +#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()); + auto* calleeFunc = &calleeObj->as(); + MOZ_ASSERT(calleeFunc->isNativeWithoutJitEntry()); + + JSNative native = calleeFunc->native(); + return native(cx, args.length(), args.base()); +} + +const void* js::jit::RedirectedCallAnyNative() { + // The simulator requires native calls to be redirected to a + // special swi instruction. If we are calling an arbitrary native + // function, we can't wrap the real target ahead of time, so we + // call a wrapper function (CallAnyNative) that calls the target + // itself, and redirect that wrapper. + JSNative target = CallAnyNative; + void* rawPtr = JS_FUNC_TO_DATA_PTR(void*, target); + void* redirected = Simulator::RedirectNativeFunction(rawPtr, Args_General3); + return redirected; +} +#endif -- cgit v1.2.3