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