/* -*- 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 "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/JitRealm.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/RegExpFlags.h" // JS::RegExpFlags #include "js/ScalarType.h" // js::Scalar::Type #include "js/Wrapper.h" #include "proxy/DOMProxy.h" // js::GetDOMProxyHandlerFamily #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/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: 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) { cx_->debugOnlyCheck(obj); } 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)); } GetterSetter* CacheIRCloner::getGetterSetterField(uint32_t stubOffset) { return reinterpret_cast(readStubWord(stubOffset)); } JSObject* CacheIRCloner::getObjectField(uint32_t stubOffset) { 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::getBaseScriptField(uint32_t stubOffset) { 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()) {} 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; } 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); } 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; } 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; } // Callers are expected to have already guarded on the shape of the // object, which guarantees the object is a DOM proxy. static void CheckDOMProxyExpandoDoesNotShadow(CacheIRWriter& writer, ProxyObject* obj, jsid id, ObjOperandId objId) { MOZ_ASSERT(IsCacheableDOMProxy(obj)); Value expandoVal = GetProxyPrivate(obj); ValOperandId expandoId; if (!expandoVal.isObject() && !expandoVal.isUndefined()) { auto expandoAndGeneration = static_cast(expandoVal.toPrivate()); uint64_t generation = expandoAndGeneration->generation; expandoId = writer.loadDOMExpandoValueGuardGeneration( objId, expandoAndGeneration, generation); expandoVal = expandoAndGeneration->expando; } else { expandoId = writer.loadDOMExpandoValue(objId); } if (expandoVal.isUndefined()) { // Guard there's no expando object. writer.guardNonDoubleType(expandoId, ValueType::Undefined); } else if (expandoVal.isObject()) { // Guard the proxy either has no expando object or, if it has one, that // the shape matches the current expando object. NativeObject& expandoObj = expandoVal.toObject().as(); 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* checkObj = obj->staticPrototype(); if (!checkObj) { return AttachDecision::NoAction; } NativeObject* holder = nullptr; Maybe prop; NativeGetPropKind kind = CanAttachNativeGetProp(cx_, checkObj, id, &holder, &prop, pc_); if (kind == NativeGetPropKind::None) { return AttachDecision::NoAction; } auto* nativeCheckObj = &checkObj->as(); maybeEmitIdGuard(id); // Guard that our expando object hasn't started shadowing this property. TestMatchingProxyReceiver(writer, obj, objId); CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId); if (holder) { // Found the property on the prototype chain. Treat it like a native // getprop. GeneratePrototypeGuards(writer, obj, holder, objId); // Guard on the holder of the property. ObjOperandId holderId = writer.loadObject(holder); TestMatchingHolder(writer, holder, holderId); if (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, nativeCheckObj, holder, *prop, receiverId); } } else { // Property was not found on the prototype chain. Deoptimize down to // proxy get call. MOZ_ASSERT(kind == NativeGetPropKind::Missing); 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) { ProxyStubType type = GetProxyStubType(cx_, obj, id); if (type == ProxyStubType::None) { return AttachDecision::NoAction; } auto proxy = obj.as(); // The proxy stubs don't currently support |super| access. if (isSuper()) { 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::ArrayBuffer: MOZ_ASSERT(obj->is()); break; case GuardClassKind::SharedArrayBuffer: MOZ_ASSERT(obj->is()); break; case GuardClassKind::DataView: 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) { 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) { 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(); 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 = cx_->global()->maybeGetPrototype(protoKey); if (!proto) { 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) { if (!val.isString() || !idVal.isInt32()) { return AttachStringChar::No; } int32_t index = idVal.toInt32(); if (index < 0) { return AttachStringChar::OutOfBounds; } JSString* str = val.toString(); if (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(); } 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_); 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(TypedArrayObject* 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) { if (!obj->is()) { return AttachDecision::NoAction; } if (!idVal_.isNumber()) { return AttachDecision::NoAction; } TypedArrayObject* 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; } 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; } static ValOperandId EmitLoadEnvironmentSlot(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); } 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 = EmitLoadEnvironmentSlot(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 = EmitLoadEnvironmentSlot(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); NativeObject* holder = nullptr; PropertyResult prop; if (hasOwn) { if (!LookupOwnPropertyPure(cx_, obj, key, &prop)) { return AttachDecision::NoAction; } holder = &obj->as(); } else { if (!LookupPropertyPure(cx_, obj, key, &holder, &prop)) { return AttachDecision::NoAction; } } if (prop.isNotFound()) { return AttachDecision::NoAction; } auto* nobj = &obj->as(); TRY_ATTACH(tryAttachMegamorphic(objId, keyId)); TRY_ATTACH(tryAttachNative(nobj, objId, key, keyId, prop, holder)); return AttachDecision::NoAction; } AttachDecision HasPropIRGenerator::tryAttachMegamorphic(ObjOperandId objId, ValOperandId keyId) { bool hasOwn = (cacheKind_ == CacheKind::HasOwn); if (mode_ != ICState::Mode::Megamorphic) { return AttachDecision::NoAction; } writer.megamorphicHasPropResult(objId, keyId, hasOwn); writer.returnFromIC(); trackAttached("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) { if (!obj->is()) { return AttachDecision::NoAction; } int64_t index; if (!ValueIsInt64Index(idVal_, &index)) { return AttachDecision::NoAction; } writer.guardIsTypedArray(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; } } auto* nobj = &obj->as(); TRY_ATTACH(tryAttachMegamorphic(objId, keyId)); TRY_ATTACH(tryAttachSlotDoesNotExist(nobj, 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; } *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; } OperandId IRGenerator::emitNumericGuard(ValOperandId valId, Scalar::Type type) { switch (type) { case Scalar::Int8: case Scalar::Uint8: case Scalar::Int16: case Scalar::Uint16: case Scalar::Int32: case Scalar::Uint32: return writer.guardToInt32ModUint32(valId); case Scalar::Float32: case Scalar::Float64: return writer.guardIsNumber(valId); case Scalar::Uint8Clamped: return writer.guardToUint8Clamped(valId); case Scalar::BigInt64: case Scalar::BigUint64: return writer.guardToBigInt(valId); case Scalar::MaxTypedArrayViewType: case Scalar::Int64: case Scalar::Simd128: break; } MOZ_CRASH("Unsupported TypedArray type"); } static bool ValueIsNumeric(Scalar::Type type, const Value& val) { if (Scalar::isBigIntType(type)) { return val.isBigInt(); } return val.isNumber(); } void SetPropIRGenerator::trackAttached(const char* name) { 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) { if (!obj->is()) { return AttachDecision::NoAction; } if (!idVal_.isNumber()) { return AttachDecision::NoAction; } TypedArrayObject* tarr = &obj->as(); Scalar::Type elementType = tarr->type(); // Don't attach if the input type doesn't match the guard added below. if (!ValueIsNumeric(elementType, rhsVal_)) { return AttachDecision::NoAction; } 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, 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 expando object hasn't started shadowing this property. TestMatchingProxyReceiver(writer, obj, objId); CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId); GeneratePrototypeGuards(writer, obj, holder, objId); // Guard on the holder of the property. ObjOperandId holderId = writer.loadObject(holder); TestMatchingHolder(writer, holder, holderId); 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 = cx_->global()->maybeEmptyIterator(); if (!emptyIter) { 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 IsArrayPrototypeOptimizable(JSContext* cx, ArrayObject* arr, NativeObject** arrProto, uint32_t* slot, JSFunction** iterFun) { // Prototype must be Array.prototype. auto* proto = cx->global()->maybeGetArrayPrototype(); if (!proto || arr->staticPrototype() != proto) { return false; } *arrProto = proto; // The object must not have an own @@iterator property. PropertyKey iteratorKey = PropertyKey::Symbol(cx->wellKnownSymbols().iterator); if (arr->lookupPure(iteratorKey)) { return false; } // Ensure that Array.prototype's @@iterator slot is unchanged. Maybe prop = proto->lookupPure(iteratorKey); if (prop.isNothing() || !prop->isDataProperty()) { return false; } *slot = prop->slot(); MOZ_ASSERT(proto->numFixedSlots() == 0, "Stub code relies on this"); const Value& iterVal = proto->getSlot(*slot); if (!iterVal.isObject() || !iterVal.toObject().is()) { return false; } *iterFun = &iterVal.toObject().as(); return IsSelfHostedFunctionWithName(*iterFun, cx->names().ArrayValues); } static bool IsArrayIteratorPrototypeOptimizable(JSContext* cx, NativeObject** arrIterProto, uint32_t* slot, JSFunction** nextFun) { auto* proto = cx->global()->maybeGetArrayIteratorPrototype(); if (!proto) { return false; } *arrIterProto = proto; // Ensure that %ArrayIteratorPrototype%'s "next" slot is unchanged. 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 = &nextVal.toObject().as(); return IsSelfHostedFunctionWithName(*nextFun, cx->names().ArrayIteratorNext); } AttachDecision OptimizeSpreadCallIRGenerator::tryAttachArray() { if (!isFirstStub_) { return AttachDecision::NoAction; } // The value must be a packed array. if (!val_.isObject()) { return AttachDecision::NoAction; } JSObject* obj = &val_.toObject(); if (!IsPackedArray(obj)) { return AttachDecision::NoAction; } // Prototype must be Array.prototype and Array.prototype[@@iterator] must not // be modified. NativeObject* arrProto; uint32_t arrProtoIterSlot; JSFunction* iterFun; if (!IsArrayPrototypeOptimizable(cx_, &obj->as(), &arrProto, &arrProtoIterSlot, &iterFun)) { return AttachDecision::NoAction; } // %ArrayIteratorPrototype%.next must not be modified. NativeObject* arrayIteratorProto; uint32_t iterNextSlot; JSFunction* nextFun; if (!IsArrayIteratorPrototypeOptimizable(cx_, &arrayIteratorProto, &iterNextSlot, &nextFun)) { return AttachDecision::NoAction; } ValOperandId valId(writer.setInputOperandId(0)); ObjOperandId objId = writer.guardToObject(valId); // Guard the object is a packed array with Array.prototype as proto. 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; } NativeObject* arrayIteratorProto; uint32_t slot; JSFunction* nextFun; if (!IsArrayIteratorPrototypeOptimizable(cx_, &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. if (isFirstStub_ || !callee->hasBaseScript() || callee->isSelfHostedBuiltin()) { writer.guardSpecificFunction(calleeId, callee); } else { 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. 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; } DataViewObject* 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::DataView); // 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. 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 (!ValueIsNumeric(type, args_[1])) { return AttachDecision::NoAction; } if (argc_ > 2 && !args_[2].isBoolean()) { return AttachDecision::NoAction; } DataViewObject* 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::DataView); // 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, 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::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) { JitCode* code; switch (native) { case InlinableNative::IntrinsicRegExpBuiltinExecForTest: case InlinableNative::IntrinsicRegExpExecForTest: code = cx->realm()->jitRealm()->ensureRegExpExecTestStubExists(cx); break; case InlinableNative::IntrinsicRegExpBuiltinExec: case InlinableNative::IntrinsicRegExpExec: code = cx->realm()->jitRealm()->ensureRegExpExecMatchStubExists(cx); break; case InlinableNative::RegExpMatcher: code = cx->realm()->jitRealm()->ensureRegExpMatcherStubExists(cx); break; case InlinableNative::RegExpSearcher: code = cx->realm()->jitRealm()->ensureRegExpSearcherStubExists(cx); break; default: MOZ_CRASH("Unexpected native"); } if (!code) { cx->recoverFromOutOfMemory(); return nullptr; } return code; } 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::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]); if (attach == AttachStringChar::No) { return AttachDecision::NoAction; } bool handleOOB = attach == AttachStringChar::OutOfBounds; // Initialize the input operand. initializeInputOperand(); // Guard callee is the 'charCodeAt' or 'charAt' 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); // 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) { strId = writer.linearizeForCharAccess(strId, int32IndexId); } // Load string char or code. if (kind == StringChar::CodeAt) { writer.loadStringCharCodeResult(strId, int32IndexId, handleOOB); } else { writer.loadStringCharResult(strId, int32IndexId, handleOOB); } writer.returnFromIC(); if (kind == StringChar::CodeAt) { trackAttached("StringCharCodeAt"); } else { trackAttached("StringCharAt"); } return AttachDecision::Attach; } AttachDecision InlinableNativeIRGenerator::tryAttachStringCharCodeAt() { return tryAttachStringChar(StringChar::CodeAt); } AttachDecision InlinableNativeIRGenerator::tryAttachStringCharAt() { 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::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::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::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()->behaviors().shouldResistFingerprinting()) { 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(TypedArrayObject* 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; } // 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 (!ValueIsNumeric(elementType, args_[2])) { return AttachDecision::NoAction; } if (!ValueIsNumeric(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, elementType); // Convert replacement value to int32/BigInt. ValOperandId replacementId = writer.loadArgumentFixedSlot(ArgumentKind::Arg3, argc_); OperandId numericReplacementId = emitNumericGuard(replacementId, 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; } // 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 (!ValueIsNumeric(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, 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; } // 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. // 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 (!ValueIsNumeric(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, 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::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::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()); 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 (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()); 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); } if (tarr->length() <= INT32_MAX) { writer.loadArrayBufferViewLengthInt32Result(objArgId); } else { writer.loadArrayBufferViewLengthDoubleResult(objArgId); } writer.returnFromIC(); trackAttached("IntrinsicTypedArrayLength"); return AttachDecision::Attach; } 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(); // 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; } NativeObject* arrayIteratorProto; uint32_t slot; JSFunction* nextFun; if (!IsArrayIteratorPrototypeOptimizable(cx_, &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; } #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::ArrayBuffer); } else { MOZ_ASSERT(obj->is()); writer.guardClass(objId, GuardClassKind::SharedArrayBuffer); } ValOperandId byteOffsetId; if (argc_ > 1) { byteOffsetId = writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_, flags_); } else { byteOffsetId = writer.loadUndefined(); } ValOperandId lengthId; if (argc_ > 2) { lengthId = writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_, flags_); } else { lengthId = writer.loadUndefined(); } writer.newTypedArrayFromArrayBufferResult(templateObj, objId, byteOffsetId, lengthId); } else { // From Array-like. writer.guardIsNotArrayBufferMaybeShared(objId); writer.guardIsNotProxy(objId); writer.newTypedArrayFromArrayResult(templateObj, objId); } } writer.returnFromIC(); trackAttached("TypedArrayConstructor"); return AttachDecision::Attach; } AttachDecision 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->infallibleGetUnresolvedName(cx_); 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: 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::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::StringCharAt: return tryAttachStringCharAt(); case InlinableNative::StringFromCharCode: return tryAttachStringFromCharCode(); case InlinableNative::StringFromCodePoint: return tryAttachStringFromCodePoint(); case InlinableNative::StringIndexOf: return tryAttachStringIndexOf(); case InlinableNative::StringStartsWith: return tryAttachStringStartsWith(); case InlinableNative::StringEndsWith: return tryAttachStringEndsWith(); case InlinableNative::StringToLowerCase: return tryAttachStringToLowerCase(); case InlinableNative::StringToUpperCase: return tryAttachStringToUpperCase(); 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::ObjectToString: return tryAttachObjectToString(); // Set intrinsics. case InlinableNative::IntrinsicGuardToSetObject: return tryAttachGuardToClass(native); case InlinableNative::IntrinsicGetNextSetEntryForIterator: return tryAttachGetNextMapSetEntryForIterator(/* isMap = */ false); // ArrayBuffer intrinsics. case InlinableNative::IntrinsicGuardToArrayBuffer: return tryAttachGuardToClass(native); case InlinableNative::IntrinsicArrayBufferByteLength: return tryAttachArrayBufferByteLength(/* isPossiblyWrapped = */ false); case InlinableNative::IntrinsicPossiblyWrappedArrayBufferByteLength: return tryAttachArrayBufferByteLength(/* isPossiblyWrapped = */ true); // SharedArrayBuffer intrinsics. case InlinableNative::IntrinsicGuardToSharedArrayBuffer: return tryAttachGuardToClass(native); // 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::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(); // 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()); 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; } 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; } 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 created 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 inInterpreter = frame->runningInInterpreter(); bool isInlined = frame->icScript()->isInlined(); if (inInterpreter && !isInlined) { return outerScript->zone()->unknownAllocSite(JS::TraceKind::Object); } return outerScript->createAllocSite(); } 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; if (holder->isFixedSlot(slot)) { size_t offset = NativeObject::getFixedSlotOffset(slot); calleeValId = writer.loadFixedSlot(holderId, offset); } else { size_t index = holder->dynamicSlotIndex(slot); calleeValId = writer.loadDynamicSlot(holderId, index); } 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; } #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()); } #endif