diff --git a/js/src/jit-test/tests/typedarray/typed-array-inline-cache.js b/js/src/jit-test/tests/typedarray/typed-array-inline-cache.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/typedarray/typed-array-inline-cache.js @@ -0,0 +1,53 @@ +// ECMA262 9.4.5.2 [[HasProperty]] +function check_in(x, a) { + return (x in a); +} + +function check_has_own(x, a) { + return a.hasOwnProperty(x); +} + +//make sure baseline gets compiled +function warmup(a) { + for (var i = 0; i < 1001; i++) { + check_in(i, a); + check_has_own(i, a); + } +} + +function check_assertions(a) { + assertEq(check_in(1, a), true); + assertEq(check_in("-0",a), false); // -0 access + assertEq(check_in(-10,a), false); // Negative access + assertEq(check_in(1012,a), false); // OOB access + + + assertEq(check_has_own(1, a), true); + assertEq(check_has_own("-0",a), false); // -0 access + assertEq(check_has_own(-10,a), false); // Negative access + assertEq(check_has_own(1012,a), false); // OOB access +} + +function test_with_no_protochain(a) { + var a = new Int32Array(1000).fill(1); + warmup(a); + check_assertions(a); +} + +// Attempting to validate against this comment: +// https://bugzilla.mozilla.org/show_bug.cgi?id=1419372#c3 +// +// "Out of bounds "in" or "hasOwnProperty" should always +// return false, and not consider the prototype chain at all" +function test_with_protochain(a) { + var a = new Int32Array(1000).fill(1); + // try to force the behaviour of 9.4.5.2 + a[1012] = "1012"; + a["-0"] = "-0"; + a[-10] = "-10"; + warmup(a); + check_assertions(a); +} + +test_with_no_protochain(); +test_with_protochain(); diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -1,4495 +1,4520 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 "jit/BaselineCacheIRCompiler.h" #include "jit/BaselineIC.h" #include "jit/CacheIRSpewer.h" #include "vm/SelfHosting.h" #include "jsobjinlines.h" #include "jit/MacroAssembler-inl.h" #include "vm/EnvironmentObject-inl.h" #include "vm/UnboxedObject-inl.h" using namespace js; using namespace js::jit; using mozilla::DebugOnly; using mozilla::Maybe; const char* js::jit::CacheKindNames[] = { #define DEFINE_KIND(kind) #kind, CACHE_IR_KINDS(DEFINE_KIND) #undef DEFINE_KIND }; 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, CacheKind cacheKind, ICState::Mode mode, bool* isTemporarilyUnoptimizable, HandleValue val, HandleValue idVal, HandleValue receiver, GetPropertyResultFlags resultFlags) : IRGenerator(cx, script, pc, cacheKind, mode), val_(val), idVal_(idVal), receiver_(receiver), isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable), resultFlags_(resultFlags), preliminaryObjectAction_(PreliminaryObjectAction::None) {} static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderOp, NativeObject* holder, Shape* shape) { if (holder->isFixedSlot(shape->slot())) { writer.loadFixedSlotResult(holderOp, NativeObject::getFixedSlotOffset(shape->slot())); } else { size_t dynamicSlotOffset = holder->dynamicSlotIndex(shape->slot()) * sizeof(Value); writer.loadDynamicSlotResult(holderOp, 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 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 == ShadowCheckFailed) { cx->clearPendingException(); return ProxyStubType::None; } if (DOMProxyIsShadowing(shadows)) { if (shadows == ShadowsViaDirectExpando || shadows == ShadowsViaIndirectExpando) return ProxyStubType::DOMExpando; return ProxyStubType::DOMShadowed; } MOZ_ASSERT(shadows == DoesntShadow || shadows == 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 (!ValueToId(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; } bool GetPropIRGenerator::tryAttachStub() { // Idempotent ICs should call tryAttachIdempotentStub instead. MOZ_ASSERT(!idempotent()); AutoAssertNoPendingException aanpe(cx_); // Non-object receivers are a degenerate case, so don't try to attach // stubs. The stubs we do emit will still perform runtime checks and // fallback as needed. if (isSuper() && !receiver_.isObject()) return false; 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 false; } if (val_.isObject()) { RootedObject obj(cx_, &val_.toObject()); ObjOperandId objId = writer.guardIsObject(valId); if (nameOrSymbol) { if (tryAttachObjectLength(obj, objId, id)) return true; if (tryAttachNative(obj, objId, id)) return true; if (tryAttachUnboxed(obj, objId, id)) return true; if (tryAttachUnboxedExpando(obj, objId, id)) return true; if (tryAttachTypedObject(obj, objId, id)) return true; if (tryAttachModuleNamespace(obj, objId, id)) return true; if (tryAttachWindowProxy(obj, objId, id)) return true; if (tryAttachCrossCompartmentWrapper(obj, objId, id)) return true; if (tryAttachXrayCrossCompartmentWrapper(obj, objId, id)) return true; if (tryAttachFunction(obj, objId, id)) return true; if (tryAttachProxy(obj, objId, id)) return true; trackNotAttached(); return false; } MOZ_ASSERT(cacheKind_ == CacheKind::GetElem || cacheKind_ == CacheKind::GetElemSuper); if (tryAttachProxyElement(obj, objId)) return true; uint32_t index; Int32OperandId indexId; if (maybeGuardInt32Index(idVal_, getElemKeyValueId(), &index, &indexId)) { if (tryAttachTypedElement(obj, objId, index, indexId)) return true; if (tryAttachDenseElement(obj, objId, index, indexId)) return true; if (tryAttachDenseElementHole(obj, objId, index, indexId)) return true; if (tryAttachArgumentsObjectArg(obj, objId, index, indexId)) return true; trackNotAttached(); return false; } trackNotAttached(); return false; } if (nameOrSymbol) { if (tryAttachPrimitive(valId, id)) return true; if (tryAttachStringLength(valId, id)) return true; if (tryAttachMagicArgumentsName(valId, id)) return true; trackNotAttached(); return false; } if (idVal_.isInt32()) { ValOperandId indexId = getElemKeyValueId(); if (tryAttachStringChar(valId, indexId)) return true; if (tryAttachMagicArgument(valId, indexId)) return true; trackNotAttached(); return false; } trackNotAttached(); return false; } bool GetPropIRGenerator::tryAttachIdempotentStub() { // For idempotent ICs, only attach stubs which we can be sure have no side // effects and produce a result which the MIR in the calling code is able // to handle, since we do not have a pc to explicitly monitor the result. MOZ_ASSERT(idempotent()); RootedObject obj(cx_, &val_.toObject()); RootedId id(cx_, NameToId(idVal_.toString()->asAtom().asPropertyName())); ValOperandId valId(writer.setInputOperandId(0)); ObjOperandId objId = writer.guardIsObject(valId); if (tryAttachNative(obj, objId, id)) return true; // Object lengths are supported only if int32 results are allowed. if ((resultFlags_ & GetPropertyResultFlags::AllowInt32) && tryAttachObjectLength(obj, objId, id)) return true; // Also support native data properties on DOMProxy prototypes. if (GetProxyStubType(cx_, obj, id) == ProxyStubType::DOMUnshadowed) return tryAttachDOMProxyUnshadowed(obj, objId, id); return false; } 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, PropertyResult prop) { if (!prop || !IsCacheableProtoChain(obj, holder)) return false; Shape* shape = prop.shape(); if (!shape->isDataProperty()) return false; return true; } static bool IsCacheableGetPropCallNative(JSObject* obj, JSObject* holder, Shape* shape) { if (!shape || !IsCacheableProtoChain(obj, holder)) return false; if (!shape->hasGetterValue() || !shape->getterValue().isObject()) return false; if (!shape->getterValue().toObject().is()) return false; JSFunction& getter = shape->getterValue().toObject().as(); if (!getter.isNative()) return false; if (getter.isClassConstructor()) return false; // Check for a getter that has jitinfo and whose jitinfo says it's // OK with both inner and outer objects. if (getter.jitInfo() && !getter.jitInfo()->needsOuterizedThisObject()) return true; // 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. return !IsWindow(obj); } static bool IsCacheableGetPropCallScripted(JSObject* obj, JSObject* holder, Shape* shape, bool* isTemporarilyUnoptimizable = nullptr) { if (!shape || !IsCacheableProtoChain(obj, holder)) return false; if (!shape->hasGetterValue() || !shape->getterValue().isObject()) return false; if (!shape->getterValue().toObject().is()) return false; // See IsCacheableGetPropCallNative. if (IsWindow(obj)) return false; JSFunction& getter = shape->getterValue().toObject().as(); if (getter.isNative()) return false; if (!getter.hasScript()) { if (isTemporarilyUnoptimizable) *isTemporarilyUnoptimizable = true; return false; } if (getter.isClassConstructor()) return false; return true; } static bool CheckHasNoSuchOwnProperty(JSContext* cx, JSObject* obj, jsid id) { if (obj->isNative()) { // 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; } else if (obj->is()) { if (obj->as().containsUnboxedOrExpandoProperty(cx, id)) return false; } else if (obj->is()) { if (obj->as().typeDescr().hasProperty(cx->names(), id)) return false; } else { return false; } return true; } static bool CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, jsid id) { JSObject* curObj = obj; do { if (!CheckHasNoSuchOwnProperty(cx, curObj, id)) return false; if (!curObj->isNative()) { // Non-native objects are only handled as the original receiver. if (curObj != obj) return false; } curObj = curObj->staticPrototype(); } while (curObj); return true; } // Return whether obj is in some PreliminaryObjectArray and has a structure // that might change in the future. static bool IsPreliminaryObject(JSObject* obj) { if (obj->isSingleton()) return false; TypeNewScript* newScript = obj->group()->newScript(); if (newScript && !newScript->analyzed()) return true; if (obj->group()->maybePreliminaryObjects()) return true; return false; } static bool IsCacheableNoProperty(JSContext* cx, JSObject* obj, JSObject* holder, Shape* shape, jsid id, jsbytecode* pc, GetPropertyResultFlags resultFlags) { if (shape) return false; MOZ_ASSERT(!holder); // Idempotent ICs may only attach missing-property stubs if undefined // results are explicitly allowed, since no monitoring is done of the // cache result. if (!pc && !(resultFlags & GetPropertyResultFlags::AllowUndefined)) return false; // If we're doing a name lookup, we have to throw a ReferenceError. If // extra warnings are enabled, we may have to report a warning. // Note that Ion does not generate idempotent caches for JSOP_GETBOUNDNAME. if ((pc && *pc == JSOP_GETBOUNDNAME) || cx->compartment()->behaviors().extraWarnings(cx)) return false; return CheckHasNoSuchProperty(cx, obj, id); } enum NativeGetPropCacheability { CanAttachNone, CanAttachReadSlot, CanAttachCallGetter, }; static NativeGetPropCacheability CanAttachNativeGetProp(JSContext* cx, HandleObject obj, HandleId id, MutableHandleNativeObject holder, MutableHandleShape shape, jsbytecode* pc, GetPropertyResultFlags resultFlags, bool* isTemporarilyUnoptimizable) { 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 (baseHolder) { if (!baseHolder->isNative()) return CanAttachNone; holder.set(&baseHolder->as()); } shape.set(prop.maybeShape()); if (IsCacheableGetPropReadSlot(obj, holder, prop)) return CanAttachReadSlot; if (IsCacheableNoProperty(cx, obj, holder, shape, id, pc, resultFlags)) return CanAttachReadSlot; // Idempotent ICs cannot call getters, see tryAttachIdempotentStub. if (pc && (resultFlags & GetPropertyResultFlags::Monitored)) { if (IsCacheableGetPropCallScripted(obj, holder, shape, isTemporarilyUnoptimizable)) return CanAttachCallGetter; if (IsCacheableGetPropCallNative(obj, holder, shape)) return CanAttachCallGetter; } return CanAttachNone; } static void GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj, JSObject* holder, ObjOperandId objId) { // The guards here protect against the effects of JSObject::swap(). If the // prototype chain is directly altered, then TI will toss the jitcode, so we // don't have to worry about it, and any other change to the holder, or // adding a shadowing property will result in reshaping the holder, and thus // the failure of the shape guard. MOZ_ASSERT(obj != holder); if (obj->hasUncacheableProto()) { // If the shape does not imply the proto, emit an explicit proto guard. writer.guardProto(objId, obj->staticPrototype()); } JSObject* pobj = obj->staticPrototype(); if (!pobj) return; while (pobj != holder) { if (pobj->hasUncacheableProto()) { ObjOperandId protoId = writer.loadObject(pobj); if (pobj->isSingleton()) { // Singletons can have their group's |proto| mutated directly. writer.guardProto(protoId, pobj->staticPrototype()); } else { writer.guardGroup(protoId, pobj->group()); } } pobj = pobj->staticPrototype(); } } static void GeneratePrototypeHoleGuards(CacheIRWriter& writer, JSObject* obj, ObjOperandId objId) { if (obj->hasUncacheableProto()) { // If the shape does not imply the proto, emit an explicit proto guard. writer.guardProto(objId, obj->staticPrototype()); } JSObject* pobj = obj->staticPrototype(); while (pobj) { ObjOperandId protoId = writer.loadObject(pobj); // Non-singletons with uncacheable protos can change their proto // without a shape change, so also guard on the group (which determines // the proto) in this case. if (pobj->hasUncacheableProto() && !pobj->isSingleton()) writer.guardGroup(protoId, pobj->group()); // 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(); } } static void TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj, ObjOperandId objId, Maybe* expandoId) { if (obj->is()) { writer.guardGroup(objId, obj->group()); if (UnboxedExpandoObject* expando = obj->as().maybeExpando()) { expandoId->emplace(writer.guardAndLoadUnboxedExpando(objId)); writer.guardShape(expandoId->ref(), expando->lastProperty()); } else { writer.guardNoUnboxedExpando(objId); } } else if (obj->is()) { writer.guardGroup(objId, obj->group()); } else { Shape* shape = obj->maybeShape(); MOZ_ASSERT(shape); writer.guardShape(objId, shape); } } static void EmitReadSlotGuard(CacheIRWriter& writer, JSObject* obj, JSObject* holder, ObjOperandId objId, Maybe* holderId) { Maybe expandoId; TestMatchingReceiver(writer, obj, objId, &expandoId); if (obj != holder) { GeneratePrototypeGuards(writer, obj, holder, objId); if (holder) { // Guard on the holder's shape. holderId->emplace(writer.loadObject(holder)); writer.guardShape(holderId->ref(), holder->as().lastProperty()); } else { // The property does not exist. Guard on everything in the prototype // chain. This is guaranteed to see only Native objects because of // CanAttachNativeGetProp(). JSObject* proto = obj->taggedProto().toObjectOrNull(); ObjOperandId lastObjId = objId; while (proto) { ObjOperandId protoId = writer.loadProto(lastObjId); writer.guardShape(protoId, proto->as().lastProperty()); proto = proto->staticPrototype(); lastObjId = protoId; } } } else if (obj->is()) { holderId->emplace(*expandoId); } else { holderId->emplace(objId); } } static void EmitReadSlotResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder, Shape* shape, ObjOperandId objId) { Maybe holderId; EmitReadSlotGuard(writer, obj, holder, objId, &holderId); if (obj == holder && obj->is()) holder = obj->as().maybeExpando(); // Slot access. if (holder) { MOZ_ASSERT(holderId->valid()); EmitLoadSlotResult(writer, *holderId, &holder->as(), shape); } else { MOZ_ASSERT(holderId.isNothing()); writer.loadUndefinedResult(); } } static void EmitReadSlotReturn(CacheIRWriter& writer, JSObject*, JSObject* holder, Shape* shape, bool wrapResult = false) { // Slot access. if (holder) { MOZ_ASSERT(shape); if (wrapResult) writer.wrapResult(); writer.typeMonitorResult(); } else { // Normally for this op, the result would have to be monitored by TI. // However, since this stub ALWAYS returns UndefinedValue(), and we can be sure // that undefined is already registered with the type-set, this can be avoided. writer.returnFromIC(); } } static void EmitCallGetterResultNoGuards(CacheIRWriter& writer, JSObject* obj, JSObject* holder, Shape* shape, ObjOperandId receiverId) { if (IsCacheableGetPropCallNative(obj, holder, shape)) { JSFunction* target = &shape->getterValue().toObject().as(); MOZ_ASSERT(target->isNative()); writer.callNativeGetterResult(receiverId, target); writer.typeMonitorResult(); return; } MOZ_ASSERT(IsCacheableGetPropCallScripted(obj, holder, shape)); JSFunction* target = &shape->getterValue().toObject().as(); MOZ_ASSERT(target->hasScript()); writer.callScriptedGetterResult(receiverId, target); writer.typeMonitorResult(); } static void EmitCallGetterResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder, Shape* shape, ObjOperandId objId, ObjOperandId receiverId, 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)) { Maybe expandoId; TestMatchingReceiver(writer, obj, objId, &expandoId); if (obj != holder) { GeneratePrototypeGuards(writer, obj, holder, objId); // Guard on the holder's shape. ObjOperandId holderId = writer.loadObject(holder); writer.guardShape(holderId, holder->as().lastProperty()); } } else { writer.guardHasGetterSetter(objId, shape); } EmitCallGetterResultNoGuards(writer, obj, holder, shape, receiverId); } static void EmitCallGetterResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder, Shape* shape, ObjOperandId objId, ICState::Mode mode) { EmitCallGetterResult(writer, obj, holder, shape, objId, objId, mode); } void GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId, jsid id, bool handleMissing) { MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic); // The stub handles the missing-properties case only if we're seeing one // now, to make sure Ion ICs correctly monitor the undefined type. if (cacheKind_ == CacheKind::GetProp || cacheKind_ == CacheKind::GetPropSuper) { writer.megamorphicLoadSlotResult(objId, JSID_TO_ATOM(id)->asPropertyName(), handleMissing); } else { MOZ_ASSERT(cacheKind_ == CacheKind::GetElem || cacheKind_ == CacheKind::GetElemSuper); writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId(), handleMissing); } writer.typeMonitorResult(); trackAttached(handleMissing ? "MegamorphicMissingNativeSlot" : "MegamorphicNativeSlot"); } bool GetPropIRGenerator::tryAttachNative(HandleObject obj, ObjOperandId objId, HandleId id) { RootedShape shape(cx_); RootedNativeObject holder(cx_); NativeGetPropCacheability type = CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_, resultFlags_, isTemporarilyUnoptimizable_); switch (type) { case CanAttachNone: return false; case CanAttachReadSlot: if (mode_ == ICState::Mode::Megamorphic) { attachMegamorphicNativeSlot(objId, id, holder == nullptr); return true; } maybeEmitIdGuard(id); if (holder) { EnsureTrackPropertyTypes(cx_, holder, id); // See the comment in StripPreliminaryObjectStubs. if (IsPreliminaryObject(obj)) preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary; else preliminaryObjectAction_ = PreliminaryObjectAction::Unlink; } EmitReadSlotResult(writer, obj, holder, shape, objId); EmitReadSlotReturn(writer, obj, holder, shape); trackAttached("NativeSlot"); return true; case CanAttachCallGetter: { // |super.prop| accesses use a |this| value that differs from lookup object MOZ_ASSERT(!idempotent()); ObjOperandId receiverId = isSuper() ? writer.guardIsObject(getSuperReceiverValueId()) : objId; maybeEmitIdGuard(id); EmitCallGetterResult(writer, obj, holder, shape, objId, receiverId, mode_); trackAttached("NativeGetter"); return true; } } MOZ_CRASH("Bad NativeGetPropCacheability"); } bool 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 (!IsWindowProxy(obj)) return false; // If we're megamorphic prefer a generic proxy stub that handles a lot more // cases. if (mode_ == ICState::Mode::Megamorphic) return false; // This must be a WindowProxy for the current Window/global. Else it would // be a cross-compartment wrapper and IsWindowProxy returns false for // those. MOZ_ASSERT(obj->getClass() == cx_->runtime()->maybeWindowProxyClass()); MOZ_ASSERT(ToWindowIfWindowProxy(obj) == cx_->global()); // Now try to do the lookup on the Window (the current global). HandleObject windowObj = cx_->global(); RootedShape shape(cx_); RootedNativeObject holder(cx_); NativeGetPropCacheability type = CanAttachNativeGetProp(cx_, windowObj, id, &holder, &shape, pc_, resultFlags_, isTemporarilyUnoptimizable_); switch (type) { case CanAttachNone: return false; case CanAttachReadSlot: { maybeEmitIdGuard(id); writer.guardClass(objId, GuardClassKind::WindowProxy); ObjOperandId windowObjId = writer.loadObject(windowObj); EmitReadSlotResult(writer, windowObj, holder, shape, windowObjId); EmitReadSlotReturn(writer, windowObj, holder, shape); trackAttached("WindowProxySlot"); return true; } case CanAttachCallGetter: { if (!IsCacheableGetPropCallNative(windowObj, holder, shape)) return false; // 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->isNative()); if (!callee->jitInfo() || callee->jitInfo()->needsOuterizedThisObject()) return false; // If a |super| access, it is not worth the complexity to attach an IC. if (isSuper()) return false; // Guard the incoming object is a WindowProxy and inline a getter call based // on the Window object. maybeEmitIdGuard(id); writer.guardClass(objId, GuardClassKind::WindowProxy); ObjOperandId windowObjId = writer.loadObject(windowObj); EmitCallGetterResult(writer, windowObj, holder, shape, windowObjId, mode_); trackAttached("WindowProxyGetter"); return true; } } MOZ_CRASH("Unreachable"); } bool 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 false; // If we're megamorphic prefer a generic proxy stub that handles a lot more // cases. if (mode_ == ICState::Mode::Megamorphic) return false; RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj)); MOZ_ASSERT(unwrapped == UnwrapOneChecked(obj)); // If we allowed different zones we would have to wrap strings. if (unwrapped->compartment()->zone() != cx_->compartment()->zone()) return false; RootedObject wrappedGlobal(cx_, &obj->global()); if (!cx_->compartment()->wrap(cx_, &wrappedGlobal)) return false; AutoCompartment ac(cx_, unwrapped); // The first CCW for iframes is almost always wrapping another WindowProxy // so we optimize for that case as well. bool isWindowProxy = IsWindowProxy(unwrapped); if (isWindowProxy) { MOZ_ASSERT(ToWindowIfWindowProxy(unwrapped) == unwrapped->compartment()->maybeGlobal()); unwrapped = cx_->global(); MOZ_ASSERT(unwrapped); } RootedShape shape(cx_); RootedNativeObject holder(cx_); NativeGetPropCacheability canCache = CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &shape, pc_, resultFlags_, isTemporarilyUnoptimizable_); if (canCache != CanAttachReadSlot) return false; if (holder) { EnsureTrackPropertyTypes(cx_, holder, id); if (unwrapped == holder) { // See the comment in StripPreliminaryObjectStubs. if (IsPreliminaryObject(unwrapped)) preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary; else preliminaryObjectAction_ = PreliminaryObjectAction::Unlink; } } 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, wrappedGlobal, unwrapped->compartment()); ObjOperandId unwrappedId = wrapperTargetId; if (isWindowProxy) { // For the WindowProxy case also unwrap the inner window. // We avoid loadObject, because storing cross compartment objects in // stubs / JIT code is tricky. writer.guardClass(wrapperTargetId, GuardClassKind::WindowProxy); unwrappedId = writer.loadWrapperTarget(wrapperTargetId); } EmitReadSlotResult(writer, unwrapped, holder, shape, unwrappedId); EmitReadSlotReturn(writer, unwrapped, holder, shape, /* wrapResult = */ true); trackAttached("CCWSlot"); return true; } 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; } bool GetPropIRGenerator::tryAttachXrayCrossCompartmentWrapper(HandleObject obj, ObjOperandId objId, HandleId id) { if (!IsProxy(obj)) return false; XrayJitInfo* info = GetXrayJitInfo(); if (!info || !info->isCrossCompartmentXray(GetProxyHandler(obj))) return false; if (!info->globalHasExclusiveExpandos(cx_->global())) return false; RootedObject target(cx_, UncheckedUnwrap(obj)); RootedObject expandoShapeWrapper(cx_); if (!GetXrayExpandoShapeWrapper(cx_, obj, &expandoShapeWrapper)) { cx_->recoverFromOutOfMemory(); return false; } // Look for a getter we can call on the xray or its prototype chain. Rooted desc(cx_); RootedObject holder(cx_, obj); AutoObjectVector prototypes(cx_); AutoObjectVector prototypeExpandoShapeWrappers(cx_); while (true) { if (!GetOwnPropertyDescriptor(cx_, holder, id, &desc)) { cx_->clearPendingException(); return false; } if (desc.object()) break; if (!GetPrototype(cx_, holder, &holder)) { cx_->clearPendingException(); return false; } if (!holder || !IsProxy(holder) || !info->isCrossCompartmentXray(GetProxyHandler(holder))) return false; RootedObject prototypeExpandoShapeWrapper(cx_); if (!GetXrayExpandoShapeWrapper(cx_, holder, &prototypeExpandoShapeWrapper) || !prototypes.append(holder) || !prototypeExpandoShapeWrappers.append(prototypeExpandoShapeWrapper)) { cx_->recoverFromOutOfMemory(); return false; } } if (!desc.isAccessorDescriptor()) return false; RootedObject getter(cx_, desc.getterObject()); if (!getter || !getter->is() || !getter->as().isNative()) return false; 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. writer.guardXrayExpandoShapeAndDefaultProto(objId, expandoShapeWrapper); for (size_t i = 0; i < prototypes.length(); i++) { JSObject* proto = prototypes[i]; ObjOperandId protoId = writer.loadObject(proto); writer.guardXrayExpandoShapeAndDefaultProto(protoId, prototypeExpandoShapeWrappers[i]); } writer.callNativeGetterResult(objId, &getter->as()); writer.typeMonitorResult(); trackAttached("XrayGetter"); return true; } bool 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.guardNotDOMProxy(objId); } if (cacheKind_ == CacheKind::GetProp || mode_ == ICState::Mode::Specialized) { MOZ_ASSERT(!isSuper()); maybeEmitIdGuard(id); writer.callProxyGetResult(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.callProxyGetByValueResult(objId, getElemKeyValueId()); } writer.typeMonitorResult(); trackAttached("GenericProxy"); return true; } ObjOperandId IRGenerator::guardDOMProxyExpandoObjectAndShape(JSObject* obj, ObjOperandId objId, const Value& expandoVal, JSObject* expandoObj) { MOZ_ASSERT(IsCacheableDOMProxy(obj)); writer.guardShape(objId, obj->maybeShape()); // 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.guardIsObject(expandoValId); writer.guardShape(expandoObjId, expandoObj->as().shape()); return expandoObjId; } bool GetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id) { 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_, resultFlags_, isTemporarilyUnoptimizable_); if (canCache != CanAttachReadSlot && canCache != CanAttachCallGetter) return false; if (!holder) return false; 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.typeMonitorResult(); } else { // Call the getter. Note that we pass objId, the DOM proxy, as |this| // and not the expando object. MOZ_ASSERT(canCache == CanAttachCallGetter); EmitCallGetterResultNoGuards(writer, expandoObj, expandoObj, propShape, objId); } trackAttached("DOMProxyExpando"); return true; } bool GetPropIRGenerator::tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId, HandleId id) { MOZ_ASSERT(IsCacheableDOMProxy(obj)); maybeEmitIdGuard(id); writer.guardShape(objId, obj->maybeShape()); // No need for more guards: we know this is a DOM proxy, since the shape // guard enforces a given JSClass, so just go ahead and emit the call to // ProxyGet. MOZ_ASSERT(!isSuper()); writer.callProxyGetResult(objId, id); writer.typeMonitorResult(); trackAttached("DOMProxyShadowed"); return true; } // 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()); expandoId = writer.loadDOMExpandoValueGuardGeneration(objId, expandoAndGeneration); expandoVal = expandoAndGeneration->expando; } else { expandoId = writer.loadDOMExpandoValue(objId); } if (expandoVal.isUndefined()) { // Guard there's no expando object. writer.guardType(expandoId, JSVAL_TYPE_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"); } } bool GetPropIRGenerator::tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId objId, HandleId id) { MOZ_ASSERT(IsCacheableDOMProxy(obj)); RootedObject checkObj(cx_, obj->staticPrototype()); if (!checkObj) return false; RootedNativeObject holder(cx_); RootedShape shape(cx_); NativeGetPropCacheability canCache = CanAttachNativeGetProp(cx_, checkObj, id, &holder, &shape, pc_, resultFlags_, isTemporarilyUnoptimizable_); if (canCache == CanAttachNone) return false; maybeEmitIdGuard(id); writer.guardShape(objId, obj->maybeShape()); // Guard that our expando object hasn't started shadowing this property. 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); writer.guardShape(holderId, holder->lastProperty()); if (canCache == CanAttachReadSlot) { EmitLoadSlotResult(writer, holderId, holder, shape); writer.typeMonitorResult(); } 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 == CanAttachCallGetter); MOZ_ASSERT(!isSuper()); EmitCallGetterResultNoGuards(writer, checkObj, holder, shape, objId); } } else { // Property was not found on the prototype chain. Deoptimize down to // proxy get call. MOZ_ASSERT(!isSuper()); writer.callProxyGetResult(objId, id); writer.typeMonitorResult(); } trackAttached("DOMProxyUnshadowed"); return true; } bool GetPropIRGenerator::tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id) { ProxyStubType type = GetProxyStubType(cx_, obj, id); if (type == ProxyStubType::None) return false; // The proxy stubs don't currently support |super| access. if (isSuper()) return false; if (mode_ == ICState::Mode::Megamorphic) return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ true); switch (type) { case ProxyStubType::None: break; case ProxyStubType::DOMExpando: if (tryAttachDOMProxyExpando(obj, objId, id)) return true; if (*isTemporarilyUnoptimizable_) { // Scripted getter without JIT code. Just wait. return false; } MOZ_FALLTHROUGH; // Fall through to the generic shadowed case. case ProxyStubType::DOMShadowed: return tryAttachDOMProxyShadowed(obj, objId, id); case ProxyStubType::DOMUnshadowed: if (tryAttachDOMProxyUnshadowed(obj, objId, id)) return true; if (*isTemporarilyUnoptimizable_) { // Scripted getter without JIT code. Just wait. return false; } return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ true); case ProxyStubType::Generic: return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ false); } MOZ_CRASH("Unexpected ProxyStubType"); } bool GetPropIRGenerator::tryAttachUnboxed(HandleObject obj, ObjOperandId objId, HandleId id) { if (!obj->is()) return false; const UnboxedLayout::Property* property = obj->as().layout().lookup(id); if (!property) return false; if (!cx_->runtime()->jitSupportsFloatingPoint) return false; maybeEmitIdGuard(id); writer.guardGroup(objId, obj->group()); writer.loadUnboxedPropertyResult(objId, property->type, UnboxedPlainObject::offsetOfData() + property->offset); if (property->type == JSVAL_TYPE_OBJECT) writer.typeMonitorResult(); else writer.returnFromIC(); preliminaryObjectAction_ = PreliminaryObjectAction::Unlink; trackAttached("Unboxed"); return true; } bool GetPropIRGenerator::tryAttachUnboxedExpando(HandleObject obj, ObjOperandId objId, HandleId id) { if (!obj->is()) return false; UnboxedExpandoObject* expando = obj->as().maybeExpando(); if (!expando) return false; Shape* shape = expando->lookup(cx_, id); if (!shape || !shape->isDataProperty()) return false; maybeEmitIdGuard(id); EmitReadSlotResult(writer, obj, obj, shape, objId); EmitReadSlotReturn(writer, obj, obj, shape); trackAttached("UnboxedExpando"); return true; } static TypedThingLayout GetTypedThingLayout(const Class* clasp) { if (IsTypedArrayClass(clasp)) return Layout_TypedArray; if (IsOutlineTypedObjectClass(clasp)) return Layout_OutlineTypedObject; if (IsInlineTypedObjectClass(clasp)) return Layout_InlineTypedObject; MOZ_CRASH("Bad object class"); } bool GetPropIRGenerator::tryAttachTypedObject(HandleObject obj, ObjOperandId objId, HandleId id) { if (!obj->is() || !cx_->runtime()->jitSupportsFloatingPoint || cx_->compartment()->detachedTypedObjects) { return false; } TypedObject* typedObj = &obj->as(); if (!typedObj->typeDescr().is()) return false; StructTypeDescr* structDescr = &typedObj->typeDescr().as(); size_t fieldIndex; if (!structDescr->fieldIndex(id, &fieldIndex)) return false; TypeDescr* fieldDescr = &structDescr->fieldDescr(fieldIndex); if (!fieldDescr->is()) return false; Shape* shape = typedObj->maybeShape(); TypedThingLayout layout = GetTypedThingLayout(shape->getObjectClass()); uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex); uint32_t typeDescr = SimpleTypeDescrKey(&fieldDescr->as()); maybeEmitIdGuard(id); writer.guardNoDetachedTypedObjects(); writer.guardShape(objId, shape); writer.loadTypedObjectResult(objId, fieldOffset, layout, typeDescr); // Only monitor the result if the type produced by this stub might vary. bool monitorLoad = false; if (SimpleTypeDescrKeyIsScalar(typeDescr)) { Scalar::Type type = ScalarTypeFromSimpleTypeDescrKey(typeDescr); monitorLoad = type == Scalar::Uint32; } else { ReferenceTypeDescr::Type type = ReferenceTypeFromSimpleTypeDescrKey(typeDescr); monitorLoad = type != ReferenceTypeDescr::TYPE_STRING; } if (monitorLoad) writer.typeMonitorResult(); else writer.returnFromIC(); trackAttached("TypedObject"); return true; } bool GetPropIRGenerator::tryAttachObjectLength(HandleObject obj, ObjOperandId objId, HandleId id) { if (!JSID_IS_ATOM(id, cx_->names().length)) return false; if (obj->is()) { // Make sure int32 is added to the TypeSet before we attach a stub, so // the stub can return int32 values without monitoring the result. if (obj->as().length() > INT32_MAX) return false; maybeEmitIdGuard(id); writer.guardClass(objId, GuardClassKind::Array); writer.loadInt32ArrayLengthResult(objId); writer.returnFromIC(); trackAttached("ArrayLength"); return true; } 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 true; } return false; } bool 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 false; JSObject* holder = nullptr; PropertyResult prop; // This property exists already, don't attach the stub. if (LookupPropertyPure(cx_, obj, id, &holder, &prop)) return false; JSFunction* fun = &obj->as(); if (JSID_IS_ATOM(id, cx_->names().length)) { // length was probably deleted from the function. if (fun->hasResolvedLength()) return false; // Lazy functions don't store the length. if (fun->isInterpretedLazy()) return false; maybeEmitIdGuard(id); writer.guardClass(objId, GuardClassKind::JSFunction); writer.loadFunctionLengthResult(objId); writer.returnFromIC(); trackAttached("FunctionLength"); return true; } return false; } bool GetPropIRGenerator::tryAttachModuleNamespace(HandleObject obj, ObjOperandId objId, HandleId id) { if (!obj->is()) return false; Rooted ns(cx_, &obj->as()); RootedModuleEnvironmentObject env(cx_); RootedShape shape(cx_); if (!ns->bindings().lookup(id, env.address(), shape.address())) return false; // Don't emit a stub until the target binding has been initialized. if (env->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) return false; if (IsIonEnabled(cx_)) EnsureTrackPropertyTypes(cx_, env, shape->propid()); // Check for the specific namespace object. maybeEmitIdGuard(id); writer.guardSpecificObject(objId, ns); ObjOperandId envId = writer.loadObject(env); EmitLoadSlotResult(writer, envId, env, shape); writer.typeMonitorResult(); trackAttached("ModuleNamespace"); return true; } bool GetPropIRGenerator::tryAttachPrimitive(ValOperandId valId, HandleId id) { JSValueType primitiveType; RootedNativeObject proto(cx_); if (val_.isString()) { if (JSID_IS_ATOM(id, cx_->names().length)) { // String length is special-cased, see js::GetProperty. return false; } primitiveType = JSVAL_TYPE_STRING; proto = MaybeNativeObject(GetBuiltinPrototypePure(cx_->global(), JSProto_String)); } else if (val_.isNumber()) { primitiveType = JSVAL_TYPE_DOUBLE; proto = MaybeNativeObject(GetBuiltinPrototypePure(cx_->global(), JSProto_Number)); } else if (val_.isBoolean()) { primitiveType = JSVAL_TYPE_BOOLEAN; proto = MaybeNativeObject(GetBuiltinPrototypePure(cx_->global(), JSProto_Boolean)); } else if (val_.isSymbol()) { primitiveType = JSVAL_TYPE_SYMBOL; proto = MaybeNativeObject(GetBuiltinPrototypePure(cx_->global(), JSProto_Symbol)); } else { MOZ_ASSERT(val_.isNullOrUndefined() || val_.isMagic()); return false; } if (!proto) return false; RootedShape shape(cx_); RootedNativeObject holder(cx_); NativeGetPropCacheability type = CanAttachNativeGetProp(cx_, proto, id, &holder, &shape, pc_, resultFlags_, isTemporarilyUnoptimizable_); if (type != CanAttachReadSlot) return false; if (holder) { // Instantiate this property, for use during Ion compilation. if (IsIonEnabled(cx_)) EnsureTrackPropertyTypes(cx_, holder, id); } writer.guardType(valId, primitiveType); maybeEmitIdGuard(id); ObjOperandId protoId = writer.loadObject(proto); EmitReadSlotResult(writer, proto, holder, shape, protoId); EmitReadSlotReturn(writer, proto, holder, shape); trackAttached("Primitive"); return true; } bool GetPropIRGenerator::tryAttachStringLength(ValOperandId valId, HandleId id) { if (!val_.isString() || !JSID_IS_ATOM(id, cx_->names().length)) return false; StringOperandId strId = writer.guardIsString(valId); maybeEmitIdGuard(id); writer.loadStringLengthResult(strId); writer.returnFromIC(); trackAttached("StringLength"); return true; } bool GetPropIRGenerator::tryAttachStringChar(ValOperandId valId, ValOperandId indexId) { MOZ_ASSERT(idVal_.isInt32()); if (!val_.isString()) 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() || str->asLinear().latin1OrTwoByteChar(index) >= StaticStrings::UNIT_STATIC_LIMIT) { return false; } StringOperandId strId = writer.guardIsString(valId); Int32OperandId int32IndexId = writer.guardIsInt32Index(indexId); writer.loadStringCharResult(strId, int32IndexId); writer.returnFromIC(); trackAttached("StringChar"); return true; } bool GetPropIRGenerator::tryAttachMagicArgumentsName(ValOperandId valId, HandleId id) { if (!val_.isMagic(JS_OPTIMIZED_ARGUMENTS)) return false; if (!JSID_IS_ATOM(id, cx_->names().length) && !JSID_IS_ATOM(id, cx_->names().callee)) return false; maybeEmitIdGuard(id); writer.guardMagicValue(valId, JS_OPTIMIZED_ARGUMENTS); writer.guardFrameHasNoArgumentsObject(); if (JSID_IS_ATOM(id, cx_->names().length)) { writer.loadFrameNumActualArgsResult(); writer.returnFromIC(); } else { MOZ_ASSERT(JSID_IS_ATOM(id, cx_->names().callee)); writer.loadFrameCalleeResult(); writer.typeMonitorResult(); } trackAttached("MagicArgumentsName"); return true; } bool GetPropIRGenerator::tryAttachMagicArgument(ValOperandId valId, ValOperandId indexId) { MOZ_ASSERT(idVal_.isInt32()); if (!val_.isMagic(JS_OPTIMIZED_ARGUMENTS)) return false; writer.guardMagicValue(valId, JS_OPTIMIZED_ARGUMENTS); writer.guardFrameHasNoArgumentsObject(); Int32OperandId int32IndexId = writer.guardIsInt32Index(indexId); writer.loadFrameArgumentResult(int32IndexId); writer.typeMonitorResult(); trackAttached("MagicArgument"); return true; } bool GetPropIRGenerator::tryAttachArgumentsObjectArg(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId) { if (!obj->is() || obj->as().hasOverriddenElement()) return false; if (obj->is()) { writer.guardClass(objId, GuardClassKind::MappedArguments); } else { MOZ_ASSERT(obj->is()); writer.guardClass(objId, GuardClassKind::UnmappedArguments); } writer.loadArgumentsObjectArgResult(objId, indexId); writer.typeMonitorResult(); trackAttached("ArgumentsObjectArg"); return true; } bool GetPropIRGenerator::tryAttachDenseElement(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId) { if (!obj->isNative()) return false; if (!obj->as().containsDenseElement(index)) return false; writer.guardShape(objId, obj->as().lastProperty()); writer.loadDenseElementResult(objId, indexId); writer.typeMonitorResult(); trackAttached("DenseElement"); return true; } static bool CanAttachDenseElementHole(JSObject* obj, bool ownProp) { // 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 (obj->isNative() && obj->as().isIndexed()) return 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; } while (true); return true; } bool GetPropIRGenerator::tryAttachDenseElementHole(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId) { if (!obj->isNative()) return false; if (obj->as().containsDenseElement(index)) return false; if (!CanAttachDenseElementHole(obj, false)) return false; // Guard on the shape, to prevent non-dense elements from appearing. writer.guardShape(objId, obj->as().lastProperty()); GeneratePrototypeHoleGuards(writer, obj, objId); writer.loadDenseElementHoleResult(objId, indexId); writer.typeMonitorResult(); trackAttached("DenseElementHole"); return true; } static bool IsPrimitiveArrayTypedObject(JSObject* obj) { if (!obj->is()) return false; TypeDescr& descr = obj->as().typeDescr(); return descr.is() && descr.as().elementType().is(); } static Scalar::Type PrimitiveArrayTypedObjectType(JSObject* obj) { MOZ_ASSERT(IsPrimitiveArrayTypedObject(obj)); TypeDescr& descr = obj->as().typeDescr(); return descr.as().elementType().as().type(); } static Scalar::Type TypedThingElementType(JSObject* obj) { return obj->is() ? obj->as().type() : PrimitiveArrayTypedObjectType(obj); } static bool TypedThingRequiresFloatingPoint(JSObject* obj) { Scalar::Type type = TypedThingElementType(obj); return type == Scalar::Uint32 || type == Scalar::Float32 || type == Scalar::Float64; } bool GetPropIRGenerator::tryAttachTypedElement(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId) { if (!obj->is() && !IsPrimitiveArrayTypedObject(obj)) return false; if (!cx_->runtime()->jitSupportsFloatingPoint && TypedThingRequiresFloatingPoint(obj)) return false; // Ensure the index is in-bounds so the element type gets monitored. if (obj->is() && index >= obj->as().length()) return false; // Don't attach typed object stubs if the underlying storage could be // detached, as the stub will always bail out. if (IsPrimitiveArrayTypedObject(obj) && cx_->compartment()->detachedTypedObjects) return false; TypedThingLayout layout = GetTypedThingLayout(obj->getClass()); if (layout != Layout_TypedArray) writer.guardNoDetachedTypedObjects(); writer.guardShape(objId, obj->as().shape()); writer.loadTypedElementResult(objId, indexId, layout, TypedThingElementType(obj)); // Reading from Uint32Array may produce an int32 now but a double value // later, so ensure we monitor the result. if (TypedThingElementType(obj) == Scalar::Type::Uint32) writer.typeMonitorResult(); else writer.returnFromIC(); trackAttached("TypedElement"); return true; } bool GetPropIRGenerator::tryAttachProxyElement(HandleObject obj, ObjOperandId objId) { if (!obj->is()) return false; // The proxy stubs don't currently support |super| access. if (isSuper()) return false; 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 CallProxyGetResult, // 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.callProxyGetByValueResult(objId, getElemKeyValueId()); writer.typeMonitorResult(); trackAttached("ProxyElement"); return true; } void GetPropIRGenerator::trackAttached(const char* name) { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "base", val_); sp.valueProperty(guard, "property", idVal_); sp.attached(guard, name); sp.endCache(guard); } #endif } void GetPropIRGenerator::trackNotAttached() { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "base", val_); sp.valueProperty(guard, "property", idVal_); sp.endCache(guard); } #endif } void IRGenerator::emitIdGuard(ValOperandId valId, jsid id) { if (JSID_IS_SYMBOL(id)) { SymbolOperandId symId = writer.guardIsSymbol(valId); writer.guardSpecificSymbol(symId, JSID_TO_SYMBOL(id)); } else { MOZ_ASSERT(JSID_IS_ATOM(id)); StringOperandId strId = writer.guardIsString(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) {} bool GetNameIRGenerator::tryAttachStub() { MOZ_ASSERT(cacheKind_ == CacheKind::GetName); AutoAssertNoPendingException aanpe(cx_); ObjOperandId envId(writer.setInputOperandId(0)); RootedId id(cx_, NameToId(name_)); if (tryAttachGlobalNameValue(envId, id)) return true; if (tryAttachGlobalNameGetter(envId, id)) return true; if (tryAttachEnvironmentName(envId, id)) return true; trackNotAttached(); return false; } 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; } bool GetNameIRGenerator::tryAttachGlobalNameValue(ObjOperandId objId, HandleId id) { if (!IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) return false; Handle globalLexical = env_.as(); MOZ_ASSERT(globalLexical->isGlobal()); RootedNativeObject holder(cx_); RootedShape shape(cx_); if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &shape)) return false; // The property must be found, and it must be found as a normal data property. if (!shape->isDataProperty()) return false; // This might still be an uninitialized lexical. if (holder->getSlot(shape->slot()).isMagic()) return false; // Instantiate this global property, for use during Ion compilation. if (IsIonEnabled(cx_)) EnsureTrackPropertyTypes(cx_, holder, id); 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, PropertyResult(shape))) return false; // 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.typeMonitorResult(); trackAttached("GlobalNameValue"); return true; } bool GetNameIRGenerator::tryAttachGlobalNameGetter(ObjOperandId objId, HandleId id) { if (!IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) return false; Handle globalLexical = env_.as(); MOZ_ASSERT(globalLexical->isGlobal()); RootedNativeObject holder(cx_); RootedShape shape(cx_); if (!CanAttachGlobalName(cx_, globalLexical, id, &holder, &shape)) return false; if (holder == globalLexical) return false; if (!IsCacheableGetPropCallNative(&globalLexical->global(), holder, shape)) return false; if (IsIonEnabled(cx_)) EnsureTrackPropertyTypes(cx_, holder, id); // 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()); } EmitCallGetterResultNoGuards(writer, &globalLexical->global(), holder, shape, globalId); trackAttached("GlobalNameGetter"); return true; } 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). The function might have been relazified under rare // conditions. In that case, we pessimistically create the guard. CallObject* callObj = &envObj->as(); JSFunction* fun = &callObj->callee(); if (!fun->hasScript() || fun->nonLazyScript()->funHasExtensibleScope()) return true; return false; } bool GetNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId, HandleId id) { if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) return false; 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 false; } if (!env->is() || env->is()) return false; MOZ_ASSERT(!env->hasUncacheableProto()); // 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, PropertyResult(shape))) return false; if (holder->getSlot(shape->slot()).isMagic()) return false; ObjOperandId lastObjId = objId; env = env_; while (env) { if (NeedEnvironmentShapeGuard(env)) writer.guardShape(lastObjId, env->maybeShape()); 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.typeMonitorResult(); trackAttached("EnvironmentName"); return true; } void GetNameIRGenerator::trackAttached(const char* name) { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "base", ObjectValue(*env_)); sp.valueProperty(guard, "property", StringValue(name_)); sp.attached(guard, name); sp.endCache(guard); } #endif } void GetNameIRGenerator::trackNotAttached() { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "base", ObjectValue(*env_)); sp.valueProperty(guard, "property", StringValue(name_)); sp.endCache(guard); } #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) {} bool BindNameIRGenerator::tryAttachStub() { MOZ_ASSERT(cacheKind_ == CacheKind::BindName); AutoAssertNoPendingException aanpe(cx_); ObjOperandId envId(writer.setInputOperandId(0)); RootedId id(cx_, NameToId(name_)); if (tryAttachGlobalName(envId, id)) return true; if (tryAttachEnvironmentName(envId, id)) return true; trackNotAttached(); return false; } bool BindNameIRGenerator::tryAttachGlobalName(ObjOperandId objId, HandleId id) { if (!IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) return false; 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 false; 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 true; } bool BindNameIRGenerator::tryAttachEnvironmentName(ObjOperandId objId, HandleId id) { if (IsGlobalOp(JSOp(*pc_)) || script_->hasNonSyntacticScope()) return false; RootedObject env(cx_, env_); RootedShape shape(cx_); while (true) { if (!env->is() && !env->is()) return false; if (env->is()) return false; MOZ_ASSERT(!env->hasUncacheableProto()); // 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 false; } ObjOperandId lastObjId = objId; env = env_; while (env) { if (NeedEnvironmentShapeGuard(env) && !env->is()) writer.guardShape(lastObjId, env->maybeShape()); if (env == holder) break; lastObjId = writer.loadEnclosingEnvironment(lastObjId); env = env->enclosingEnvironment(); } writer.loadObjectResult(lastObjId); writer.returnFromIC(); trackAttached("EnvironmentName"); return true; } void BindNameIRGenerator::trackAttached(const char* name) { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "base", ObjectValue(*env_)); sp.valueProperty(guard, "property", StringValue(name_)); sp.attached(guard, name); sp.endCache(guard); } #endif } void BindNameIRGenerator::trackNotAttached() { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "base", ObjectValue(*env_)); sp.valueProperty(guard, "property", StringValue(name_)); sp.endCache(guard); } #endif } HasPropIRGenerator::HasPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind, ICState::Mode mode, HandleValue idVal, HandleValue val) : IRGenerator(cx, script, pc, cacheKind, mode), val_(val), idVal_(idVal) { } bool HasPropIRGenerator::tryAttachDense(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId) { if (!obj->isNative()) return false; if (!obj->as().containsDenseElement(index)) return false; // Guard shape to ensure object class is NativeObject. writer.guardShape(objId, obj->as().lastProperty()); writer.loadDenseElementExistsResult(objId, indexId); writer.returnFromIC(); trackAttached("DenseHasProp"); return true; } bool HasPropIRGenerator::tryAttachDenseHole(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId) { bool hasOwn = (cacheKind_ == CacheKind::HasOwn); if (!obj->isNative()) return false; if (obj->as().containsDenseElement(index)) return false; if (!CanAttachDenseElementHole(obj, hasOwn)) return false; // 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. writer.guardShape(objId, obj->as().lastProperty()); // Generate prototype guards if needed. This includes monitoring that // properties were not added in the chain. if (!hasOwn) GeneratePrototypeHoleGuards(writer, obj, objId); writer.loadDenseElementHoleExistsResult(objId, indexId); writer.returnFromIC(); trackAttached("DenseHasPropHole"); return true; } bool 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 false; holder = obj; } else { if (!LookupPropertyPure(cx_, obj, key, &holder, &prop)) return false; } if (!prop) return false; if (tryAttachMegamorphic(objId, keyId)) return true; if (tryAttachNative(obj, objId, key, keyId, prop, holder)) return true; if (tryAttachUnboxed(obj, objId, key, keyId)) return true; if (tryAttachTypedObject(obj, objId, key, keyId)) return true; if (tryAttachUnboxedExpando(obj, objId, key, keyId)) return true; return false; } bool HasPropIRGenerator::tryAttachMegamorphic(ObjOperandId objId, ValOperandId keyId) { bool hasOwn = (cacheKind_ == CacheKind::HasOwn); if (mode_ != ICState::Mode::Megamorphic) return false; writer.megamorphicHasPropResult(objId, keyId, hasOwn); writer.returnFromIC(); trackAttached("MegamorphicHasProp"); return true; } bool HasPropIRGenerator::tryAttachNative(JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId, PropertyResult prop, JSObject* holder) { if (!prop.isNativeProperty()) return false; if (!IsCacheableProtoChain(obj, holder)) return false; Maybe tempId; emitIdGuard(keyId, key); EmitReadSlotGuard(writer, obj, holder, objId, &tempId); writer.loadBooleanResult(true); writer.returnFromIC(); trackAttached("NativeHasProp"); return true; } bool HasPropIRGenerator::tryAttachUnboxed(JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId) { if (!obj->is()) return false; const UnboxedLayout::Property* prop = obj->as().layout().lookup(key); if (!prop) return false; emitIdGuard(keyId, key); writer.guardGroup(objId, obj->group()); writer.loadBooleanResult(true); writer.returnFromIC(); trackAttached("UnboxedHasProp"); return true; } bool HasPropIRGenerator::tryAttachUnboxedExpando(JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId) { if (!obj->is()) return false; UnboxedExpandoObject* expando = obj->as().maybeExpando(); if (!expando) return false; Shape* shape = expando->lookup(cx_, key); if (!shape) return false; Maybe tempId; emitIdGuard(keyId, key); EmitReadSlotGuard(writer, obj, obj, objId, &tempId); writer.loadBooleanResult(true); writer.returnFromIC(); trackAttached("UnboxedExpandoHasProp"); return true; } bool +HasPropIRGenerator::tryAttachTypedArray(HandleObject obj, ObjOperandId objId, + uint32_t index, Int32OperandId indexId) +{ + if (!obj->is() && !IsPrimitiveArrayTypedObject(obj)) + return false; + + // Don't attach typed object stubs if the underlying storage could be + // detached, as the stub will always bail out. + if (IsPrimitiveArrayTypedObject(obj) && cx_->compartment()->detachedTypedObjects) + return false; + + TypedThingLayout layout = GetTypedThingLayout(obj->getClass()); + writer.guardShape(objId, obj->as().shape()); + + writer.loadTypedElementExistsResult(objId, indexId, layout); + + writer.returnFromIC(); + + trackAttached("TypedArrayObject"); + return true; +} + +bool HasPropIRGenerator::tryAttachTypedObject(JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId) { if (!obj->is()) return false; if (!obj->as().typeDescr().hasProperty(cx_->names(), key)) return false; emitIdGuard(keyId, key); writer.guardGroup(objId, obj->group()); writer.loadBooleanResult(true); writer.returnFromIC(); trackAttached("TypedObjectHasProp"); return true; } bool HasPropIRGenerator::tryAttachSlotDoesNotExist(JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId) { bool hasOwn = (cacheKind_ == CacheKind::HasOwn); emitIdGuard(keyId, key); if (hasOwn) { Maybe tempId; TestMatchingReceiver(writer, obj, objId, &tempId); } else { Maybe tempId; EmitReadSlotGuard(writer, obj, nullptr, objId, &tempId); } writer.loadBooleanResult(false); writer.returnFromIC(); trackAttached("DoesNotExist"); return true; } bool 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 Native/Unboxed/Typed objects with a NativeObject prototype // chain. They return false if unknown such as resolve hooks or proxies. if (hasOwn) { if (!CheckHasNoSuchOwnProperty(cx_, obj, key)) return false; } else { if (!CheckHasNoSuchProperty(cx_, obj, key)) return false; } if (tryAttachMegamorphic(objId, keyId)) return true; if (tryAttachSlotDoesNotExist(obj, objId, key, keyId)) return true; return false; } bool HasPropIRGenerator::tryAttachProxyElement(HandleObject obj, ObjOperandId objId, ValOperandId keyId) { bool hasOwn = (cacheKind_ == CacheKind::HasOwn); if (!obj->is()) return false; writer.guardIsProxy(objId); writer.callProxyHasPropResult(objId, keyId, hasOwn); writer.returnFromIC(); trackAttached("ProxyHasProp"); return true; } bool 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()) { trackNotAttached(); return false; } RootedObject obj(cx_, &val_.toObject()); ObjOperandId objId = writer.guardIsObject(valId); // Optimize Proxies if (tryAttachProxyElement(obj, objId, keyId)) return true; RootedId id(cx_); bool nameOrSymbol; if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) { cx_->clearPendingException(); return false; } if (nameOrSymbol) { if (tryAttachNamedProp(obj, objId, id, keyId)) return true; if (tryAttachDoesNotExist(obj, objId, id, keyId)) return true; trackNotAttached(); return false; } uint32_t index; Int32OperandId indexId; if (maybeGuardInt32Index(idVal_, keyId, &index, &indexId)) { if (tryAttachDense(obj, objId, index, indexId)) return true; if (tryAttachDenseHole(obj, objId, index, indexId)) return true; + if (tryAttachTypedArray(obj, objId, index, indexId)) + return true; trackNotAttached(); return false; } trackNotAttached(); return false; } void HasPropIRGenerator::trackAttached(const char* name) { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "base", val_); sp.valueProperty(guard, "property", idVal_); sp.attached(guard, name); sp.endCache(guard); } #endif } void HasPropIRGenerator::trackNotAttached() { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "base", val_); sp.valueProperty(guard, "property", idVal_); sp.endCache(guard); } #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 (!cx_->runtime()->jitSupportsFloatingPoint) return false; } if (indexSigned < 0) return false; *int32Index = uint32_t(indexSigned); *int32IndexId = writer.guardIsInt32Index(indexId); return true; } if (index.isString()) { int32_t indexSigned = GetIndexFromString(index.toString()); if (indexSigned < 0) return false; StringOperandId strId = writer.guardIsString(indexId); *int32Index = uint32_t(indexSigned); *int32IndexId = writer.guardAndGetIndexFromString(strId); return true; } return false; } SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind, ICState::Mode mode, bool* isTemporarilyUnoptimizable, HandleValue lhsVal, HandleValue idVal, HandleValue rhsVal, bool needsTypeBarrier, bool maybeHasExtraIndexedProps) : IRGenerator(cx, script, pc, cacheKind, mode), lhsVal_(lhsVal), idVal_(idVal), rhsVal_(rhsVal), isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable), typeCheckInfo_(cx, needsTypeBarrier), preliminaryObjectAction_(PreliminaryObjectAction::None), attachedTypedArrayOOBStub_(false), maybeHasExtraIndexedProps_(maybeHasExtraIndexedProps) {} bool 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 false; } if (lhsVal_.isObject()) { RootedObject obj(cx_, &lhsVal_.toObject()); ObjOperandId objId = writer.guardIsObject(objValId); if (IsPropertySetOp(JSOp(*pc_))) { if (tryAttachMegamorphicSetElement(obj, objId, rhsValId)) return true; } if (nameOrSymbol) { if (tryAttachNativeSetSlot(obj, objId, id, rhsValId)) return true; if (tryAttachUnboxedExpandoSetSlot(obj, objId, id, rhsValId)) return true; if (tryAttachUnboxedProperty(obj, objId, id, rhsValId)) return true; if (tryAttachTypedObjectProperty(obj, objId, id, rhsValId)) return true; if (tryAttachSetArrayLength(obj, objId, id, rhsValId)) return true; if (IsPropertySetOp(JSOp(*pc_))) { if (tryAttachSetter(obj, objId, id, rhsValId)) return true; if (tryAttachWindowProxy(obj, objId, id, rhsValId)) return true; if (tryAttachProxy(obj, objId, id, rhsValId)) return true; } return false; } if (IsPropertySetOp(JSOp(*pc_))) { if (tryAttachProxyElement(obj, objId, rhsValId)) return true; } uint32_t index; Int32OperandId indexId; if (maybeGuardInt32Index(idVal_, setElemKeyValueId(), &index, &indexId)) { if (tryAttachSetDenseElement(obj, objId, index, indexId, rhsValId)) return true; if (tryAttachSetDenseElementHole(obj, objId, index, indexId, rhsValId)) return true; if (tryAttachSetTypedElement(obj, objId, index, indexId, rhsValId)) return true; return false; } return false; } return false; } 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(JSContext* cx, JSOp op, HandleObject obj, HandleId id, bool* isTemporarilyUnoptimizable, MutableHandleShape propShape) { if (!obj->isNative()) return false; propShape.set(LookupShapeForSetSlot(op, &obj->as(), id)); if (!propShape) return false; ObjectGroup* group = JSObject::getGroup(cx, obj); if (!group) { cx->recoverFromOutOfMemory(); return false; } // For some property writes, such as the initial overwrite of global // properties, TI will not mark the property as having been // overwritten. Don't attach a stub in this case, so that we don't // execute another write to the property without TI seeing that write. EnsureTrackPropertyTypes(cx, obj, id); if (!PropertyHasBeenMarkedNonConstant(obj, id)) { *isTemporarilyUnoptimizable = true; return false; } return true; } bool SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { RootedShape propShape(cx_); if (!CanAttachNativeSetSlot(cx_, JSOp(*pc_), obj, id, isTemporarilyUnoptimizable_, &propShape)) return false; if (mode_ == ICState::Mode::Megamorphic && cacheKind_ == CacheKind::SetProp) { writer.megamorphicStoreSlot(objId, JSID_TO_ATOM(id)->asPropertyName(), rhsId, typeCheckInfo_.needsTypeBarrier()); writer.returnFromIC(); trackAttached("MegamorphicNativeSlot"); return true; } maybeEmitIdGuard(id); // If we need a property type barrier (always in Baseline, sometimes in // Ion), guard on both the shape and the group. If Ion knows the property // types match, we don't need the group guard. NativeObject* nobj = &obj->as(); if (typeCheckInfo_.needsTypeBarrier()) writer.guardGroup(objId, nobj->group()); writer.guardShape(objId, nobj->lastProperty()); if (IsPreliminaryObject(obj)) preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary; else preliminaryObjectAction_ = PreliminaryObjectAction::Unlink; typeCheckInfo_.set(nobj->group(), id); EmitStoreSlotAndReturn(writer, objId, nobj, propShape, rhsId); trackAttached("NativeSlot"); return true; } bool SetPropIRGenerator::tryAttachUnboxedExpandoSetSlot(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { if (!obj->is()) return false; UnboxedExpandoObject* expando = obj->as().maybeExpando(); if (!expando) return false; Shape* propShape = LookupShapeForSetSlot(JSOp(*pc_), expando, id); if (!propShape) return false; maybeEmitIdGuard(id); writer.guardGroup(objId, obj->group()); ObjOperandId expandoId = writer.guardAndLoadUnboxedExpando(objId); writer.guardShape(expandoId, expando->lastProperty()); // Property types must be added to the unboxed object's group, not the // expando's group (it has unknown properties). typeCheckInfo_.set(obj->group(), id); EmitStoreSlotAndReturn(writer, expandoId, expando, propShape, rhsId); trackAttached("UnboxedExpando"); return true; } static void EmitGuardUnboxedPropertyType(CacheIRWriter& writer, JSValueType propType, ValOperandId valId) { if (propType == JSVAL_TYPE_OBJECT) { // Unboxed objects store NullValue as nullptr object. writer.guardIsObjectOrNull(valId); } else { writer.guardType(valId, propType); } } bool SetPropIRGenerator::tryAttachUnboxedProperty(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { if (!obj->is() || !cx_->runtime()->jitSupportsFloatingPoint) return false; const UnboxedLayout::Property* property = obj->as().layout().lookup(id); if (!property) return false; maybeEmitIdGuard(id); writer.guardGroup(objId, obj->group()); EmitGuardUnboxedPropertyType(writer, property->type, rhsId); writer.storeUnboxedProperty(objId, property->type, UnboxedPlainObject::offsetOfData() + property->offset, rhsId); writer.returnFromIC(); typeCheckInfo_.set(obj->group(), id); preliminaryObjectAction_ = PreliminaryObjectAction::Unlink; trackAttached("Unboxed"); return true; } bool SetPropIRGenerator::tryAttachTypedObjectProperty(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { if (!obj->is() || !cx_->runtime()->jitSupportsFloatingPoint) return false; if (cx_->compartment()->detachedTypedObjects) return false; if (!obj->as().typeDescr().is()) return false; StructTypeDescr* structDescr = &obj->as().typeDescr().as(); size_t fieldIndex; if (!structDescr->fieldIndex(id, &fieldIndex)) return false; TypeDescr* fieldDescr = &structDescr->fieldDescr(fieldIndex); if (!fieldDescr->is()) return false; uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex); TypedThingLayout layout = GetTypedThingLayout(obj->getClass()); maybeEmitIdGuard(id); writer.guardNoDetachedTypedObjects(); writer.guardShape(objId, obj->as().shape()); writer.guardGroup(objId, obj->group()); typeCheckInfo_.set(obj->group(), id); // Scalar types can always be stored without a type update stub. if (fieldDescr->is()) { Scalar::Type type = fieldDescr->as().type(); writer.storeTypedObjectScalarProperty(objId, fieldOffset, layout, type, rhsId); writer.returnFromIC(); trackAttached("TypedObject"); return true; } // For reference types, guard on the RHS type first, so that // StoreTypedObjectReferenceProperty is infallible. ReferenceTypeDescr::Type type = fieldDescr->as().type(); switch (type) { case ReferenceTypeDescr::TYPE_ANY: break; case ReferenceTypeDescr::TYPE_OBJECT: writer.guardIsObjectOrNull(rhsId); break; case ReferenceTypeDescr::TYPE_STRING: writer.guardType(rhsId, JSVAL_TYPE_STRING); break; } writer.storeTypedObjectReferenceProperty(objId, fieldOffset, layout, type, rhsId); writer.returnFromIC(); trackAttached("TypedObject"); return true; } void SetPropIRGenerator::trackAttached(const char* name) { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "base", lhsVal_); sp.valueProperty(guard, "property", idVal_); sp.valueProperty(guard, "value", rhsVal_); sp.attached(guard, name); sp.endCache(guard); } #endif } void SetPropIRGenerator::trackNotAttached() { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "base", lhsVal_); sp.valueProperty(guard, "property", idVal_); sp.valueProperty(guard, "value", rhsVal_); sp.endCache(guard); } #endif } static bool IsCacheableSetPropCallNative(JSObject* obj, JSObject* holder, Shape* shape) { if (!shape || !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.isNative()) return false; if (setter.isClassConstructor()) return false; if (setter.jitInfo() && !setter.jitInfo()->needsOuterizedThisObject()) return true; return !IsWindow(obj); } static bool IsCacheableSetPropCallScripted(JSObject* obj, JSObject* holder, Shape* shape, bool* isTemporarilyUnoptimizable = nullptr) { if (!shape || !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.isNative()) return false; if (!setter.hasScript()) { if (isTemporarilyUnoptimizable) *isTemporarilyUnoptimizable = true; return false; } if (setter.isClassConstructor()) return false; return true; } static bool CanAttachSetter(JSContext* cx, jsbytecode* pc, HandleObject obj, HandleId id, MutableHandleObject holder, MutableHandleShape propShape, bool* isTemporarilyUnoptimizable) { // 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.isNonNativeProperty()) return false; propShape.set(prop.maybeShape()); if (!IsCacheableSetPropCallScripted(obj, holder, propShape, isTemporarilyUnoptimizable) && !IsCacheableSetPropCallNative(obj, holder, propShape)) { return false; } return true; } static void EmitCallSetterNoGuards(CacheIRWriter& writer, JSObject* obj, JSObject* holder, Shape* shape, ObjOperandId objId, ValOperandId rhsId) { if (IsCacheableSetPropCallNative(obj, holder, shape)) { JSFunction* target = &shape->setterValue().toObject().as(); MOZ_ASSERT(target->isNative()); writer.callNativeSetter(objId, target, rhsId); writer.returnFromIC(); return; } MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, shape)); JSFunction* target = &shape->setterValue().toObject().as(); MOZ_ASSERT(target->hasScript()); writer.callScriptedSetter(objId, target, rhsId); writer.returnFromIC(); } bool SetPropIRGenerator::tryAttachSetter(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { RootedObject holder(cx_); RootedShape propShape(cx_); if (!CanAttachSetter(cx_, pc_, obj, id, &holder, &propShape, isTemporarilyUnoptimizable_)) return false; 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)) { Maybe expandoId; TestMatchingReceiver(writer, obj, objId, &expandoId); if (obj != holder) { GeneratePrototypeGuards(writer, obj, holder, objId); // Guard on the holder's shape. ObjOperandId holderId = writer.loadObject(holder); writer.guardShape(holderId, holder->as().lastProperty()); } } else { writer.guardHasGetterSetter(objId, propShape); } EmitCallSetterNoGuards(writer, obj, holder, propShape, objId, rhsId); trackAttached("Setter"); return true; } bool SetPropIRGenerator::tryAttachSetArrayLength(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { if (!obj->is() || !JSID_IS_ATOM(id, cx_->names().length) || !obj->as().lengthIsWritable()) { return false; } maybeEmitIdGuard(id); writer.guardClass(objId, GuardClassKind::Array); writer.callSetArrayLength(objId, IsStrictSetPC(pc_), rhsId); writer.returnFromIC(); trackAttached("SetArrayLength"); return true; } bool SetPropIRGenerator::tryAttachSetDenseElement(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId, ValOperandId rhsId) { if (!obj->isNative()) return false; NativeObject* nobj = &obj->as(); if (!nobj->containsDenseElement(index) || nobj->getElementsHeader()->isFrozen()) return false; if (typeCheckInfo_.needsTypeBarrier()) writer.guardGroup(objId, nobj->group()); writer.guardShape(objId, nobj->shape()); writer.storeDenseElement(objId, indexId, rhsId); writer.returnFromIC(); // Type inference uses JSID_VOID for the element types. typeCheckInfo_.set(nobj->group(), JSID_VOID); trackAttached("SetDenseElement"); return true; } static bool CanAttachAddElement(JSObject* 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->isNative() && obj->as().isIndexed()) return false; const Class* 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; obj = proto; } while (true); return true; } static void ShapeGuardProtoChain(CacheIRWriter& writer, JSObject* obj, ObjOperandId objId) { while (true) { // Guard on the proto if the shape does not imply the proto. Singleton // objects always trigger a shape change when the proto changes, so we // don't need a guard in that case. bool guardProto = obj->hasUncacheableProto() && !obj->isSingleton(); obj = obj->staticPrototype(); if (!obj) return; objId = writer.loadProto(objId); if (guardProto) writer.guardSpecificObject(objId, obj); writer.guardShape(objId, obj->as().shape()); } } bool SetPropIRGenerator::tryAttachSetDenseElementHole(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId, ValOperandId rhsId) { if (!obj->isNative() || rhsVal_.isMagic(JS_ELEMENTS_HOLE)) return false; JSOp op = JSOp(*pc_); MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op)); if (op == JSOP_INITHIDDENELEM) return false; NativeObject* nobj = &obj->as(); if (!nobj->nonProxyIsExtensible()) return false; MOZ_ASSERT(!nobj->getElementsHeader()->isFrozen(), "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. // Don't handle the adding case if the current accesss is in bounds, to // ensure we always call noteArrayWriteHole. bool isAdd = index == initLength; bool isHoleInBounds = index < initLength && !nobj->containsDenseElement(index); if (!isAdd && !isHoleInBounds) return false; // Can't add new elements to arrays with non-writable length. if (isAdd && nobj->is() && !nobj->as().lengthIsWritable()) return false; // Typed arrays don't have dense elements. if (nobj->is()) return false; // Check for other indexed properties or class hooks. if (!CanAttachAddElement(nobj, IsPropertyInitOp(op))) return false; if (typeCheckInfo_.needsTypeBarrier()) writer.guardGroup(objId, nobj->group()); writer.guardShape(objId, nobj->shape()); // Also shape guard the proto chain, unless this is an INITELEM or we know // the proto chain has no indexed props. if (IsPropertySetOp(op) && maybeHasExtraIndexedProps_) ShapeGuardProtoChain(writer, obj, objId); writer.storeDenseElementHole(objId, indexId, rhsId, isAdd); writer.returnFromIC(); // Type inference uses JSID_VOID for the element types. typeCheckInfo_.set(nobj->group(), JSID_VOID); trackAttached(isAdd ? "AddDenseElement" : "StoreDenseElementHole"); return true; } bool SetPropIRGenerator::tryAttachSetTypedElement(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId, ValOperandId rhsId) { if (!obj->is() && !IsPrimitiveArrayTypedObject(obj)) return false; if (!rhsVal_.isNumber()) return false; if (!cx_->runtime()->jitSupportsFloatingPoint && TypedThingRequiresFloatingPoint(obj)) return false; bool handleOutOfBounds = false; if (obj->is()) { handleOutOfBounds = (index >= obj->as().length()); } else { // Typed objects throw on out of bounds accesses. Don't attach // a stub in this case. if (index >= obj->as().length()) return false; // Don't attach stubs if the underlying storage for typed objects // in the compartment could be detached, as the stub will always // bail out. if (cx_->compartment()->detachedTypedObjects) return false; } Scalar::Type elementType = TypedThingElementType(obj); TypedThingLayout layout = GetTypedThingLayout(obj->getClass()); if (!obj->is()) writer.guardNoDetachedTypedObjects(); writer.guardShape(objId, obj->as().shape()); writer.storeTypedElement(objId, indexId, rhsId, layout, elementType, handleOutOfBounds); writer.returnFromIC(); if (handleOutOfBounds) attachedTypedArrayOOBStub_ = true; trackAttached(handleOutOfBounds ? "SetTypedElementOOB" : "SetTypedElement"); return true; } bool 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.guardNotDOMProxy(objId); } if (cacheKind_ == CacheKind::SetProp || mode_ == ICState::Mode::Specialized) { maybeEmitIdGuard(id); writer.callProxySet(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.callProxySetByValue(objId, setElemKeyValueId(), rhsId, IsStrictSetPC(pc_)); } writer.returnFromIC(); trackAttached("GenericProxy"); return true; } bool SetPropIRGenerator::tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { MOZ_ASSERT(IsCacheableDOMProxy(obj)); maybeEmitIdGuard(id); writer.guardShape(objId, obj->maybeShape()); // No need for more guards: we know this is a DOM proxy, since the shape // guard enforces a given JSClass, so just go ahead and emit the call to // ProxySet. writer.callProxySet(objId, id, rhsId, IsStrictSetPC(pc_)); writer.returnFromIC(); trackAttached("DOMProxyShadowed"); return true; } bool SetPropIRGenerator::tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { MOZ_ASSERT(IsCacheableDOMProxy(obj)); RootedObject proto(cx_, obj->staticPrototype()); if (!proto) return false; RootedObject holder(cx_); RootedShape propShape(cx_); if (!CanAttachSetter(cx_, pc_, proto, id, &holder, &propShape, isTemporarilyUnoptimizable_)) return false; maybeEmitIdGuard(id); writer.guardShape(objId, obj->maybeShape()); // Guard that our expando object hasn't started shadowing this property. CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId); GeneratePrototypeGuards(writer, obj, holder, objId); // Guard on the holder of the property. ObjOperandId holderId = writer.loadObject(holder); writer.guardShape(holderId, holder->as().lastProperty()); // 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(writer, proto, holder, propShape, objId, rhsId); trackAttached("DOMProxyUnshadowed"); return true; } bool 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(cx_, JSOp(*pc_), expandoObj, id, isTemporarilyUnoptimizable_, &propShape)) { maybeEmitIdGuard(id); ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj); NativeObject* nativeExpandoObj = &expandoObj->as(); writer.guardGroup(expandoObjId, nativeExpandoObj->group()); typeCheckInfo_.set(nativeExpandoObj->group(), id); EmitStoreSlotAndReturn(writer, expandoObjId, nativeExpandoObj, propShape, rhsId); trackAttached("DOMProxyExpandoSlot"); return true; } RootedObject holder(cx_); if (CanAttachSetter(cx_, pc_, expandoObj, id, &holder, &propShape, isTemporarilyUnoptimizable_)) { // 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(writer, expandoObj, expandoObj, propShape, objId, rhsId); trackAttached("DOMProxyExpandoSetter"); return true; } return false; } bool 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 false; if (mode_ == ICState::Mode::Megamorphic) return tryAttachGenericProxy(obj, objId, id, rhsId, /* handleDOMProxies = */ true); switch (type) { case ProxyStubType::None: break; case ProxyStubType::DOMExpando: if (tryAttachDOMProxyExpando(obj, objId, id, rhsId)) return true; if (*isTemporarilyUnoptimizable_) { // Scripted setter without JIT code. Just wait. return false; } MOZ_FALLTHROUGH; // Fall through to the generic shadowed case. case ProxyStubType::DOMShadowed: return tryAttachDOMProxyShadowed(obj, objId, id, rhsId); case ProxyStubType::DOMUnshadowed: if (tryAttachDOMProxyUnshadowed(obj, objId, id, rhsId)) return true; if (*isTemporarilyUnoptimizable_) { // Scripted setter without JIT code. Just wait. return false; } return tryAttachGenericProxy(obj, objId, id, rhsId, /* handleDOMProxies = */ true); case ProxyStubType::Generic: return tryAttachGenericProxy(obj, objId, id, rhsId, /* handleDOMProxies = */ false); } MOZ_CRASH("Unexpected ProxyStubType"); } bool 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 false; 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.callProxySetByValue(objId, setElemKeyValueId(), rhsId, IsStrictSetPC(pc_)); writer.returnFromIC(); trackAttached("ProxyElement"); return true; } bool SetPropIRGenerator::tryAttachMegamorphicSetElement(HandleObject obj, ObjOperandId objId, ValOperandId rhsId) { MOZ_ASSERT(IsPropertySetOp(JSOp(*pc_))); if (mode_ != ICState::Mode::Megamorphic || cacheKind_ != CacheKind::SetElem) return false; // The generic proxy stubs are faster. if (obj->is()) return false; writer.megamorphicSetElement(objId, setElemKeyValueId(), rhsId, IsStrictSetPC(pc_)); writer.returnFromIC(); trackAttached("MegamorphicSetElement"); return true; } bool 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 (!IsWindowProxy(obj)) return false; // If we're megamorphic prefer a generic proxy stub that handles a lot more // cases. if (mode_ == ICState::Mode::Megamorphic) return false; // This must be a WindowProxy for the current Window/global. Else it would // be a cross-compartment wrapper and IsWindowProxy returns false for // those. MOZ_ASSERT(obj->getClass() == cx_->runtime()->maybeWindowProxyClass()); MOZ_ASSERT(ToWindowIfWindowProxy(obj) == cx_->global()); // Now try to do the set on the Window (the current global). Handle windowObj = cx_->global(); RootedShape propShape(cx_); if (!CanAttachNativeSetSlot(cx_, JSOp(*pc_), windowObj, id, isTemporarilyUnoptimizable_, &propShape)) { return false; } maybeEmitIdGuard(id); writer.guardClass(objId, GuardClassKind::WindowProxy); ObjOperandId windowObjId = writer.loadObject(windowObj); writer.guardShape(windowObjId, windowObj->lastProperty()); writer.guardGroup(windowObjId, windowObj->group()); typeCheckInfo_.set(windowObj->group(), id); EmitStoreSlotAndReturn(writer, windowObjId, windowObj, propShape, rhsId); trackAttached("WindowProxySlot"); return true; } bool SetPropIRGenerator::tryAttachAddSlotStub(HandleObjectGroup oldGroup, HandleShape oldShape) { 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 false; } if (!lhsVal_.isObject() || !nameOrSymbol) return false; RootedObject obj(cx_, &lhsVal_.toObject()); PropertyResult prop; JSObject* holder; if (!LookupPropertyPure(cx_, obj, id, &holder, &prop)) return false; if (obj != holder) return false; Shape* propShape = nullptr; NativeObject* holderOrExpando = nullptr; if (obj->isNative()) { propShape = prop.shape(); holderOrExpando = &obj->as(); } else { if (!obj->is()) return false; UnboxedExpandoObject* expando = obj->as().maybeExpando(); if (!expando) return false; propShape = expando->lookupPure(id); if (!propShape) return false; holderOrExpando = expando; } MOZ_ASSERT(propShape); // The property must be the last added property of the object. if (holderOrExpando->lastProperty() != propShape) return false; // Object must be extensible, oldShape must be immediate parent of // current shape. if (!obj->nonProxyIsExtensible() || propShape->previous() != oldShape) return false; // Basic shape checks. if (propShape->inDictionary() || !propShape->isDataProperty() || !propShape->writable()) { return false; } // Watch out for resolve hooks. if (ClassMayResolveId(cx_->names(), obj->getClass(), id, obj)) { // The JSFunction resolve hook defines a (non-configurable and // non-enumerable) |prototype| property on certain functions. Scripts // often assign a custom |prototype| object and we want to optimize // this |prototype| set and eliminate the default object allocation. // // We check group->maybeInterpretedFunction() here and guard on the // group. The group is unique for a particular function so this ensures // we don't add the default prototype property to functions that don't // have it. if (!obj->is() || !JSID_IS_ATOM(id, cx_->names().prototype) || !oldGroup->maybeInterpretedFunction() || !obj->as().needsPrototypeProperty()) { return false; } MOZ_ASSERT(!propShape->configurable()); MOZ_ASSERT(!propShape->enumerable()); } // 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->hasDefaultSetter()) 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; } } ObjOperandId objId = writer.guardIsObject(objValId); maybeEmitIdGuard(id); writer.guardGroup(objId, oldGroup); // If we are adding a property to an object for which the new script // properties analysis hasn't been performed yet, make sure the stub fails // after we run the analysis as a group change may be required here. The // group change is not required for correctness but improves type // information elsewhere. if (oldGroup->newScript() && !oldGroup->newScript()->analyzed()) { writer.guardGroupHasUnanalyzedNewScript(oldGroup); MOZ_ASSERT(IsPreliminaryObject(obj)); preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary; } else { preliminaryObjectAction_ = PreliminaryObjectAction::Unlink; } // Shape guard the holder. ObjOperandId holderId = objId; if (!obj->isNative()) { MOZ_ASSERT(obj->as().maybeExpando()); holderId = writer.guardAndLoadUnboxedExpando(objId); } writer.guardShape(holderId, oldShape); ShapeGuardProtoChain(writer, obj, objId); ObjectGroup* newGroup = obj->group(); // Check if we have to change the object's group. If we're adding an // unboxed expando property, we pass the expando object to AddAndStore*Slot. // That's okay because we only have to do a group change if the object is a // PlainObject. bool changeGroup = oldGroup != newGroup; MOZ_ASSERT_IF(changeGroup, obj->is()); if (holderOrExpando->isFixedSlot(propShape->slot())) { size_t offset = NativeObject::getFixedSlotOffset(propShape->slot()); writer.addAndStoreFixedSlot(holderId, offset, rhsValId, propShape, changeGroup, newGroup); trackAttached("AddSlot"); } else { size_t offset = holderOrExpando->dynamicSlotIndex(propShape->slot()) * sizeof(Value); uint32_t numOldSlots = NativeObject::dynamicSlotsCount(oldShape); uint32_t numNewSlots = NativeObject::dynamicSlotsCount(propShape); if (numOldSlots == numNewSlots) { writer.addAndStoreDynamicSlot(holderId, offset, rhsValId, propShape, changeGroup, newGroup); trackAttached("AddSlot"); } else { MOZ_ASSERT(numNewSlots > numOldSlots); writer.allocateAndStoreDynamicSlot(holderId, offset, rhsValId, propShape, changeGroup, newGroup, numNewSlots); trackAttached("AllocateSlot"); } } writer.returnFromIC(); typeCheckInfo_.set(oldGroup, id); return true; } TypeOfIRGenerator::TypeOfIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode, HandleValue value) : IRGenerator(cx, script, pc, CacheKind::TypeOf, mode), val_(value) { } bool TypeOfIRGenerator::tryAttachStub() { MOZ_ASSERT(cacheKind_ == CacheKind::TypeOf); AutoAssertNoPendingException aanpe(cx_); ValOperandId valId(writer.setInputOperandId(0)); if (tryAttachPrimitive(valId)) return true; MOZ_ALWAYS_TRUE(tryAttachObject(valId)); return true; } bool TypeOfIRGenerator::tryAttachPrimitive(ValOperandId valId) { if (!val_.isPrimitive()) return false; writer.guardType(valId, val_.isNumber() ? JSVAL_TYPE_DOUBLE : val_.extractNonDoubleType()); writer.loadStringResult(TypeName(js::TypeOfValue(val_), cx_->names())); writer.returnFromIC(); return true; } bool TypeOfIRGenerator::tryAttachObject(ValOperandId valId) { if (!val_.isObject()) return false; ObjOperandId objId = writer.guardIsObject(valId); writer.loadTypeOfObjectResult(objId); writer.returnFromIC(); return true; } GetIteratorIRGenerator::GetIteratorIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode, HandleValue value) : IRGenerator(cx, script, pc, CacheKind::GetIterator, mode), val_(value) { } bool GetIteratorIRGenerator::tryAttachStub() { MOZ_ASSERT(cacheKind_ == CacheKind::GetIterator); AutoAssertNoPendingException aanpe(cx_); if (mode_ == ICState::Mode::Megamorphic) return false; ValOperandId valId(writer.setInputOperandId(0)); if (!val_.isObject()) return false; RootedObject obj(cx_, &val_.toObject()); ObjOperandId objId = writer.guardIsObject(valId); if (tryAttachNativeIterator(objId, obj)) return true; return false; } bool GetIteratorIRGenerator::tryAttachNativeIterator(ObjOperandId objId, HandleObject obj) { MOZ_ASSERT(JSOp(*pc_) == JSOP_ITER); PropertyIteratorObject* iterobj = LookupInIteratorCache(cx_, obj); if (!iterobj) return false; MOZ_ASSERT(obj->isNative() || obj->is()); // Guard on the receiver's shape/group. Maybe expandoId; TestMatchingReceiver(writer, obj, objId, &expandoId); // Ensure the receiver or its expando object has no dense elements. if (obj->isNative()) writer.guardNoDenseElements(objId); else if (expandoId) writer.guardNoDenseElements(*expandoId); // Do the same for the objects on the proto chain. GeneratePrototypeHoleGuards(writer, obj, objId); ObjOperandId iterId = writer.guardAndGetIterator(objId, iterobj, &cx_->compartment()->enumerators); writer.loadObjectResult(iterId); writer.returnFromIC(); return true; } CallIRGenerator::CallIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, JSOp op, ICCall_Fallback* stub, ICState::Mode mode, uint32_t argc, HandleValue callee, HandleValue thisval, HandleValueArray args) : IRGenerator(cx, script, pc, CacheKind::Call, mode), op_(op), argc_(argc), callee_(callee), thisval_(thisval), args_(args), typeCheckInfo_(cx, /* needsTypeBarrier = */ true), cacheIRStubKind_(BaselineCacheIRStubKind::Regular) { } bool CallIRGenerator::tryAttachStringSplit() { // Only optimize StringSplitString(str, str) if (argc_ != 2 || !args_[0].isString() || !args_[1].isString()) return false; // Just for now: if they're both atoms, then do not optimize using // CacheIR and allow the legacy "ConstStringSplit" BaselineIC optimization // to proceed. if (args_[0].toString()->isAtom() && args_[1].toString()->isAtom()) return false; // Get the object group to use for this location. RootedObjectGroup group(cx_, ObjectGroupCompartment::getStringSplitStringGroup(cx_)); if (!group) return false; AutoAssertNoPendingException aanpe(cx_); Int32OperandId argcId(writer.setInputOperandId(0)); // Ensure argc == 1. writer.guardSpecificInt32Immediate(argcId, 2); // 2 arguments. Stack-layout here is (bottom to top): // // 3: Callee // 2: ThisValue // 1: Arg0 // 0: Arg1 <-- Top of stack // Ensure callee is the |String_split| native function. ValOperandId calleeValId = writer.loadStackValue(3); ObjOperandId calleeObjId = writer.guardIsObject(calleeValId); writer.guardIsNativeFunction(calleeObjId, js::intrinsic_StringSplitString); // Ensure arg0 is a string. ValOperandId arg0ValId = writer.loadStackValue(1); StringOperandId arg0StrId = writer.guardIsString(arg0ValId); // Ensure arg1 is a string. ValOperandId arg1ValId = writer.loadStackValue(0); StringOperandId arg1StrId = writer.guardIsString(arg1ValId); // Call custom string splitter VM-function. writer.callStringSplitResult(arg0StrId, arg1StrId, group); writer.typeMonitorResult(); cacheIRStubKind_ = BaselineCacheIRStubKind::Monitored; trackAttached("StringSplitString"); TypeScript::Monitor(cx_, script_, pc_, TypeSet::ObjectType(group)); return true; } bool CallIRGenerator::tryAttachArrayPush() { // Only optimize on obj.push(val); if (argc_ != 1 || !thisval_.isObject()) return false; // Where |obj| is a native array. RootedObject thisobj(cx_, &thisval_.toObject()); if (!thisobj->is()) return false; RootedArrayObject thisarray(cx_, &thisobj->as()); // And the object group for the array is not collecting preliminary objects. if (thisobj->group()->maybePreliminaryObjects()) return false; // Check for other indexed properties or class hooks. if (!CanAttachAddElement(thisobj, /* isInit = */ false)) return false; // Can't add new elements to arrays with non-writable length. if (!thisarray->lengthIsWritable()) return false; // Check that array is extensible. if (!thisarray->nonProxyIsExtensible()) return false; MOZ_ASSERT(!thisarray->getElementsHeader()->isFrozen(), "Extensible arrays should not have frozen elements"); MOZ_ASSERT(thisarray->lengthIsWritable()); // After this point, we can generate code fine. // Generate code. AutoAssertNoPendingException aanpe(cx_); Int32OperandId argcId(writer.setInputOperandId(0)); // Ensure argc == 1. writer.guardSpecificInt32Immediate(argcId, 1); // 1 argument only. Stack-layout here is (bottom to top): // // 2: Callee // 1: ThisValue // 0: Arg0 <-- Top of stack. // Guard callee is the |js::array_push| native function. ValOperandId calleeValId = writer.loadStackValue(2); ObjOperandId calleeObjId = writer.guardIsObject(calleeValId); writer.guardIsNativeFunction(calleeObjId, js::array_push); // Guard this is an array object. ValOperandId thisValId = writer.loadStackValue(1); ObjOperandId thisObjId = writer.guardIsObject(thisValId); writer.guardClass(thisObjId, GuardClassKind::Array); // This is a soft assert, documenting the fact that we pass 'true' // for needsTypeBarrier when constructing typeCheckInfo_ for CallIRGenerator. // Can be removed safely if the assumption becomes false. MOZ_ASSERT(typeCheckInfo_.needsTypeBarrier()); // Guard that the group and shape matches. if (typeCheckInfo_.needsTypeBarrier()) writer.guardGroup(thisObjId, thisobj->group()); writer.guardShape(thisObjId, thisarray->shape()); // Guard proto chain shapes. ShapeGuardProtoChain(writer, thisobj, thisObjId); // arr.push(x) is equivalent to arr[arr.length] = x for regular arrays. ValOperandId argId = writer.loadStackValue(0); writer.arrayPush(thisObjId, argId); writer.returnFromIC(); // Set the type-check info, and the stub kind to Updated typeCheckInfo_.set(thisobj->group(), JSID_VOID); cacheIRStubKind_ = BaselineCacheIRStubKind::Updated; trackAttached("ArrayPush"); return true; } bool CallIRGenerator::tryAttachArrayJoin() { // Only handle argc <= 1. if (argc_ > 1) return false; // Only optimize on obj.join(...); if (!thisval_.isObject()) return false; // Where |obj| is a native array. RootedObject thisobj(cx_, &thisval_.toObject()); if (!thisobj->is()) return false; RootedArrayObject thisarray(cx_, &thisobj->as()); // And the array is of length 0 or 1. if (thisarray->length() > 1) return false; // And the array is packed. if (thisarray->getDenseInitializedLength() != thisarray->length()) return false; // We don't need to worry about indexed properties because we can perform // hole check manually. // Generate code. AutoAssertNoPendingException aanpe(cx_); Int32OperandId argcId(writer.setInputOperandId(0)); // if 0 arguments: // 1: Callee // 0: ThisValue <-- Top of stack. // // if 1 argument: // 2: Callee // 1: ThisValue // 0: Arg0 [optional] <-- Top of stack. // Guard callee is the |js::array_join| native function. uint32_t calleeIndex = (argc_ == 0) ? 1 : 2; ValOperandId calleeValId = writer.loadStackValue(calleeIndex); ObjOperandId calleeObjId = writer.guardIsObject(calleeValId); writer.guardIsNativeFunction(calleeObjId, js::array_join); if (argc_ == 1) { // If argcount is 1, guard that the argument is a string. ValOperandId argValId = writer.loadStackValue(0); writer.guardIsString(argValId); } // Guard this is an array object. uint32_t thisIndex = (argc_ == 0) ? 0 : 1; ValOperandId thisValId = writer.loadStackValue(thisIndex); ObjOperandId thisObjId = writer.guardIsObject(thisValId); writer.guardClass(thisObjId, GuardClassKind::Array); // Do the join. writer.arrayJoinResult(thisObjId); writer.returnFromIC(); // The result of this stub does not need to be monitored because it will // always return a string. We will add String to the stack typeset when // attaching this stub. // Set the stub kind to Regular cacheIRStubKind_ = BaselineCacheIRStubKind::Regular; trackAttached("ArrayJoin"); return true; } bool CallIRGenerator::tryAttachStub() { // Only optimize on JSOP_CALL or JSOP_CALL_IGNORES_RV. No fancy business for now. if ((op_ != JSOP_CALL) && (op_ != JSOP_CALL_IGNORES_RV)) return false; // Only optimize when the mode is Specialized. if (mode_ != ICState::Mode::Specialized) return false; // Ensure callee is a function. if (!callee_.isObject() || !callee_.toObject().is()) return false; RootedFunction calleeFunc(cx_, &callee_.toObject().as()); // Check for native-function optimizations. if (calleeFunc->isNative()) { if (calleeFunc->native() == js::intrinsic_StringSplitString) { if (tryAttachStringSplit()) return true; } if (calleeFunc->native() == js::array_push) { if (tryAttachArrayPush()) return true; } if (calleeFunc->native() == js::array_join) { if (tryAttachArrayJoin()) return true; } } return false; } void CallIRGenerator::trackAttached(const char* name) { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "callee", callee_); sp.valueProperty(guard, "thisval", thisval_); sp.valueProperty(guard, "argc", Int32Value(argc_)); sp.attached(guard, name); sp.endCache(guard); } #endif } // Class which holds a shape pointer for use when caches might reference data in other zones. static const Class 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); { AutoCompartment ac(cx, obj); wrapper = NewObjectWithClassProto(cx, &shapeContainerClass, nullptr); if (!obj) 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, detail::ProxyReservedSlots::offsetOfPrivateSlot()); masm.branchTestObject(Assembler::NotEqual, privateAddr, failure); masm.unboxObject(privateAddr, dst); masm.unboxNonDouble(Address(dst, NativeObject::getFixedSlotOffset(SHAPE_CONTAINER_SLOT)), dst); } void CallIRGenerator::trackNotAttached() { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "callee", callee_); sp.valueProperty(guard, "thisval", thisval_); sp.valueProperty(guard, "argc", Int32Value(argc_)); sp.endCache(guard); } #endif } 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) { } bool CompareIRGenerator::tryAttachString(ValOperandId lhsId, ValOperandId rhsId) { MOZ_ASSERT(IsEqualityOp(op_)); if (!lhsVal_.isString() || !rhsVal_.isString()) return false; StringOperandId lhsStrId = writer.guardIsString(lhsId); StringOperandId rhsStrId = writer.guardIsString(rhsId); writer.compareStringResult(op_, lhsStrId, rhsStrId); writer.returnFromIC(); trackAttached("String"); return true; } bool CompareIRGenerator::tryAttachObject(ValOperandId lhsId, ValOperandId rhsId) { MOZ_ASSERT(IsEqualityOp(op_)); if (!lhsVal_.isObject() || !rhsVal_.isObject()) return false; ObjOperandId lhsObjId = writer.guardIsObject(lhsId); ObjOperandId rhsObjId = writer.guardIsObject(rhsId); writer.compareObjectResult(op_, lhsObjId, rhsObjId); writer.returnFromIC(); trackAttached("Object"); return true; } bool CompareIRGenerator::tryAttachSymbol(ValOperandId lhsId, ValOperandId rhsId) { MOZ_ASSERT(IsEqualityOp(op_)); if (!lhsVal_.isSymbol() || !rhsVal_.isSymbol()) return false; SymbolOperandId lhsSymId = writer.guardIsSymbol(lhsId); SymbolOperandId rhsSymId = writer.guardIsSymbol(rhsId); writer.compareSymbolResult(op_, lhsSymId, rhsSymId); writer.returnFromIC(); trackAttached("Symbol"); return true; } bool CompareIRGenerator::tryAttachStub() { MOZ_ASSERT(cacheKind_ == CacheKind::Compare); MOZ_ASSERT(IsEqualityOp(op_) || op_ == JSOP_LE || op_ == JSOP_LT || op_ == JSOP_GE || op_ == JSOP_GT); AutoAssertNoPendingException aanpe(cx_); ValOperandId lhsId(writer.setInputOperandId(0)); ValOperandId rhsId(writer.setInputOperandId(1)); if (IsEqualityOp(op_)) { if (tryAttachString(lhsId, rhsId)) return true; if (tryAttachObject(lhsId, rhsId)) return true; if (tryAttachSymbol(lhsId, rhsId)) return true; trackNotAttached(); return false; } trackNotAttached(); return false; } void CompareIRGenerator::trackAttached(const char* name) { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "lhs", lhsVal_); sp.valueProperty(guard, "rhs", rhsVal_); sp.attached(guard, name); sp.endCache(guard); } #endif } void CompareIRGenerator::trackNotAttached() { #ifdef JS_CACHEIR_SPEW CacheIRSpewer& sp = CacheIRSpewer::singleton(); if (sp.enabled()) { LockGuard guard(sp.lock()); sp.beginCache(guard, *this); sp.valueProperty(guard, "lhs", lhsVal_); sp.valueProperty(guard, "rhs", rhsVal_); sp.endCache(guard); } #endif } diff --git a/js/src/jit/CacheIR.h b/js/src/jit/CacheIR.h --- a/js/src/jit/CacheIR.h +++ b/js/src/jit/CacheIR.h @@ -1,1556 +1,1564 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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/. */ #ifndef jit_CacheIR_h #define jit_CacheIR_h #include "mozilla/Maybe.h" #include "NamespaceImports.h" #include "gc/Rooting.h" #include "jit/CompactBuffer.h" #include "jit/ICState.h" #include "jit/SharedIC.h" namespace js { namespace jit { enum class BaselineCacheIRStubKind; // CacheIR is an (extremely simple) linear IR language for inline caches. // From this IR, we can generate machine code for Baseline or Ion IC stubs. // // IRWriter // -------- // CacheIR bytecode is written using IRWriter. This class also records some // metadata that's used by the Baseline and Ion code generators to generate // (efficient) machine code. // // Sharing Baseline stub code // -------------------------- // Baseline stores data (like Shape* and fixed slot offsets) inside the ICStub // structure, instead of embedding them directly in the JitCode. This makes // Baseline IC code slightly slower, but allows us to share IC code between // caches. CacheIR makes it easy to share code between stubs: stubs that have // the same CacheIR (and CacheKind), will have the same Baseline stub code. // // Baseline stubs that share JitCode also share a CacheIRStubInfo structure. // This class stores the CacheIR and the location of GC things stored in the // stub, for the GC. // // JitCompartment has a CacheIRStubInfo* -> JitCode* weak map that's used to // share both the IR and JitCode between CacheIR stubs. This HashMap owns the // stubInfo (it uses UniquePtr), so once there are no references left to the // shared stub code, we can also free the CacheIRStubInfo. // // Ion stubs // --------- // Unlike Baseline stubs, Ion stubs do not share stub code, and data stored in // the IonICStub is baked into JIT code. This is one of the reasons Ion stubs // are faster than Baseline stubs. Also note that Ion ICs contain more state // (see IonGetPropertyIC for example) and use dynamic input/output registers, // so sharing stub code for Ion would be much more difficult. // An OperandId represents either a cache input or a value returned by a // CacheIR instruction. Most code should use the ValOperandId and ObjOperandId // classes below. The ObjOperandId class represents an operand that's known to // be an object. class OperandId { protected: static const uint16_t InvalidId = UINT16_MAX; uint16_t id_; OperandId() : id_(InvalidId) {} explicit OperandId(uint16_t id) : id_(id) {} public: uint16_t id() const { return id_; } bool valid() const { return id_ != InvalidId; } }; class ValOperandId : public OperandId { public: ValOperandId() = default; explicit ValOperandId(uint16_t id) : OperandId(id) {} }; class ObjOperandId : public OperandId { public: ObjOperandId() = default; explicit ObjOperandId(uint16_t id) : OperandId(id) {} bool operator==(const ObjOperandId& other) const { return id_ == other.id_; } bool operator!=(const ObjOperandId& other) const { return id_ != other.id_; } }; class StringOperandId : public OperandId { public: StringOperandId() = default; explicit StringOperandId(uint16_t id) : OperandId(id) {} }; class SymbolOperandId : public OperandId { public: SymbolOperandId() = default; explicit SymbolOperandId(uint16_t id) : OperandId(id) {} }; class Int32OperandId : public OperandId { public: Int32OperandId() = default; explicit Int32OperandId(uint16_t id) : OperandId(id) {} }; class TypedOperandId : public OperandId { JSValueType type_; public: MOZ_IMPLICIT TypedOperandId(ObjOperandId id) : OperandId(id.id()), type_(JSVAL_TYPE_OBJECT) {} MOZ_IMPLICIT TypedOperandId(StringOperandId id) : OperandId(id.id()), type_(JSVAL_TYPE_STRING) {} MOZ_IMPLICIT TypedOperandId(SymbolOperandId id) : OperandId(id.id()), type_(JSVAL_TYPE_SYMBOL) {} MOZ_IMPLICIT TypedOperandId(Int32OperandId id) : OperandId(id.id()), type_(JSVAL_TYPE_INT32) {} TypedOperandId(ValOperandId val, JSValueType type) : OperandId(val.id()), type_(type) {} JSValueType type() const { return type_; } }; #define CACHE_IR_KINDS(_) \ _(GetProp) \ _(GetElem) \ _(GetName) \ _(GetPropSuper) \ _(GetElemSuper) \ _(SetProp) \ _(SetElem) \ _(BindName) \ _(In) \ _(HasOwn) \ _(TypeOf) \ _(GetIterator) \ _(Compare) \ _(Call) enum class CacheKind : uint8_t { #define DEFINE_KIND(kind) kind, CACHE_IR_KINDS(DEFINE_KIND) #undef DEFINE_KIND }; extern const char* CacheKindNames[]; #define CACHE_IR_OPS(_) \ _(GuardIsObject) \ _(GuardIsObjectOrNull) \ _(GuardIsString) \ _(GuardIsSymbol) \ _(GuardIsInt32Index) \ _(GuardType) \ _(GuardShape) \ _(GuardGroup) \ _(GuardProto) \ _(GuardClass) /* Guard an object class, per GuardClassKind */ \ _(GuardAnyClass) /* Guard an arbitrary class for an object */ \ _(GuardCompartment) \ _(GuardIsNativeFunction) \ _(GuardIsProxy) \ _(GuardHasProxyHandler) \ _(GuardNotDOMProxy) \ _(GuardSpecificObject) \ _(GuardSpecificAtom) \ _(GuardSpecificSymbol) \ _(GuardSpecificInt32Immediate) \ _(GuardNoDetachedTypedObjects) \ _(GuardMagicValue) \ _(GuardFrameHasNoArgumentsObject) \ _(GuardNoDenseElements) \ _(GuardNoUnboxedExpando) \ _(GuardAndLoadUnboxedExpando) \ _(GuardAndGetIndexFromString) \ _(GuardAndGetIterator) \ _(GuardHasGetterSetter) \ _(GuardGroupHasUnanalyzedNewScript) \ _(GuardIndexIsNonNegative) \ _(GuardXrayExpandoShapeAndDefaultProto) \ _(LoadStackValue) \ _(LoadObject) \ _(LoadProto) \ _(LoadEnclosingEnvironment) \ _(LoadWrapperTarget) \ \ _(MegamorphicLoadSlotResult) \ _(MegamorphicLoadSlotByValueResult) \ _(MegamorphicStoreSlot) \ _(MegamorphicSetElement) \ _(MegamorphicHasPropResult) \ \ /* See CacheIR.cpp 'DOM proxies' comment. */ \ _(LoadDOMExpandoValue) \ _(LoadDOMExpandoValueGuardGeneration) \ _(LoadDOMExpandoValueIgnoreGeneration)\ _(GuardDOMExpandoMissingOrGuardShape) \ \ _(StoreFixedSlot) \ _(StoreDynamicSlot) \ _(AddAndStoreFixedSlot) \ _(AddAndStoreDynamicSlot) \ _(AllocateAndStoreDynamicSlot) \ _(StoreTypedObjectReferenceProperty) \ _(StoreTypedObjectScalarProperty) \ _(StoreUnboxedProperty) \ _(StoreDenseElement) \ _(StoreDenseElementHole) \ _(ArrayPush) \ _(ArrayJoinResult) \ _(StoreTypedElement) \ _(CallNativeSetter) \ _(CallScriptedSetter) \ _(CallSetArrayLength) \ _(CallProxySet) \ _(CallProxySetByValue) \ \ /* The *Result ops load a value into the cache's result register. */ \ _(LoadFixedSlotResult) \ _(LoadDynamicSlotResult) \ _(LoadUnboxedPropertyResult) \ _(LoadTypedObjectResult) \ _(LoadDenseElementResult) \ _(LoadDenseElementHoleResult) \ _(LoadDenseElementExistsResult) \ + _(LoadTypedElementExistsResult) \ _(LoadDenseElementHoleExistsResult) \ _(LoadTypedElementResult) \ _(LoadInt32ArrayLengthResult) \ _(LoadArgumentsObjectArgResult) \ _(LoadArgumentsObjectLengthResult) \ _(LoadFunctionLengthResult) \ _(LoadStringCharResult) \ _(LoadStringLengthResult) \ _(LoadFrameCalleeResult) \ _(LoadFrameNumActualArgsResult) \ _(LoadFrameArgumentResult) \ _(LoadEnvironmentFixedSlotResult) \ _(LoadEnvironmentDynamicSlotResult) \ _(LoadObjectResult) \ _(CallScriptedGetterResult) \ _(CallNativeGetterResult) \ _(CallProxyGetResult) \ _(CallProxyGetByValueResult) \ _(CallProxyHasPropResult) \ _(LoadUndefinedResult) \ _(LoadBooleanResult) \ _(LoadStringResult) \ _(LoadTypeOfObjectResult) \ \ _(CallStringSplitResult) \ \ _(CompareStringResult) \ _(CompareObjectResult) \ _(CompareSymbolResult) \ \ _(CallPrintString) \ _(Breakpoint) \ \ _(TypeMonitorResult) \ _(ReturnFromIC) \ _(WrapResult) enum class CacheOp { #define DEFINE_OP(op) op, CACHE_IR_OPS(DEFINE_OP) #undef DEFINE_OP }; class StubField { public: enum class Type : uint8_t { // These fields take up a single word. RawWord, Shape, ObjectGroup, JSObject, Symbol, String, Id, // These fields take up 64 bits on all platforms. RawInt64, First64BitType = RawInt64, DOMExpandoGeneration, Value, Limit }; static bool sizeIsWord(Type type) { MOZ_ASSERT(type != Type::Limit); return type < Type::First64BitType; } static bool sizeIsInt64(Type type) { MOZ_ASSERT(type != Type::Limit); return type >= Type::First64BitType; } static size_t sizeInBytes(Type type) { if (sizeIsWord(type)) return sizeof(uintptr_t); MOZ_ASSERT(sizeIsInt64(type)); return sizeof(int64_t); } private: union { uintptr_t dataWord_; uint64_t dataInt64_; }; Type type_; public: StubField(uint64_t data, Type type) : dataInt64_(data), type_(type) { MOZ_ASSERT_IF(sizeIsWord(), data <= UINTPTR_MAX); } Type type() const { return type_; } bool sizeIsWord() const { return sizeIsWord(type_); } bool sizeIsInt64() const { return sizeIsInt64(type_); } uintptr_t asWord() const { MOZ_ASSERT(sizeIsWord()); return dataWord_; } uint64_t asInt64() const { MOZ_ASSERT(sizeIsInt64()); return dataInt64_; } } JS_HAZ_GC_POINTER; // We use this enum as GuardClass operand, instead of storing Class* pointers // in the IR, to keep the IR compact and the same size on all platforms. enum class GuardClassKind : uint8_t { Array, MappedArguments, UnmappedArguments, WindowProxy, JSFunction, }; // Some ops refer to shapes that might be in other zones. Instead of putting // cross-zone pointers in the caches themselves (which would complicate tracing // enormously), these ops instead contain wrappers for objects in the target // zone, which refer to the actual shape via a reserved slot. JSObject* NewWrapperWithObjectShape(JSContext* cx, HandleNativeObject obj); void LoadShapeWrapperContents(MacroAssembler& masm, Register obj, Register dst, Label* failure); // Class to record CacheIR + some additional metadata for code generation. class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter { CompactBufferWriter buffer_; uint32_t nextOperandId_; uint32_t nextInstructionId_; uint32_t numInputOperands_; // The data (shapes, slot offsets, etc.) that will be stored in the ICStub. Vector stubFields_; size_t stubDataSize_; // For each operand id, record which instruction accessed it last. This // information greatly improves register allocation. Vector operandLastUsed_; // OperandId and stub offsets are stored in a single byte, so make sure // this doesn't overflow. We use a very conservative limit for now. static const size_t MaxOperandIds = 20; static const size_t MaxStubDataSizeInBytes = 20 * sizeof(uintptr_t); bool tooLarge_; void writeOp(CacheOp op) { MOZ_ASSERT(uint32_t(op) <= UINT8_MAX); buffer_.writeByte(uint32_t(op)); nextInstructionId_++; } void writeOperandId(OperandId opId) { if (opId.id() < MaxOperandIds) { static_assert(MaxOperandIds <= UINT8_MAX, "operand id must fit in a single byte"); buffer_.writeByte(opId.id()); } else { tooLarge_ = true; return; } if (opId.id() >= operandLastUsed_.length()) { buffer_.propagateOOM(operandLastUsed_.resize(opId.id() + 1)); if (buffer_.oom()) return; } MOZ_ASSERT(nextInstructionId_ > 0); operandLastUsed_[opId.id()] = nextInstructionId_ - 1; } void writeInt32Immediate(int32_t i32) { buffer_.writeSigned(i32); } void writeUint32Immediate(uint32_t u32) { buffer_.writeUnsigned(u32); } void writePointer(void* ptr) { buffer_.writeRawPointer(ptr); } void writeOpWithOperandId(CacheOp op, OperandId opId) { writeOp(op); writeOperandId(opId); } void addStubField(uint64_t value, StubField::Type fieldType) { size_t newStubDataSize = stubDataSize_ + StubField::sizeInBytes(fieldType); if (newStubDataSize < MaxStubDataSizeInBytes) { buffer_.propagateOOM(stubFields_.append(StubField(value, fieldType))); MOZ_ASSERT((stubDataSize_ % sizeof(uintptr_t)) == 0); buffer_.writeByte(stubDataSize_ / sizeof(uintptr_t)); stubDataSize_ = newStubDataSize; } else { tooLarge_ = true; } } CacheIRWriter(const CacheIRWriter&) = delete; CacheIRWriter& operator=(const CacheIRWriter&) = delete; public: explicit CacheIRWriter(JSContext* cx) : CustomAutoRooter(cx), nextOperandId_(0), nextInstructionId_(0), numInputOperands_(0), stubDataSize_(0), tooLarge_(false) {} bool failed() const { return buffer_.oom() || tooLarge_; } uint32_t numInputOperands() const { return numInputOperands_; } uint32_t numOperandIds() const { return nextOperandId_; } uint32_t numInstructions() const { return nextInstructionId_; } size_t numStubFields() const { return stubFields_.length(); } StubField::Type stubFieldType(uint32_t i) const { return stubFields_[i].type(); } uint32_t setInputOperandId(uint32_t op) { MOZ_ASSERT(op == nextOperandId_); nextOperandId_++; numInputOperands_++; return op; } void trace(JSTracer* trc) override { // For now, assert we only GC before we append stub fields. MOZ_RELEASE_ASSERT(stubFields_.empty()); } size_t stubDataSize() const { return stubDataSize_; } void copyStubData(uint8_t* dest) const; bool stubDataEqualsMaybeUpdate(uint8_t* stubData, bool* updated) const; bool operandIsDead(uint32_t operandId, uint32_t currentInstruction) const { if (operandId >= operandLastUsed_.length()) return false; return currentInstruction > operandLastUsed_[operandId]; } const uint8_t* codeStart() const { MOZ_ASSERT(!failed()); return buffer_.buffer(); } const uint8_t* codeEnd() const { MOZ_ASSERT(!failed()); return buffer_.buffer() + buffer_.length(); } uint32_t codeLength() const { MOZ_ASSERT(!failed()); return buffer_.length(); } // This should not be used when compiling Baseline code, as Baseline code // shouldn't bake in stub values. StubField readStubFieldForIon(size_t i, StubField::Type type) const { MOZ_ASSERT(stubFields_[i].type() == type); return stubFields_[i]; } ObjOperandId guardIsObject(ValOperandId val) { writeOpWithOperandId(CacheOp::GuardIsObject, val); return ObjOperandId(val.id()); } StringOperandId guardIsString(ValOperandId val) { writeOpWithOperandId(CacheOp::GuardIsString, val); return StringOperandId(val.id()); } SymbolOperandId guardIsSymbol(ValOperandId val) { writeOpWithOperandId(CacheOp::GuardIsSymbol, val); return SymbolOperandId(val.id()); } Int32OperandId guardIsInt32Index(ValOperandId val) { Int32OperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::GuardIsInt32Index, val); writeOperandId(res); return res; } void guardType(ValOperandId val, JSValueType type) { writeOpWithOperandId(CacheOp::GuardType, val); static_assert(sizeof(type) == sizeof(uint8_t), "JSValueType should fit in a byte"); buffer_.writeByte(uint32_t(type)); } void guardIsObjectOrNull(ValOperandId val) { writeOpWithOperandId(CacheOp::GuardIsObjectOrNull, val); } void guardShape(ObjOperandId obj, Shape* shape) { writeOpWithOperandId(CacheOp::GuardShape, obj); addStubField(uintptr_t(shape), StubField::Type::Shape); } void guardXrayExpandoShapeAndDefaultProto(ObjOperandId obj, JSObject* shapeWrapper) { writeOpWithOperandId(CacheOp::GuardXrayExpandoShapeAndDefaultProto, obj); buffer_.writeByte(uint32_t(!!shapeWrapper)); addStubField(uintptr_t(shapeWrapper), StubField::Type::JSObject); } void guardGroup(ObjOperandId obj, ObjectGroup* group) { writeOpWithOperandId(CacheOp::GuardGroup, obj); addStubField(uintptr_t(group), StubField::Type::ObjectGroup); } void guardProto(ObjOperandId obj, JSObject* proto) { writeOpWithOperandId(CacheOp::GuardProto, obj); addStubField(uintptr_t(proto), StubField::Type::JSObject); } void guardClass(ObjOperandId obj, GuardClassKind kind) { static_assert(sizeof(GuardClassKind) == sizeof(uint8_t), "GuardClassKind must fit in a byte"); writeOpWithOperandId(CacheOp::GuardClass, obj); buffer_.writeByte(uint32_t(kind)); } void guardAnyClass(ObjOperandId obj, const Class* clasp) { writeOpWithOperandId(CacheOp::GuardAnyClass, obj); addStubField(uintptr_t(clasp), StubField::Type::RawWord); } void guardIsNativeFunction(ObjOperandId obj, JSNative nativeFunc) { writeOpWithOperandId(CacheOp::GuardIsNativeFunction, obj); writePointer(JS_FUNC_TO_DATA_PTR(void*, nativeFunc)); } void guardIsProxy(ObjOperandId obj) { writeOpWithOperandId(CacheOp::GuardIsProxy, obj); } void guardHasProxyHandler(ObjOperandId obj, const void* handler) { writeOpWithOperandId(CacheOp::GuardHasProxyHandler, obj); addStubField(uintptr_t(handler), StubField::Type::RawWord); } void guardNotDOMProxy(ObjOperandId obj) { writeOpWithOperandId(CacheOp::GuardNotDOMProxy, obj); } void guardSpecificObject(ObjOperandId obj, JSObject* expected) { writeOpWithOperandId(CacheOp::GuardSpecificObject, obj); addStubField(uintptr_t(expected), StubField::Type::JSObject); } void guardSpecificAtom(StringOperandId str, JSAtom* expected) { writeOpWithOperandId(CacheOp::GuardSpecificAtom, str); addStubField(uintptr_t(expected), StubField::Type::String); } void guardSpecificSymbol(SymbolOperandId sym, JS::Symbol* expected) { writeOpWithOperandId(CacheOp::GuardSpecificSymbol, sym); addStubField(uintptr_t(expected), StubField::Type::Symbol); } void guardSpecificInt32Immediate(Int32OperandId operand, int32_t expected, Assembler::Condition cond = Assembler::Equal) { writeOp(CacheOp::GuardSpecificInt32Immediate); writeOperandId(operand); writeInt32Immediate(expected); buffer_.writeByte(uint32_t(cond)); } void guardMagicValue(ValOperandId val, JSWhyMagic magic) { writeOpWithOperandId(CacheOp::GuardMagicValue, val); buffer_.writeByte(uint32_t(magic)); } void guardCompartment(ObjOperandId obj, JSObject* global, JSCompartment* compartment) { writeOpWithOperandId(CacheOp::GuardCompartment, obj); // Add a reference to the compartment's global to keep it alive. addStubField(uintptr_t(global), StubField::Type::JSObject); // Use RawWord, because compartments never move and it can't be GCed. addStubField(uintptr_t(compartment), StubField::Type::RawWord); } void guardNoDetachedTypedObjects() { writeOp(CacheOp::GuardNoDetachedTypedObjects); } void guardFrameHasNoArgumentsObject() { writeOp(CacheOp::GuardFrameHasNoArgumentsObject); } Int32OperandId guardAndGetIndexFromString(StringOperandId str) { Int32OperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::GuardAndGetIndexFromString, str); writeOperandId(res); return res; } ObjOperandId guardAndGetIterator(ObjOperandId obj, PropertyIteratorObject* iter, NativeIterator** enumeratorsAddr) { ObjOperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::GuardAndGetIterator, obj); addStubField(uintptr_t(iter), StubField::Type::JSObject); addStubField(uintptr_t(enumeratorsAddr), StubField::Type::RawWord); writeOperandId(res); return res; } void guardHasGetterSetter(ObjOperandId obj, Shape* shape) { writeOpWithOperandId(CacheOp::GuardHasGetterSetter, obj); addStubField(uintptr_t(shape), StubField::Type::Shape); } void guardGroupHasUnanalyzedNewScript(ObjectGroup* group) { writeOp(CacheOp::GuardGroupHasUnanalyzedNewScript); addStubField(uintptr_t(group), StubField::Type::ObjectGroup); } void guardIndexIsNonNegative(Int32OperandId index) { writeOpWithOperandId(CacheOp::GuardIndexIsNonNegative, index); } void loadFrameCalleeResult() { writeOp(CacheOp::LoadFrameCalleeResult); } void loadFrameNumActualArgsResult() { writeOp(CacheOp::LoadFrameNumActualArgsResult); } void loadFrameArgumentResult(Int32OperandId index) { writeOpWithOperandId(CacheOp::LoadFrameArgumentResult, index); } void guardNoDenseElements(ObjOperandId obj) { writeOpWithOperandId(CacheOp::GuardNoDenseElements, obj); } void guardNoUnboxedExpando(ObjOperandId obj) { writeOpWithOperandId(CacheOp::GuardNoUnboxedExpando, obj); } ObjOperandId guardAndLoadUnboxedExpando(ObjOperandId obj) { ObjOperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::GuardAndLoadUnboxedExpando, obj); writeOperandId(res); return res; } ValOperandId loadStackValue(uint32_t idx) { ValOperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::LoadStackValue, res); writeUint32Immediate(idx); return res; } ObjOperandId loadObject(JSObject* obj) { ObjOperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::LoadObject, res); addStubField(uintptr_t(obj), StubField::Type::JSObject); return res; } ObjOperandId loadProto(ObjOperandId obj) { ObjOperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::LoadProto, obj); writeOperandId(res); return res; } ObjOperandId loadEnclosingEnvironment(ObjOperandId obj) { ObjOperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::LoadEnclosingEnvironment, obj); writeOperandId(res); return res; } ObjOperandId loadWrapperTarget(ObjOperandId obj) { ObjOperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::LoadWrapperTarget, obj); writeOperandId(res); return res; } ValOperandId loadDOMExpandoValue(ObjOperandId obj) { ValOperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::LoadDOMExpandoValue, obj); writeOperandId(res); return res; } void guardDOMExpandoMissingOrGuardShape(ValOperandId expando, Shape* shape) { writeOpWithOperandId(CacheOp::GuardDOMExpandoMissingOrGuardShape, expando); addStubField(uintptr_t(shape), StubField::Type::Shape); } ValOperandId loadDOMExpandoValueGuardGeneration(ObjOperandId obj, ExpandoAndGeneration* expandoAndGeneration) { ValOperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::LoadDOMExpandoValueGuardGeneration, obj); addStubField(uintptr_t(expandoAndGeneration), StubField::Type::RawWord); addStubField(expandoAndGeneration->generation, StubField::Type::DOMExpandoGeneration); writeOperandId(res); return res; } ValOperandId loadDOMExpandoValueIgnoreGeneration(ObjOperandId obj) { ValOperandId res(nextOperandId_++); writeOpWithOperandId(CacheOp::LoadDOMExpandoValueIgnoreGeneration, obj); writeOperandId(res); return res; } void storeFixedSlot(ObjOperandId obj, size_t offset, ValOperandId rhs) { writeOpWithOperandId(CacheOp::StoreFixedSlot, obj); addStubField(offset, StubField::Type::RawWord); writeOperandId(rhs); } void storeDynamicSlot(ObjOperandId obj, size_t offset, ValOperandId rhs) { writeOpWithOperandId(CacheOp::StoreDynamicSlot, obj); addStubField(offset, StubField::Type::RawWord); writeOperandId(rhs); } void addAndStoreFixedSlot(ObjOperandId obj, size_t offset, ValOperandId rhs, Shape* newShape, bool changeGroup, ObjectGroup* newGroup) { writeOpWithOperandId(CacheOp::AddAndStoreFixedSlot, obj); addStubField(offset, StubField::Type::RawWord); writeOperandId(rhs); buffer_.writeByte(changeGroup); addStubField(uintptr_t(newGroup), StubField::Type::ObjectGroup); addStubField(uintptr_t(newShape), StubField::Type::Shape); } void addAndStoreDynamicSlot(ObjOperandId obj, size_t offset, ValOperandId rhs, Shape* newShape, bool changeGroup, ObjectGroup* newGroup) { writeOpWithOperandId(CacheOp::AddAndStoreDynamicSlot, obj); addStubField(offset, StubField::Type::RawWord); writeOperandId(rhs); buffer_.writeByte(changeGroup); addStubField(uintptr_t(newGroup), StubField::Type::ObjectGroup); addStubField(uintptr_t(newShape), StubField::Type::Shape); } void allocateAndStoreDynamicSlot(ObjOperandId obj, size_t offset, ValOperandId rhs, Shape* newShape, bool changeGroup, ObjectGroup* newGroup, uint32_t numNewSlots) { writeOpWithOperandId(CacheOp::AllocateAndStoreDynamicSlot, obj); addStubField(offset, StubField::Type::RawWord); writeOperandId(rhs); buffer_.writeByte(changeGroup); addStubField(uintptr_t(newGroup), StubField::Type::ObjectGroup); addStubField(uintptr_t(newShape), StubField::Type::Shape); addStubField(numNewSlots, StubField::Type::RawWord); } void storeTypedObjectReferenceProperty(ObjOperandId obj, uint32_t offset, TypedThingLayout layout, ReferenceTypeDescr::Type type, ValOperandId rhs) { writeOpWithOperandId(CacheOp::StoreTypedObjectReferenceProperty, obj); addStubField(offset, StubField::Type::RawWord); buffer_.writeByte(uint32_t(layout)); buffer_.writeByte(uint32_t(type)); writeOperandId(rhs); } void storeTypedObjectScalarProperty(ObjOperandId obj, uint32_t offset, TypedThingLayout layout, Scalar::Type type, ValOperandId rhs) { writeOpWithOperandId(CacheOp::StoreTypedObjectScalarProperty, obj); addStubField(offset, StubField::Type::RawWord); buffer_.writeByte(uint32_t(layout)); buffer_.writeByte(uint32_t(type)); writeOperandId(rhs); } void storeUnboxedProperty(ObjOperandId obj, JSValueType type, size_t offset, ValOperandId rhs) { writeOpWithOperandId(CacheOp::StoreUnboxedProperty, obj); buffer_.writeByte(uint32_t(type)); addStubField(offset, StubField::Type::RawWord); writeOperandId(rhs); } void storeDenseElement(ObjOperandId obj, Int32OperandId index, ValOperandId rhs) { writeOpWithOperandId(CacheOp::StoreDenseElement, obj); writeOperandId(index); writeOperandId(rhs); } void storeTypedElement(ObjOperandId obj, Int32OperandId index, ValOperandId rhs, TypedThingLayout layout, Scalar::Type elementType, bool handleOOB) { writeOpWithOperandId(CacheOp::StoreTypedElement, obj); writeOperandId(index); writeOperandId(rhs); buffer_.writeByte(uint32_t(layout)); buffer_.writeByte(uint32_t(elementType)); buffer_.writeByte(uint32_t(handleOOB)); } void storeDenseElementHole(ObjOperandId obj, Int32OperandId index, ValOperandId rhs, bool handleAdd) { writeOpWithOperandId(CacheOp::StoreDenseElementHole, obj); writeOperandId(index); writeOperandId(rhs); buffer_.writeByte(handleAdd); } void arrayPush(ObjOperandId obj, ValOperandId rhs) { writeOpWithOperandId(CacheOp::ArrayPush, obj); writeOperandId(rhs); } void arrayJoinResult(ObjOperandId obj) { writeOpWithOperandId(CacheOp::ArrayJoinResult, obj); } void callScriptedSetter(ObjOperandId obj, JSFunction* setter, ValOperandId rhs) { writeOpWithOperandId(CacheOp::CallScriptedSetter, obj); addStubField(uintptr_t(setter), StubField::Type::JSObject); writeOperandId(rhs); } void callNativeSetter(ObjOperandId obj, JSFunction* setter, ValOperandId rhs) { writeOpWithOperandId(CacheOp::CallNativeSetter, obj); addStubField(uintptr_t(setter), StubField::Type::JSObject); writeOperandId(rhs); } void callSetArrayLength(ObjOperandId obj, bool strict, ValOperandId rhs) { writeOpWithOperandId(CacheOp::CallSetArrayLength, obj); buffer_.writeByte(uint32_t(strict)); writeOperandId(rhs); } void callProxySet(ObjOperandId obj, jsid id, ValOperandId rhs, bool strict) { writeOpWithOperandId(CacheOp::CallProxySet, obj); writeOperandId(rhs); addStubField(uintptr_t(JSID_BITS(id)), StubField::Type::Id); buffer_.writeByte(uint32_t(strict)); } void callProxySetByValue(ObjOperandId obj, ValOperandId id, ValOperandId rhs, bool strict) { writeOpWithOperandId(CacheOp::CallProxySetByValue, obj); writeOperandId(id); writeOperandId(rhs); buffer_.writeByte(uint32_t(strict)); } void megamorphicLoadSlotResult(ObjOperandId obj, PropertyName* name, bool handleMissing) { writeOpWithOperandId(CacheOp::MegamorphicLoadSlotResult, obj); addStubField(uintptr_t(name), StubField::Type::String); buffer_.writeByte(uint32_t(handleMissing)); } void megamorphicLoadSlotByValueResult(ObjOperandId obj, ValOperandId id, bool handleMissing) { writeOpWithOperandId(CacheOp::MegamorphicLoadSlotByValueResult, obj); writeOperandId(id); buffer_.writeByte(uint32_t(handleMissing)); } void megamorphicStoreSlot(ObjOperandId obj, PropertyName* name, ValOperandId rhs, bool needsTypeBarrier) { writeOpWithOperandId(CacheOp::MegamorphicStoreSlot, obj); addStubField(uintptr_t(name), StubField::Type::String); writeOperandId(rhs); buffer_.writeByte(needsTypeBarrier); } void megamorphicSetElement(ObjOperandId obj, ValOperandId id, ValOperandId rhs, bool strict) { writeOpWithOperandId(CacheOp::MegamorphicSetElement, obj); writeOperandId(id); writeOperandId(rhs); buffer_.writeByte(uint32_t(strict)); } void megamorphicHasPropResult(ObjOperandId obj, ValOperandId id, bool hasOwn) { writeOpWithOperandId(CacheOp::MegamorphicHasPropResult, obj); writeOperandId(id); buffer_.writeByte(uint32_t(hasOwn)); } void loadBooleanResult(bool val) { writeOp(CacheOp::LoadBooleanResult); buffer_.writeByte(uint32_t(val)); } void loadUndefinedResult() { writeOp(CacheOp::LoadUndefinedResult); } void loadStringResult(JSString* str) { writeOp(CacheOp::LoadStringResult); addStubField(uintptr_t(str), StubField::Type::String); } void loadFixedSlotResult(ObjOperandId obj, size_t offset) { writeOpWithOperandId(CacheOp::LoadFixedSlotResult, obj); addStubField(offset, StubField::Type::RawWord); } void loadDynamicSlotResult(ObjOperandId obj, size_t offset) { writeOpWithOperandId(CacheOp::LoadDynamicSlotResult, obj); addStubField(offset, StubField::Type::RawWord); } void loadUnboxedPropertyResult(ObjOperandId obj, JSValueType type, size_t offset) { writeOpWithOperandId(CacheOp::LoadUnboxedPropertyResult, obj); buffer_.writeByte(uint32_t(type)); addStubField(offset, StubField::Type::RawWord); } void loadTypedObjectResult(ObjOperandId obj, uint32_t offset, TypedThingLayout layout, uint32_t typeDescr) { MOZ_ASSERT(uint32_t(layout) <= UINT8_MAX); MOZ_ASSERT(typeDescr <= UINT8_MAX); writeOpWithOperandId(CacheOp::LoadTypedObjectResult, obj); buffer_.writeByte(uint32_t(layout)); buffer_.writeByte(typeDescr); addStubField(offset, StubField::Type::RawWord); } void loadInt32ArrayLengthResult(ObjOperandId obj) { writeOpWithOperandId(CacheOp::LoadInt32ArrayLengthResult, obj); } void loadArgumentsObjectLengthResult(ObjOperandId obj) { writeOpWithOperandId(CacheOp::LoadArgumentsObjectLengthResult, obj); } void loadFunctionLengthResult(ObjOperandId obj) { writeOpWithOperandId(CacheOp::LoadFunctionLengthResult, obj); } void loadArgumentsObjectArgResult(ObjOperandId obj, Int32OperandId index) { writeOpWithOperandId(CacheOp::LoadArgumentsObjectArgResult, obj); writeOperandId(index); } void loadDenseElementResult(ObjOperandId obj, Int32OperandId index) { writeOpWithOperandId(CacheOp::LoadDenseElementResult, obj); writeOperandId(index); } void loadDenseElementHoleResult(ObjOperandId obj, Int32OperandId index) { writeOpWithOperandId(CacheOp::LoadDenseElementHoleResult, obj); writeOperandId(index); } void loadDenseElementExistsResult(ObjOperandId obj, Int32OperandId index) { writeOpWithOperandId(CacheOp::LoadDenseElementExistsResult, obj); writeOperandId(index); } + void loadTypedElementExistsResult(ObjOperandId obj, Int32OperandId index, TypedThingLayout layout) { + writeOpWithOperandId(CacheOp::LoadTypedElementExistsResult, obj); + writeOperandId(index); + buffer_.writeByte(uint32_t(layout)); + } void loadDenseElementHoleExistsResult(ObjOperandId obj, Int32OperandId index) { writeOpWithOperandId(CacheOp::LoadDenseElementHoleExistsResult, obj); writeOperandId(index); } void loadTypedElementResult(ObjOperandId obj, Int32OperandId index, TypedThingLayout layout, Scalar::Type elementType) { writeOpWithOperandId(CacheOp::LoadTypedElementResult, obj); writeOperandId(index); buffer_.writeByte(uint32_t(layout)); buffer_.writeByte(uint32_t(elementType)); } void loadStringLengthResult(StringOperandId str) { writeOpWithOperandId(CacheOp::LoadStringLengthResult, str); } void loadStringCharResult(StringOperandId str, Int32OperandId index) { writeOpWithOperandId(CacheOp::LoadStringCharResult, str); writeOperandId(index); } void callScriptedGetterResult(ObjOperandId obj, JSFunction* getter) { writeOpWithOperandId(CacheOp::CallScriptedGetterResult, obj); addStubField(uintptr_t(getter), StubField::Type::JSObject); } void callNativeGetterResult(ObjOperandId obj, JSFunction* getter) { writeOpWithOperandId(CacheOp::CallNativeGetterResult, obj); addStubField(uintptr_t(getter), StubField::Type::JSObject); } void callProxyGetResult(ObjOperandId obj, jsid id) { writeOpWithOperandId(CacheOp::CallProxyGetResult, obj); addStubField(uintptr_t(JSID_BITS(id)), StubField::Type::Id); } void callProxyGetByValueResult(ObjOperandId obj, ValOperandId idVal) { writeOpWithOperandId(CacheOp::CallProxyGetByValueResult, obj); writeOperandId(idVal); } void callProxyHasPropResult(ObjOperandId obj, ValOperandId idVal, bool hasOwn) { writeOpWithOperandId(CacheOp::CallProxyHasPropResult, obj); writeOperandId(idVal); buffer_.writeByte(uint32_t(hasOwn)); } void loadEnvironmentFixedSlotResult(ObjOperandId obj, size_t offset) { writeOpWithOperandId(CacheOp::LoadEnvironmentFixedSlotResult, obj); addStubField(offset, StubField::Type::RawWord); } void loadEnvironmentDynamicSlotResult(ObjOperandId obj, size_t offset) { writeOpWithOperandId(CacheOp::LoadEnvironmentDynamicSlotResult, obj); addStubField(offset, StubField::Type::RawWord); } void loadObjectResult(ObjOperandId obj) { writeOpWithOperandId(CacheOp::LoadObjectResult, obj); } void loadTypeOfObjectResult(ObjOperandId obj) { writeOpWithOperandId(CacheOp::LoadTypeOfObjectResult, obj); } void callStringSplitResult(StringOperandId str, StringOperandId sep, ObjectGroup* group) { writeOp(CacheOp::CallStringSplitResult); writeOperandId(str); writeOperandId(sep); addStubField(uintptr_t(group), StubField::Type::ObjectGroup); } void compareStringResult(uint32_t op, StringOperandId lhs, StringOperandId rhs) { writeOpWithOperandId(CacheOp::CompareStringResult, lhs); writeOperandId(rhs); buffer_.writeByte(uint32_t(op)); } void compareObjectResult(uint32_t op, ObjOperandId lhs, ObjOperandId rhs) { writeOpWithOperandId(CacheOp::CompareObjectResult, lhs); writeOperandId(rhs); buffer_.writeByte(uint32_t(op)); } void compareSymbolResult(uint32_t op, SymbolOperandId lhs, SymbolOperandId rhs) { writeOpWithOperandId(CacheOp::CompareSymbolResult, lhs); writeOperandId(rhs); buffer_.writeByte(uint32_t(op)); } void callPrintString(const char* str) { writeOp(CacheOp::CallPrintString); writePointer(const_cast(str)); } void breakpoint() { writeOp(CacheOp::Breakpoint); } void typeMonitorResult() { writeOp(CacheOp::TypeMonitorResult); } void returnFromIC() { writeOp(CacheOp::ReturnFromIC); } void wrapResult() { writeOp(CacheOp::WrapResult); } }; class CacheIRStubInfo; // Helper class for reading CacheIR bytecode. class MOZ_RAII CacheIRReader { CompactBufferReader buffer_; CacheIRReader(const CacheIRReader&) = delete; CacheIRReader& operator=(const CacheIRReader&) = delete; public: CacheIRReader(const uint8_t* start, const uint8_t* end) : buffer_(start, end) {} explicit CacheIRReader(const CacheIRWriter& writer) : CacheIRReader(writer.codeStart(), writer.codeEnd()) {} explicit CacheIRReader(const CacheIRStubInfo* stubInfo); bool more() const { return buffer_.more(); } CacheOp readOp() { return CacheOp(buffer_.readByte()); } ValOperandId valOperandId() { return ValOperandId(buffer_.readByte()); } ObjOperandId objOperandId() { return ObjOperandId(buffer_.readByte()); } StringOperandId stringOperandId() { return StringOperandId(buffer_.readByte()); } SymbolOperandId symbolOperandId() { return SymbolOperandId(buffer_.readByte()); } Int32OperandId int32OperandId() { return Int32OperandId(buffer_.readByte()); } uint32_t stubOffset() { return buffer_.readByte() * sizeof(uintptr_t); } GuardClassKind guardClassKind() { return GuardClassKind(buffer_.readByte()); } JSValueType valueType() { return JSValueType(buffer_.readByte()); } TypedThingLayout typedThingLayout() { return TypedThingLayout(buffer_.readByte()); } Scalar::Type scalarType() { return Scalar::Type(buffer_.readByte()); } uint32_t typeDescrKey() { return buffer_.readByte(); } JSWhyMagic whyMagic() { return JSWhyMagic(buffer_.readByte()); } JSOp jsop() { return JSOp(buffer_.readByte()); } int32_t int32Immediate() { return buffer_.readSigned(); } uint32_t uint32Immediate() { return buffer_.readUnsigned(); } void* pointer() { return buffer_.readRawPointer(); } ReferenceTypeDescr::Type referenceTypeDescrType() { return ReferenceTypeDescr::Type(buffer_.readByte()); } uint8_t readByte() { return buffer_.readByte(); } bool readBool() { uint8_t b = buffer_.readByte(); MOZ_ASSERT(b <= 1); return bool(b); } bool matchOp(CacheOp op) { const uint8_t* pos = buffer_.currentPosition(); if (readOp() == op) return true; buffer_.seek(pos, 0); return false; } bool matchOp(CacheOp op, OperandId id) { const uint8_t* pos = buffer_.currentPosition(); if (readOp() == op && buffer_.readByte() == id.id()) return true; buffer_.seek(pos, 0); return false; } bool matchOpEither(CacheOp op1, CacheOp op2) { const uint8_t* pos = buffer_.currentPosition(); CacheOp op = readOp(); if (op == op1 || op == op2) return true; buffer_.seek(pos, 0); return false; } }; class MOZ_RAII IRGenerator { protected: CacheIRWriter writer; JSContext* cx_; HandleScript script_; jsbytecode* pc_; CacheKind cacheKind_; ICState::Mode mode_; IRGenerator(const IRGenerator&) = delete; IRGenerator& operator=(const IRGenerator&) = delete; bool maybeGuardInt32Index(const Value& index, ValOperandId indexId, uint32_t* int32Index, Int32OperandId* int32IndexId); ObjOperandId guardDOMProxyExpandoObjectAndShape(JSObject* obj, ObjOperandId objId, const Value& expandoVal, JSObject* expandoObj); void emitIdGuard(ValOperandId valId, jsid id); friend class CacheIRSpewer; public: explicit IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind, ICState::Mode mode); const CacheIRWriter& writerRef() const { return writer; } CacheKind cacheKind() const { return cacheKind_; } }; // Flags used to describe what values a GetProperty cache may produce. enum class GetPropertyResultFlags { None = 0, // Values produced by this cache will go through a type barrier, // so the cache may produce any type of value that is compatible with its // result operand. Monitored = 1 << 0, // Whether particular primitives may be produced by this cache. AllowUndefined = 1 << 1, AllowInt32 = 1 << 2, AllowDouble = 1 << 3, All = Monitored | AllowUndefined | AllowInt32 | AllowDouble }; static inline bool operator&(GetPropertyResultFlags a, GetPropertyResultFlags b) { return static_cast(a) & static_cast(b); } static inline GetPropertyResultFlags operator|(GetPropertyResultFlags a, GetPropertyResultFlags b) { return static_cast(static_cast(a) | static_cast(b)); } static inline GetPropertyResultFlags& operator|=(GetPropertyResultFlags& lhs, GetPropertyResultFlags b) { lhs = lhs | b; return lhs; } // GetPropIRGenerator generates CacheIR for a GetProp IC. class MOZ_RAII GetPropIRGenerator : public IRGenerator { HandleValue val_; HandleValue idVal_; HandleValue receiver_; bool* isTemporarilyUnoptimizable_; GetPropertyResultFlags resultFlags_; enum class PreliminaryObjectAction { None, Unlink, NotePreliminary }; PreliminaryObjectAction preliminaryObjectAction_; bool tryAttachNative(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachUnboxed(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachUnboxedExpando(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachTypedObject(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachObjectLength(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachModuleNamespace(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachWindowProxy(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachCrossCompartmentWrapper(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachXrayCrossCompartmentWrapper(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachFunction(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id, bool handleDOMProxies); bool tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id); bool tryAttachPrimitive(ValOperandId valId, HandleId id); bool tryAttachStringChar(ValOperandId valId, ValOperandId indexId); bool tryAttachStringLength(ValOperandId valId, HandleId id); bool tryAttachMagicArgumentsName(ValOperandId valId, HandleId id); bool tryAttachMagicArgument(ValOperandId valId, ValOperandId indexId); bool tryAttachArgumentsObjectArg(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId); bool tryAttachDenseElement(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId); bool tryAttachDenseElementHole(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId); bool tryAttachTypedElement(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId); bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId); void attachMegamorphicNativeSlot(ObjOperandId objId, jsid id, bool handleMissing); ValOperandId getElemKeyValueId() const { MOZ_ASSERT(cacheKind_ == CacheKind::GetElem || cacheKind_ == CacheKind::GetElemSuper); return ValOperandId(1); } ValOperandId getSuperReceiverValueId() const { if (cacheKind_ == CacheKind::GetPropSuper) return ValOperandId(1); MOZ_ASSERT(cacheKind_ == CacheKind::GetElemSuper); return ValOperandId(2); } bool isSuper() const { return (cacheKind_ == CacheKind::GetPropSuper || cacheKind_ == CacheKind::GetElemSuper); } // No pc if idempotent, as there can be multiple bytecode locations // due to GVN. bool idempotent() const { return pc_ == nullptr; } // If this is a GetElem cache, emit instructions to guard the incoming Value // matches |id|. void maybeEmitIdGuard(jsid id); void trackAttached(const char* name); void trackNotAttached(); public: GetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind, ICState::Mode mode, bool* isTemporarilyUnoptimizable, HandleValue val, HandleValue idVal, HandleValue receiver, GetPropertyResultFlags resultFlags); bool tryAttachStub(); bool tryAttachIdempotentStub(); bool shouldUnlinkPreliminaryObjectStubs() const { return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink; } bool shouldNotePreliminaryObjectStub() const { return preliminaryObjectAction_ == PreliminaryObjectAction::NotePreliminary; } }; // GetNameIRGenerator generates CacheIR for a GetName IC. class MOZ_RAII GetNameIRGenerator : public IRGenerator { HandleObject env_; HandlePropertyName name_; bool tryAttachGlobalNameValue(ObjOperandId objId, HandleId id); bool tryAttachGlobalNameGetter(ObjOperandId objId, HandleId id); bool tryAttachEnvironmentName(ObjOperandId objId, HandleId id); void trackAttached(const char* name); void trackNotAttached(); public: GetNameIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode, HandleObject env, HandlePropertyName name); bool tryAttachStub(); }; // BindNameIRGenerator generates CacheIR for a BindName IC. class MOZ_RAII BindNameIRGenerator : public IRGenerator { HandleObject env_; HandlePropertyName name_; bool tryAttachGlobalName(ObjOperandId objId, HandleId id); bool tryAttachEnvironmentName(ObjOperandId objId, HandleId id); void trackAttached(const char* name); void trackNotAttached(); public: BindNameIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode, HandleObject env, HandlePropertyName name); bool tryAttachStub(); }; // Information used by SetProp/SetElem stubs to check/update property types. class MOZ_RAII PropertyTypeCheckInfo { RootedObjectGroup group_; RootedId id_; bool needsTypeBarrier_; PropertyTypeCheckInfo(const PropertyTypeCheckInfo&) = delete; void operator=(const PropertyTypeCheckInfo&) = delete; public: PropertyTypeCheckInfo(JSContext* cx, bool needsTypeBarrier) : group_(cx), id_(cx), needsTypeBarrier_(needsTypeBarrier) {} bool needsTypeBarrier() const { return needsTypeBarrier_; } bool isSet() const { return group_ != nullptr; } ObjectGroup* group() const { MOZ_ASSERT(isSet()); return group_; } jsid id() const { MOZ_ASSERT(isSet()); return id_; } void set(ObjectGroup* group, jsid id) { MOZ_ASSERT(!group_); MOZ_ASSERT(group); if (needsTypeBarrier_) { group_ = group; id_ = id; } } }; // SetPropIRGenerator generates CacheIR for a SetProp IC. class MOZ_RAII SetPropIRGenerator : public IRGenerator { HandleValue lhsVal_; HandleValue idVal_; HandleValue rhsVal_; bool* isTemporarilyUnoptimizable_; PropertyTypeCheckInfo typeCheckInfo_; enum class PreliminaryObjectAction { None, Unlink, NotePreliminary }; PreliminaryObjectAction preliminaryObjectAction_; bool attachedTypedArrayOOBStub_; bool maybeHasExtraIndexedProps_; ValOperandId setElemKeyValueId() const { MOZ_ASSERT(cacheKind_ == CacheKind::SetElem); return ValOperandId(1); } ValOperandId rhsValueId() const { if (cacheKind_ == CacheKind::SetProp) return ValOperandId(1); MOZ_ASSERT(cacheKind_ == CacheKind::SetElem); return ValOperandId(2); } // If this is a SetElem cache, emit instructions to guard the incoming Value // matches |id|. void maybeEmitIdGuard(jsid id); bool tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId); bool tryAttachUnboxedExpandoSetSlot(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId); bool tryAttachUnboxedProperty(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId); bool tryAttachTypedObjectProperty(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId); bool tryAttachSetter(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId); bool tryAttachSetArrayLength(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId); bool tryAttachWindowProxy(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId); bool tryAttachSetDenseElement(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId, ValOperandId rhsId); bool tryAttachSetTypedElement(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId, ValOperandId rhsId); bool tryAttachSetDenseElementHole(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId, ValOperandId rhsId); bool tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId, bool handleDOMProxies); bool tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId); bool tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId); bool tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId); bool tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId); bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId, ValOperandId rhsId); bool tryAttachMegamorphicSetElement(HandleObject obj, ObjOperandId objId, ValOperandId rhsId); void trackAttached(const char* name); public: SetPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind, ICState::Mode mode, bool* isTemporarilyUnoptimizable, HandleValue lhsVal, HandleValue idVal, HandleValue rhsVal, bool needsTypeBarrier = true, bool maybeHasExtraIndexedProps = true); bool tryAttachStub(); bool tryAttachAddSlotStub(HandleObjectGroup oldGroup, HandleShape oldShape); void trackNotAttached(); bool shouldUnlinkPreliminaryObjectStubs() const { return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink; } bool shouldNotePreliminaryObjectStub() const { return preliminaryObjectAction_ == PreliminaryObjectAction::NotePreliminary; } const PropertyTypeCheckInfo* typeCheckInfo() const { return &typeCheckInfo_; } bool attachedTypedArrayOOBStub() const { return attachedTypedArrayOOBStub_; } }; // HasPropIRGenerator generates CacheIR for a HasProp IC. Used for // CacheKind::In / CacheKind::HasOwn. class MOZ_RAII HasPropIRGenerator : public IRGenerator { HandleValue val_; HandleValue idVal_; bool tryAttachDense(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId); bool tryAttachDenseHole(HandleObject obj, ObjOperandId objId, uint32_t index, Int32OperandId indexId); + bool tryAttachTypedArray(HandleObject obj, ObjOperandId objId, + uint32_t index, Int32OperandId indexId); bool tryAttachNamedProp(HandleObject obj, ObjOperandId objId, HandleId key, ValOperandId keyId); bool tryAttachMegamorphic(ObjOperandId objId, ValOperandId keyId); bool tryAttachNative(JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId, PropertyResult prop, JSObject* holder); bool tryAttachUnboxed(JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId); bool tryAttachUnboxedExpando(JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId); bool tryAttachTypedObject(JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId); bool tryAttachSlotDoesNotExist(JSObject* obj, ObjOperandId objId, jsid key, ValOperandId keyId); bool tryAttachDoesNotExist(HandleObject obj, ObjOperandId objId, HandleId key, ValOperandId keyId); bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId, ValOperandId keyId); void trackAttached(const char* name); void trackNotAttached(); public: // NOTE: Argument order is PROPERTY, OBJECT HasPropIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, CacheKind cacheKind, ICState::Mode mode, HandleValue idVal, HandleValue val); bool tryAttachStub(); }; class MOZ_RAII TypeOfIRGenerator : public IRGenerator { HandleValue val_; bool tryAttachPrimitive(ValOperandId valId); bool tryAttachObject(ValOperandId valId); public: TypeOfIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, ICState::Mode mode, HandleValue value); bool tryAttachStub(); }; class MOZ_RAII GetIteratorIRGenerator : public IRGenerator { HandleValue val_; bool tryAttachNativeIterator(ObjOperandId objId, HandleObject obj); public: GetIteratorIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, ICState::Mode mode, HandleValue value); bool tryAttachStub(); }; class MOZ_RAII CallIRGenerator : public IRGenerator { private: JSOp op_; uint32_t argc_; HandleValue callee_; HandleValue thisval_; HandleValueArray args_; PropertyTypeCheckInfo typeCheckInfo_; BaselineCacheIRStubKind cacheIRStubKind_; bool tryAttachStringSplit(); bool tryAttachArrayPush(); bool tryAttachArrayJoin(); void trackAttached(const char* name); void trackNotAttached(); public: CallIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, JSOp op, ICCall_Fallback* stub, ICState::Mode mode, uint32_t argc, HandleValue callee, HandleValue thisval, HandleValueArray args); bool tryAttachStub(); BaselineCacheIRStubKind cacheIRStubKind() const { return cacheIRStubKind_; } const PropertyTypeCheckInfo* typeCheckInfo() const { return &typeCheckInfo_; } }; class MOZ_RAII CompareIRGenerator : public IRGenerator { JSOp op_; HandleValue lhsVal_; HandleValue rhsVal_; bool tryAttachString(ValOperandId lhsId, ValOperandId rhsId); bool tryAttachObject(ValOperandId lhsId, ValOperandId rhsId); bool tryAttachSymbol(ValOperandId lhsId, ValOperandId rhsId); void trackAttached(const char* name); void trackNotAttached(); public: CompareIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, ICState::Mode mode, JSOp op, HandleValue lhsVal, HandleValue rhsVal); bool tryAttachStub(); }; } // namespace jit } // namespace js #endif /* jit_CacheIR_h */ diff --git a/js/src/jit/CacheIRCompiler.cpp b/js/src/jit/CacheIRCompiler.cpp --- a/js/src/jit/CacheIRCompiler.cpp +++ b/js/src/jit/CacheIRCompiler.cpp @@ -1,2543 +1,2567 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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/CacheIRCompiler.h" #include "jit/IonIC.h" #include "jit/SharedICHelpers.h" #include "jscompartmentinlines.h" #include "jit/MacroAssembler-inl.h" using namespace js; using namespace js::jit; using mozilla::Maybe; ValueOperand CacheRegisterAllocator::useValueRegister(MacroAssembler& masm, ValOperandId op) { OperandLocation& loc = operandLocations_[op.id()]; switch (loc.kind()) { case OperandLocation::ValueReg: currentOpRegs_.add(loc.valueReg()); return loc.valueReg(); case OperandLocation::ValueStack: { ValueOperand reg = allocateValueRegister(masm); popValue(masm, &loc, reg); return reg; } case OperandLocation::BaselineFrame: { ValueOperand reg = allocateValueRegister(masm); Address addr = addressOf(masm, loc.baselineFrameSlot()); masm.loadValue(addr, reg); loc.setValueReg(reg); return reg; } case OperandLocation::Constant: { ValueOperand reg = allocateValueRegister(masm); masm.moveValue(loc.constant(), reg); loc.setValueReg(reg); return reg; } case OperandLocation::PayloadReg: { // Temporarily add the payload register to currentOpRegs_ so // allocateValueRegister will stay away from it. currentOpRegs_.add(loc.payloadReg()); ValueOperand reg = allocateValueRegister(masm); masm.tagValue(loc.payloadType(), loc.payloadReg(), reg); currentOpRegs_.take(loc.payloadReg()); availableRegs_.add(loc.payloadReg()); loc.setValueReg(reg); return reg; } case OperandLocation::PayloadStack: { ValueOperand reg = allocateValueRegister(masm); popPayload(masm, &loc, reg.scratchReg()); masm.tagValue(loc.payloadType(), reg.scratchReg(), reg); loc.setValueReg(reg); return reg; } case OperandLocation::DoubleReg: { ValueOperand reg = allocateValueRegister(masm); masm.boxDouble(loc.doubleReg(), reg, ScratchDoubleReg); loc.setValueReg(reg); return reg; } case OperandLocation::Uninitialized: break; } MOZ_CRASH(); } ValueOperand CacheRegisterAllocator::useFixedValueRegister(MacroAssembler& masm, ValOperandId valId, ValueOperand reg) { allocateFixedValueRegister(masm, reg); OperandLocation& loc = operandLocations_[valId.id()]; switch (loc.kind()) { case OperandLocation::ValueReg: masm.moveValue(loc.valueReg(), reg); MOZ_ASSERT(!currentOpRegs_.aliases(loc.valueReg()), "Register shouldn't be in use"); availableRegs_.add(loc.valueReg()); break; case OperandLocation::ValueStack: popValue(masm, &loc, reg); break; case OperandLocation::BaselineFrame: { Address addr = addressOf(masm, loc.baselineFrameSlot()); masm.loadValue(addr, reg); break; } case OperandLocation::Constant: masm.moveValue(loc.constant(), reg); break; case OperandLocation::PayloadReg: masm.tagValue(loc.payloadType(), loc.payloadReg(), reg); MOZ_ASSERT(!currentOpRegs_.has(loc.payloadReg()), "Register shouldn't be in use"); availableRegs_.add(loc.payloadReg()); break; case OperandLocation::PayloadStack: popPayload(masm, &loc, reg.scratchReg()); masm.tagValue(loc.payloadType(), reg.scratchReg(), reg); break; case OperandLocation::DoubleReg: masm.boxDouble(loc.doubleReg(), reg, ScratchDoubleReg); break; case OperandLocation::Uninitialized: MOZ_CRASH(); } loc.setValueReg(reg); return reg; } Register CacheRegisterAllocator::useRegister(MacroAssembler& masm, TypedOperandId typedId) { OperandLocation& loc = operandLocations_[typedId.id()]; switch (loc.kind()) { case OperandLocation::PayloadReg: currentOpRegs_.add(loc.payloadReg()); return loc.payloadReg(); case OperandLocation::ValueReg: { // It's possible the value is still boxed: as an optimization, we unbox // the first time we use a value as object. ValueOperand val = loc.valueReg(); availableRegs_.add(val); Register reg = val.scratchReg(); availableRegs_.take(reg); masm.unboxObject(val, reg); loc.setPayloadReg(reg, typedId.type()); currentOpRegs_.add(reg); return reg; } case OperandLocation::PayloadStack: { Register reg = allocateRegister(masm); popPayload(masm, &loc, reg); return reg; } case OperandLocation::ValueStack: { // The value is on the stack, but boxed. If it's on top of the stack we // unbox it and then remove it from the stack, else we just unbox. Register reg = allocateRegister(masm); if (loc.valueStack() == stackPushed_) { masm.unboxObject(Address(masm.getStackPointer(), 0), reg); masm.addToStackPtr(Imm32(sizeof(js::Value))); MOZ_ASSERT(stackPushed_ >= sizeof(js::Value)); stackPushed_ -= sizeof(js::Value); } else { MOZ_ASSERT(loc.valueStack() < stackPushed_); masm.unboxObject(Address(masm.getStackPointer(), stackPushed_ - loc.valueStack()), reg); } loc.setPayloadReg(reg, typedId.type()); return reg; } case OperandLocation::BaselineFrame: { Register reg = allocateRegister(masm); Address addr = addressOf(masm, loc.baselineFrameSlot()); masm.unboxNonDouble(addr, reg); loc.setPayloadReg(reg, typedId.type()); return reg; }; case OperandLocation::Constant: { Value v = loc.constant(); Register reg = allocateRegister(masm); if (v.isString()) masm.movePtr(ImmGCPtr(v.toString()), reg); else if (v.isSymbol()) masm.movePtr(ImmGCPtr(v.toSymbol()), reg); else MOZ_CRASH("Unexpected Value"); loc.setPayloadReg(reg, v.extractNonDoubleType()); return reg; } case OperandLocation::DoubleReg: case OperandLocation::Uninitialized: break; } MOZ_CRASH(); } ConstantOrRegister CacheRegisterAllocator::useConstantOrRegister(MacroAssembler& masm, ValOperandId val) { OperandLocation& loc = operandLocations_[val.id()]; switch (loc.kind()) { case OperandLocation::Constant: return loc.constant(); case OperandLocation::PayloadReg: case OperandLocation::PayloadStack: { JSValueType payloadType = loc.payloadType(); Register reg = useRegister(masm, TypedOperandId(val, payloadType)); return TypedOrValueRegister(MIRTypeFromValueType(payloadType), AnyRegister(reg)); } case OperandLocation::ValueReg: case OperandLocation::ValueStack: case OperandLocation::BaselineFrame: return TypedOrValueRegister(useValueRegister(masm, val)); case OperandLocation::DoubleReg: return TypedOrValueRegister(MIRType::Double, AnyRegister(loc.doubleReg())); case OperandLocation::Uninitialized: break; } MOZ_CRASH(); } Register CacheRegisterAllocator::defineRegister(MacroAssembler& masm, TypedOperandId typedId) { OperandLocation& loc = operandLocations_[typedId.id()]; MOZ_ASSERT(loc.kind() == OperandLocation::Uninitialized); Register reg = allocateRegister(masm); loc.setPayloadReg(reg, typedId.type()); return reg; } ValueOperand CacheRegisterAllocator::defineValueRegister(MacroAssembler& masm, ValOperandId val) { OperandLocation& loc = operandLocations_[val.id()]; MOZ_ASSERT(loc.kind() == OperandLocation::Uninitialized); ValueOperand reg = allocateValueRegister(masm); loc.setValueReg(reg); return reg; } void CacheRegisterAllocator::freeDeadOperandLocations(MacroAssembler& masm) { // See if any operands are dead so we can reuse their registers. Note that // we skip the input operands, as those are also used by failure paths, and // we currently don't track those uses. for (size_t i = writer_.numInputOperands(); i < operandLocations_.length(); i++) { if (!writer_.operandIsDead(i, currentInstruction_)) continue; OperandLocation& loc = operandLocations_[i]; switch (loc.kind()) { case OperandLocation::PayloadReg: availableRegs_.add(loc.payloadReg()); break; case OperandLocation::ValueReg: availableRegs_.add(loc.valueReg()); break; case OperandLocation::PayloadStack: masm.propagateOOM(freePayloadSlots_.append(loc.payloadStack())); break; case OperandLocation::ValueStack: masm.propagateOOM(freeValueSlots_.append(loc.valueStack())); break; case OperandLocation::Uninitialized: case OperandLocation::BaselineFrame: case OperandLocation::Constant: case OperandLocation::DoubleReg: break; } loc.setUninitialized(); } } void CacheRegisterAllocator::discardStack(MacroAssembler& masm) { // This should only be called when we are no longer using the operands, // as we're discarding everything from the native stack. Set all operand // locations to Uninitialized to catch bugs. for (size_t i = 0; i < operandLocations_.length(); i++) operandLocations_[i].setUninitialized(); if (stackPushed_ > 0) { masm.addToStackPtr(Imm32(stackPushed_)); stackPushed_ = 0; } freePayloadSlots_.clear(); freeValueSlots_.clear(); } Register CacheRegisterAllocator::allocateRegister(MacroAssembler& masm) { if (availableRegs_.empty()) freeDeadOperandLocations(masm); if (availableRegs_.empty()) { // Still no registers available, try to spill unused operands to // the stack. for (size_t i = 0; i < operandLocations_.length(); i++) { OperandLocation& loc = operandLocations_[i]; if (loc.kind() == OperandLocation::PayloadReg) { Register reg = loc.payloadReg(); if (currentOpRegs_.has(reg)) continue; spillOperandToStack(masm, &loc); availableRegs_.add(reg); break; // We got a register, so break out of the loop. } if (loc.kind() == OperandLocation::ValueReg) { ValueOperand reg = loc.valueReg(); if (currentOpRegs_.aliases(reg)) continue; spillOperandToStack(masm, &loc); availableRegs_.add(reg); break; // Break out of the loop. } } } if (availableRegs_.empty() && !availableRegsAfterSpill_.empty()) { Register reg = availableRegsAfterSpill_.takeAny(); masm.push(reg); stackPushed_ += sizeof(uintptr_t); masm.propagateOOM(spilledRegs_.append(SpilledRegister(reg, stackPushed_))); availableRegs_.add(reg); } // At this point, there must be a free register. MOZ_RELEASE_ASSERT(!availableRegs_.empty()); Register reg = availableRegs_.takeAny(); currentOpRegs_.add(reg); return reg; } void CacheRegisterAllocator::allocateFixedRegister(MacroAssembler& masm, Register reg) { // Fixed registers should be allocated first, to ensure they're // still available. MOZ_ASSERT(!currentOpRegs_.has(reg), "Register is in use"); freeDeadOperandLocations(masm); if (availableRegs_.has(reg)) { availableRegs_.take(reg); currentOpRegs_.add(reg); return; } // The register must be used by some operand. Spill it to the stack. for (size_t i = 0; i < operandLocations_.length(); i++) { OperandLocation& loc = operandLocations_[i]; if (loc.kind() == OperandLocation::PayloadReg) { if (loc.payloadReg() != reg) continue; spillOperandToStackOrRegister(masm, &loc); currentOpRegs_.add(reg); return; } if (loc.kind() == OperandLocation::ValueReg) { if (!loc.valueReg().aliases(reg)) continue; ValueOperand valueReg = loc.valueReg(); spillOperandToStackOrRegister(masm, &loc); availableRegs_.add(valueReg); availableRegs_.take(reg); currentOpRegs_.add(reg); return; } } MOZ_CRASH("Invalid register"); } void CacheRegisterAllocator::allocateFixedValueRegister(MacroAssembler& masm, ValueOperand reg) { #ifdef JS_NUNBOX32 allocateFixedRegister(masm, reg.payloadReg()); allocateFixedRegister(masm, reg.typeReg()); #else allocateFixedRegister(masm, reg.valueReg()); #endif } ValueOperand CacheRegisterAllocator::allocateValueRegister(MacroAssembler& masm) { #ifdef JS_NUNBOX32 Register reg1 = allocateRegister(masm); Register reg2 = allocateRegister(masm); return ValueOperand(reg1, reg2); #else Register reg = allocateRegister(masm); return ValueOperand(reg); #endif } bool CacheRegisterAllocator::init() { if (!origInputLocations_.resize(writer_.numInputOperands())) return false; if (!operandLocations_.resize(writer_.numOperandIds())) return false; return true; } void CacheRegisterAllocator::initAvailableRegsAfterSpill() { // Registers not in availableRegs_ and not used by input operands are // available after being spilled. availableRegsAfterSpill_.set() = GeneralRegisterSet::Intersect(GeneralRegisterSet::Not(availableRegs_.set()), GeneralRegisterSet::Not(inputRegisterSet())); } void CacheRegisterAllocator::fixupAliasedInputs(MacroAssembler& masm) { // If IC inputs alias each other, make sure they are stored in different // locations so we don't have to deal with this complexity in the rest of // the allocator. // // Note that this can happen in IonMonkey with something like |o.foo = o| // or |o[i] = i|. size_t numInputs = writer_.numInputOperands(); MOZ_ASSERT(origInputLocations_.length() == numInputs); for (size_t i = 1; i < numInputs; i++) { OperandLocation& loc1 = operandLocations_[i]; if (!loc1.isInRegister()) continue; for (size_t j = 0; j < i; j++) { OperandLocation& loc2 = operandLocations_[j]; if (!loc1.aliasesReg(loc2)) continue; // loc1 and loc2 alias so we spill one of them. If one is a // ValueReg and the other is a PayloadReg, we have to spill the // PayloadReg: spilling the ValueReg instead would leave its type // register unallocated on 32-bit platforms. if (loc1.kind() == OperandLocation::ValueReg) { spillOperandToStack(masm, &loc2); } else { MOZ_ASSERT(loc1.kind() == OperandLocation::PayloadReg); spillOperandToStack(masm, &loc1); break; // Spilled loc1, so nothing else will alias it. } } } } GeneralRegisterSet CacheRegisterAllocator::inputRegisterSet() const { MOZ_ASSERT(origInputLocations_.length() == writer_.numInputOperands()); AllocatableGeneralRegisterSet result; for (size_t i = 0; i < writer_.numInputOperands(); i++) { const OperandLocation& loc = operandLocations_[i]; MOZ_ASSERT(loc == origInputLocations_[i]); switch (loc.kind()) { case OperandLocation::PayloadReg: result.addUnchecked(loc.payloadReg()); continue; case OperandLocation::ValueReg: result.addUnchecked(loc.valueReg()); continue; case OperandLocation::PayloadStack: case OperandLocation::ValueStack: case OperandLocation::BaselineFrame: case OperandLocation::Constant: case OperandLocation::DoubleReg: continue; case OperandLocation::Uninitialized: break; } MOZ_CRASH("Invalid kind"); } return result.set(); } JSValueType CacheRegisterAllocator::knownType(ValOperandId val) const { const OperandLocation& loc = operandLocations_[val.id()]; switch (loc.kind()) { case OperandLocation::ValueReg: case OperandLocation::ValueStack: case OperandLocation::BaselineFrame: return JSVAL_TYPE_UNKNOWN; case OperandLocation::PayloadStack: case OperandLocation::PayloadReg: return loc.payloadType(); case OperandLocation::Constant: return loc.constant().isDouble() ? JSVAL_TYPE_DOUBLE : loc.constant().extractNonDoubleType(); case OperandLocation::DoubleReg: return JSVAL_TYPE_DOUBLE; case OperandLocation::Uninitialized: break; } MOZ_CRASH("Invalid kind"); } void CacheRegisterAllocator::initInputLocation(size_t i, const TypedOrValueRegister& reg) { if (reg.hasValue()) { initInputLocation(i, reg.valueReg()); } else if (reg.typedReg().isFloat()) { MOZ_ASSERT(reg.type() == MIRType::Double); initInputLocation(i, reg.typedReg().fpu()); } else { initInputLocation(i, reg.typedReg().gpr(), ValueTypeFromMIRType(reg.type())); } } void CacheRegisterAllocator::initInputLocation(size_t i, const ConstantOrRegister& value) { if (value.constant()) initInputLocation(i, value.value()); else initInputLocation(i, value.reg()); } void CacheRegisterAllocator::spillOperandToStack(MacroAssembler& masm, OperandLocation* loc) { MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end()); if (loc->kind() == OperandLocation::ValueReg) { if (!freeValueSlots_.empty()) { uint32_t stackPos = freeValueSlots_.popCopy(); MOZ_ASSERT(stackPos <= stackPushed_); masm.storeValue(loc->valueReg(), Address(masm.getStackPointer(), stackPushed_ - stackPos)); loc->setValueStack(stackPos); return; } stackPushed_ += sizeof(js::Value); masm.pushValue(loc->valueReg()); loc->setValueStack(stackPushed_); return; } MOZ_ASSERT(loc->kind() == OperandLocation::PayloadReg); if (!freePayloadSlots_.empty()) { uint32_t stackPos = freePayloadSlots_.popCopy(); MOZ_ASSERT(stackPos <= stackPushed_); masm.storePtr(loc->payloadReg(), Address(masm.getStackPointer(), stackPushed_ - stackPos)); loc->setPayloadStack(stackPos, loc->payloadType()); return; } stackPushed_ += sizeof(uintptr_t); masm.push(loc->payloadReg()); loc->setPayloadStack(stackPushed_, loc->payloadType()); } void CacheRegisterAllocator::spillOperandToStackOrRegister(MacroAssembler& masm, OperandLocation* loc) { MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end()); // If enough registers are available, use them. if (loc->kind() == OperandLocation::ValueReg) { static const size_t BoxPieces = sizeof(Value) / sizeof(uintptr_t); if (availableRegs_.set().size() >= BoxPieces) { ValueOperand reg = availableRegs_.takeAnyValue(); masm.moveValue(loc->valueReg(), reg); loc->setValueReg(reg); return; } } else { MOZ_ASSERT(loc->kind() == OperandLocation::PayloadReg); if (!availableRegs_.empty()) { Register reg = availableRegs_.takeAny(); masm.movePtr(loc->payloadReg(), reg); loc->setPayloadReg(reg, loc->payloadType()); return; } } // Not enough registers available, spill to the stack. spillOperandToStack(masm, loc); } void CacheRegisterAllocator::popPayload(MacroAssembler& masm, OperandLocation* loc, Register dest) { MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end()); MOZ_ASSERT(stackPushed_ >= sizeof(uintptr_t)); // The payload is on the stack. If it's on top of the stack we can just // pop it, else we emit a load. if (loc->payloadStack() == stackPushed_) { masm.pop(dest); stackPushed_ -= sizeof(uintptr_t); } else { MOZ_ASSERT(loc->payloadStack() < stackPushed_); masm.loadPtr(Address(masm.getStackPointer(), stackPushed_ - loc->payloadStack()), dest); masm.propagateOOM(freePayloadSlots_.append(loc->payloadStack())); } loc->setPayloadReg(dest, loc->payloadType()); } void CacheRegisterAllocator::popValue(MacroAssembler& masm, OperandLocation* loc, ValueOperand dest) { MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end()); MOZ_ASSERT(stackPushed_ >= sizeof(js::Value)); // The Value is on the stack. If it's on top of the stack we can just // pop it, else we emit a load. if (loc->valueStack() == stackPushed_) { masm.popValue(dest); stackPushed_ -= sizeof(js::Value); } else { MOZ_ASSERT(loc->valueStack() < stackPushed_); masm.loadValue(Address(masm.getStackPointer(), stackPushed_ - loc->valueStack()), dest); masm.propagateOOM(freeValueSlots_.append(loc->valueStack())); } loc->setValueReg(dest); } bool OperandLocation::aliasesReg(const OperandLocation& other) const { MOZ_ASSERT(&other != this); switch (other.kind_) { case PayloadReg: return aliasesReg(other.payloadReg()); case ValueReg: return aliasesReg(other.valueReg()); case PayloadStack: case ValueStack: case BaselineFrame: case Constant: case DoubleReg: return false; case Uninitialized: break; } MOZ_CRASH("Invalid kind"); } void CacheRegisterAllocator::restoreInputState(MacroAssembler& masm, bool shouldDiscardStack) { size_t numInputOperands = origInputLocations_.length(); MOZ_ASSERT(writer_.numInputOperands() == numInputOperands); for (size_t j = 0; j < numInputOperands; j++) { const OperandLocation& dest = origInputLocations_[j]; OperandLocation& cur = operandLocations_[j]; if (dest == cur) continue; auto autoAssign = mozilla::MakeScopeExit([&] { cur = dest; }); // We have a cycle if a destination register will be used later // as source register. If that happens, just push the current value // on the stack and later get it from there. for (size_t k = j + 1; k < numInputOperands; k++) { OperandLocation& laterSource = operandLocations_[k]; if (dest.aliasesReg(laterSource)) spillOperandToStack(masm, &laterSource); } if (dest.kind() == OperandLocation::ValueReg) { // We have to restore a Value register. switch (cur.kind()) { case OperandLocation::ValueReg: masm.moveValue(cur.valueReg(), dest.valueReg()); continue; case OperandLocation::PayloadReg: masm.tagValue(cur.payloadType(), cur.payloadReg(), dest.valueReg()); continue; case OperandLocation::PayloadStack: { Register scratch = dest.valueReg().scratchReg(); popPayload(masm, &cur, scratch); masm.tagValue(cur.payloadType(), scratch, dest.valueReg()); continue; } case OperandLocation::ValueStack: popValue(masm, &cur, dest.valueReg()); continue; case OperandLocation::Constant: case OperandLocation::BaselineFrame: case OperandLocation::DoubleReg: case OperandLocation::Uninitialized: break; } } else if (dest.kind() == OperandLocation::PayloadReg) { // We have to restore a payload register. switch (cur.kind()) { case OperandLocation::ValueReg: MOZ_ASSERT(dest.payloadType() != JSVAL_TYPE_DOUBLE); masm.unboxNonDouble(cur.valueReg(), dest.payloadReg()); continue; case OperandLocation::PayloadReg: MOZ_ASSERT(cur.payloadType() == dest.payloadType()); masm.mov(cur.payloadReg(), dest.payloadReg()); continue; case OperandLocation::PayloadStack: { MOZ_ASSERT(cur.payloadType() == dest.payloadType()); popPayload(masm, &cur, dest.payloadReg()); continue; } case OperandLocation::ValueStack: MOZ_ASSERT(stackPushed_ >= sizeof(js::Value)); MOZ_ASSERT(cur.valueStack() <= stackPushed_); MOZ_ASSERT(dest.payloadType() != JSVAL_TYPE_DOUBLE); masm.unboxNonDouble(Address(masm.getStackPointer(), stackPushed_ - cur.valueStack()), dest.payloadReg()); continue; case OperandLocation::Constant: case OperandLocation::BaselineFrame: case OperandLocation::DoubleReg: case OperandLocation::Uninitialized: break; } } else if (dest.kind() == OperandLocation::Constant || dest.kind() == OperandLocation::BaselineFrame || dest.kind() == OperandLocation::DoubleReg) { // Nothing to do. continue; } MOZ_CRASH("Invalid kind"); } for (const SpilledRegister& spill : spilledRegs_) { MOZ_ASSERT(stackPushed_ >= sizeof(uintptr_t)); if (spill.stackPushed == stackPushed_) { masm.pop(spill.reg); stackPushed_ -= sizeof(uintptr_t); } else { MOZ_ASSERT(spill.stackPushed < stackPushed_); masm.loadPtr(Address(masm.getStackPointer(), stackPushed_ - spill.stackPushed), spill.reg); } } if (shouldDiscardStack) discardStack(masm); } size_t CacheIRStubInfo::stubDataSize() const { size_t field = 0; size_t size = 0; while (true) { StubField::Type type = fieldType(field++); if (type == StubField::Type::Limit) return size; size += StubField::sizeInBytes(type); } } void CacheIRStubInfo::copyStubData(ICStub* src, ICStub* dest) const { uint8_t* srcBytes = reinterpret_cast(src); uint8_t* destBytes = reinterpret_cast(dest); size_t field = 0; size_t offset = 0; while (true) { StubField::Type type = fieldType(field); switch (type) { case StubField::Type::RawWord: *reinterpret_cast(destBytes + offset) = *reinterpret_cast(srcBytes + offset); break; case StubField::Type::RawInt64: case StubField::Type::DOMExpandoGeneration: *reinterpret_cast(destBytes + offset) = *reinterpret_cast(srcBytes + offset); break; case StubField::Type::Shape: getStubField(dest, offset).init(getStubField(src, offset)); break; case StubField::Type::JSObject: getStubField(dest, offset).init(getStubField(src, offset)); break; case StubField::Type::ObjectGroup: getStubField(dest, offset).init(getStubField(src, offset)); break; case StubField::Type::Symbol: getStubField(dest, offset).init(getStubField(src, offset)); break; case StubField::Type::String: getStubField(dest, offset).init(getStubField(src, offset)); break; case StubField::Type::Id: getStubField(dest, offset).init(getStubField(src, offset)); break; case StubField::Type::Value: getStubField(dest, offset).init(getStubField(src, offset)); break; case StubField::Type::Limit: return; // Done. } field++; offset += StubField::sizeInBytes(type); } } template static GCPtr* AsGCPtr(uintptr_t* ptr) { return reinterpret_cast*>(ptr); } template GCPtr& CacheIRStubInfo::getStubField(Stub* stub, uint32_t offset) const { uint8_t* stubData = (uint8_t*)stub + stubDataOffset_; MOZ_ASSERT(uintptr_t(stubData) % sizeof(uintptr_t) == 0); return *AsGCPtr((uintptr_t*)(stubData + offset)); } template GCPtr& CacheIRStubInfo::getStubField(ICStub* stub, uint32_t offset) const; template GCPtr& CacheIRStubInfo::getStubField(ICStub* stub, uint32_t offset) const; template GCPtr& CacheIRStubInfo::getStubField(ICStub* stub, uint32_t offset) const; template GCPtr& CacheIRStubInfo::getStubField(ICStub* stub, uint32_t offset) const; template GCPtr& CacheIRStubInfo::getStubField(ICStub* stub, uint32_t offset) const; template GCPtr& CacheIRStubInfo::getStubField(ICStub* stub, uint32_t offset) const; template GCPtr& CacheIRStubInfo::getStubField(ICStub* stub, uint32_t offset) const; template static void InitGCPtr(uintptr_t* ptr, V val) { AsGCPtr(ptr)->init(mozilla::BitwiseCast(val)); } void CacheIRWriter::copyStubData(uint8_t* dest) const { MOZ_ASSERT(!failed()); uintptr_t* destWords = reinterpret_cast(dest); for (const StubField& field : stubFields_) { switch (field.type()) { case StubField::Type::RawWord: *destWords = field.asWord(); break; case StubField::Type::Shape: InitGCPtr(destWords, field.asWord()); break; case StubField::Type::JSObject: InitGCPtr(destWords, field.asWord()); break; case StubField::Type::ObjectGroup: InitGCPtr(destWords, field.asWord()); break; case StubField::Type::Symbol: InitGCPtr(destWords, field.asWord()); break; case StubField::Type::String: InitGCPtr(destWords, field.asWord()); break; case StubField::Type::Id: InitGCPtr(destWords, field.asWord()); break; case StubField::Type::RawInt64: case StubField::Type::DOMExpandoGeneration: *reinterpret_cast(destWords) = field.asInt64(); break; case StubField::Type::Value: InitGCPtr(destWords, field.asInt64()); break; case StubField::Type::Limit: MOZ_CRASH("Invalid type"); } destWords += StubField::sizeInBytes(field.type()) / sizeof(uintptr_t); } } template void jit::TraceCacheIRStub(JSTracer* trc, T* stub, const CacheIRStubInfo* stubInfo) { uint32_t field = 0; size_t offset = 0; while (true) { StubField::Type fieldType = stubInfo->fieldType(field); switch (fieldType) { case StubField::Type::RawWord: case StubField::Type::RawInt64: case StubField::Type::DOMExpandoGeneration: break; case StubField::Type::Shape: TraceNullableEdge(trc, &stubInfo->getStubField(stub, offset), "cacheir-shape"); break; case StubField::Type::ObjectGroup: TraceNullableEdge(trc, &stubInfo->getStubField(stub, offset), "cacheir-group"); break; case StubField::Type::JSObject: TraceNullableEdge(trc, &stubInfo->getStubField(stub, offset), "cacheir-object"); break; case StubField::Type::Symbol: TraceNullableEdge(trc, &stubInfo->getStubField(stub, offset), "cacheir-symbol"); break; case StubField::Type::String: TraceNullableEdge(trc, &stubInfo->getStubField(stub, offset), "cacheir-string"); break; case StubField::Type::Id: TraceEdge(trc, &stubInfo->getStubField(stub, offset), "cacheir-id"); break; case StubField::Type::Value: TraceEdge(trc, &stubInfo->getStubField(stub, offset), "cacheir-value"); break; case StubField::Type::Limit: return; // Done. } field++; offset += StubField::sizeInBytes(fieldType); } } template void jit::TraceCacheIRStub(JSTracer* trc, ICStub* stub, const CacheIRStubInfo* stubInfo); template void jit::TraceCacheIRStub(JSTracer* trc, IonICStub* stub, const CacheIRStubInfo* stubInfo); bool CacheIRWriter::stubDataEqualsMaybeUpdate(uint8_t* stubData, bool* updated) const { MOZ_ASSERT(!failed()); *updated = false; const uintptr_t* stubDataWords = reinterpret_cast(stubData); // If DOMExpandoGeneration fields are different but all other stub fields // are exactly the same, we overwrite the old stub data instead of attaching // a new stub, as the old stub is never going to succeed. This works because // even Ion stubs read the DOMExpandoGeneration field from the stub instead // of baking it in. bool expandoGenerationIsDifferent = false; for (const StubField& field : stubFields_) { if (field.sizeIsWord()) { if (field.asWord() != *stubDataWords) return false; stubDataWords++; continue; } if (field.asInt64() != *reinterpret_cast(stubDataWords)) { if (field.type() != StubField::Type::DOMExpandoGeneration) return false; expandoGenerationIsDifferent = true; } stubDataWords += sizeof(uint64_t) / sizeof(uintptr_t); } if (expandoGenerationIsDifferent) { copyStubData(stubData); *updated = true; } return true; } HashNumber CacheIRStubKey::hash(const CacheIRStubKey::Lookup& l) { HashNumber hash = mozilla::HashBytes(l.code, l.length); hash = mozilla::AddToHash(hash, uint32_t(l.kind)); hash = mozilla::AddToHash(hash, uint32_t(l.engine)); return hash; } bool CacheIRStubKey::match(const CacheIRStubKey& entry, const CacheIRStubKey::Lookup& l) { if (entry.stubInfo->kind() != l.kind) return false; if (entry.stubInfo->engine() != l.engine) return false; if (entry.stubInfo->codeLength() != l.length) return false; if (!mozilla::PodEqual(entry.stubInfo->code(), l.code, l.length)) return false; return true; } CacheIRReader::CacheIRReader(const CacheIRStubInfo* stubInfo) : CacheIRReader(stubInfo->code(), stubInfo->code() + stubInfo->codeLength()) {} CacheIRStubInfo* CacheIRStubInfo::New(CacheKind kind, ICStubEngine engine, bool makesGCCalls, uint32_t stubDataOffset, const CacheIRWriter& writer) { size_t numStubFields = writer.numStubFields(); size_t bytesNeeded = sizeof(CacheIRStubInfo) + writer.codeLength() + (numStubFields + 1); // +1 for the GCType::Limit terminator. uint8_t* p = js_pod_malloc(bytesNeeded); if (!p) return nullptr; // Copy the CacheIR code. uint8_t* codeStart = p + sizeof(CacheIRStubInfo); mozilla::PodCopy(codeStart, writer.codeStart(), writer.codeLength()); static_assert(sizeof(StubField::Type) == sizeof(uint8_t), "StubField::Type must fit in uint8_t"); // Copy the stub field types. uint8_t* fieldTypes = codeStart + writer.codeLength(); for (size_t i = 0; i < numStubFields; i++) fieldTypes[i] = uint8_t(writer.stubFieldType(i)); fieldTypes[numStubFields] = uint8_t(StubField::Type::Limit); return new(p) CacheIRStubInfo(kind, engine, makesGCCalls, stubDataOffset, codeStart, writer.codeLength(), fieldTypes); } bool OperandLocation::operator==(const OperandLocation& other) const { if (kind_ != other.kind_) return false; switch (kind()) { case Uninitialized: return true; case PayloadReg: return payloadReg() == other.payloadReg() && payloadType() == other.payloadType(); case ValueReg: return valueReg() == other.valueReg(); case PayloadStack: return payloadStack() == other.payloadStack() && payloadType() == other.payloadType(); case ValueStack: return valueStack() == other.valueStack(); case BaselineFrame: return baselineFrameSlot() == other.baselineFrameSlot(); case Constant: return constant() == other.constant(); case DoubleReg: return doubleReg() == other.doubleReg(); } MOZ_CRASH("Invalid OperandLocation kind"); } AutoOutputRegister::AutoOutputRegister(CacheIRCompiler& compiler) : output_(compiler.outputUnchecked_.ref()), alloc_(compiler.allocator) { if (output_.hasValue()) alloc_.allocateFixedValueRegister(compiler.masm, output_.valueReg()); else if (!output_.typedReg().isFloat()) alloc_.allocateFixedRegister(compiler.masm, output_.typedReg().gpr()); } AutoOutputRegister::~AutoOutputRegister() { if (output_.hasValue()) alloc_.releaseValueRegister(output_.valueReg()); else if (!output_.typedReg().isFloat()) alloc_.releaseRegister(output_.typedReg().gpr()); } bool FailurePath::canShareFailurePath(const FailurePath& other) const { if (stackPushed_ != other.stackPushed_) return false; if (spilledRegs_.length() != other.spilledRegs_.length()) return false; for (size_t i = 0; i < spilledRegs_.length(); i++) { if (spilledRegs_[i] != other.spilledRegs_[i]) return false; } MOZ_ASSERT(inputs_.length() == other.inputs_.length()); for (size_t i = 0; i < inputs_.length(); i++) { if (inputs_[i] != other.inputs_[i]) return false; } return true; } bool CacheIRCompiler::addFailurePath(FailurePath** failure) { FailurePath newFailure; for (size_t i = 0; i < writer_.numInputOperands(); i++) { if (!newFailure.appendInput(allocator.operandLocation(i))) return false; } if (!newFailure.setSpilledRegs(allocator.spilledRegs())) return false; newFailure.setStackPushed(allocator.stackPushed()); // Reuse the previous failure path if the current one is the same, to // avoid emitting duplicate code. if (failurePaths.length() > 0 && failurePaths.back().canShareFailurePath(newFailure)) { *failure = &failurePaths.back(); return true; } if (!failurePaths.append(Move(newFailure))) return false; *failure = &failurePaths.back(); return true; } bool CacheIRCompiler::emitFailurePath(size_t index) { FailurePath& failure = failurePaths[index]; allocator.setStackPushed(failure.stackPushed()); for (size_t i = 0; i < writer_.numInputOperands(); i++) allocator.setOperandLocation(i, failure.input(i)); if (!allocator.setSpilledRegs(failure.spilledRegs())) return false; masm.bind(failure.label()); allocator.restoreInputState(masm); return true; } bool CacheIRCompiler::emitGuardIsObject() { ValOperandId inputId = reader.valOperandId(); if (allocator.knownType(inputId) == JSVAL_TYPE_OBJECT) return true; ValueOperand input = allocator.useValueRegister(masm, inputId); FailurePath* failure; if (!addFailurePath(&failure)) return false; masm.branchTestObject(Assembler::NotEqual, input, failure->label()); return true; } bool CacheIRCompiler::emitGuardIsObjectOrNull() { ValOperandId inputId = reader.valOperandId(); JSValueType knownType = allocator.knownType(inputId); if (knownType == JSVAL_TYPE_OBJECT || knownType == JSVAL_TYPE_NULL) return true; ValueOperand input = allocator.useValueRegister(masm, inputId); FailurePath* failure; if (!addFailurePath(&failure)) return false; Label done; masm.branchTestObject(Assembler::Equal, input, &done); masm.branchTestNull(Assembler::NotEqual, input, failure->label()); masm.bind(&done); return true; } bool CacheIRCompiler::emitGuardIsString() { ValOperandId inputId = reader.valOperandId(); if (allocator.knownType(inputId) == JSVAL_TYPE_STRING) return true; ValueOperand input = allocator.useValueRegister(masm, inputId); FailurePath* failure; if (!addFailurePath(&failure)) return false; masm.branchTestString(Assembler::NotEqual, input, failure->label()); return true; } bool CacheIRCompiler::emitGuardIsSymbol() { ValOperandId inputId = reader.valOperandId(); if (allocator.knownType(inputId) == JSVAL_TYPE_SYMBOL) return true; ValueOperand input = allocator.useValueRegister(masm, inputId); FailurePath* failure; if (!addFailurePath(&failure)) return false; masm.branchTestSymbol(Assembler::NotEqual, input, failure->label()); return true; } bool CacheIRCompiler::emitGuardIsInt32Index() { ValOperandId inputId = reader.valOperandId(); Register output = allocator.defineRegister(masm, reader.int32OperandId()); if (allocator.knownType(inputId) == JSVAL_TYPE_INT32) { Register input = allocator.useRegister(masm, Int32OperandId(inputId.id())); masm.move32(input, output); return true; } ValueOperand input = allocator.useValueRegister(masm, inputId); FailurePath* failure; if (!addFailurePath(&failure)) return false; Label notInt32, done; masm.branchTestInt32(Assembler::NotEqual, input, ¬Int32); masm.unboxInt32(input, output); masm.jump(&done); masm.bind(¬Int32); if (cx_->runtime()->jitSupportsFloatingPoint) { masm.branchTestDouble(Assembler::NotEqual, input, failure->label()); // If we're compiling a Baseline IC, FloatReg0 is always available. Label failurePopReg; if (mode_ != Mode::Baseline) masm.push(FloatReg0); masm.unboxDouble(input, FloatReg0); // ToPropertyKey(-0.0) is "0", so we can truncate -0.0 to 0 here. masm.convertDoubleToInt32(FloatReg0, output, (mode_ == Mode::Baseline) ? failure->label() : &failurePopReg, false); if (mode_ != Mode::Baseline) { masm.pop(FloatReg0); masm.jump(&done); masm.bind(&failurePopReg); masm.pop(FloatReg0); masm.jump(failure->label()); } } else { masm.jump(failure->label()); } masm.bind(&done); return true; } bool CacheIRCompiler::emitGuardType() { ValOperandId inputId = reader.valOperandId(); JSValueType type = reader.valueType(); if (allocator.knownType(inputId) == type) return true; ValueOperand input = allocator.useValueRegister(masm, inputId); FailurePath* failure; if (!addFailurePath(&failure)) return false; switch (type) { case JSVAL_TYPE_STRING: masm.branchTestString(Assembler::NotEqual, input, failure->label()); break; case JSVAL_TYPE_SYMBOL: masm.branchTestSymbol(Assembler::NotEqual, input, failure->label()); break; case JSVAL_TYPE_INT32: masm.branchTestInt32(Assembler::NotEqual, input, failure->label()); break; case JSVAL_TYPE_DOUBLE: masm.branchTestNumber(Assembler::NotEqual, input, failure->label()); break; case JSVAL_TYPE_BOOLEAN: masm.branchTestBoolean(Assembler::NotEqual, input, failure->label()); break; case JSVAL_TYPE_UNDEFINED: masm.branchTestUndefined(Assembler::NotEqual, input, failure->label()); break; case JSVAL_TYPE_NULL: masm.branchTestNull(Assembler::NotEqual, input, failure->label()); break; default: MOZ_CRASH("Unexpected type"); } return true; } bool CacheIRCompiler::emitGuardClass() { Register obj = allocator.useRegister(masm, reader.objOperandId()); AutoScratchRegister scratch(allocator, masm); FailurePath* failure; if (!addFailurePath(&failure)) return false; const Class* clasp = nullptr; switch (reader.guardClassKind()) { case GuardClassKind::Array: clasp = &ArrayObject::class_; break; case GuardClassKind::MappedArguments: clasp = &MappedArgumentsObject::class_; break; case GuardClassKind::UnmappedArguments: clasp = &UnmappedArgumentsObject::class_; break; case GuardClassKind::WindowProxy: clasp = cx_->runtime()->maybeWindowProxyClass(); break; case GuardClassKind::JSFunction: clasp = &JSFunction::class_; break; } MOZ_ASSERT(clasp); masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, clasp, failure->label()); return true; } bool CacheIRCompiler::emitGuardIsNativeFunction() { Register obj = allocator.useRegister(masm, reader.objOperandId()); JSNative nativeFunc = reinterpret_cast(reader.pointer()); AutoScratchRegister scratch(allocator, masm); FailurePath* failure; if (!addFailurePath(&failure)) return false; // Ensure obj is a function. const Class* clasp = &JSFunction::class_; masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, clasp, failure->label()); // Ensure function native matches. masm.branchPtr(Assembler::NotEqual, Address(obj, JSFunction::offsetOfNativeOrEnv()), ImmPtr(nativeFunc), failure->label()); return true; } bool CacheIRCompiler::emitGuardIsProxy() { Register obj = allocator.useRegister(masm, reader.objOperandId()); AutoScratchRegister scratch(allocator, masm); FailurePath* failure; if (!addFailurePath(&failure)) return false; masm.branchTestObjectIsProxy(false, obj, scratch, failure->label()); return true; } bool CacheIRCompiler::emitGuardNotDOMProxy() { Register obj = allocator.useRegister(masm, reader.objOperandId()); AutoScratchRegister scratch(allocator, masm); FailurePath* failure; if (!addFailurePath(&failure)) return false; masm.branchTestProxyHandlerFamily(Assembler::Equal, obj, scratch, GetDOMProxyHandlerFamily(), failure->label()); return true; } bool CacheIRCompiler::emitGuardSpecificInt32Immediate() { Register reg = allocator.useRegister(masm, reader.int32OperandId()); int32_t ival = reader.int32Immediate(); Assembler::Condition cond = (Assembler::Condition) reader.readByte(); FailurePath* failure; if (!addFailurePath(&failure)) return false; masm.branch32(Assembler::InvertCondition(cond), reg, Imm32(ival), failure->label()); return true; } bool CacheIRCompiler::emitGuardMagicValue() { ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId()); JSWhyMagic magic = reader.whyMagic(); FailurePath* failure; if (!addFailurePath(&failure)) return false; masm.branchTestMagicValue(Assembler::NotEqual, val, magic, failure->label()); return true; } bool CacheIRCompiler::emitGuardNoUnboxedExpando() { Register obj = allocator.useRegister(masm, reader.objOperandId()); FailurePath* failure; if (!addFailurePath(&failure)) return false; Address expandoAddr(obj, UnboxedPlainObject::offsetOfExpando()); masm.branchPtr(Assembler::NotEqual, expandoAddr, ImmWord(0), failure->label()); return true; } bool CacheIRCompiler::emitGuardAndLoadUnboxedExpando() { Register obj = allocator.useRegister(masm, reader.objOperandId()); Register output = allocator.defineRegister(masm, reader.objOperandId()); FailurePath* failure; if (!addFailurePath(&failure)) return false; Address expandoAddr(obj, UnboxedPlainObject::offsetOfExpando()); masm.loadPtr(expandoAddr, output); masm.branchTestPtr(Assembler::Zero, output, output, failure->label()); return true; } bool CacheIRCompiler::emitGuardNoDetachedTypedObjects() { FailurePath* failure; if (!addFailurePath(&failure)) return false; // All stubs manipulating typed objects must check the compartment-wide // flag indicating whether their underlying storage might be detached, to // bail out if needed. int32_t* address = &cx_->compartment()->detachedTypedObjects; masm.branch32(Assembler::NotEqual, AbsoluteAddress(address), Imm32(0), failure->label()); return true; } bool CacheIRCompiler::emitGuardNoDenseElements() { Register obj = allocator.useRegister(masm, reader.objOperandId()); AutoScratchRegister scratch(allocator, masm); FailurePath* failure; if (!addFailurePath(&failure)) return false; // Load obj->elements. masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch); // Make sure there are no dense elements. Address initLength(scratch, ObjectElements::offsetOfInitializedLength()); masm.branch32(Assembler::NotEqual, initLength, Imm32(0), failure->label()); return true; } bool CacheIRCompiler::emitGuardAndGetIndexFromString() { Register str = allocator.useRegister(masm, reader.stringOperandId()); Register output = allocator.defineRegister(masm, reader.int32OperandId()); FailurePath* failure; if (!addFailurePath(&failure)) return false; Label vmCall, done; masm.loadStringIndexValue(str, output, &vmCall); masm.jump(&done); { masm.bind(&vmCall); LiveRegisterSet save(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); masm.PushRegsInMask(save); masm.setupUnalignedABICall(output); masm.passABIArg(str); masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, GetIndexFromString)); masm.mov(ReturnReg, output); LiveRegisterSet ignore; ignore.add(output); masm.PopRegsInMaskIgnore(save, ignore); // GetIndexFromString returns a negative value on failure. masm.branchTest32(Assembler::Signed, output, output, failure->label()); } masm.bind(&done); return true; } bool CacheIRCompiler::emitLoadProto() { Register obj = allocator.useRegister(masm, reader.objOperandId()); Register reg = allocator.defineRegister(masm, reader.objOperandId()); masm.loadObjProto(obj, reg); return true; } bool CacheIRCompiler::emitLoadEnclosingEnvironment() { Register obj = allocator.useRegister(masm, reader.objOperandId()); Register reg = allocator.defineRegister(masm, reader.objOperandId()); masm.extractObject(Address(obj, EnvironmentObject::offsetOfEnclosingEnvironment()), reg); return true; } bool CacheIRCompiler::emitLoadWrapperTarget() { Register obj = allocator.useRegister(masm, reader.objOperandId()); Register reg = allocator.defineRegister(masm, reader.objOperandId()); masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), reg); masm.unboxObject(Address(reg, detail::ProxyReservedSlots::offsetOfPrivateSlot()), reg); return true; } bool CacheIRCompiler::emitLoadDOMExpandoValue() { Register obj = allocator.useRegister(masm, reader.objOperandId()); ValueOperand val = allocator.defineValueRegister(masm, reader.valOperandId()); masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), val.scratchReg()); masm.loadValue(Address(val.scratchReg(), detail::ProxyReservedSlots::offsetOfPrivateSlot()), val); return true; } bool CacheIRCompiler::emitLoadDOMExpandoValueIgnoreGeneration() { Register obj = allocator.useRegister(masm, reader.objOperandId()); ValueOperand output = allocator.defineValueRegister(masm, reader.valOperandId()); // Determine the expando's Address. Register scratch = output.scratchReg(); masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), scratch); Address expandoAddr(scratch, detail::ProxyReservedSlots::offsetOfPrivateSlot()); #ifdef DEBUG // Private values are stored as doubles, so assert we have a double. Label ok; masm.branchTestDouble(Assembler::Equal, expandoAddr, &ok); masm.assumeUnreachable("DOM expando is not a PrivateValue!"); masm.bind(&ok); #endif // Load the ExpandoAndGeneration* from the PrivateValue. masm.loadPrivate(expandoAddr, scratch); // Load expandoAndGeneration->expando into the output Value register. masm.loadValue(Address(scratch, ExpandoAndGeneration::offsetOfExpando()), output); return true; } bool CacheIRCompiler::emitLoadUndefinedResult() { AutoOutputRegister output(*this); if (output.hasValue()) masm.moveValue(UndefinedValue(), output.valueReg()); else masm.assumeUnreachable("Should have monitored undefined result"); return true; } static void EmitStoreBoolean(MacroAssembler& masm, bool b, const AutoOutputRegister& output) { if (output.hasValue()) { Value val = BooleanValue(b); masm.moveValue(val, output.valueReg()); } else { MOZ_ASSERT(output.type() == JSVAL_TYPE_BOOLEAN); masm.movePtr(ImmWord(b), output.typedReg().gpr()); } } bool CacheIRCompiler::emitLoadBooleanResult() { AutoOutputRegister output(*this); bool b = reader.readBool(); EmitStoreBoolean(masm, b, output); return true; } static void EmitStoreResult(MacroAssembler& masm, Register reg, JSValueType type, const AutoOutputRegister& output) { if (output.hasValue()) { masm.tagValue(type, reg, output.valueReg()); return; } if (type == JSVAL_TYPE_INT32 && output.typedReg().isFloat()) { masm.convertInt32ToDouble(reg, output.typedReg().fpu()); return; } if (type == output.type()) { masm.mov(reg, output.typedReg().gpr()); return; } masm.assumeUnreachable("Should have monitored result"); } bool CacheIRCompiler::emitLoadInt32ArrayLengthResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); FailurePath* failure; if (!addFailurePath(&failure)) return false; masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch); masm.load32(Address(scratch, ObjectElements::offsetOfLength()), scratch); // Guard length fits in an int32. masm.branchTest32(Assembler::Signed, scratch, scratch, failure->label()); EmitStoreResult(masm, scratch, JSVAL_TYPE_INT32, output); return true; } bool CacheIRCompiler::emitLoadArgumentsObjectLengthResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); FailurePath* failure; if (!addFailurePath(&failure)) return false; // Get initial length value. masm.unboxInt32(Address(obj, ArgumentsObject::getInitialLengthSlotOffset()), scratch); // Test if length has been overridden. masm.branchTest32(Assembler::NonZero, scratch, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT), failure->label()); // Shift out arguments length and return it. No need to type monitor // because this stub always returns int32. masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), scratch); EmitStoreResult(masm, scratch, JSVAL_TYPE_INT32, output); return true; } bool CacheIRCompiler::emitLoadFunctionLengthResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); FailurePath* failure; if (!addFailurePath(&failure)) return false; // Get the JSFunction flags. masm.load16ZeroExtend(Address(obj, JSFunction::offsetOfFlags()), scratch); // Functions with lazy scripts don't store their length. // If the length was resolved before the length property might be shadowed. masm.branchTest32(Assembler::NonZero, scratch, Imm32(JSFunction::INTERPRETED_LAZY | JSFunction::RESOLVED_LENGTH), failure->label()); Label boundFunction; masm.branchTest32(Assembler::NonZero, scratch, Imm32(JSFunction::BOUND_FUN), &boundFunction); Label interpreted; masm.branchTest32(Assembler::NonZero, scratch, Imm32(JSFunction::INTERPRETED), &interpreted); // Load the length of the native function. masm.load16ZeroExtend(Address(obj, JSFunction::offsetOfNargs()), scratch); Label done; masm.jump(&done); masm.bind(&boundFunction); // Bound functions might have a non-int32 length. Address boundLength(obj, FunctionExtended::offsetOfExtendedSlot(BOUND_FUN_LENGTH_SLOT)); masm.branchTestInt32(Assembler::NotEqual, boundLength, failure->label()); masm.unboxInt32(boundLength, scratch); masm.jump(&done); masm.bind(&interpreted); // Load the length from the function's script. masm.loadPtr(Address(obj, JSFunction::offsetOfScript()), scratch); masm.load16ZeroExtend(Address(scratch, JSScript::offsetOfFunLength()), scratch); masm.bind(&done); EmitStoreResult(masm, scratch, JSVAL_TYPE_INT32, output); return true; } bool CacheIRCompiler::emitLoadStringLengthResult() { AutoOutputRegister output(*this); Register str = allocator.useRegister(masm, reader.stringOperandId()); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); masm.loadStringLength(str, scratch); EmitStoreResult(masm, scratch, JSVAL_TYPE_INT32, output); return true; } bool CacheIRCompiler::emitLoadStringCharResult() { AutoOutputRegister output(*this); Register str = allocator.useRegister(masm, reader.stringOperandId()); Register index = allocator.useRegister(masm, reader.int32OperandId()); AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output); AutoScratchRegister scratch2(allocator, masm); FailurePath* failure; if (!addFailurePath(&failure)) return false; // Bounds check, load string char. masm.branch32(Assembler::BelowOrEqual, Address(str, JSString::offsetOfLength()), index, failure->label()); masm.loadStringChar(str, index, scratch1, failure->label()); // Load StaticString for this char. masm.branch32(Assembler::AboveOrEqual, scratch1, Imm32(StaticStrings::UNIT_STATIC_LIMIT), failure->label()); masm.movePtr(ImmPtr(&cx_->staticStrings().unitStaticTable), scratch2); masm.loadPtr(BaseIndex(scratch2, scratch1, ScalePointer), scratch2); EmitStoreResult(masm, scratch2, JSVAL_TYPE_STRING, output); return true; } bool CacheIRCompiler::emitLoadArgumentsObjectArgResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); Register index = allocator.useRegister(masm, reader.int32OperandId()); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); FailurePath* failure; if (!addFailurePath(&failure)) return false; // Get initial length value. masm.unboxInt32(Address(obj, ArgumentsObject::getInitialLengthSlotOffset()), scratch); // Ensure no overridden length/element. masm.branchTest32(Assembler::NonZero, scratch, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT | ArgumentsObject::ELEMENT_OVERRIDDEN_BIT), failure->label()); // Bounds check. masm.rshift32(Imm32(ArgumentsObject::PACKED_BITS_COUNT), scratch); masm.branch32(Assembler::AboveOrEqual, index, scratch, failure->label()); // Load ArgumentsData. masm.loadPrivate(Address(obj, ArgumentsObject::getDataSlotOffset()), scratch); // Fail if we have a RareArgumentsData (elements were deleted). masm.branchPtr(Assembler::NotEqual, Address(scratch, offsetof(ArgumentsData, rareData)), ImmWord(0), failure->label()); // Guard the argument is not a FORWARD_TO_CALL_SLOT MagicValue. BaseValueIndex argValue(scratch, index, ArgumentsData::offsetOfArgs()); masm.branchTestMagic(Assembler::Equal, argValue, failure->label()); masm.loadValue(argValue, output.valueReg()); return true; } bool CacheIRCompiler::emitLoadDenseElementResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); Register index = allocator.useRegister(masm, reader.int32OperandId()); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); FailurePath* failure; if (!addFailurePath(&failure)) return false; // Load obj->elements. masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch); // Bounds check. Address initLength(scratch, ObjectElements::offsetOfInitializedLength()); masm.branch32(Assembler::BelowOrEqual, initLength, index, failure->label()); // Hole check. BaseObjectElementIndex element(scratch, index); masm.branchTestMagic(Assembler::Equal, element, failure->label()); masm.loadTypedOrValue(element, output); return true; } bool CacheIRCompiler::emitGuardIndexIsNonNegative() { Register index = allocator.useRegister(masm, reader.int32OperandId()); FailurePath* failure; if (!addFailurePath(&failure)) return false; masm.branch32(Assembler::LessThan, index, Imm32(0), failure->label()); return true; } bool CacheIRCompiler::emitLoadDenseElementHoleResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); Register index = allocator.useRegister(masm, reader.int32OperandId()); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); if (!output.hasValue()) { masm.assumeUnreachable("Should have monitored undefined value after attaching stub"); return true; } FailurePath* failure; if (!addFailurePath(&failure)) return false; // Make sure the index is nonnegative. masm.branch32(Assembler::LessThan, index, Imm32(0), failure->label()); // Load obj->elements. masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch); // Guard on the initialized length. Label hole; Address initLength(scratch, ObjectElements::offsetOfInitializedLength()); masm.branch32(Assembler::BelowOrEqual, initLength, index, &hole); // Load the value. Label done; masm.loadValue(BaseObjectElementIndex(scratch, index), output.valueReg()); masm.branchTestMagic(Assembler::NotEqual, output.valueReg(), &done); // Load undefined for the hole. masm.bind(&hole); masm.moveValue(UndefinedValue(), output.valueReg()); masm.bind(&done); return true; } bool +CacheIRCompiler::emitLoadTypedElementExistsResult() +{ + AutoOutputRegister output(*this); + Register obj = allocator.useRegister(masm, reader.objOperandId()); + Register index = allocator.useRegister(masm, reader.int32OperandId()); + TypedThingLayout layout = reader.typedThingLayout(); + AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); + + Label outOfBounds, done; + + // Bound check. + LoadTypedThingLength(masm, layout, obj, scratch); + masm.branch32(Assembler::BelowOrEqual, scratch, index, &outOfBounds); + EmitStoreBoolean(masm, true, output); + masm.jump(&done); + + masm.bind(&outOfBounds); + EmitStoreBoolean(masm, false, output); + + masm.bind(&done); + return true; +} + +bool CacheIRCompiler::emitLoadDenseElementExistsResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); Register index = allocator.useRegister(masm, reader.int32OperandId()); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); FailurePath* failure; if (!addFailurePath(&failure)) return false; // Load obj->elements. masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch); // Bounds check. Unsigned compare sends negative indices to next IC. Address initLength(scratch, ObjectElements::offsetOfInitializedLength()); masm.branch32(Assembler::BelowOrEqual, initLength, index, failure->label()); // Hole check. BaseObjectElementIndex element(scratch, index); masm.branchTestMagic(Assembler::Equal, element, failure->label()); EmitStoreBoolean(masm, true, output); return true; } bool CacheIRCompiler::emitLoadDenseElementHoleExistsResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); Register index = allocator.useRegister(masm, reader.int32OperandId()); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); FailurePath* failure; if (!addFailurePath(&failure)) return false; // Make sure the index is nonnegative. masm.branch32(Assembler::LessThan, index, Imm32(0), failure->label()); // Load obj->elements. masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch); // Guard on the initialized length. Label hole; Address initLength(scratch, ObjectElements::offsetOfInitializedLength()); masm.branch32(Assembler::BelowOrEqual, initLength, index, &hole); // Load value and replace with true. Label done; BaseObjectElementIndex element(scratch, index); masm.branchTestMagic(Assembler::Equal, element, &hole); EmitStoreBoolean(masm, true, output); masm.jump(&done); // Load false for the hole. masm.bind(&hole); EmitStoreBoolean(masm, false, output); masm.bind(&done); return true; } bool CacheIRCompiler::emitArrayJoinResult() { ObjOperandId objId = reader.objOperandId(); AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, objId); AutoScratchRegister scratch(allocator, masm); FailurePath* failure; if (!addFailurePath(&failure)) return false; // Load obj->elements in scratch. masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch); Address lengthAddr(scratch, ObjectElements::offsetOfLength()); // If array length is 0, return empty string. Label finished; { Label arrayNotEmpty; masm.branch32(Assembler::NotEqual, lengthAddr, Imm32(0), &arrayNotEmpty); masm.movePtr(ImmGCPtr(cx_->names().empty), scratch); masm.tagValue(JSVAL_TYPE_STRING, scratch, output.valueReg()); masm.jump(&finished); masm.bind(&arrayNotEmpty); } // Otherwise, handle array length 1 case. masm.branch32(Assembler::NotEqual, lengthAddr, Imm32(1), failure->label()); // But only if initializedLength is also 1. Address initLength(scratch, ObjectElements::offsetOfInitializedLength()); masm.branch32(Assembler::NotEqual, initLength, Imm32(1), failure->label()); // And only if elem0 is a string. Address elementAddr(scratch, 0); masm.branchTestString(Assembler::NotEqual, elementAddr, failure->label()); // Store the value. masm.loadValue(elementAddr, output.valueReg()); masm.bind(&finished); return true; } bool CacheIRCompiler::emitLoadTypedElementResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); Register index = allocator.useRegister(masm, reader.int32OperandId()); TypedThingLayout layout = reader.typedThingLayout(); Scalar::Type type = reader.scalarType(); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); if (!output.hasValue()) { if (type == Scalar::Float32 || type == Scalar::Float64) { if (output.type() != JSVAL_TYPE_DOUBLE) { masm.assumeUnreachable("Should have monitored double after attaching stub"); return true; } } else { if (output.type() != JSVAL_TYPE_INT32 && output.type() != JSVAL_TYPE_DOUBLE) { masm.assumeUnreachable("Should have monitored int32 after attaching stub"); return true; } } } FailurePath* failure; if (!addFailurePath(&failure)) return false; // Bounds check. LoadTypedThingLength(masm, layout, obj, scratch); masm.branch32(Assembler::BelowOrEqual, scratch, index, failure->label()); // Load the elements vector. LoadTypedThingData(masm, layout, obj, scratch); // Load the value. BaseIndex source(scratch, index, ScaleFromElemWidth(Scalar::byteSize(type))); if (output.hasValue()) { masm.loadFromTypedArray(type, source, output.valueReg(), *allowDoubleResult_, scratch, failure->label()); } else { bool needGpr = (type == Scalar::Int8 || type == Scalar::Uint8 || type == Scalar::Int16 || type == Scalar::Uint16 || type == Scalar::Uint8Clamped || type == Scalar::Int32); if (needGpr && output.type() == JSVAL_TYPE_DOUBLE) { // Load the element as integer, then convert it to double. masm.loadFromTypedArray(type, source, AnyRegister(scratch), scratch, failure->label()); masm.convertInt32ToDouble(source, output.typedReg().fpu()); } else { masm.loadFromTypedArray(type, source, output.typedReg(), scratch, failure->label()); } } return true; } void CacheIRCompiler::emitLoadTypedObjectResultShared(const Address& fieldAddr, Register scratch, TypedThingLayout layout, uint32_t typeDescr, const AutoOutputRegister& output) { MOZ_ASSERT(output.hasValue()); if (SimpleTypeDescrKeyIsScalar(typeDescr)) { Scalar::Type type = ScalarTypeFromSimpleTypeDescrKey(typeDescr); masm.loadFromTypedArray(type, fieldAddr, output.valueReg(), /* allowDouble = */ true, scratch, nullptr); } else { ReferenceTypeDescr::Type type = ReferenceTypeFromSimpleTypeDescrKey(typeDescr); switch (type) { case ReferenceTypeDescr::TYPE_ANY: masm.loadValue(fieldAddr, output.valueReg()); break; case ReferenceTypeDescr::TYPE_OBJECT: { Label notNull, done; masm.loadPtr(fieldAddr, scratch); masm.branchTestPtr(Assembler::NonZero, scratch, scratch, ¬Null); masm.moveValue(NullValue(), output.valueReg()); masm.jump(&done); masm.bind(¬Null); masm.tagValue(JSVAL_TYPE_OBJECT, scratch, output.valueReg()); masm.bind(&done); break; } case ReferenceTypeDescr::TYPE_STRING: masm.loadPtr(fieldAddr, scratch); masm.tagValue(JSVAL_TYPE_STRING, scratch, output.valueReg()); break; default: MOZ_CRASH("Invalid ReferenceTypeDescr"); } } } bool CacheIRCompiler::emitLoadObjectResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); if (output.hasValue()) masm.tagValue(JSVAL_TYPE_OBJECT, obj, output.valueReg()); else masm.mov(obj, output.typedReg().gpr()); return true; } bool CacheIRCompiler::emitLoadTypeOfObjectResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); Label slowCheck, isObject, isCallable, isUndefined, done; masm.typeOfObject(obj, scratch, &slowCheck, &isObject, &isCallable, &isUndefined); masm.bind(&isCallable); masm.moveValue(StringValue(cx_->names().function), output.valueReg()); masm.jump(&done); masm.bind(&isUndefined); masm.moveValue(StringValue(cx_->names().undefined), output.valueReg()); masm.jump(&done); masm.bind(&isObject); masm.moveValue(StringValue(cx_->names().object), output.valueReg()); masm.jump(&done); { masm.bind(&slowCheck); LiveRegisterSet save(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); masm.PushRegsInMask(save); masm.setupUnalignedABICall(scratch); masm.passABIArg(obj); masm.movePtr(ImmPtr(cx_->runtime()), scratch); masm.passABIArg(scratch); masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, TypeOfObject)); masm.mov(ReturnReg, scratch); LiveRegisterSet ignore; ignore.add(scratch); masm.PopRegsInMaskIgnore(save, ignore); masm.tagValue(JSVAL_TYPE_STRING, scratch, output.valueReg()); } masm.bind(&done); return true; } bool CacheIRCompiler::emitCompareStringResult() { AutoOutputRegister output(*this); Register left = allocator.useRegister(masm, reader.stringOperandId()); Register right = allocator.useRegister(masm, reader.stringOperandId()); JSOp op = reader.jsop(); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); FailurePath* failure; if (!addFailurePath(&failure)) return false; masm.compareStrings(op, left, right, scratch, failure->label()); masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg()); return true; } bool CacheIRCompiler::emitComparePointerResultShared(bool symbol) { AutoOutputRegister output(*this); Register left = symbol ? allocator.useRegister(masm, reader.symbolOperandId()) : allocator.useRegister(masm, reader.objOperandId()); Register right = symbol ? allocator.useRegister(masm, reader.symbolOperandId()) : allocator.useRegister(masm, reader.objOperandId()); JSOp op = reader.jsop(); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); Label ifTrue, done; masm.branchPtr(JSOpToCondition(op, /* signed = */true), left, right, &ifTrue); masm.moveValue(BooleanValue(false), output.valueReg()); masm.jump(&done); masm.bind(&ifTrue); masm.moveValue(BooleanValue(true), output.valueReg()); masm.bind(&done); return true; } bool CacheIRCompiler::emitCompareObjectResult() { return emitComparePointerResultShared(false); } bool CacheIRCompiler::emitCompareSymbolResult() { return emitComparePointerResultShared(true); } bool CacheIRCompiler::emitCallPrintString() { const char* str = reinterpret_cast(reader.pointer()); masm.printf(str); return true; } bool CacheIRCompiler::emitBreakpoint() { masm.breakpoint(); return true; } void CacheIRCompiler::emitStoreTypedObjectReferenceProp(ValueOperand val, ReferenceTypeDescr::Type type, const Address& dest, Register scratch) { switch (type) { case ReferenceTypeDescr::TYPE_ANY: EmitPreBarrier(masm, dest, MIRType::Value); masm.storeValue(val, dest); break; case ReferenceTypeDescr::TYPE_OBJECT: { EmitPreBarrier(masm, dest, MIRType::Object); Label isNull, done; masm.branchTestObject(Assembler::NotEqual, val, &isNull); masm.unboxObject(val, scratch); masm.storePtr(scratch, dest); masm.jump(&done); masm.bind(&isNull); masm.storePtr(ImmWord(0), dest); masm.bind(&done); break; } case ReferenceTypeDescr::TYPE_STRING: EmitPreBarrier(masm, dest, MIRType::String); masm.unboxString(val, scratch); masm.storePtr(scratch, dest); break; } } void CacheIRCompiler::emitRegisterEnumerator(Register enumeratorsList, Register iter, Register scratch) { // iter->next = list masm.storePtr(enumeratorsList, Address(iter, NativeIterator::offsetOfNext())); // iter->prev = list->prev masm.loadPtr(Address(enumeratorsList, NativeIterator::offsetOfPrev()), scratch); masm.storePtr(scratch, Address(iter, NativeIterator::offsetOfPrev())); // list->prev->next = iter masm.storePtr(iter, Address(scratch, NativeIterator::offsetOfNext())); // list->prev = ni masm.storePtr(iter, Address(enumeratorsList, NativeIterator::offsetOfPrev())); } void CacheIRCompiler::emitPostBarrierShared(Register obj, const ConstantOrRegister& val, Register scratch, Register maybeIndex) { if (!cx_->nursery().exists()) return; if (val.constant()) { MOZ_ASSERT_IF(val.value().isObject(), !IsInsideNursery(&val.value().toObject())); return; } TypedOrValueRegister reg = val.reg(); if (reg.hasTyped() && reg.type() != MIRType::Object) return; Label skipBarrier; if (reg.hasValue()) { masm.branchValueIsNurseryObject(Assembler::NotEqual, reg.valueReg(), scratch, &skipBarrier); } else { masm.branchPtrInNurseryChunk(Assembler::NotEqual, reg.typedReg().gpr(), scratch, &skipBarrier); } masm.branchPtrInNurseryChunk(Assembler::Equal, obj, scratch, &skipBarrier); // Call one of these, depending on maybeIndex: // // void PostWriteBarrier(JSRuntime* rt, JSObject* obj); // void PostWriteElementBarrier(JSRuntime* rt, JSObject* obj, // int32_t index); LiveRegisterSet save(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); masm.PushRegsInMask(save); masm.setupUnalignedABICall(scratch); masm.movePtr(ImmPtr(cx_->runtime()), scratch); masm.passABIArg(scratch); masm.passABIArg(obj); if (maybeIndex != InvalidReg) { masm.passABIArg(maybeIndex); masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (PostWriteElementBarrier))); } else { masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier)); } masm.PopRegsInMask(save); masm.bind(&skipBarrier); } bool CacheIRCompiler::emitWrapResult() { AutoOutputRegister output(*this); AutoScratchRegister scratch(allocator, masm); FailurePath* failure; if (!addFailurePath(&failure)) return false; Label done; // We only have to wrap objects, because we are in the same zone. masm.branchTestObject(Assembler::NotEqual, output.valueReg(), &done); Register obj = output.valueReg().scratchReg(); masm.unboxObject(output.valueReg(), obj); LiveRegisterSet save(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); masm.PushRegsInMask(save); masm.setupUnalignedABICall(scratch); masm.loadJSContext(scratch); masm.passABIArg(scratch); masm.passABIArg(obj); masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, WrapObjectPure)); masm.mov(ReturnReg, obj); LiveRegisterSet ignore; ignore.add(obj); masm.PopRegsInMaskIgnore(save, ignore); // We could not get a wrapper for this object. masm.branchTestPtr(Assembler::Zero, obj, obj, failure->label()); // We clobbered the output register, so we have to retag. masm.tagValue(JSVAL_TYPE_OBJECT, obj, output.valueReg()); masm.bind(&done); return true; } bool CacheIRCompiler::emitMegamorphicLoadSlotByValueResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); ValueOperand idVal = allocator.useValueRegister(masm, reader.valOperandId()); bool handleMissing = reader.readBool(); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); FailurePath* failure; if (!addFailurePath(&failure)) return false; // The object must be Native. masm.loadObjClass(obj, scratch); masm.branchTest32(Assembler::NonZero, Address(scratch, Class::offsetOfFlags()), Imm32(Class::NON_NATIVE), failure->label()); // idVal will be in vp[0], result will be stored in vp[1]. masm.reserveStack(sizeof(Value)); masm.Push(idVal); masm.moveStackPtrTo(idVal.scratchReg()); LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); volatileRegs.takeUnchecked(scratch); volatileRegs.takeUnchecked(idVal); masm.PushRegsInMask(volatileRegs); masm.setupUnalignedABICall(scratch); masm.loadJSContext(scratch); masm.passABIArg(scratch); masm.passABIArg(obj); masm.passABIArg(idVal.scratchReg()); if (handleMissing) masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataPropertyByValue))); else masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, (GetNativeDataPropertyByValue))); masm.mov(ReturnReg, scratch); masm.PopRegsInMask(volatileRegs); masm.Pop(idVal); Label ok; uint32_t framePushed = masm.framePushed(); masm.branchIfTrueBool(scratch, &ok); masm.adjustStack(sizeof(Value)); masm.jump(failure->label()); masm.bind(&ok); masm.setFramePushed(framePushed); masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output); masm.adjustStack(sizeof(Value)); return true; } bool CacheIRCompiler::emitMegamorphicHasPropResult() { AutoOutputRegister output(*this); Register obj = allocator.useRegister(masm, reader.objOperandId()); ValueOperand idVal = allocator.useValueRegister(masm, reader.valOperandId()); bool hasOwn = reader.readBool(); AutoScratchRegisterMaybeOutput scratch(allocator, masm, output); FailurePath* failure; if (!addFailurePath(&failure)) return false; // idVal will be in vp[0], result will be stored in vp[1]. masm.reserveStack(sizeof(Value)); masm.Push(idVal); masm.moveStackPtrTo(idVal.scratchReg()); LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs()); volatileRegs.takeUnchecked(scratch); volatileRegs.takeUnchecked(idVal); masm.PushRegsInMask(volatileRegs); masm.setupUnalignedABICall(scratch); masm.loadJSContext(scratch); masm.passABIArg(scratch); masm.passABIArg(obj); masm.passABIArg(idVal.scratchReg()); if (hasOwn) masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, HasNativeDataProperty)); else masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, HasNativeDataProperty)); masm.mov(ReturnReg, scratch); masm.PopRegsInMask(volatileRegs); masm.Pop(idVal); Label ok; uint32_t framePushed = masm.framePushed(); masm.branchIfTrueBool(scratch, &ok); masm.adjustStack(sizeof(Value)); masm.jump(failure->label()); masm.bind(&ok); masm.setFramePushed(framePushed); masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output); masm.adjustStack(sizeof(Value)); return true; } diff --git a/js/src/jit/CacheIRCompiler.h b/js/src/jit/CacheIRCompiler.h --- a/js/src/jit/CacheIRCompiler.h +++ b/js/src/jit/CacheIRCompiler.h @@ -1,721 +1,722 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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/. */ #ifndef jit_CacheIRCompiler_h #define jit_CacheIRCompiler_h #include "jit/CacheIR.h" namespace js { namespace jit { // The ops below are defined in CacheIRCompiler and codegen is shared between // BaselineCacheIRCompiler and IonCacheIRCompiler. #define CACHE_IR_SHARED_OPS(_) \ _(GuardIsObject) \ _(GuardIsObjectOrNull) \ _(GuardIsString) \ _(GuardIsSymbol) \ _(GuardIsInt32Index) \ _(GuardType) \ _(GuardClass) \ _(GuardIsNativeFunction) \ _(GuardIsProxy) \ _(GuardNotDOMProxy) \ _(GuardSpecificInt32Immediate) \ _(GuardMagicValue) \ _(GuardNoUnboxedExpando) \ _(GuardAndLoadUnboxedExpando) \ _(GuardNoDetachedTypedObjects) \ _(GuardNoDenseElements) \ _(GuardAndGetIndexFromString) \ _(GuardIndexIsNonNegative) \ _(LoadProto) \ _(LoadEnclosingEnvironment) \ _(LoadWrapperTarget) \ _(LoadDOMExpandoValue) \ _(LoadDOMExpandoValueIgnoreGeneration)\ _(LoadUndefinedResult) \ _(LoadBooleanResult) \ _(LoadInt32ArrayLengthResult) \ _(LoadArgumentsObjectLengthResult) \ _(LoadFunctionLengthResult) \ _(LoadStringLengthResult) \ _(LoadStringCharResult) \ _(LoadArgumentsObjectArgResult) \ _(LoadDenseElementResult) \ _(LoadDenseElementHoleResult) \ _(LoadDenseElementExistsResult) \ _(LoadDenseElementHoleExistsResult) \ + _(LoadTypedElementExistsResult) \ _(LoadTypedElementResult) \ _(LoadObjectResult) \ _(LoadTypeOfObjectResult) \ _(CompareStringResult) \ _(CompareObjectResult) \ _(CompareSymbolResult) \ _(ArrayJoinResult) \ _(CallPrintString) \ _(Breakpoint) \ _(MegamorphicLoadSlotByValueResult) \ _(MegamorphicHasPropResult) \ _(WrapResult) // Represents a Value on the Baseline frame's expression stack. Slot 0 is the // value on top of the stack (the most recently pushed value), slot 1 is the // value pushed before that, etc. class BaselineFrameSlot { uint32_t slot_; public: explicit BaselineFrameSlot(uint32_t slot) : slot_(slot) {} uint32_t slot() const { return slot_; } bool operator==(const BaselineFrameSlot& other) const { return slot_ == other.slot_; } bool operator!=(const BaselineFrameSlot& other) const { return slot_ != other.slot_; } }; // OperandLocation represents the location of an OperandId. The operand is // either in a register or on the stack, and is either boxed or unboxed. class OperandLocation { public: enum Kind { Uninitialized = 0, PayloadReg, DoubleReg, ValueReg, PayloadStack, ValueStack, BaselineFrame, Constant, }; private: Kind kind_; union Data { struct { Register reg; JSValueType type; } payloadReg; FloatRegister doubleReg; ValueOperand valueReg; struct { uint32_t stackPushed; JSValueType type; } payloadStack; uint32_t valueStackPushed; BaselineFrameSlot baselineFrameSlot; Value constant; Data() : valueStackPushed(0) {} }; Data data_; public: OperandLocation() : kind_(Uninitialized) {} Kind kind() const { return kind_; } void setUninitialized() { kind_ = Uninitialized; } ValueOperand valueReg() const { MOZ_ASSERT(kind_ == ValueReg); return data_.valueReg; } Register payloadReg() const { MOZ_ASSERT(kind_ == PayloadReg); return data_.payloadReg.reg; } FloatRegister doubleReg() const { MOZ_ASSERT(kind_ == DoubleReg); return data_.doubleReg; } uint32_t payloadStack() const { MOZ_ASSERT(kind_ == PayloadStack); return data_.payloadStack.stackPushed; } uint32_t valueStack() const { MOZ_ASSERT(kind_ == ValueStack); return data_.valueStackPushed; } JSValueType payloadType() const { if (kind_ == PayloadReg) return data_.payloadReg.type; MOZ_ASSERT(kind_ == PayloadStack); return data_.payloadStack.type; } Value constant() const { MOZ_ASSERT(kind_ == Constant); return data_.constant; } BaselineFrameSlot baselineFrameSlot() const { MOZ_ASSERT(kind_ == BaselineFrame); return data_.baselineFrameSlot; } void setPayloadReg(Register reg, JSValueType type) { kind_ = PayloadReg; data_.payloadReg.reg = reg; data_.payloadReg.type = type; } void setDoubleReg(FloatRegister reg) { kind_ = DoubleReg; data_.doubleReg = reg; } void setValueReg(ValueOperand reg) { kind_ = ValueReg; data_.valueReg = reg; } void setPayloadStack(uint32_t stackPushed, JSValueType type) { kind_ = PayloadStack; data_.payloadStack.stackPushed = stackPushed; data_.payloadStack.type = type; } void setValueStack(uint32_t stackPushed) { kind_ = ValueStack; data_.valueStackPushed = stackPushed; } void setConstant(const Value& v) { kind_ = Constant; data_.constant = v; } void setBaselineFrame(BaselineFrameSlot slot) { kind_ = BaselineFrame; data_.baselineFrameSlot = slot; } bool isInRegister() const { return kind_ == PayloadReg || kind_ == ValueReg; } bool isOnStack() const { return kind_ == PayloadStack || kind_ == ValueStack; } size_t stackPushed() const { if (kind_ == PayloadStack) return data_.payloadStack.stackPushed; MOZ_ASSERT(kind_ == ValueStack); return data_.valueStackPushed; } size_t stackSizeInBytes() const { if (kind_ == PayloadStack) return sizeof(uintptr_t); MOZ_ASSERT(kind_ == ValueStack); return sizeof(js::Value); } void adjustStackPushed(int32_t diff) { if (kind_ == PayloadStack) { data_.payloadStack.stackPushed += diff; return; } MOZ_ASSERT(kind_ == ValueStack); data_.valueStackPushed += diff; } bool aliasesReg(Register reg) const { if (kind_ == PayloadReg) return payloadReg() == reg; if (kind_ == ValueReg) return valueReg().aliases(reg); return false; } bool aliasesReg(ValueOperand reg) const { #if defined(JS_NUNBOX32) return aliasesReg(reg.typeReg()) || aliasesReg(reg.payloadReg()); #else return aliasesReg(reg.valueReg()); #endif } bool aliasesReg(const OperandLocation& other) const; bool operator==(const OperandLocation& other) const; bool operator!=(const OperandLocation& other) const { return !operator==(other); } }; struct SpilledRegister { Register reg; uint32_t stackPushed; SpilledRegister(Register reg, uint32_t stackPushed) : reg(reg), stackPushed(stackPushed) {} bool operator==(const SpilledRegister& other) const { return reg == other.reg && stackPushed == other.stackPushed; } bool operator!=(const SpilledRegister& other) const { return !(*this == other); } }; using SpilledRegisterVector = Vector; // Class to track and allocate registers while emitting IC code. class MOZ_RAII CacheRegisterAllocator { // The original location of the inputs to the cache. Vector origInputLocations_; // The current location of each operand. Vector operandLocations_; // Free lists for value- and payload-slots on stack Vector freeValueSlots_; Vector freePayloadSlots_; // The registers allocated while emitting the current CacheIR op. // This prevents us from allocating a register and then immediately // clobbering it for something else, while we're still holding on to it. LiveGeneralRegisterSet currentOpRegs_; const AllocatableGeneralRegisterSet allocatableRegs_; // Registers that are currently unused and available. AllocatableGeneralRegisterSet availableRegs_; // Registers that are available, but before use they must be saved and // then restored when returning from the stub. AllocatableGeneralRegisterSet availableRegsAfterSpill_; // Registers we took from availableRegsAfterSpill_ and spilled to the stack. SpilledRegisterVector spilledRegs_; // The number of bytes pushed on the native stack. uint32_t stackPushed_; // The index of the CacheIR instruction we're currently emitting. uint32_t currentInstruction_; const CacheIRWriter& writer_; CacheRegisterAllocator(const CacheRegisterAllocator&) = delete; CacheRegisterAllocator& operator=(const CacheRegisterAllocator&) = delete; void freeDeadOperandLocations(MacroAssembler& masm); void spillOperandToStack(MacroAssembler& masm, OperandLocation* loc); void spillOperandToStackOrRegister(MacroAssembler& masm, OperandLocation* loc); void popPayload(MacroAssembler& masm, OperandLocation* loc, Register dest); void popValue(MacroAssembler& masm, OperandLocation* loc, ValueOperand dest); public: friend class AutoScratchRegister; friend class AutoScratchRegisterExcluding; explicit CacheRegisterAllocator(const CacheIRWriter& writer) : allocatableRegs_(GeneralRegisterSet::All()), stackPushed_(0), currentInstruction_(0), writer_(writer) {} MOZ_MUST_USE bool init(); void initAvailableRegs(const AllocatableGeneralRegisterSet& available) { availableRegs_ = available; } void initAvailableRegsAfterSpill(); void fixupAliasedInputs(MacroAssembler& masm); OperandLocation operandLocation(size_t i) const { return operandLocations_[i]; } void setOperandLocation(size_t i, const OperandLocation& loc) { operandLocations_[i] = loc; } OperandLocation origInputLocation(size_t i) const { return origInputLocations_[i]; } void initInputLocation(size_t i, ValueOperand reg) { origInputLocations_[i].setValueReg(reg); operandLocations_[i].setValueReg(reg); } void initInputLocation(size_t i, Register reg, JSValueType type) { origInputLocations_[i].setPayloadReg(reg, type); operandLocations_[i].setPayloadReg(reg, type); } void initInputLocation(size_t i, FloatRegister reg) { origInputLocations_[i].setDoubleReg(reg); operandLocations_[i].setDoubleReg(reg); } void initInputLocation(size_t i, const Value& v) { origInputLocations_[i].setConstant(v); operandLocations_[i].setConstant(v); } void initInputLocation(size_t i, BaselineFrameSlot slot) { origInputLocations_[i].setBaselineFrame(slot); operandLocations_[i].setBaselineFrame(slot); } void initInputLocation(size_t i, const TypedOrValueRegister& reg); void initInputLocation(size_t i, const ConstantOrRegister& value); const SpilledRegisterVector& spilledRegs() const { return spilledRegs_; } MOZ_MUST_USE bool setSpilledRegs(const SpilledRegisterVector& regs) { spilledRegs_.clear(); return spilledRegs_.appendAll(regs); } void nextOp() { currentOpRegs_.clear(); currentInstruction_++; } uint32_t stackPushed() const { return stackPushed_; } void setStackPushed(uint32_t pushed) { stackPushed_ = pushed; } bool isAllocatable(Register reg) const { return allocatableRegs_.has(reg); } // Allocates a new register. Register allocateRegister(MacroAssembler& masm); ValueOperand allocateValueRegister(MacroAssembler& masm); void allocateFixedRegister(MacroAssembler& masm, Register reg); void allocateFixedValueRegister(MacroAssembler& masm, ValueOperand reg); // Releases a register so it can be reused later. void releaseRegister(Register reg) { MOZ_ASSERT(currentOpRegs_.has(reg)); availableRegs_.add(reg); currentOpRegs_.take(reg); } void releaseValueRegister(ValueOperand reg) { #ifdef JS_NUNBOX32 releaseRegister(reg.payloadReg()); releaseRegister(reg.typeReg()); #else releaseRegister(reg.valueReg()); #endif } // Removes spilled values from the native stack. This should only be // called after all registers have been allocated. void discardStack(MacroAssembler& masm); Address addressOf(MacroAssembler& masm, BaselineFrameSlot slot) const; // Returns the register for the given operand. If the operand is currently // not in a register, it will load it into one. ValueOperand useValueRegister(MacroAssembler& masm, ValOperandId val); ValueOperand useFixedValueRegister(MacroAssembler& masm, ValOperandId valId, ValueOperand reg); Register useRegister(MacroAssembler& masm, TypedOperandId typedId); ConstantOrRegister useConstantOrRegister(MacroAssembler& masm, ValOperandId val); // Allocates an output register for the given operand. Register defineRegister(MacroAssembler& masm, TypedOperandId typedId); ValueOperand defineValueRegister(MacroAssembler& masm, ValOperandId val); // Returns |val|'s JSValueType or JSVAL_TYPE_UNKNOWN. JSValueType knownType(ValOperandId val) const; // Emits code to restore registers and stack to the state at the start of // the stub. void restoreInputState(MacroAssembler& masm, bool discardStack = true); // Returns the set of registers storing the IC input operands. GeneralRegisterSet inputRegisterSet() const; void saveIonLiveRegisters(MacroAssembler& masm, LiveRegisterSet liveRegs, Register scratch, IonScript* ionScript); void restoreIonLiveRegisters(MacroAssembler& masm, LiveRegisterSet liveRegs); }; // RAII class to allocate a scratch register and release it when we're done // with it. class MOZ_RAII AutoScratchRegister { CacheRegisterAllocator& alloc_; Register reg_; AutoScratchRegister(const AutoScratchRegister&) = delete; void operator=(const AutoScratchRegister&) = delete; public: AutoScratchRegister(CacheRegisterAllocator& alloc, MacroAssembler& masm, Register reg = InvalidReg) : alloc_(alloc) { if (reg != InvalidReg) { alloc.allocateFixedRegister(masm, reg); reg_ = reg; } else { reg_ = alloc.allocateRegister(masm); } MOZ_ASSERT(alloc_.currentOpRegs_.has(reg_)); } ~AutoScratchRegister() { alloc_.releaseRegister(reg_); } Register get() const { return reg_; } operator Register() const { return reg_; } }; // The FailurePath class stores everything we need to generate a failure path // at the end of the IC code. The failure path restores the input registers, if // needed, and jumps to the next stub. class FailurePath { Vector inputs_; SpilledRegisterVector spilledRegs_; NonAssertingLabel label_; uint32_t stackPushed_; public: FailurePath() = default; FailurePath(FailurePath&& other) : inputs_(Move(other.inputs_)), spilledRegs_(Move(other.spilledRegs_)), label_(other.label_), stackPushed_(other.stackPushed_) {} Label* label() { return &label_; } void setStackPushed(uint32_t i) { stackPushed_ = i; } uint32_t stackPushed() const { return stackPushed_; } MOZ_MUST_USE bool appendInput(const OperandLocation& loc) { return inputs_.append(loc); } OperandLocation input(size_t i) const { return inputs_[i]; } const SpilledRegisterVector& spilledRegs() const { return spilledRegs_; } MOZ_MUST_USE bool setSpilledRegs(const SpilledRegisterVector& regs) { MOZ_ASSERT(spilledRegs_.empty()); return spilledRegs_.appendAll(regs); } // If canShareFailurePath(other) returns true, the same machine code will // be emitted for two failure paths, so we can share them. bool canShareFailurePath(const FailurePath& other) const; }; class AutoOutputRegister; // Base class for BaselineCacheIRCompiler and IonCacheIRCompiler. class MOZ_RAII CacheIRCompiler { protected: friend class AutoOutputRegister; enum class Mode { Baseline, Ion }; JSContext* cx_; CacheIRReader reader; const CacheIRWriter& writer_; MacroAssembler masm; CacheRegisterAllocator allocator; Vector failurePaths; // Float registers that are live. Registers not in this set can be // clobbered and don't need to be saved before performing a VM call. // Doing this for non-float registers is a bit more complicated because // the IC register allocator allocates GPRs. LiveFloatRegisterSet liveFloatRegs_; Maybe outputUnchecked_; Mode mode_; // Whether this IC may read double values from uint32 arrays. Maybe allowDoubleResult_; CacheIRCompiler(JSContext* cx, const CacheIRWriter& writer, Mode mode) : cx_(cx), reader(writer), writer_(writer), allocator(writer_), liveFloatRegs_(FloatRegisterSet::All()), mode_(mode) { MOZ_ASSERT(!writer.failed()); } MOZ_MUST_USE bool addFailurePath(FailurePath** failure); MOZ_MUST_USE bool emitFailurePath(size_t i); // Returns the set of volatile float registers that are live. These // registers need to be saved when making non-GC calls with callWithABI. FloatRegisterSet liveVolatileFloatRegs() const { return FloatRegisterSet::Intersect(liveFloatRegs_.set(), FloatRegisterSet::Volatile()); } void emitLoadTypedObjectResultShared(const Address& fieldAddr, Register scratch, TypedThingLayout layout, uint32_t typeDescr, const AutoOutputRegister& output); void emitStoreTypedObjectReferenceProp(ValueOperand val, ReferenceTypeDescr::Type type, const Address& dest, Register scratch); void emitRegisterEnumerator(Register enumeratorsList, Register iter, Register scratch); private: void emitPostBarrierShared(Register obj, const ConstantOrRegister& val, Register scratch, Register maybeIndex); void emitPostBarrierShared(Register obj, ValueOperand val, Register scratch, Register maybeIndex) { emitPostBarrierShared(obj, ConstantOrRegister(val), scratch, maybeIndex); } protected: template void emitPostBarrierSlot(Register obj, const T& val, Register scratch) { emitPostBarrierShared(obj, val, scratch, InvalidReg); } template void emitPostBarrierElement(Register obj, const T& val, Register scratch, Register index) { MOZ_ASSERT(index != InvalidReg); emitPostBarrierShared(obj, val, scratch, index); } bool emitComparePointerResultShared(bool symbol); #define DEFINE_SHARED_OP(op) MOZ_MUST_USE bool emit##op(); CACHE_IR_SHARED_OPS(DEFINE_SHARED_OP) #undef DEFINE_SHARED_OP }; // Ensures the IC's output register is available for writing. class MOZ_RAII AutoOutputRegister { TypedOrValueRegister output_; CacheRegisterAllocator& alloc_; AutoOutputRegister(const AutoOutputRegister&) = delete; void operator=(const AutoOutputRegister&) = delete; public: explicit AutoOutputRegister(CacheIRCompiler& compiler); ~AutoOutputRegister(); Register maybeReg() const { if (output_.hasValue()) return output_.valueReg().scratchReg(); if (!output_.typedReg().isFloat()) return output_.typedReg().gpr(); return InvalidReg; } bool hasValue() const { return output_.hasValue(); } ValueOperand valueReg() const { return output_.valueReg(); } AnyRegister typedReg() const { return output_.typedReg(); } JSValueType type() const { MOZ_ASSERT(!hasValue()); return ValueTypeFromMIRType(output_.type()); } operator TypedOrValueRegister() const { return output_; } }; // Like AutoScratchRegister, but reuse a register of |output| if possible. class MOZ_RAII AutoScratchRegisterMaybeOutput { mozilla::Maybe scratch_; Register scratchReg_; AutoScratchRegisterMaybeOutput(const AutoScratchRegisterMaybeOutput&) = delete; void operator=(const AutoScratchRegisterMaybeOutput&) = delete; public: AutoScratchRegisterMaybeOutput(CacheRegisterAllocator& alloc, MacroAssembler& masm, const AutoOutputRegister& output) { scratchReg_ = output.maybeReg(); if (scratchReg_ == InvalidReg) { scratch_.emplace(alloc, masm); scratchReg_ = scratch_.ref(); } } operator Register() const { return scratchReg_; } }; // See the 'Sharing Baseline stub code' comment in CacheIR.h for a description // of this class. class CacheIRStubInfo { // These fields don't require 8 bits, but GCC complains if these fields are // smaller than the size of the enums. CacheKind kind_ : 8; ICStubEngine engine_ : 8; bool makesGCCalls_ : 1; uint8_t stubDataOffset_; const uint8_t* code_; uint32_t length_; const uint8_t* fieldTypes_; CacheIRStubInfo(CacheKind kind, ICStubEngine engine, bool makesGCCalls, uint32_t stubDataOffset, const uint8_t* code, uint32_t codeLength, const uint8_t* fieldTypes) : kind_(kind), engine_(engine), makesGCCalls_(makesGCCalls), stubDataOffset_(stubDataOffset), code_(code), length_(codeLength), fieldTypes_(fieldTypes) { MOZ_ASSERT(kind_ == kind, "Kind must fit in bitfield"); MOZ_ASSERT(engine_ == engine, "Engine must fit in bitfield"); MOZ_ASSERT(stubDataOffset_ == stubDataOffset, "stubDataOffset must fit in uint8_t"); } CacheIRStubInfo(const CacheIRStubInfo&) = delete; CacheIRStubInfo& operator=(const CacheIRStubInfo&) = delete; public: CacheKind kind() const { return kind_; } ICStubEngine engine() const { return engine_; } bool makesGCCalls() const { return makesGCCalls_; } const uint8_t* code() const { return code_; } uint32_t codeLength() const { return length_; } uint32_t stubDataOffset() const { return stubDataOffset_; } size_t stubDataSize() const; StubField::Type fieldType(uint32_t i) const { return (StubField::Type)fieldTypes_[i]; } static CacheIRStubInfo* New(CacheKind kind, ICStubEngine engine, bool canMakeCalls, uint32_t stubDataOffset, const CacheIRWriter& writer); template js::GCPtr& getStubField(Stub* stub, uint32_t field) const; template js::GCPtr& getStubField(ICStub* stub, uint32_t field) const { return getStubField(stub, field); } void copyStubData(ICStub* src, ICStub* dest) const; }; template void TraceCacheIRStub(JSTracer* trc, T* stub, const CacheIRStubInfo* stubInfo); } // namespace jit } // namespace js #endif /* jit_CacheIRCompiler_h */