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
#include "jit/CacheIR.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/FloatingPoint.h"
#include "jsapi.h"
#include "jsmath.h"
#include "builtin/DataViewObject.h"
#include "builtin/MapObject.h"
#include "builtin/ModuleObject.h"
#include "builtin/Object.h"
#include "jit/BaselineIC.h"
#include "jit/CacheIRCloner.h"
#include "jit/CacheIRCompiler.h"
#include "jit/CacheIRGenerator.h"
#include "jit/CacheIRSpewer.h"
#include "jit/CacheIRWriter.h"
#include "jit/InlinableNatives.h"
#include "jit/JitContext.h"
#include "jit/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/RegExpFlags.h" // JS::RegExpFlags
#include "js/ScalarType.h" // js::Scalar::Type
#include "js/Wrapper.h"
#include "proxy/DOMProxy.h" // js::GetDOMProxyHandlerFamily
#include "proxy/ScriptedProxyHandler.h"
#include "util/DifferentialTesting.h"
#include "util/Unicode.h"
#include "vm/ArrayBufferObject.h"
#include "vm/BoundFunctionObject.h"
#include "vm/BytecodeUtil.h"
#include "vm/Compartment.h"
#include "vm/Iteration.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/ProxyObject.h"
#include "vm/RegExpObject.h"
#include "vm/SelfHosting.h"
#include "vm/ThrowMsgKind.h" // ThrowCondition
#include "vm/Watchtower.h"
#include "wasm/WasmInstance.h"
#include "jit/BaselineFrame-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/BytecodeUtil-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/JSContext-inl.h"
#include "vm/JSFunction-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/PlainObject-inl.h"
#include "vm/StringObject-inl.h"
#include "wasm/WasmInstance-inl.h"
using namespace js;
using namespace js::jit;
using mozilla::DebugOnly;
using mozilla::Maybe;
using JS::DOMProxyShadowsResult;
using JS::ExpandoAndGeneration;
const char* const js::jit::CacheKindNames[] = {
#define DEFINE_KIND(kind) #kind,
CACHE_IR_KINDS(DEFINE_KIND)
#undef DEFINE_KIND
};
const char* const js::jit::CacheIROpNames[] = {
#define OPNAME(op, ...) #op,
CACHE_IR_OPS(OPNAME)
#undef OPNAME
};
const CacheIROpInfo js::jit::CacheIROpInfos[] = {
#define OPINFO(op, len, transpile, ...) {len, transpile},
CACHE_IR_OPS(OPINFO)
#undef OPINFO
};
const uint32_t js::jit::CacheIROpHealth[] = {
#define OPHEALTH(op, len, transpile, health) health,
CACHE_IR_OPS(OPHEALTH)
#undef OPHEALTH
};
size_t js::jit::NumInputsForCacheKind(CacheKind kind) {
switch (kind) {
case CacheKind::NewArray:
case CacheKind::NewObject:
case CacheKind::GetIntrinsic:
return 0;
case CacheKind::GetProp:
case CacheKind::TypeOf:
case CacheKind::ToPropertyKey:
case CacheKind::GetIterator:
case CacheKind::ToBool:
case CacheKind::UnaryArith:
case CacheKind::GetName:
case CacheKind::BindName:
case CacheKind::Call:
case CacheKind::OptimizeSpreadCall:
case CacheKind::CloseIter:
return 1;
case CacheKind::Compare:
case CacheKind::GetElem:
case CacheKind::GetPropSuper:
case CacheKind::SetProp:
case CacheKind::In:
case CacheKind::HasOwn:
case CacheKind::CheckPrivateField:
case CacheKind::InstanceOf:
case CacheKind::BinaryArith:
return 2;
case CacheKind::GetElemSuper:
case CacheKind::SetElem:
return 3;
}
MOZ_CRASH("Invalid kind");
}
#ifdef DEBUG
void CacheIRWriter::assertSameCompartment(JSObject* obj) {
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)
: writer(cx),
cx_(cx),
script_(script),
pc_(pc),
cacheKind_(cacheKind),
mode_(state.mode()),
isFirstStub_(state.newStubIsFirstStub()) {}
GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script,
jsbytecode* pc, ICState state,
CacheKind cacheKind, HandleValue val,
HandleValue idVal)
: IRGenerator(cx, script, pc, cacheKind, state), val_(val), idVal_(idVal) {}
static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderId,
NativeObject* holder, PropertyInfo prop) {
if (holder->isFixedSlot(prop.slot())) {
writer.loadFixedSlotResult(holderId,
NativeObject::getFixedSlotOffset(prop.slot()));
} else {
size_t dynamicSlotOffset =
holder->dynamicSlotIndex(prop.slot()) * sizeof(Value);
writer.loadDynamicSlotResult(holderId, dynamicSlotOffset);
}
}
// DOM proxies
// -----------
//
// DOM proxies are proxies that are used to implement various DOM objects like
// HTMLDocument and NodeList. DOM proxies may have an expando object - a native
// object that stores extra properties added to the object. The following
// CacheIR instructions are only used with DOM proxies:
//
// * LoadDOMExpandoValue: returns the Value in the proxy's expando slot. This
// returns either an UndefinedValue (no expando), ObjectValue (the expando
// object), or PrivateValue(ExpandoAndGeneration*).
//
// * LoadDOMExpandoValueGuardGeneration: guards the Value in the proxy's expando
// slot is the same PrivateValue(ExpandoAndGeneration*), then guards on its
// generation, then returns expandoAndGeneration->expando. This Value is
// either an UndefinedValue or ObjectValue.
//
// * LoadDOMExpandoValueIgnoreGeneration: assumes the Value in the proxy's
// expando slot is a PrivateValue(ExpandoAndGeneration*), unboxes it, and
// returns the expandoAndGeneration->expando Value.
//
// * GuardDOMExpandoMissingOrGuardShape: takes an expando Value as input, then
// guards it's either UndefinedValue or an object with the expected shape.
enum class ProxyStubType {
None,
DOMExpando,
DOMShadowed,
DOMUnshadowed,
Generic
};
static bool IsCacheableDOMProxy(ProxyObject* obj) {
const BaseProxyHandler* handler = obj->handler();
if (handler->family() != GetDOMProxyHandlerFamily()) {
return false;
}
// Some DOM proxies have dynamic prototypes. We can't really cache those very
// well.
return obj->hasStaticPrototype();
}
static ProxyStubType GetProxyStubType(JSContext* cx, HandleObject obj,
HandleId id) {
if (!obj->is<ProxyObject>()) {
return ProxyStubType::None;
}
auto proxy = obj.as<ProxyObject>();
if (!IsCacheableDOMProxy(proxy)) {
return ProxyStubType::Generic;
}
DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, proxy, id);
if (shadows == DOMProxyShadowsResult::ShadowCheckFailed) {
cx->clearPendingException();
return ProxyStubType::None;
}
if (DOMProxyIsShadowing(shadows)) {
if (shadows == DOMProxyShadowsResult::ShadowsViaDirectExpando ||
shadows == DOMProxyShadowsResult::ShadowsViaIndirectExpando) {
return ProxyStubType::DOMExpando;
}
return ProxyStubType::DOMShadowed;
}
MOZ_ASSERT(shadows == DOMProxyShadowsResult::DoesntShadow ||
shadows == DOMProxyShadowsResult::DoesntShadowUnique);
return ProxyStubType::DOMUnshadowed;
}
static bool ValueToNameOrSymbolId(JSContext* cx, HandleValue idVal,
MutableHandleId id, bool* nameOrSymbol) {
*nameOrSymbol = false;
if (!idVal.isString() && !idVal.isSymbol() && !idVal.isUndefined() &&
!idVal.isNull()) {
return true;
}
if (!PrimitiveValueToId<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);
if (nameOrSymbol) {
TRY_ATTACH(tryAttachObjectLength(obj, objId, id));
TRY_ATTACH(tryAttachTypedArray(obj, objId, id));
TRY_ATTACH(tryAttachDataView(obj, objId, id));
TRY_ATTACH(tryAttachArrayBufferMaybeShared(obj, objId, id));
TRY_ATTACH(tryAttachRegExp(obj, objId, id));
TRY_ATTACH(tryAttachMap(obj, objId, id));
TRY_ATTACH(tryAttachSet(obj, objId, id));
TRY_ATTACH(tryAttachNative(obj, objId, id, receiverId));
TRY_ATTACH(tryAttachModuleNamespace(obj, objId, id));
TRY_ATTACH(tryAttachWindowProxy(obj, objId, id));
TRY_ATTACH(tryAttachCrossCompartmentWrapper(obj, objId, id));
TRY_ATTACH(
tryAttachXrayCrossCompartmentWrapper(obj, objId, id, receiverId));
TRY_ATTACH(tryAttachFunction(obj, objId, id));
TRY_ATTACH(tryAttachArgumentsObjectIterator(obj, objId, id));
TRY_ATTACH(tryAttachArgumentsObjectCallee(obj, objId, id));
TRY_ATTACH(tryAttachProxy(obj, objId, id, receiverId));
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
cacheKind_ == CacheKind::GetElemSuper);
TRY_ATTACH(tryAttachProxyElement(obj, objId));
TRY_ATTACH(tryAttachTypedArrayElement(obj, objId));
uint32_t index;
Int32OperandId indexId;
if (maybeGuardInt32Index(idVal_, getElemKeyValueId(), &index, &indexId)) {
TRY_ATTACH(tryAttachDenseElement(obj, objId, index, indexId));
TRY_ATTACH(tryAttachDenseElementHole(obj, objId, index, indexId));
TRY_ATTACH(tryAttachSparseElement(obj, objId, index, indexId));
TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, index, indexId));
TRY_ATTACH(tryAttachArgumentsObjectArgHole(obj, objId, index, indexId));
TRY_ATTACH(
tryAttachGenericElement(obj, objId, index, indexId, receiverId));
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
if (nameOrSymbol) {
TRY_ATTACH(tryAttachPrimitive(valId, id));
TRY_ATTACH(tryAttachStringLength(valId, id));
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
if (idVal_.isInt32()) {
ValOperandId indexId = getElemKeyValueId();
TRY_ATTACH(tryAttachStringChar(valId, indexId));
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
#ifdef DEBUG
// Any property lookups performed when trying to attach ICs must be pure, i.e.
// must use LookupPropertyPure() or similar functions. Pure lookups are
// guaranteed to never modify the prototype chain. This ensures that the holder
// object can always be found on the prototype chain.
static bool IsCacheableProtoChain(NativeObject* obj, NativeObject* holder) {
while (obj != holder) {
JSObject* proto = obj->staticPrototype();
if (!proto || !proto->is<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) {
MOZ_ASSERT(IsCacheableProtoChain(obj, holder));
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;
}
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());
}
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");
}
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>();
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
return window == &script->global();
}
// Guards objId is a WindowProxy for windowObj. Returns the window's operand id.
static ObjOperandId GuardAndLoadWindowProxyWindow(CacheIRWriter& writer,
ObjOperandId objId,
GlobalObject* windowObj) {
writer.guardClass(objId, GuardClassKind::WindowProxy);
ObjOperandId windowObjId = writer.loadWrapperTarget(objId);
writer.guardSpecificObject(windowObjId, windowObj);
return windowObjId;
}
// Whether a getter/setter on the global should have the WindowProxy as |this|
// value instead of the Window (the global object). This always returns true for
// scripted functions.
static bool GetterNeedsWindowProxyThis(NativeObject* holder,
PropertyInfo prop) {
JSFunction* callee = &holder->getGetter(prop)->as<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);
// 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);
// 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);