Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/CacheIR.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/FloatingPoint.h"
#include "jsapi.h"
#include "jsdate.h"
#include "jsmath.h"
#include "jsnum.h"
#include "builtin/DataViewObject.h"
#include "builtin/MapObject.h"
#include "builtin/ModuleObject.h"
#include "builtin/Object.h"
#include "jit/BaselineIC.h"
#include "jit/CacheIRCloner.h"
#include "jit/CacheIRCompiler.h"
#include "jit/CacheIRGenerator.h"
#include "jit/CacheIRSpewer.h"
#include "jit/CacheIRWriter.h"
#include "jit/InlinableNatives.h"
#include "jit/JitContext.h"
#include "jit/JitZone.h"
#include "js/experimental/JitInfo.h" // JSJitInfo
#include "js/friend/DOMProxy.h" // JS::ExpandoAndGeneration
#include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowIfWindowProxy
#include "js/friend/XrayJitInfo.h" // js::jit::GetXrayJitInfo, JS::XrayJitInfo
#include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis
#include "js/Prefs.h" // JS::Prefs
#include "js/RegExpFlags.h" // JS::RegExpFlags
#include "js/ScalarType.h" // js::Scalar::Type
#include "js/Utility.h" // JS::AutoEnterOOMUnsafeRegion
#include "js/Wrapper.h"
#include "proxy/DOMProxy.h" // js::GetDOMProxyHandlerFamily
#include "proxy/ScriptedProxyHandler.h"
#include "util/DifferentialTesting.h"
#include "util/Unicode.h"
#include "vm/ArrayBufferObject.h"
#include "vm/BoundFunctionObject.h"
#include "vm/BytecodeUtil.h"
#include "vm/Compartment.h"
#include "vm/DateObject.h"
#include "vm/Iteration.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/ProxyObject.h"
#include "vm/RegExpObject.h"
#include "vm/SelfHosting.h"
#include "vm/ThrowMsgKind.h" // ThrowCondition
#include "vm/TypeofEqOperand.h" // TypeofEqOperand
#include "vm/Watchtower.h"
#include "wasm/WasmInstance.h"
#include "jit/BaselineFrame-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/BytecodeUtil-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/JSContext-inl.h"
#include "vm/JSFunction-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/List-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/PlainObject-inl.h"
#include "vm/StringObject-inl.h"
#include "wasm/WasmInstance-inl.h"
using namespace js;
using namespace js::jit;
using mozilla::DebugOnly;
using mozilla::Maybe;
using JS::DOMProxyShadowsResult;
using JS::ExpandoAndGeneration;
const char* const js::jit::CacheKindNames[] = {
#define DEFINE_KIND(kind) #kind,
CACHE_IR_KINDS(DEFINE_KIND)
#undef DEFINE_KIND
};
const char* const js::jit::CacheIROpNames[] = {
#define OPNAME(op, ...) #op,
CACHE_IR_OPS(OPNAME)
#undef OPNAME
};
const CacheIROpInfo js::jit::CacheIROpInfos[] = {
#define OPINFO(op, len, transpile, ...) {len, transpile},
CACHE_IR_OPS(OPINFO)
#undef OPINFO
};
const uint32_t js::jit::CacheIROpHealth[] = {
#define OPHEALTH(op, len, transpile, health) health,
CACHE_IR_OPS(OPHEALTH)
#undef OPHEALTH
};
size_t js::jit::NumInputsForCacheKind(CacheKind kind) {
switch (kind) {
case CacheKind::NewArray:
case CacheKind::NewObject:
case CacheKind::Lambda:
case CacheKind::LazyConstant:
case CacheKind::GetImport:
return 0;
case CacheKind::GetProp:
case CacheKind::TypeOf:
case CacheKind::TypeOfEq:
case CacheKind::ToPropertyKey:
case CacheKind::GetIterator:
case CacheKind::ToBool:
case CacheKind::UnaryArith:
case CacheKind::GetName:
case CacheKind::BindName:
case CacheKind::Call:
case CacheKind::OptimizeSpreadCall:
case CacheKind::CloseIter:
case CacheKind::OptimizeGetIterator:
return 1;
case CacheKind::Compare:
case CacheKind::GetElem:
case CacheKind::GetPropSuper:
case CacheKind::SetProp:
case CacheKind::In:
case CacheKind::HasOwn:
case CacheKind::CheckPrivateField:
case CacheKind::InstanceOf:
case CacheKind::BinaryArith:
return 2;
case CacheKind::GetElemSuper:
case CacheKind::SetElem:
return 3;
}
MOZ_CRASH("Invalid kind");
}
#ifdef DEBUG
void CacheIRWriter::assertSameCompartment(JSObject* obj) {
MOZ_ASSERT(cx_->compartment() == obj->compartment());
}
void CacheIRWriter::assertSameZone(Shape* shape) {
MOZ_ASSERT(cx_->zone() == shape->zone());
}
#endif
StubField CacheIRWriter::readStubField(uint32_t offset,
StubField::Type type) const {
size_t index = 0;
size_t currentOffset = 0;
// If we've seen an offset earlier than this before, we know we can start the
// search there at least, otherwise, we start the search from the beginning.
if (lastOffset_ < offset) {
currentOffset = lastOffset_;
index = lastIndex_;
}
while (currentOffset != offset) {
currentOffset += StubField::sizeInBytes(stubFields_[index].type());
index++;
MOZ_ASSERT(index < stubFields_.length());
}
MOZ_ASSERT(stubFields_[index].type() == type);
lastOffset_ = currentOffset;
lastIndex_ = index;
return stubFields_[index];
}
CacheIRCloner::CacheIRCloner(ICCacheIRStub* stub)
: stubInfo_(stub->stubInfo()), stubData_(stub->stubDataStart()) {}
void CacheIRCloner::cloneOp(CacheOp op, CacheIRReader& reader,
CacheIRWriter& writer) {
switch (op) {
#define DEFINE_OP(op, ...) \
case CacheOp::op: \
clone##op(reader, writer); \
break;
CACHE_IR_OPS(DEFINE_OP)
#undef DEFINE_OP
default:
MOZ_CRASH("Invalid op");
}
}
uintptr_t CacheIRCloner::readStubWord(uint32_t offset) {
return stubInfo_->getStubRawWord(stubData_, offset);
}
int64_t CacheIRCloner::readStubInt64(uint32_t offset) {
return stubInfo_->getStubRawInt64(stubData_, offset);
}
Shape* CacheIRCloner::getShapeField(uint32_t stubOffset) {
return reinterpret_cast<Shape*>(readStubWord(stubOffset));
}
Shape* CacheIRCloner::getWeakShapeField(uint32_t stubOffset) {
// No barrier is required to clone a weak pointer.
return reinterpret_cast<Shape*>(readStubWord(stubOffset));
}
GetterSetter* CacheIRCloner::getWeakGetterSetterField(uint32_t stubOffset) {
// No barrier is required to clone a weak pointer.
return reinterpret_cast<GetterSetter*>(readStubWord(stubOffset));
}
JSObject* CacheIRCloner::getObjectField(uint32_t stubOffset) {
return reinterpret_cast<JSObject*>(readStubWord(stubOffset));
}
JSObject* CacheIRCloner::getWeakObjectField(uint32_t stubOffset) {
// No barrier is required to clone a weak pointer.
return reinterpret_cast<JSObject*>(readStubWord(stubOffset));
}
JSString* CacheIRCloner::getStringField(uint32_t stubOffset) {
return reinterpret_cast<JSString*>(readStubWord(stubOffset));
}
JSAtom* CacheIRCloner::getAtomField(uint32_t stubOffset) {
return reinterpret_cast<JSAtom*>(readStubWord(stubOffset));
}
JS::Symbol* CacheIRCloner::getSymbolField(uint32_t stubOffset) {
return reinterpret_cast<JS::Symbol*>(readStubWord(stubOffset));
}
BaseScript* CacheIRCloner::getWeakBaseScriptField(uint32_t stubOffset) {
// No barrier is required to clone a weak pointer.
return reinterpret_cast<BaseScript*>(readStubWord(stubOffset));
}
JitCode* CacheIRCloner::getJitCodeField(uint32_t stubOffset) {
return reinterpret_cast<JitCode*>(readStubWord(stubOffset));
}
uint32_t CacheIRCloner::getRawInt32Field(uint32_t stubOffset) {
return uint32_t(reinterpret_cast<uintptr_t>(readStubWord(stubOffset)));
}
const void* CacheIRCloner::getRawPointerField(uint32_t stubOffset) {
return reinterpret_cast<const void*>(readStubWord(stubOffset));
}
uint64_t CacheIRCloner::getRawInt64Field(uint32_t stubOffset) {
return static_cast<uint64_t>(readStubInt64(stubOffset));
}
gc::AllocSite* CacheIRCloner::getAllocSiteField(uint32_t stubOffset) {
return reinterpret_cast<gc::AllocSite*>(readStubWord(stubOffset));
}
jsid CacheIRCloner::getIdField(uint32_t stubOffset) {
return jsid::fromRawBits(readStubWord(stubOffset));
}
const Value CacheIRCloner::getValueField(uint32_t stubOffset) {
return Value::fromRawBits(uint64_t(readStubInt64(stubOffset)));
}
double CacheIRCloner::getDoubleField(uint32_t stubOffset) {
uint64_t bits = uint64_t(readStubInt64(stubOffset));
return mozilla::BitwiseCast<double>(bits);
}
IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
CacheKind cacheKind, ICState state,
BaselineFrame* maybeFrame)
: writer(cx),
cx_(cx),
script_(script),
pc_(pc),
maybeFrame_(maybeFrame),
cacheKind_(cacheKind),
mode_(state.mode()),
isFirstStub_(state.newStubIsFirstStub()),
numOptimizedStubs_(state.numOptimizedStubs()) {}
// Allocation sites are usually created during baseline compilation, but we also
// need to create them when an IC stub is added to a baseline compiled script
// and when trial inlining.
gc::AllocSite* IRGenerator::maybeCreateAllocSite() {
MOZ_ASSERT(BytecodeOpCanHaveAllocSite(JSOp(*pc_)));
BaselineFrame* frame = maybeFrame_;
MOZ_ASSERT(frame);
JSScript* outerScript = frame->outerScript();
bool hasBaselineScript = outerScript->hasBaselineScript();
bool isInlined = frame->icScript()->isInlined();
if (!hasBaselineScript && !isInlined) {
MOZ_ASSERT(frame->runningInInterpreter());
return outerScript->zone()->unknownAllocSite(JS::TraceKind::Object);
}
uint32_t pcOffset = frame->script()->pcToOffset(pc_);
return frame->icScript()->getOrCreateAllocSite(outerScript, pcOffset);
}
GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script,
jsbytecode* pc, ICState state,
CacheKind cacheKind, HandleValue val,
HandleValue idVal)
: IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {}
static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderId,
NativeObject* holder, PropertyInfo prop) {
if (holder->isFixedSlot(prop.slot())) {
writer.loadFixedSlotResult(holderId,
NativeObject::getFixedSlotOffset(prop.slot()));
} else {
size_t dynamicSlotOffset =
holder->dynamicSlotIndex(prop.slot()) * sizeof(Value);
writer.loadDynamicSlotResult(holderId, dynamicSlotOffset);
}
}
// DOM proxies
// -----------
//
// DOM proxies are proxies that are used to implement various DOM objects like
// HTMLDocument and NodeList. DOM proxies may have an expando object - a native
// object that stores extra properties added to the object. The following
// CacheIR instructions are only used with DOM proxies:
//
// * LoadDOMExpandoValue: returns the Value in the proxy's expando slot. This
// returns either an UndefinedValue (no expando), ObjectValue (the expando
// object), or PrivateValue(ExpandoAndGeneration*).
//
// * LoadDOMExpandoValueGuardGeneration: guards the Value in the proxy's expando
// slot is the same PrivateValue(ExpandoAndGeneration*), then guards on its
// generation, then returns expandoAndGeneration->expando. This Value is
// either an UndefinedValue or ObjectValue.
//
// * LoadDOMExpandoValueIgnoreGeneration: assumes the Value in the proxy's
// expando slot is a PrivateValue(ExpandoAndGeneration*), unboxes it, and
// returns the expandoAndGeneration->expando Value.
//
// * GuardDOMExpandoMissingOrGuardShape: takes an expando Value as input, then
// guards it's either UndefinedValue or an object with the expected shape.
enum class ProxyStubType {
None,
DOMExpando,
DOMShadowed,
DOMUnshadowed,
Generic
};
static bool IsCacheableDOMProxy(ProxyObject* obj) {
const BaseProxyHandler* handler = obj->handler();
if (handler->family() != GetDOMProxyHandlerFamily()) {
return false;
}
// Some DOM proxies have dynamic prototypes. We can't really cache those very
// well.
return obj->hasStaticPrototype();
}
static ProxyStubType GetProxyStubType(JSContext* cx, HandleObject obj,
HandleId id) {
if (!obj->is<ProxyObject>()) {
return ProxyStubType::None;
}
auto proxy = obj.as<ProxyObject>();
if (!IsCacheableDOMProxy(proxy)) {
return ProxyStubType::Generic;
}
// Private fields are defined on a separate expando object.
if (id.isPrivateName()) {
return ProxyStubType::Generic;
}
DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, proxy, id);
if (shadows == DOMProxyShadowsResult::ShadowCheckFailed) {
cx->clearPendingException();
return ProxyStubType::None;
}
if (DOMProxyIsShadowing(shadows)) {
if (shadows == DOMProxyShadowsResult::ShadowsViaDirectExpando ||
shadows == DOMProxyShadowsResult::ShadowsViaIndirectExpando) {
return ProxyStubType::DOMExpando;
}
return ProxyStubType::DOMShadowed;
}
MOZ_ASSERT(shadows == DOMProxyShadowsResult::DoesntShadow ||
shadows == DOMProxyShadowsResult::DoesntShadowUnique);
return ProxyStubType::DOMUnshadowed;
}
static bool ValueToNameOrSymbolId(JSContext* cx, HandleValue idVal,
MutableHandleId id, bool* nameOrSymbol) {
*nameOrSymbol = false;
if (idVal.isObject() || idVal.isBigInt()) {
return true;
}
MOZ_ASSERT(idVal.isString() || idVal.isSymbol() || idVal.isBoolean() ||
idVal.isUndefined() || idVal.isNull() || idVal.isNumber());
if (IsNumberIndex(idVal)) {
return true;
}
if (!PrimitiveValueToId<CanGC>(cx, idVal, id)) {
return false;
}
if (!id.isAtom() && !id.isSymbol()) {
id.set(JS::PropertyKey::Void());
return true;
}
if (id.isAtom() && id.toAtom()->isIndex()) {
id.set(JS::PropertyKey::Void());
return true;
}
*nameOrSymbol = true;
return true;
}
AttachDecision GetPropIRGenerator::tryAttachStub() {
AutoAssertNoPendingException aanpe(cx_);
ValOperandId valId(writer.setInputOperandId(0));
if (cacheKind_ != CacheKind::GetProp) {
MOZ_ASSERT_IF(cacheKind_ == CacheKind::GetPropSuper,
getSuperReceiverValueId().id() == 1);
MOZ_ASSERT_IF(cacheKind_ != CacheKind::GetPropSuper,
getElemKeyValueId().id() == 1);
writer.setInputOperandId(1);
}
if (cacheKind_ == CacheKind::GetElemSuper) {
MOZ_ASSERT(getSuperReceiverValueId().id() == 2);
writer.setInputOperandId(2);
}
RootedId id(cx_);
bool nameOrSymbol;
if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
cx_->clearPendingException();
return AttachDecision::NoAction;
}
// |super.prop| getter calls use a |this| value that differs from lookup
// object.
ValOperandId receiverId = isSuper() ? getSuperReceiverValueId() : valId;
if (val_.isObject()) {
RootedObject obj(cx_, &val_.toObject());
ObjOperandId objId = writer.guardToObject(valId);
TRY_ATTACH(tryAttachTypedArrayElement(obj, objId));
if (nameOrSymbol) {
TRY_ATTACH(tryAttachObjectLength(obj, objId, id));
TRY_ATTACH(tryAttachTypedArray(obj, objId, id));
TRY_ATTACH(tryAttachDataView(obj, objId, id));
TRY_ATTACH(tryAttachArrayBufferMaybeShared(obj, objId, id));
TRY_ATTACH(tryAttachRegExp(obj, objId, id));
TRY_ATTACH(tryAttachMap(obj, objId, id));
TRY_ATTACH(tryAttachSet(obj, objId, id));
TRY_ATTACH(tryAttachNative(obj, objId, id, receiverId));
TRY_ATTACH(tryAttachModuleNamespace(obj, objId, id));
TRY_ATTACH(tryAttachWindowProxy(obj, objId, id));
TRY_ATTACH(tryAttachCrossCompartmentWrapper(obj, objId, id));
TRY_ATTACH(
tryAttachXrayCrossCompartmentWrapper(obj, objId, id, receiverId));
TRY_ATTACH(tryAttachFunction(obj, objId, id));
TRY_ATTACH(tryAttachArgumentsObjectIterator(obj, objId, id));
TRY_ATTACH(tryAttachArgumentsObjectCallee(obj, objId, id));
TRY_ATTACH(tryAttachProxy(obj, objId, id, receiverId));
if (!isSuper() && mode_ == ICState::Mode::Megamorphic &&
JSOp(*pc_) != JSOp::GetBoundName) {
attachMegamorphicNativeSlotPermissive(objId, id);
return AttachDecision::Attach;
}
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
cacheKind_ == CacheKind::GetElemSuper);
TRY_ATTACH(tryAttachProxyElement(obj, objId));
uint32_t index;
Int32OperandId indexId;
if (maybeGuardInt32Index(idVal_, getElemKeyValueId(), &index, &indexId)) {
TRY_ATTACH(tryAttachDenseElement(obj, objId, index, indexId));
TRY_ATTACH(tryAttachDenseElementHole(obj, objId, index, indexId));
TRY_ATTACH(tryAttachSparseElement(obj, objId, index, indexId));
TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, index, indexId));
TRY_ATTACH(tryAttachArgumentsObjectArgHole(obj, objId, index, indexId));
TRY_ATTACH(
tryAttachGenericElement(obj, objId, index, indexId, receiverId));
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
if (nameOrSymbol) {
TRY_ATTACH(tryAttachPrimitive(valId, id));
TRY_ATTACH(tryAttachStringLength(valId, id));
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
if (idVal_.isInt32()) {
ValOperandId indexId = getElemKeyValueId();
TRY_ATTACH(tryAttachStringChar(valId, indexId));
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
#ifdef DEBUG
// Any property lookups performed when trying to attach ICs must be pure, i.e.
// must use LookupPropertyPure() or similar functions. Pure lookups are
// guaranteed to never modify the prototype chain. This ensures that the holder
// object can always be found on the prototype chain.
static bool IsCacheableProtoChain(NativeObject* obj, NativeObject* holder) {
while (obj != holder) {
JSObject* proto = obj->staticPrototype();
if (!proto || !proto->is<NativeObject>()) {
return false;
}
obj = &proto->as<NativeObject>();
}
return true;
}
#endif
static bool IsCacheableGetPropSlot(NativeObject* obj, NativeObject* holder,
PropertyInfo prop) {
MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
return prop.isDataProperty();
}
enum class NativeGetPropKind {
None,
Missing,
Slot,
NativeGetter,
ScriptedGetter,
};
static NativeGetPropKind IsCacheableGetPropCall(NativeObject* obj,
NativeObject* holder,
PropertyInfo prop,
jsbytecode* pc = nullptr) {
MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
if (pc && JSOp(*pc) == JSOp::GetBoundName) {
return NativeGetPropKind::None;
}
if (!prop.isAccessorProperty()) {
return NativeGetPropKind::None;
}
JSObject* getterObject = holder->getGetter(prop);
if (!getterObject || !getterObject->is<JSFunction>()) {
return NativeGetPropKind::None;
}
JSFunction& getter = getterObject->as<JSFunction>();
if (getter.isClassConstructor()) {
return NativeGetPropKind::None;
}
// Scripted functions and natives with JIT entry can use the scripted path.
if (getter.hasJitEntry()) {
return NativeGetPropKind::ScriptedGetter;
}
MOZ_ASSERT(getter.isNativeWithoutJitEntry());
return NativeGetPropKind::NativeGetter;
}
static bool CheckHasNoSuchOwnProperty(JSContext* cx, JSObject* obj, jsid id) {
if (!obj->is<NativeObject>()) {
return false;
}
// Don't handle objects with resolve hooks.
if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj)) {
return false;
}
if (obj->as<NativeObject>().contains(cx, id)) {
return false;
}
if (obj->is<TypedArrayObject>() && ToTypedArrayIndex(id).isSome()) {
return false;
}
return true;
}
static bool CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, jsid id) {
JSObject* curObj = obj;
do {
if (!CheckHasNoSuchOwnProperty(cx, curObj, id)) {
return false;
}
curObj = curObj->staticPrototype();
} while (curObj);
return true;
}
static bool IsCacheableNoProperty(JSContext* cx, NativeObject* obj,
NativeObject* holder, jsid id,
jsbytecode* pc) {
MOZ_ASSERT(!holder);
// If we're doing a name lookup, we have to throw a ReferenceError.
if (JSOp(*pc) == JSOp::GetBoundName) {
return false;
}
return CheckHasNoSuchProperty(cx, obj, id);
}
static NativeGetPropKind CanAttachNativeGetProp(JSContext* cx, JSObject* obj,
PropertyKey id,
NativeObject** holder,
Maybe<PropertyInfo>* propInfo,
jsbytecode* pc) {
MOZ_ASSERT(id.isString() || id.isSymbol());
MOZ_ASSERT(!*holder);
// The lookup needs to be universally pure, otherwise we risk calling hooks
// out of turn. We don't mind doing this even when purity isn't required,
// because we only miss out on shape hashification, which is only a temporary
// perf cost. The limits were arbitrarily set, anyways.
NativeObject* baseHolder = nullptr;
PropertyResult prop;
if (!LookupPropertyPure(cx, obj, id, &baseHolder, &prop)) {
return NativeGetPropKind::None;
}
auto* nobj = &obj->as<NativeObject>();
if (prop.isNativeProperty()) {
MOZ_ASSERT(baseHolder);
*holder = baseHolder;
*propInfo = mozilla::Some(prop.propertyInfo());
if (IsCacheableGetPropSlot(nobj, *holder, propInfo->ref())) {
return NativeGetPropKind::Slot;
}
return IsCacheableGetPropCall(nobj, *holder, propInfo->ref(), pc);
}
if (!prop.isFound()) {
if (IsCacheableNoProperty(cx, nobj, *holder, id, pc)) {
return NativeGetPropKind::Missing;
}
}
return NativeGetPropKind::None;
}
static void GuardReceiverProto(CacheIRWriter& writer, NativeObject* obj,
ObjOperandId objId) {
// Note: we guard on the actual prototype and not on the shape because this is
// used for sparse elements where we expect shape changes.
if (JSObject* proto = obj->staticPrototype()) {
writer.guardProto(objId, proto);
} else {
writer.guardNullProto(objId);
}
}
// Guard that a given object has same class and same OwnProperties (excluding
// dense elements and dynamic properties).
static void TestMatchingNativeReceiver(CacheIRWriter& writer, NativeObject* obj,
ObjOperandId objId) {
writer.guardShapeForOwnProperties(objId, obj->shape());
}
// Similar to |TestMatchingNativeReceiver|, but specialized for ProxyObject.
static void TestMatchingProxyReceiver(CacheIRWriter& writer, ProxyObject* obj,
ObjOperandId objId) {
writer.guardShapeForClass(objId, obj->shape());
}
static void GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj,
NativeObject* holder, ObjOperandId objId) {
// Assuming target property is on |holder|, generate appropriate guards to
// ensure |holder| is still on the prototype chain of |obj| and we haven't
// introduced any shadowing definitions.
//
// For each item in the proto chain before holder, we must ensure that
// [[GetPrototypeOf]] still has the expected result, and that
// [[GetOwnProperty]] has no definition of the target property.
//
//
// [SMDOC] Shape Teleporting Optimization
// --------------------------------------
//
// Starting with the assumption (and guideline to developers) that mutating
// prototypes is an uncommon and fair-to-penalize operation we move cost
// from the access side to the mutation side.
//
// Consider the following proto chain, with B defining a property 'x':
//
// D -> C -> B{x: 3} -> A -> null
//
// When accessing |D.x| we refer to D as the "receiver", and B as the
// "holder". To optimize this access we need to ensure that neither D nor C
// has since defined a shadowing property 'x'. Since C is a prototype that
// we assume is rarely mutated we would like to avoid checking each time if
// new properties are added. To do this we require that whenever C starts
// shadowing a property on its proto chain, we invalidate (and opt out of) the
// teleporting optimization by setting the InvalidatedTeleporting flag on the
// object we're shadowing, triggering a shape change of that object. As a
// result, checking the shape of D and B is sufficient. Note that we do not
// care if the shape or properties of A change since the lookup of 'x' will
// stop at B.
//
// The second condition we must verify is that the prototype chain was not
// mutated. The same mechanism as above is used. When the prototype link is
// changed, we generate a new shape for the object. If the object whose
// link we are mutating is itself a prototype, we regenerate shapes down
// the chain by setting the InvalidatedTeleporting flag on them. This means
// the same two shape checks as above are sufficient.
//
// Once the InvalidatedTeleporting flag is set, it means the shape will no
// longer be changed by ReshapeForProtoMutation and ReshapeForShadowedProp.
// In this case we can no longer apply the optimization.
//
// See:
// - ReshapeForProtoMutation
// - ReshapeForShadowedProp
MOZ_ASSERT(holder);
MOZ_ASSERT(obj != holder);
// Receiver guards (see TestMatchingReceiver) ensure the receiver's proto is
// unchanged so peel off the receiver.
JSObject* pobj = obj->staticPrototype();
MOZ_ASSERT(pobj->isUsedAsPrototype());
// If teleporting is supported for this holder, we are done.
if (!holder->hasInvalidatedTeleporting()) {
return;
}
// If already at the holder, no further proto checks are needed.
if (pobj == holder) {
return;
}
// Synchronize pobj and protoId.
MOZ_ASSERT(pobj == obj->staticPrototype());
ObjOperandId protoId = writer.loadProto(objId);
// Shape guard each prototype object between receiver and holder. This guards
// against both proto changes and shadowing properties.
while (pobj != holder) {
writer.guardShape(protoId, pobj->shape());
pobj = pobj->staticPrototype();
protoId = writer.loadProto(protoId);
}
}
static void GeneratePrototypeHoleGuards(CacheIRWriter& writer,
NativeObject* obj, ObjOperandId objId,
bool alwaysGuardFirstProto) {
if (alwaysGuardFirstProto) {
GuardReceiverProto(writer, obj, objId);
}
JSObject* pobj = obj->staticPrototype();
while (pobj) {
ObjOperandId protoId = writer.loadObject(pobj);
// Make sure the shape matches, to ensure the proto is unchanged and to
// avoid non-dense elements or anything else that is being checked by
// CanAttachDenseElementHole.
MOZ_ASSERT(pobj->is<NativeObject>());
writer.guardShape(protoId, pobj->shape());
// Also make sure there are no dense elements.
writer.guardNoDenseElements(protoId);
pobj = pobj->staticPrototype();
}
}
// Similar to |TestMatchingReceiver|, but for the holder object (when it
// differs from the receiver). The holder may also be the expando of the
// receiver if it exists.
static void TestMatchingHolder(CacheIRWriter& writer, NativeObject* obj,
ObjOperandId objId) {
// The GeneratePrototypeGuards + TestMatchingHolder checks only support
// prototype chains composed of NativeObject (excluding the receiver
// itself).
writer.guardShapeForOwnProperties(objId, obj->shape());
}
enum class IsCrossCompartment { No, Yes };
// Emit a shape guard for all objects on the proto chain. This does NOT include
// the receiver; callers must ensure the receiver's proto is the first proto by
// either emitting a shape guard or a prototype guard for |objId|.
//
// Note: this relies on shape implying proto.
template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
static void ShapeGuardProtoChain(CacheIRWriter& writer, NativeObject* obj,
ObjOperandId objId) {
uint32_t depth = 0;
static const uint32_t MAX_CACHED_LOADS = 4;
ObjOperandId receiverObjId = objId;
while (true) {
JSObject* proto = obj->staticPrototype();
if (!proto) {
return;
}
obj = &proto->as<NativeObject>();
// After guarding the shape of an object, we can safely bake that
// object's proto into the stub data. Compared to LoadProto, this
// takes one load instead of three (object -> shape -> baseshape
// -> proto). We cap the depth to avoid bloating the size of the
// stub data. To avoid compartment mismatch, we skip this optimization
// in the cross-compartment case.
if (depth < MAX_CACHED_LOADS &&
MaybeCrossCompartment == IsCrossCompartment::No) {
objId = writer.loadProtoObject(obj, receiverObjId);
} else {
objId = writer.loadProto(objId);
}
depth++;
writer.guardShape(objId, obj->shape());
}
}
// For cross compartment guards we shape-guard the prototype chain to avoid
// referencing the holder object.
//
// This peels off the first layer because it's guarded against obj == holder.
//
// Returns the holder's OperandId.
static ObjOperandId ShapeGuardProtoChainForCrossCompartmentHolder(
CacheIRWriter& writer, NativeObject* obj, ObjOperandId objId,
NativeObject* holder) {
MOZ_ASSERT(obj != holder);
MOZ_ASSERT(holder);
while (true) {
MOZ_ASSERT(obj->staticPrototype());
obj = &obj->staticPrototype()->as<NativeObject>();
objId = writer.loadProto(objId);
if (obj == holder) {
TestMatchingHolder(writer, obj, objId);
return objId;
}
writer.guardShapeForOwnProperties(objId, obj->shape());
}
}
// Emit guards for reading a data property on |holder|. Returns the holder's
// OperandId.
template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
static ObjOperandId EmitReadSlotGuard(CacheIRWriter& writer, NativeObject* obj,
NativeObject* holder,
ObjOperandId objId) {
MOZ_ASSERT(holder);
TestMatchingNativeReceiver(writer, obj, objId);
if (obj == holder) {
return objId;
}
if (MaybeCrossCompartment == IsCrossCompartment::Yes) {
// Guard proto chain integrity.
// We use a variant of guards that avoid baking in any cross-compartment
// object pointers.
return ShapeGuardProtoChainForCrossCompartmentHolder(writer, obj, objId,
holder);
}
// Guard proto chain integrity.
GeneratePrototypeGuards(writer, obj, holder, objId);
// Guard on the holder's shape.
ObjOperandId holderId = writer.loadObject(holder);
TestMatchingHolder(writer, holder, holderId);
return holderId;
}
template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
static void EmitMissingPropGuard(CacheIRWriter& writer, NativeObject* obj,
ObjOperandId objId) {
TestMatchingNativeReceiver(writer, obj, objId);
// The property does not exist. Guard on everything in the prototype
// chain. This is guaranteed to see only Native objects because of
// CanAttachNativeGetProp().
ShapeGuardProtoChain<MaybeCrossCompartment>(writer, obj, objId);
}
template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
static void EmitReadSlotResult(CacheIRWriter& writer, NativeObject* obj,
NativeObject* holder, PropertyInfo prop,
ObjOperandId objId) {
MOZ_ASSERT(holder);
ObjOperandId holderId =
EmitReadSlotGuard<MaybeCrossCompartment>(writer, obj, holder, objId);
MOZ_ASSERT(holderId.valid());
EmitLoadSlotResult(writer, holderId, holder, prop);
}
template <IsCrossCompartment MaybeCrossCompartment = IsCrossCompartment::No>
static void EmitMissingPropResult(CacheIRWriter& writer, NativeObject* obj,
ObjOperandId objId) {
EmitMissingPropGuard<MaybeCrossCompartment>(writer, obj, objId);
writer.loadUndefinedResult();
}
static void EmitCallGetterResultNoGuards(JSContext* cx, CacheIRWriter& writer,
NativeGetPropKind kind,
NativeObject* obj,
NativeObject* holder,
PropertyInfo prop,
ValOperandId receiverId) {
MOZ_ASSERT(IsCacheableGetPropCall(obj, holder, prop) == kind);
JSFunction* target = &holder->getGetter(prop)->as<JSFunction>();
bool sameRealm = cx->realm() == target->realm();
switch (kind) {
case NativeGetPropKind::NativeGetter: {
writer.callNativeGetterResult(receiverId, target, sameRealm);
writer.returnFromIC();
break;
}
case NativeGetPropKind::ScriptedGetter: {
writer.callScriptedGetterResult(receiverId, target, sameRealm);
writer.returnFromIC();
break;
}
default:
// CanAttachNativeGetProp guarantees that the getter is either a native or
// a scripted function.
MOZ_ASSERT_UNREACHABLE("Can't attach getter");
break;
}
}
// See the SMDOC comment in vm/GetterSetter.h for more info on Getter/Setter
// properties
static void EmitGuardGetterSetterSlot(CacheIRWriter& writer,
NativeObject* holder, PropertyInfo prop,
ObjOperandId holderId,
bool holderIsConstant = false) {
// If the holder is guaranteed to be the same object, and it never had a
// slot holding a GetterSetter mutated or deleted, its Shape will change when
// that does happen so we don't need to guard on the GetterSetter.
if (holderIsConstant && !holder->hadGetterSetterChange()) {
return;
}
size_t slot = prop.slot();
Value slotVal = holder->getSlot(slot);
MOZ_ASSERT(slotVal.isPrivateGCThing());
if (holder->isFixedSlot(slot)) {
size_t offset = NativeObject::getFixedSlotOffset(slot);
writer.guardFixedSlotValue(holderId, offset, slotVal);
} else {
size_t offset = holder->dynamicSlotIndex(slot) * sizeof(Value);
writer.guardDynamicSlotValue(holderId, offset, slotVal);
}
}
static void EmitCallGetterResultGuards(CacheIRWriter& writer, NativeObject* obj,
NativeObject* holder, HandleId id,
PropertyInfo prop, ObjOperandId objId,
ICState::Mode mode) {
// Use the megamorphic guard if we're in megamorphic mode, except if |obj|
// is a Window as GuardHasGetterSetter doesn't support this yet (Window may
// require outerizing).
MOZ_ASSERT(holder->containsPure(id, prop));
if (mode == ICState::Mode::Specialized || IsWindow(obj)) {
TestMatchingNativeReceiver(writer, obj, objId);
if (obj != holder) {
GeneratePrototypeGuards(writer, obj, holder, objId);
// Guard on the holder's shape.
ObjOperandId holderId = writer.loadObject(holder);
TestMatchingHolder(writer, holder, holderId);
EmitGuardGetterSetterSlot(writer, holder, prop, holderId,
/* holderIsConstant = */ true);
} else {
EmitGuardGetterSetterSlot(writer, holder, prop, objId);
}
} else {
GetterSetter* gs = holder->getGetterSetter(prop);
writer.guardHasGetterSetter(objId, id, gs);
}
}
static void EmitCallGetterResult(JSContext* cx, CacheIRWriter& writer,
NativeGetPropKind kind, NativeObject* obj,
NativeObject* holder, HandleId id,
PropertyInfo prop, ObjOperandId objId,
ValOperandId receiverId, ICState::Mode mode) {
EmitCallGetterResultGuards(writer, obj, holder, id, prop, objId, mode);
EmitCallGetterResultNoGuards(cx, writer, kind, obj, holder, prop, receiverId);
}
static bool CanAttachDOMCall(JSContext* cx, JSJitInfo::OpType type,
JSObject* obj, JSFunction* fun,
ICState::Mode mode) {
MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter ||
type == JSJitInfo::Method);
if (mode != ICState::Mode::Specialized) {
return false;
}
if (!fun->hasJitInfo()) {
return false;
}
if (cx->realm() != fun->realm()) {
return false;
}
const JSJitInfo* jitInfo = fun->jitInfo();
if (jitInfo->type() != type) {
return false;
}
MOZ_ASSERT_IF(IsWindow(obj), !jitInfo->needsOuterizedThisObject());
const JSClass* clasp = obj->getClass();
if (!clasp->isDOMClass()) {
return false;
}
if (type != JSJitInfo::Method && clasp->isProxyObject()) {
return false;
}
// Ion codegen expects DOM_OBJECT_SLOT to be a fixed slot in LoadDOMPrivate.
// It can be a dynamic slot if we transplanted this reflector object with a
// proxy.
if (obj->is<NativeObject>() && obj->as<NativeObject>().numFixedSlots() == 0) {
return false;
}
// Tell the analysis the |DOMInstanceClassHasProtoAtDepth| hook can't GC.
JS::AutoSuppressGCAnalysis nogc;
DOMInstanceClassHasProtoAtDepth instanceChecker =
cx->runtime()->DOMcallbacks->instanceClassMatchesProto;
return instanceChecker(clasp, jitInfo->protoID, jitInfo->depth);
}
static bool CanAttachDOMGetterSetter(JSContext* cx, JSJitInfo::OpType type,
NativeObject* obj, NativeObject* holder,
PropertyInfo prop, ICState::Mode mode) {
MOZ_ASSERT(type == JSJitInfo::Getter || type == JSJitInfo::Setter);
JSObject* accessor = type == JSJitInfo::Getter ? holder->getGetter(prop)
: holder->getSetter(prop);
JSFunction* fun = &accessor->as<JSFunction>();
return CanAttachDOMCall(cx, type, obj, fun, mode);
}
static void EmitCallDOMGetterResultNoGuards(CacheIRWriter& writer,
NativeObject* holder,
PropertyInfo prop,
ObjOperandId objId) {
JSFunction* getter = &holder->getGetter(prop)->as<JSFunction>();
writer.callDOMGetterResult(objId, getter->jitInfo());
writer.returnFromIC();
}
static void EmitCallDOMGetterResult(JSContext* cx, CacheIRWriter& writer,
NativeObject* obj, NativeObject* holder,
HandleId id, PropertyInfo prop,
ObjOperandId objId) {
// Note: this relies on EmitCallGetterResultGuards emitting a shape guard
// for specialized stubs.
// The shape guard ensures the receiver's Class is valid for this DOM getter.
EmitCallGetterResultGuards(writer, obj, holder, id, prop, objId,
ICState::Mode::Specialized);
EmitCallDOMGetterResultNoGuards(writer, holder, prop, objId);
}
static ValOperandId EmitLoadSlot(CacheIRWriter& writer, NativeObject* holder,
ObjOperandId holderId, uint32_t slot) {
if (holder->isFixedSlot(slot)) {
return writer.loadFixedSlot(holderId,
NativeObject::getFixedSlotOffset(slot));
}
size_t dynamicSlotIndex = holder->dynamicSlotIndex(slot);
return writer.loadDynamicSlot(holderId, dynamicSlotIndex);
}
void GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId,
jsid id) {
MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
// We don't support GetBoundName because environment objects have
// lookupProperty hooks and GetBoundName is usually not megamorphic.
MOZ_ASSERT(JSOp(*pc_) != JSOp::GetBoundName);
if (cacheKind_ == CacheKind::GetProp ||
cacheKind_ == CacheKind::GetPropSuper) {
writer.megamorphicLoadSlotResult(objId, id);
} else {
MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
cacheKind_ == CacheKind::GetElemSuper);
writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId());
}
writer.returnFromIC();
trackAttached("GetProp.MegamorphicNativeSlot");
}
void GetPropIRGenerator::attachMegamorphicNativeSlotPermissive(
ObjOperandId objId, jsid id) {
MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
// We don't support GetBoundName because environment objects have
// lookupProperty hooks and GetBoundName is usually not megamorphic.
MOZ_ASSERT(JSOp(*pc_) != JSOp::GetBoundName);
// It is not worth the complexity to support super here because we'd have
// to plumb the receiver through everywhere, so we just skip it.
MOZ_ASSERT(!isSuper());
if (cacheKind_ == CacheKind::GetProp) {
writer.megamorphicLoadSlotPermissiveResult(objId, id);
} else {
MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
writer.megamorphicLoadSlotByValuePermissiveResult(objId,
getElemKeyValueId());
}
writer.returnFromIC();
trackAttached("GetProp.MegamorphicNativeSlotPermissive");
}
AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj,
ObjOperandId objId,
HandleId id,
ValOperandId receiverId) {
Maybe<PropertyInfo> prop;
NativeObject* holder = nullptr;
NativeGetPropKind kind =
CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
switch (kind) {
case NativeGetPropKind::None:
return AttachDecision::NoAction;
case NativeGetPropKind::Missing:
case NativeGetPropKind::Slot: {
auto* nobj = &obj->as<NativeObject>();
if (mode_ == ICState::Mode::Megamorphic &&
JSOp(*pc_) != JSOp::GetBoundName) {
attachMegamorphicNativeSlot(objId, id);
return AttachDecision::Attach;
}
maybeEmitIdGuard(id);
if (kind == NativeGetPropKind::Slot) {
EmitReadSlotResult(writer, nobj, holder, *prop, objId);
writer.returnFromIC();
trackAttached("GetProp.NativeSlot");
} else {
EmitMissingPropResult(writer, nobj, objId);
writer.returnFromIC();
trackAttached("GetProp.Missing");
}
return AttachDecision::Attach;
}
case NativeGetPropKind::ScriptedGetter:
case NativeGetPropKind::NativeGetter: {
auto* nobj = &obj->as<NativeObject>();
MOZ_ASSERT(!IsWindow(nobj));
// If we're in megamorphic mode, we assume that a specialized
// getter call is just going to end up failing later, so we let this
// get handled further down the chain by
// attachMegamorphicNativeSlotPermissive
if (!isSuper() && mode_ == ICState::Mode::Megamorphic) {
return AttachDecision::NoAction;
}
maybeEmitIdGuard(id);
if (!isSuper() && CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, nobj,
holder, *prop, mode_)) {
EmitCallDOMGetterResult(cx_, writer, nobj, holder, id, *prop, objId);
trackAttached("GetProp.DOMGetter");
return AttachDecision::Attach;
}
EmitCallGetterResult(cx_, writer, kind, nobj, holder, id, *prop, objId,
receiverId, mode_);
trackAttached("GetProp.NativeGetter");
return AttachDecision::Attach;
}
}
MOZ_CRASH("Bad NativeGetPropKind");
}
// Returns whether obj is a WindowProxy wrapping the script's global.
static bool IsWindowProxyForScriptGlobal(JSScript* script, JSObject* obj) {
if (!IsWindowProxy(obj)) {
return false;
}
MOZ_ASSERT(obj->getClass() ==
script->runtimeFromMainThread()->maybeWindowProxyClass());
JSObject* window = ToWindowIfWindowProxy(obj);
// Ion relies on the WindowProxy's group changing (and the group getting
// marked as having unknown properties) on navigation. If we ever stop
// transplanting same-compartment WindowProxies, this assert will fail and we
// need to fix that code.
MOZ_ASSERT(window == &obj->nonCCWGlobal());
// This must be a WindowProxy for a global in this compartment. Else it would
// be a cross-compartment wrapper and IsWindowProxy returns false for
// those.
MOZ_ASSERT(script->compartment() == obj->compartment());
// Only optimize lookups on the WindowProxy for the current global. Other
// WindowProxies in the compartment may require security checks (based on
// mutable document.domain). See bug 1516775.
return window == &script->global();
}
// Guards objId is a WindowProxy for windowObj. Returns the window's operand id.
static ObjOperandId GuardAndLoadWindowProxyWindow(CacheIRWriter& writer,
ObjOperandId objId,
GlobalObject* windowObj) {
writer.guardClass(objId, GuardClassKind::WindowProxy);
ObjOperandId windowObjId = writer.loadWrapperTarget(objId,
/*fallible = */ false);
writer.guardSpecificObject(windowObjId, windowObj);
return windowObjId;
}
// Whether a getter/setter on the global should have the WindowProxy as |this|
// value instead of the Window (the global object). This always returns true for
// scripted functions.
static bool GetterNeedsWindowProxyThis(NativeObject* holder,
PropertyInfo prop) {
JSFunction* callee = &holder->getGetter(prop)->as<JSFunction>();
return !callee->hasJitInfo() || callee->jitInfo()->needsOuterizedThisObject();
}
static bool SetterNeedsWindowProxyThis(NativeObject* holder,
PropertyInfo prop) {
JSFunction* callee = &holder->getSetter(prop)->as<JSFunction>();
return !callee->hasJitInfo() || callee->jitInfo()->needsOuterizedThisObject();
}
AttachDecision GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
ObjOperandId objId,
HandleId id) {
// Attach a stub when the receiver is a WindowProxy and we can do the lookup
// on the Window (the global object).
if (!IsWindowProxyForScriptGlobal(script_, obj)) {
return AttachDecision::NoAction;
}
// If we're megamorphic prefer a generic proxy stub that handles a lot more
// cases.
if (mode_ == ICState::Mode::Megamorphic) {
return AttachDecision::NoAction;
}
// Now try to do the lookup on the Window (the current global).
GlobalObject* windowObj = cx_->global();
NativeObject* holder = nullptr;
Maybe<PropertyInfo> prop;
NativeGetPropKind kind =
CanAttachNativeGetProp(cx_, windowObj, id, &holder, &prop, pc_);
switch (kind) {
case NativeGetPropKind::None:
return AttachDecision::NoAction;
case NativeGetPropKind::Slot: {
maybeEmitIdGuard(id);
ObjOperandId windowObjId =
GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
EmitReadSlotResult(writer, windowObj, holder, *prop, windowObjId);
writer.returnFromIC();
trackAttached("GetProp.WindowProxySlot");
return AttachDecision::Attach;
}
case NativeGetPropKind::Missing: {
maybeEmitIdGuard(id);
ObjOperandId windowObjId =
GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
EmitMissingPropResult(writer, windowObj, windowObjId);
writer.returnFromIC();
trackAttached("GetProp.WindowProxyMissing");
return AttachDecision::Attach;
}
case NativeGetPropKind::NativeGetter:
case NativeGetPropKind::ScriptedGetter: {
// If a |super| access, it is not worth the complexity to attach an IC.
if (isSuper()) {
return AttachDecision::NoAction;
}
bool needsWindowProxy = GetterNeedsWindowProxyThis(holder, *prop);
// Guard the incoming object is a WindowProxy and inline a getter call
// based on the Window object.
maybeEmitIdGuard(id);
ObjOperandId windowObjId =
GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
if (CanAttachDOMGetterSetter(cx_, JSJitInfo::Getter, windowObj, holder,
*prop, mode_)) {
MOZ_ASSERT(!needsWindowProxy);
EmitCallDOMGetterResult(cx_, writer, windowObj, holder, id, *prop,
windowObjId);
trackAttached("GetProp.WindowProxyDOMGetter");
} else {
ValOperandId receiverId =
writer.boxObject(needsWindowProxy ? objId : windowObjId);
EmitCallGetterResult(cx_, writer, kind, windowObj, holder, id, *prop,
windowObjId, receiverId, mode_);
trackAttached("GetProp.WindowProxyGetter");
}
return AttachDecision::Attach;
}
}
MOZ_CRASH("Unreachable");
}
AttachDecision GetPropIRGenerator::tryAttachCrossCompartmentWrapper(
HandleObject obj, ObjOperandId objId, HandleId id) {
// We can only optimize this very wrapper-handler, because others might
// have a security policy.
if (!IsWrapper(obj) ||
Wrapper::wrapperHandler(obj) != &CrossCompartmentWrapper::singleton) {
return AttachDecision::NoAction;
}
// If we're megamorphic prefer a generic proxy stub that handles a lot more
// cases.
if (mode_ == ICState::Mode::Megamorphic) {
return AttachDecision::NoAction;
}
RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj));
MOZ_ASSERT(unwrapped == UnwrapOneCheckedStatic(obj));
MOZ_ASSERT(!IsCrossCompartmentWrapper(unwrapped),
"CCWs must not wrap other CCWs");
// If we allowed different zones we would have to wrap strings.
if (unwrapped->compartment()->zone() != cx_->compartment()->zone()) {
return AttachDecision::NoAction;
}
// Take the unwrapped object's global, and wrap in a
// this-compartment wrapper. This is what will be stored in the IC
// keep the compartment alive.
RootedObject wrappedTargetGlobal(cx_, &unwrapped->nonCCWGlobal());
if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal)) {
cx_->clearPendingException();
return AttachDecision::NoAction;
}
NativeObject* holder = nullptr;
Maybe<PropertyInfo> prop;
// Enter realm of target to prevent failing compartment assertions when doing
// the lookup.
{
AutoRealm ar(cx_, unwrapped);
NativeGetPropKind kind =
CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &prop, pc_);
if (kind != NativeGetPropKind::Slot && kind != NativeGetPropKind::Missing) {
return AttachDecision::NoAction;
}
}
auto* unwrappedNative = &unwrapped->as<NativeObject>();
maybeEmitIdGuard(id);
writer.guardIsProxy(objId);
writer.guardHasProxyHandler(objId, Wrapper::wrapperHandler(obj));
// Load the object wrapped by the CCW
ObjOperandId wrapperTargetId =
writer.loadWrapperTarget(objId, /*fallible = */ false);
// If the compartment of the wrapped object is different we should fail.
writer.guardCompartment(wrapperTargetId, wrappedTargetGlobal,
unwrappedNative->compartment());
ObjOperandId unwrappedId = wrapperTargetId;
if (holder) {
EmitReadSlotResult<IsCrossCompartment::Yes>(writer, unwrappedNative, holder,
*prop, unwrappedId);
writer.wrapResult();
writer.returnFromIC();
trackAttached("GetProp.CCWSlot");
} else {
EmitMissingPropResult<IsCrossCompartment::Yes>(writer, unwrappedNative,
unwrappedId);
writer.returnFromIC();
trackAttached("GetProp.CCWMissing");
}
return AttachDecision::Attach;
}
static JSObject* NewWrapperWithObjectShape(JSContext* cx,
Handle<NativeObject*> obj);
static bool GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray,
MutableHandleObject wrapper) {
Value v = GetProxyReservedSlot(xray, GetXrayJitInfo()->xrayHolderSlot);
if (v.isObject()) {
NativeObject* holder = &v.toObject().as<NativeObject>();
v = holder->getFixedSlot(GetXrayJitInfo()->holderExpandoSlot);
if (v.isObject()) {
Rooted<NativeObject*> expando(
cx, &UncheckedUnwrap(&v.toObject())->as<NativeObject>());
wrapper.set(NewWrapperWithObjectShape(cx, expando));
return wrapper != nullptr;
}
}
wrapper.set(nullptr);
return true;
}
AttachDecision GetPropIRGenerator::tryAttachXrayCrossCompartmentWrapper(
HandleObject obj, ObjOperandId objId, HandleId id,
ValOperandId receiverId) {
if (!obj->is<ProxyObject>()) {
return AttachDecision::NoAction;
}
JS::XrayJitInfo* info = GetXrayJitInfo();
if (!info || !info->isCrossCompartmentXray(GetProxyHandler(obj))) {
return AttachDecision::NoAction;
}
if (!info->compartmentHasExclusiveExpandos(obj)) {
return AttachDecision::NoAction;
}
RootedObject target(cx_, UncheckedUnwrap(obj));
RootedObject expandoShapeWrapper(cx_);
if (!GetXrayExpandoShapeWrapper(cx_, obj, &expandoShapeWrapper)) {
cx_->recoverFromOutOfMemory();
return AttachDecision::NoAction;
}
// Look for a getter we can call on the xray or its prototype chain.
Rooted<Maybe<PropertyDescriptor>> desc(cx_);
RootedObject holder(cx_, obj);
RootedObjectVector prototypes(cx_);
RootedObjectVector prototypeExpandoShapeWrappers(cx_);
while (true) {
if (!GetOwnPropertyDescriptor(cx_, holder, id, &desc)) {
cx_->clearPendingException();
return AttachDecision::NoAction;
}
if (desc.isSome()) {
break;
}
if (!GetPrototype(cx_, holder, &holder)) {
cx_->clearPendingException();
return AttachDecision::NoAction;
}
if (!holder || !holder->is<ProxyObject>() ||
!info->isCrossCompartmentXray(GetProxyHandler(holder))) {
return AttachDecision::NoAction;
}
RootedObject prototypeExpandoShapeWrapper(cx_);
if (!GetXrayExpandoShapeWrapper(cx_, holder,
&prototypeExpandoShapeWrapper) ||
!prototypes.append(holder) ||
!prototypeExpandoShapeWrappers.append(prototypeExpandoShapeWrapper)) {
cx_->recoverFromOutOfMemory();
return AttachDecision::NoAction;
}
}
if (!desc->isAccessorDescriptor()) {
return AttachDecision::NoAction;
}
RootedObject getter(cx_, desc->getter());
if (!getter || !getter->is<JSFunction>() ||
!getter->as<JSFunction>().isNativeWithoutJitEntry()) {
return AttachDecision::NoAction;
}
maybeEmitIdGuard(id);
writer.guardIsProxy(objId);
writer.guardHasProxyHandler(objId, GetProxyHandler(obj));
// Load the object wrapped by the CCW
ObjOperandId wrapperTargetId =
writer.loadWrapperTarget(objId, /*fallible = */ false);
// Test the wrapped object's class. The properties held by xrays or their
// prototypes will be invariant for objects of a given class, except for
// changes due to xray expandos or xray prototype mutations.
writer.guardAnyClass(wrapperTargetId, target->getClass());
// Make sure the expandos on the xray and its prototype chain match up with
// what we expect. The expando shape needs to be consistent, to ensure it
// has not had any shadowing properties added, and the expando cannot have
// any custom prototype (xray prototypes are stable otherwise).
//
// We can only do this for xrays with exclusive access to their expandos
// (as we checked earlier), which store a pointer to their expando
// directly. Xrays in other compartments may share their expandos with each
// other and a VM call is needed just to find the expando.
if (expandoShapeWrapper) {
writer.guardXrayExpandoShapeAndDefaultProto(objId, expandoShapeWrapper);
} else {
writer.guardXrayNoExpando(objId);
}
for (size_t i = 0; i < prototypes.length(); i++) {
JSObject* proto = prototypes[i];
ObjOperandId protoId = writer.loadObject(proto);
if (JSObject* protoShapeWrapper = prototypeExpandoShapeWrappers[i]) {
writer.guardXrayExpandoShapeAndDefaultProto(protoId, protoShapeWrapper);
} else {
writer.guardXrayNoExpando(protoId);
}
}
bool sameRealm = cx_->realm() == getter->as<JSFunction>().realm();
writer.callNativeGetterResult(receiverId, &getter->as<JSFunction>(),
sameRealm);
writer.returnFromIC();
trackAttached("GetProp.XrayCCW");
return AttachDecision::Attach;
}
#ifdef JS_PUNBOX64
AttachDecision GetPropIRGenerator::tryAttachScriptedProxy(
Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id) {
if (cacheKind_ != CacheKind::GetProp && cacheKind_ != CacheKind::GetElem) {
return AttachDecision::NoAction;
}
if (cacheKind_ == CacheKind::GetElem) {
if (!idVal_.isString() && !idVal_.isInt32() && !idVal_.isSymbol()) {
return AttachDecision::NoAction;
}
}
JSObject* handlerObj = ScriptedProxyHandler::handlerObject(obj);
if (!handlerObj) {
return AttachDecision::NoAction;
}
NativeObject* trapHolder = nullptr;
Maybe<PropertyInfo> trapProp;
// We call with pc_ even though that's not the actual corresponding pc. It
// should, however, be fine, because it's just used to check if this is a
// GetBoundName, which it's not.
NativeGetPropKind trapKind = CanAttachNativeGetProp(
cx_, handlerObj, NameToId(cx_->names().get), &trapHolder, &trapProp, pc_);
if (trapKind != NativeGetPropKind::Missing &&
trapKind != NativeGetPropKind::Slot) {
return AttachDecision::NoAction;
}
if (trapKind != NativeGetPropKind::Missing) {
uint32_t trapSlot = trapProp->slot();
const Value& trapVal = trapHolder->getSlot(trapSlot);
if (!trapVal.isObject()) {
return AttachDecision::NoAction;
}
JSObject* trapObj = &trapVal.toObject();
if (!trapObj->is<JSFunction>()) {
return AttachDecision::NoAction;
}
JSFunction* trapFn = &trapObj->as<JSFunction>();
if (trapFn->isClassConstructor()) {
return AttachDecision::NoAction;
}
if (!trapFn->hasJitEntry()) {
return AttachDecision::NoAction;
}
if (cx_->realm() != trapFn->realm()) {
return AttachDecision::NoAction;
}
}
NativeObject* nHandlerObj = &handlerObj->as<NativeObject>();
JSObject* targetObj = obj->target();
MOZ_ASSERT(targetObj, "Guaranteed by the scripted Proxy constructor");
// We just require that the target is a NativeObject to make our lives
// easier. There's too much nonsense we might have to handle otherwise and
// we're not set up to recursively call GetPropIRGenerator::tryAttachStub
// for the target object.
if (!targetObj->is<NativeObject>()) {
return AttachDecision::NoAction;
}
writer.guardIsProxy(objId);
writer.guardHasProxyHandler(objId, &ScriptedProxyHandler::singleton);
ObjOperandId handlerObjId = writer.loadScriptedProxyHandler(objId);
ObjOperandId targetObjId =
writer.loadWrapperTarget(objId, /*fallible =*/true);
writer.guardIsNativeObject(targetObjId);
if (trapKind == NativeGetPropKind::Missing) {
EmitMissingPropGuard(writer, nHandlerObj, handlerObjId);
if (cacheKind_ == CacheKind::GetProp) {
writer.megamorphicLoadSlotResult(targetObjId, id);
} else {
writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId());
}
} else {
uint32_t trapSlot = trapProp->slot();
const Value& trapVal = trapHolder->getSlot(trapSlot);
JSObject* trapObj = &trapVal.toObject();
JSFunction* trapFn = &trapObj->as<JSFunction>();
ObjOperandId trapHolderId =
EmitReadSlotGuard(writer, nHandlerObj, trapHolder, handlerObjId);
ValOperandId fnValId =
EmitLoadSlot(writer, trapHolder, trapHolderId, trapSlot);
ObjOperandId fnObjId = writer.guardToObject(fnValId);
emitCalleeGuard(fnObjId, trapFn);
ValOperandId targetValId = writer.boxObject(targetObjId);
if (cacheKind_ == CacheKind::GetProp) {
writer.callScriptedProxyGetResult(targetValId, objId, handlerObjId,
fnObjId, trapFn, id);
} else {
ValOperandId idId = getElemKeyValueId();
ValOperandId stringIdId = writer.idToStringOrSymbol(idId);
writer.callScriptedProxyGetByValueResult(targetValId, objId, handlerObjId,
stringIdId, fnObjId, trapFn);
}
}
writer.returnFromIC();
trackAttached("GetScriptedProxy");
return AttachDecision::Attach;
}
#endif
AttachDecision GetPropIRGenerator::tryAttachGenericProxy(
Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
bool handleDOMProxies) {
writer.guardIsProxy(objId);
if (!handleDOMProxies) {
// Ensure that the incoming object is not a DOM proxy, so that we can get to
// the specialized stubs
writer.guardIsNotDOMProxy(objId);
}
if (cacheKind_ == CacheKind::GetProp || mode_ == ICState::Mode::Specialized) {
MOZ_ASSERT(!isSuper());
maybeEmitIdGuard(id);
writer.proxyGetResult(objId, id);
} else {
// Attach a stub that handles every id.
MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
MOZ_ASSERT(!isSuper());
writer.proxyGetByValueResult(objId, getElemKeyValueId());
}
writer.returnFromIC();
trackAttached("GetProp.GenericProxy");
return AttachDecision::Attach;
}
static bool ValueIsInt64Index(const Value& val, int64_t* index) {
// Try to convert the Value to a TypedArray index or DataView offset.
if (val.isInt32()) {
*index = val.toInt32();
return true;
}
if (val.isDouble()) {
// Use NumberEqualsInt64 because ToPropertyKey(-0) is 0.
return mozilla::NumberEqualsInt64(val.toDouble(), index);
}
return false;
}
IntPtrOperandId IRGenerator::guardToIntPtrIndex(const Value& index,
ValOperandId indexId,
bool supportOOB) {
#ifdef DEBUG
int64_t indexInt64;
MOZ_ASSERT_IF(!supportOOB, ValueIsInt64Index(index, &indexInt64));
#endif
if (index.isInt32()) {
Int32OperandId int32IndexId = writer.guardToInt32(indexId);
return writer.int32ToIntPtr(int32IndexId);
}
MOZ_ASSERT(index.isNumber());
NumberOperandId numberIndexId = writer.guardIsNumber(indexId);
return writer.guardNumberToIntPtrIndex(numberIndexId, supportOOB);
}
ObjOperandId IRGenerator::guardDOMProxyExpandoObjectAndShape(
ProxyObject* obj, ObjOperandId objId, const Value& expandoVal,
NativeObject* expandoObj) {
MOZ_ASSERT(IsCacheableDOMProxy(obj));
TestMatchingProxyReceiver(writer, obj, objId);
// Shape determines Class, so now it must be a DOM proxy.
ValOperandId expandoValId;
if (expandoVal.isObject()) {
expandoValId = writer.loadDOMExpandoValue(objId);
} else {
expandoValId = writer.loadDOMExpandoValueIgnoreGeneration(objId);
}
// Guard the expando is an object and shape guard.
ObjOperandId expandoObjId = writer.guardToObject(expandoValId);
TestMatchingHolder(writer, expandoObj, expandoObjId);
return expandoObjId;
}
AttachDecision GetPropIRGenerator::tryAttachDOMProxyExpando(
Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
ValOperandId receiverId) {
MOZ_ASSERT(IsCacheableDOMProxy(obj));
Value expandoVal = GetProxyPrivate(obj);
JSObject* expandoObj;
if (expandoVal.isObject()) {
expandoObj = &expandoVal.toObject();
} else {
MOZ_ASSERT(!expandoVal.isUndefined(),
"How did a missing expando manage to shadow things?");
auto expandoAndGeneration =
static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
MOZ_ASSERT(expandoAndGeneration);
expandoObj = &expandoAndGeneration->expando.toObject();
}
// Try to do the lookup on the expando object.
NativeObject* holder = nullptr;
Maybe<PropertyInfo> prop;
NativeGetPropKind kind =
CanAttachNativeGetProp(cx_, expandoObj, id, &holder, &prop, pc_);
if (kind == NativeGetPropKind::None) {
return AttachDecision::NoAction;
}
if (!holder) {
return AttachDecision::NoAction;
}
auto* nativeExpandoObj = &expandoObj->as<NativeObject>();
MOZ_ASSERT(holder == nativeExpandoObj);
maybeEmitIdGuard(id);
ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(
obj, objId, expandoVal, nativeExpandoObj);
if (kind == NativeGetPropKind::Slot) {
// Load from the expando's slots.
EmitLoadSlotResult(writer, expandoObjId, nativeExpandoObj, *prop);
writer.returnFromIC();
} else {
// Call the getter. Note that we pass objId, the DOM proxy, as |this|
// and not the expando object.
MOZ_ASSERT(kind == NativeGetPropKind::NativeGetter ||
kind == NativeGetPropKind::ScriptedGetter);
EmitGuardGetterSetterSlot(writer, nativeExpandoObj, *prop, expandoObjId);
EmitCallGetterResultNoGuards(cx_, writer, kind, nativeExpandoObj,
nativeExpandoObj, *prop, receiverId);
}
trackAttached("GetProp.DOMProxyExpando");
return AttachDecision::Attach;
}
AttachDecision GetPropIRGenerator::tryAttachDOMProxyShadowed(
Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id) {
MOZ_ASSERT(!isSuper());
MOZ_ASSERT(IsCacheableDOMProxy(obj));
maybeEmitIdGuard(id);
TestMatchingProxyReceiver(writer, obj, objId);
writer.proxyGetResult(objId, id);
writer.returnFromIC();
trackAttached("GetProp.DOMProxyShadowed");
return AttachDecision::Attach;
}
// Emit CacheIR to guard the DOM proxy doesn't shadow |id|. There are two types
// of DOM proxies:
//
// (a) DOM proxies marked LegacyOverrideBuiltIns in WebIDL, for example
// HTMLDocument or HTMLFormElement. These proxies look up properties in this
// order:
//
// (1) The expando object.
// (2) The proxy's named-property handler.
// (3) The prototype chain.
//
// To optimize properties on the prototype chain, we have to guard that (1)
// and (2) don't shadow (3). We handle (1) by either emitting a shape guard
// for the expando object or by guarding the proxy has no expando object. To
// efficiently handle (2), the proxy must have an ExpandoAndGeneration*
// stored as PrivateValue. We guard on its generation field to ensure the
// set of names hasn't changed.
//
// Missing properties can be optimized in a similar way by emitting shape
// guards for the prototype chain.
//
// (b) Other DOM proxies. These proxies look up properties in this
// order:
//
// (1) The expando object.
// (2) The prototype chain.
// (3) The proxy's named-property handler.
//
// To optimize properties on the prototype chain, we only have to guard the
// expando object doesn't shadow it.
//
// Missing properties can't be optimized in this case because we don't have
// an efficient way to guard against the proxy handler shadowing the
// property (there's no ExpandoAndGeneration*).
//
// See also:
// * DOMProxyShadows in DOMJSProxyHandler.cpp
// the end)
//
// Callers are expected to have already guarded on the shape of the
// object, which guarantees the object is a DOM proxy.
static void CheckDOMProxyDoesNotShadow(CacheIRWriter& writer, ProxyObject* obj,
jsid id, ObjOperandId objId,
bool* canOptimizeMissing) {
MOZ_ASSERT(IsCacheableDOMProxy(obj));
Value expandoVal = GetProxyPrivate(obj);
ValOperandId expandoId;
if (!expandoVal.isObject() && !expandoVal.isUndefined()) {
// Case (a).
auto expandoAndGeneration =
static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
uint64_t generation = expandoAndGeneration->generation;
expandoId = writer.loadDOMExpandoValueGuardGeneration(
objId, expandoAndGeneration, generation);
expandoVal = expandoAndGeneration->expando;
*canOptimizeMissing = true;
} else {
// Case (b).
expandoId = writer.loadDOMExpandoValue(objId);
*canOptimizeMissing = false;
}
if (expandoVal.isUndefined()) {
// Guard there's no expando object.
writer.guardNonDoubleType(expandoId, ValueType::Undefined);
} else if (expandoVal.isObject()) {
// Guard the proxy either has no expando object or, if it has one, that
// the shape matches the current expando object.
NativeObject& expandoObj = expandoVal.toObject().as<NativeObject>();
MOZ_ASSERT(!expandoObj.containsPure(id));
writer.guardDOMExpandoMissingOrGuardShape(expandoId, expandoObj.shape());
} else {
MOZ_CRASH("Invalid expando value");
}
}
AttachDecision GetPropIRGenerator::tryAttachDOMProxyUnshadowed(
Handle<ProxyObject*> obj, ObjOperandId objId, HandleId id,
ValOperandId receiverId) {
MOZ_ASSERT(IsCacheableDOMProxy(obj));
JSObject* protoObj = obj->staticPrototype();
if (!protoObj) {
return AttachDecision::NoAction;
}
NativeObject* holder = nullptr;
Maybe<PropertyInfo> prop;
NativeGetPropKind kind =
CanAttachNativeGetProp(cx_, protoObj, id, &holder, &prop, pc_);
if (kind == NativeGetPropKind::None) {
return AttachDecision::NoAction;
}
auto* nativeProtoObj = &protoObj->as<NativeObject>();
maybeEmitIdGuard(id);
// Guard that our proxy (expando) object hasn't started shadowing this
// property.
TestMatchingProxyReceiver(writer, obj, objId);
bool canOptimizeMissing = false;
CheckDOMProxyDoesNotShadow(writer, obj, id, objId, &canOptimizeMissing);
if (holder) {
// Found the property on the prototype chain. Treat it like a native
// getprop.
GeneratePrototypeGuards(writer, obj, holder, objId);
// Guard on the holder of the property.
ObjOperandId holderId = writer.loadObject(holder);
TestMatchingHolder(writer, holder, holderId);
if (kind == NativeGetPropKind::Slot) {
EmitLoadSlotResult(writer, holderId, holder, *prop);
writer.returnFromIC();
} else {
// EmitCallGetterResultNoGuards expects |obj| to be the object the
// property is on to do some checks. Since we actually looked at
// checkObj, and no extra guards will be generated, we can just
// pass that instead.
MOZ_ASSERT(kind == NativeGetPropKind::NativeGetter ||
kind == NativeGetPropKind::ScriptedGetter);
MOZ_ASSERT(!isSuper());
EmitGuardGetterSetterSlot(writer, holder, *prop, holderId,
/* holderIsConstant = */ true);
EmitCallGetterResultNoGuards(cx_, writer, kind, nativeProtoObj, holder,
*prop, receiverId);
}
} else {
// Property was not found on the prototype chain.
MOZ_ASSERT(kind == NativeGetPropKind::Missing);
if (canOptimizeMissing) {
// We already guarded on the proxy's shape, so now shape guard the proto
// chain.
ObjOperandId protoId = writer.loadObject(nativeProtoObj);
EmitMissingPropResult(writer, nativeProtoObj, protoId);
} else {
MOZ_ASSERT(!isSuper());
writer.proxyGetResult(objId, id);
}
writer.returnFromIC();
}
trackAttached("GetProp.DOMProxyUnshadowed");
return AttachDecision::Attach;
}
AttachDecision GetPropIRGenerator::tryAttachProxy(HandleObject obj,
ObjOperandId objId,
HandleId id,
ValOperandId receiverId) {
// The proxy stubs don't currently support |super| access.
if (isSuper()) {
return AttachDecision::NoAction;
}
// Always try to attach scripted proxy get even if we're megamorphic.
// In Speedometer 3 we'll often run into cases where we're megamorphic
// overall, but monomorphic for the proxy case. This is because there
// are functions which lazily turn various differently-shaped objects
// into proxies. So the un-proxified objects are megamorphic, but the
// proxy handlers are actually monomorphic. There is room for a bit
// more sophistication here, but this should do for now.
if (!obj->is<ProxyObject>()) {
return AttachDecision::NoAction;
}
auto proxy = obj.as<ProxyObject>();
#ifdef JS_PUNBOX64
if (proxy->handler()->isScripted()) {
TRY_ATTACH(tryAttachScriptedProxy(proxy, objId, id));
}
#endif
ProxyStubType type = GetProxyStubType(cx_, obj, id);
if (type == ProxyStubType::None) {
return AttachDecision::NoAction;
}
if (mode_ == ICState::Mode::Megamorphic) {
return tryAttachGenericProxy(proxy, objId, id,
/* handleDOMProxies = */ true);
}
switch (type) {
case ProxyStubType::None:
break;
case ProxyStubType::DOMExpando:
TRY_ATTACH(tryAttachDOMProxyExpando(proxy, objId, id, receiverId));
[[fallthrough]]; // Fall through to the generic shadowed case.
case ProxyStubType::DOMShadowed:
return tryAttachDOMProxyShadowed(proxy, objId, id);
case ProxyStubType::DOMUnshadowed:
TRY_ATTACH(tryAttachDOMProxyUnshadowed(proxy, objId, id, receiverId));
return tryAttachGenericProxy(proxy, objId, id,
/* handleDOMProxies = */ true);
case ProxyStubType::Generic:
return tryAttachGenericProxy(proxy, objId, id,
/* handleDOMProxies = */ false);
}
MOZ_CRASH("Unexpected ProxyStubType");
}
const JSClass* js::jit::ClassFor(GuardClassKind kind) {
switch (kind) {
case GuardClassKind::Array:
return &ArrayObject::class_;
case GuardClassKind::PlainObject:
return &PlainObject::class_;
case GuardClassKind::FixedLengthArrayBuffer:
return &FixedLengthArrayBufferObject::class_;
case GuardClassKind::ResizableArrayBuffer:
return &ResizableArrayBufferObject::class_;
case GuardClassKind::FixedLengthSharedArrayBuffer:
return &FixedLengthSharedArrayBufferObject::class_;
case GuardClassKind::GrowableSharedArrayBuffer:
return &GrowableSharedArrayBufferObject::class_;
case GuardClassKind::FixedLengthDataView:
return &FixedLengthDataViewObject::class_;
case GuardClassKind::ResizableDataView:
return &ResizableDataViewObject::class_;
case GuardClassKind::MappedArguments:
return &MappedArgumentsObject::class_;
case GuardClassKind::UnmappedArguments:
return &UnmappedArgumentsObject::class_;
case GuardClassKind::WindowProxy:
// Caller needs to handle this case, see
// JSRuntime::maybeWindowProxyClass().
break;
case GuardClassKind::JSFunction:
// Caller needs to handle this case. Can be either |js::FunctionClass| or
// |js::ExtendedFunctionClass|.
break;
case GuardClassKind::BoundFunction:
return &BoundFunctionObject::class_;
case GuardClassKind::Set:
return &SetObject::class_;
case GuardClassKind::Map:
return &MapObject::class_;
case GuardClassKind::Date:
return &DateObject::class_;
}
MOZ_CRASH("unexpected kind");
}
// Guards the class of an object. Because shape implies class, and a shape guard
// is faster than a class guard, if this is our first time attaching a stub, we
// instead generate a shape guard.
void IRGenerator::emitOptimisticClassGuard(ObjOperandId objId, JSObject* obj,
GuardClassKind kind) {
#ifdef DEBUG
switch (kind) {
case GuardClassKind::Array:
case GuardClassKind::PlainObject:
case GuardClassKind::FixedLengthArrayBuffer:
case GuardClassKind::ResizableArrayBuffer:
case GuardClassKind::FixedLengthSharedArrayBuffer:
case GuardClassKind::GrowableSharedArrayBuffer:
case GuardClassKind::FixedLengthDataView:
case GuardClassKind::ResizableDataView:
case GuardClassKind::Set:
case GuardClassKind::Map:
case GuardClassKind::Date:
MOZ_ASSERT(obj->hasClass(ClassFor(kind)));
break;
case GuardClassKind::MappedArguments:
case GuardClassKind::UnmappedArguments:
case GuardClassKind::JSFunction:
case GuardClassKind::BoundFunction:
case GuardClassKind::WindowProxy:
// Arguments, functions, and the global object have
// less consistent shapes.
MOZ_CRASH("GuardClassKind not supported");
}
#endif
if (isFirstStub_) {
writer.guardShapeForClass(objId, obj->shape());
} else {
writer.guardClass(objId, kind);
}
}
static void AssertArgumentsCustomDataProp(ArgumentsObject* obj,
PropertyKey key) {
#ifdef DEBUG
// The property must still be a custom data property if it has been resolved.
// If this assertion fails, we're probably missing a call to mark this
// property overridden.
Maybe<PropertyInfo> prop = obj->lookupPure(key);
MOZ_ASSERT_IF(prop, prop->isCustomDataProperty());
#endif
}
AttachDecision GetPropIRGenerator::tryAttachObjectLength(HandleObject obj,
ObjOperandId objId,
HandleId id) {
if (!id.isAtom(cx_->names().length)) {
return AttachDecision::NoAction;
}
if (obj->is<ArrayObject>()) {
if (obj->as<ArrayObject>().length() > INT32_MAX) {
return AttachDecision::NoAction;
}
maybeEmitIdGuard(id);
emitOptimisticClassGuard(objId, obj, GuardClassKind::Array);
writer.loadInt32ArrayLengthResult(objId);
writer.returnFromIC();
trackAttached("GetProp.ArrayLength");
return AttachDecision::Attach;
}
if (obj->is<ArgumentsObject>() &&
!obj->as<ArgumentsObject>().hasOverriddenLength()) {
AssertArgumentsCustomDataProp(&obj->as<ArgumentsObject>(), id);
maybeEmitIdGuard(id);
if (obj->is<MappedArgumentsObject>()) {
writer.guardClass(objId, GuardClassKind::MappedArguments);
} else {
MOZ_ASSERT(obj->is<UnmappedArgumentsObject>());
writer.guardClass(objId, GuardClassKind::UnmappedArguments);
}
writer.loadArgumentsObjectLengthResult(objId);
writer.returnFromIC();
trackAttached("GetProp.ArgumentsObjectLength");
return AttachDecision::Attach;
}
return AttachDecision::NoAction;
}
AttachDecision GetPropIRGenerator::tryAttachTypedArray(HandleObject obj,
ObjOperandId objId,
HandleId id) {
if (!obj->is<TypedArrayObject>()) {
return AttachDecision::NoAction;
}
if (mode_ != ICState::Mode::Specialized) {
return AttachDecision::NoAction;
}
// Receiver should be the object.
if (isSuper()) {
return AttachDecision::NoAction;
}
bool isLength = id.isAtom(cx_->names().length);
bool isByteOffset = id.isAtom(cx_->names().byteOffset);
if (!isLength && !isByteOffset && !id.isAtom(cx_->names().byteLength)) {
return AttachDecision::NoAction;
}
NativeObject* holder = nullptr;
Maybe<PropertyInfo> prop;
NativeGetPropKind kind =
CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
if (kind != NativeGetPropKind::NativeGetter) {
return AttachDecision::NoAction;
}
JSFunction& fun = holder->getGetter(*prop)->as<JSFunction>();
if (isLength) {
if (!TypedArrayObject::isOriginalLengthGetter(fun.native())) {
return AttachDecision::NoAction;
}
} else if (isByteOffset) {
if (!TypedArrayObject::isOriginalByteOffsetGetter(fun.native())) {
return AttachDecision::NoAction;
}
} else {
if (!TypedArrayObject::isOriginalByteLengthGetter(fun.native())) {
return AttachDecision::NoAction;
}
}
auto* tarr = &obj->as<TypedArrayObject>();
maybeEmitIdGuard(id);
// Emit all the normal guards for calling this native, but specialize
// callNativeGetterResult.
EmitCallGetterResultGuards(writer, tarr, holder, id, *prop, objId, mode_);
if (isLength) {
size_t length = tarr->length().valueOr(0);
if (tarr->is<FixedLengthTypedArrayObject>()) {
if (length <= INT32_MAX) {
writer.loadArrayBufferViewLengthInt32Result(objId);
} else {
writer.loadArrayBufferViewLengthDoubleResult(objId);
}
} else {
if (length <= INT32_MAX) {
writer.resizableTypedArrayLengthInt32Result(objId);
} else {
writer.resizableTypedArrayLengthDoubleResult(objId);
}
}
trackAttached("GetProp.TypedArrayLength");
} else if (isByteOffset) {
// byteOffset doesn't need to use different code paths for fixed-length and
// resizable TypedArrays.
size_t byteOffset = tarr->byteOffset().valueOr(0);
if (byteOffset <= INT32_MAX) {
writer.arrayBufferViewByteOffsetInt32Result(objId);
} else {
writer.arrayBufferViewByteOffsetDoubleResult(objId);
}
trackAttached("GetProp.TypedArrayByteOffset");
} else {
size_t byteLength = tarr->byteLength().valueOr(0);
if (tarr->is<FixedLengthTypedArrayObject>()) {
if (byteLength <= INT32_MAX) {
writer.typedArrayByteLengthInt32Result(objId);
} else {
writer.typedArrayByteLengthDoubleResult(objId);
}
} else {
if (byteLength <= INT32_MAX) {
writer.resizableTypedArrayByteLengthInt32Result(objId);
} else {
writer.resizableTypedArrayByteLengthDoubleResult(objId);
}
}
trackAttached("GetProp.TypedArrayByteLength");
}
writer.returnFromIC();
return AttachDecision::Attach;
}
AttachDecision GetPropIRGenerator::tryAttachDataView(HandleObject obj,
ObjOperandId objId,
HandleId id) {
if (!obj->is<DataViewObject>()) {
return AttachDecision::NoAction;
}
auto* dv = &obj->as<DataViewObject>();
if (mode_ != ICState::Mode::Specialized) {
return AttachDecision::NoAction;
}
// Receiver should be the object.
if (isSuper()) {
return AttachDecision::NoAction;
}
bool isByteOffset = id.isAtom(cx_->names().byteOffset);
if (!isByteOffset && !id.isAtom(cx_->names().byteLength)) {
return AttachDecision::NoAction;
}
// byteOffset and byteLength both throw when the ArrayBuffer is detached.
if (dv->hasDetachedBuffer()) {
return AttachDecision::NoAction;
}
// byteOffset and byteLength both throw when the ArrayBuffer is out-of-bounds.
if (dv->is<ResizableDataViewObject>() &&
dv->as<ResizableDataViewObject>().isOutOfBounds()) {
return AttachDecision::NoAction;
}
NativeObject* holder = nullptr;
Maybe<PropertyInfo> prop;
NativeGetPropKind kind =
CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
if (kind != NativeGetPropKind::NativeGetter) {
return AttachDecision::NoAction;
}
auto& fun = holder->getGetter(*prop)->as<JSFunction>();
if (isByteOffset) {
if (!DataViewObject::isOriginalByteOffsetGetter(fun.native())) {
return AttachDecision::NoAction;
}
} else {
if (!DataViewObject::isOriginalByteLengthGetter(fun.native())) {
return AttachDecision::NoAction;
}
}
maybeEmitIdGuard(id);
// Emit all the normal guards for calling this native, but specialize
// callNativeGetterResult.
EmitCallGetterResultGuards(writer, dv, holder, id, *prop, objId, mode_);
writer.guardHasAttachedArrayBuffer(objId);
if (dv->is<ResizableDataViewObject>()) {
writer.guardResizableArrayBufferViewInBounds(objId);
}
if (isByteOffset) {
// byteOffset doesn't need to use different code paths for fixed-length and
// resizable DataViews.
size_t byteOffset = dv->byteOffset().valueOr(0);
if (byteOffset <= INT32_MAX) {
writer.arrayBufferViewByteOffsetInt32Result(objId);
} else {
writer.arrayBufferViewByteOffsetDoubleResult(objId);
}
trackAttached("GetProp.DataViewByteOffset");
} else {
size_t byteLength = dv->byteLength().valueOr(0);
if (dv->is<FixedLengthDataViewObject>()) {
if (byteLength <= INT32_MAX) {
writer.loadArrayBufferViewLengthInt32Result(objId);
} else {
writer.loadArrayBufferViewLengthDoubleResult(objId);
}
} else {
if (byteLength <= INT32_MAX) {
writer.resizableDataViewByteLengthInt32Result(objId);
} else {
writer.resizableDataViewByteLengthDoubleResult(objId);
}
}
trackAttached("GetProp.DataViewByteLength");
}
writer.returnFromIC();
return AttachDecision::Attach;
}
AttachDecision GetPropIRGenerator::tryAttachArrayBufferMaybeShared(
HandleObject obj, ObjOperandId objId, HandleId id) {
if (!obj->is<ArrayBufferObjectMaybeShared>()) {
return AttachDecision::NoAction;
}
auto* buf = &obj->as<ArrayBufferObjectMaybeShared>();
if (mode_ != ICState::Mode::Specialized) {
return AttachDecision::NoAction;
}
// Receiver should be the object.
if (isSuper()) {
return AttachDecision::NoAction;
}
if (!id.isAtom(cx_->names().byteLength)) {
return AttachDecision::NoAction;
}
NativeObject* holder = nullptr;
Maybe<PropertyInfo> prop;
NativeGetPropKind kind =
CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
if (kind != NativeGetPropKind::NativeGetter) {
return AttachDecision::NoAction;
}
auto& fun = holder->getGetter(*prop)->as<JSFunction>();
if (buf->is<ArrayBufferObject>()) {
if (!ArrayBufferObject::isOriginalByteLengthGetter(fun.native())) {
return AttachDecision::NoAction;
}
} else {
if (!SharedArrayBufferObject::isOriginalByteLengthGetter(fun.native())) {
return AttachDecision::NoAction;
}
}
maybeEmitIdGuard(id);
// Emit all the normal guards for calling this native, but specialize
// callNativeGetterResult.
EmitCallGetterResultGuards(writer, buf, holder, id, *prop, objId, mode_);
if (!buf->is<GrowableSharedArrayBufferObject>()) {
if (buf->byteLength() <= INT32_MAX) {
writer.loadArrayBufferByteLengthInt32Result(objId);
} else {
writer.loadArrayBufferByteLengthDoubleResult(objId);
}
} else {
if (buf->byteLength() <= INT32_MAX) {
writer.growableSharedArrayBufferByteLengthInt32Result(objId);
} else {
writer.growableSharedArrayBufferByteLengthDoubleResult(objId);
}
}
writer.returnFromIC();
trackAttached("GetProp.ArrayBufferMaybeSharedByteLength");
return AttachDecision::Attach;
}
AttachDecision GetPropIRGenerator::tryAttachRegExp(HandleObject obj,
ObjOperandId objId,
HandleId id) {
if (!obj->is<RegExpObject>()) {
return AttachDecision::NoAction;
}
auto* regExp = &obj->as<RegExpObject>();
if (mode_ != ICState::Mode::Specialized) {
return AttachDecision::NoAction;
}
// Receiver should be the object.
if (isSuper()) {
return AttachDecision::NoAction;
}
NativeObject* holder = nullptr;
Maybe<PropertyInfo> prop;
NativeGetPropKind kind =
CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
if (kind != NativeGetPropKind::NativeGetter) {
return AttachDecision::NoAction;
}
auto& fun = holder->getGetter(*prop)->as<JSFunction>();
JS::RegExpFlags flags = JS::RegExpFlag::NoFlags;
if (!RegExpObject::isOriginalFlagGetter(fun.native(), &flags)) {
return AttachDecision::NoAction;
}
maybeEmitIdGuard(id);
// Emit all the normal guards for calling this native, but specialize
// callNativeGetterResult.
EmitCallGetterResultGuards(writer, regExp, holder, id, *prop, objId, mode_);
writer.regExpFlagResult(objId, flags.value());
writer.returnFromIC();
trackAttached("GetProp.RegExpFlag");
return AttachDecision::Attach;
}
AttachDecision GetPropIRGenerator::tryAttachMap(HandleObject obj,
ObjOperandId objId,
HandleId id) {
if (!obj->is<MapObject>()) {
return AttachDecision::NoAction;
}
auto* mapObj = &obj->as<MapObject>();
if (mode_ != ICState::Mode::Specialized) {
return AttachDecision::NoAction;
}
// Receiver should be the object.
if (isSuper()) {
return AttachDecision::NoAction;
}
if (!id.isAtom(cx_->names().size)) {
return AttachDecision::NoAction;
}
NativeObject* holder = nullptr;
Maybe<PropertyInfo> prop;
NativeGetPropKind kind =
CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
if (kind != NativeGetPropKind::NativeGetter) {
return AttachDecision::NoAction;
}
auto& fun = holder->getGetter(*prop)->as<JSFunction>();
if (!MapObject::isOriginalSizeGetter(fun.native())) {
return AttachDecision::NoAction;
}
maybeEmitIdGuard(id);
// Emit all the normal guards for calling this native, but specialize
// callNativeGetterResult.
EmitCallGetterResultGuards(writer, mapObj, holder, id, *prop, objId, mode_);
writer.mapSizeResult(objId);
writer.returnFromIC();
trackAttached("GetProp.MapSize");
return AttachDecision::Attach;
}
AttachDecision GetPropIRGenerator::tryAttachSet(HandleObject obj,
ObjOperandId objId,
HandleId id) {
if (!obj->is<SetObject>()) {
return AttachDecision::NoAction;
}
auto* setObj = &obj->as<SetObject>();
if (mode_ != ICState::Mode::Specialized) {
return AttachDecision::NoAction;
}
// Receiver should be the object.
if (isSuper()) {
return AttachDecision::NoAction;
}
if (!id.isAtom(cx_->names().size)) {
return AttachDecision::NoAction;
}
NativeObject* holder = nullptr;
Maybe<PropertyInfo> prop;
NativeGetPropKind kind =
CanAttachNativeGetProp(cx_, obj, id, &holder, &prop, pc_);
if (kind != NativeGetPropKind::NativeGetter) {
return AttachDecision::NoAction;
}
auto& fun = holder->getGetter(*prop)->as<JSFunction>();
if (!SetObject::isOriginalSizeGetter(fun.native())) {
return AttachDecision::NoAction;
}
maybeEmitIdGuard(id);
// Emit all the normal guards for calling this native, but specialize
// callNativeGetterResult.
EmitCallGetterResultGuards(writer, setObj, holder, id, *prop, objId, mode_);
writer.setSizeResult(objId);
writer.returnFromIC();
trackAttached("GetProp.SetSize");
return AttachDecision::Attach;
}
AttachDecision GetPropIRGenerator::tryAttachFunction(HandleObject obj,
ObjOperandId objId,
HandleId id) {
// Function properties are lazily resolved so they might not be defined yet.
// And we might end up in a situation where we always have a fresh function
// object during the IC generation.
if (!obj->is<JSFunction>()) {
return AttachDecision::NoAction;
}
bool isLength = id.isAtom(cx_->names().length);
if (!isLength && !id.isAtom(cx_->names().name)) {
return AttachDecision::NoAction;
}
NativeObject* holder = nullptr;
PropertyResult prop;
// If this property exists already, don't attach the stub.
if (LookupPropertyPure(cx_, obj, id, &holder, &prop)) {
return AttachDecision::NoAction;
}
JSFunction* fun = &obj->as<JSFunction>();
if (isLength) {
// length was probably deleted from the function.
if (fun->hasResolvedLength()) {
return AttachDecision::NoAction;
}
// Lazy functions don't store the length.
if (!fun->hasBytecode()) {
return AttachDecision::NoAction;
}
} else {
// name was probably deleted from the function.
if (fun->hasResolvedName()) {
return AttachDecision::NoAction;
}
}
maybeEmitIdGuard(id);
writer.guardClass(objId, GuardClassKind::JSFunction);
if (isLength) {
writer.loadFunctionLengthResult(objId);
writer.returnFromIC();
trackAttached("GetProp.FunctionLength");
} else {
writer.loadFunctionNameResult(objId);
writer.returnFromIC();
trackAttached("GetProp.FunctionName");
}
return AttachDecision::Attach;
}
AttachDecision GetPropIRGenerator::tryAttachArgumentsObjectIterator(
HandleObject obj, ObjOperandId objId, HandleId id) {
if (!obj->is<ArgumentsObject>()) {
return AttachDecision::NoAction;
}
if (!id.isWellKnownSymbol(JS::SymbolCode::iterator)) {
return AttachDecision::NoAction;
}
Handle<ArgumentsObject*> args = obj.as<ArgumentsObject>();
if (args->hasOverriddenIterator()) {
return AttachDecision::NoAction;
}
if (cx_->realm() != args->realm()) {
return AttachDecision::NoAction;
}
AssertArgumentsCustomDataProp(args, id);
RootedValue iterator(cx_);
if (!ArgumentsObject::getArgumentsIterator(cx_, &iterator)) {
cx_->recoverFromOutOfMemory();
return AttachDecision::NoAction;
}
MOZ_ASSERT(iterator.isObject());
maybeEmitIdGuard(id);
if (args->is<MappedArgumentsObject>()) {
writer.guardClass(objId, GuardClassKind::MappedArguments);
} else {
MOZ_ASSERT(args->is<UnmappedArgumentsObject>());
writer.guardClass(objId, GuardClassKind::UnmappedArguments);
}
uint32_t flags = ArgumentsObject::ITERATOR_OVERRIDDEN_BIT;