Source code

Revision control

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/DebugOnly.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Unused.h"
#include "builtin/DataViewObject.h"
#include "builtin/MapObject.h"
#include "jit/BaselineCacheIRCompiler.h"
#include "jit/BaselineIC.h"
#include "jit/CacheIRSpewer.h"
#include "jit/InlinableNatives.h"
#include "jit/Ion.h" // IsIonEnabled
#include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowIfWindowProxy
#include "js/ScalarType.h" // js::Scalar::Type
#include "js/Wrapper.h"
#include "util/Unicode.h"
#include "vm/ArrayBufferObject.h"
#include "vm/BytecodeUtil.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/SelfHosting.h"
#include "vm/ThrowMsgKind.h" // ThrowCondition
#include "jit/MacroAssembler-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/JSContext-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/StringObject-inl.h"
#include "vm/TypeInference-inl.h"
using namespace js;
using namespace js::jit;
using mozilla::DebugOnly;
using mozilla::Maybe;
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 uint32_t js::jit::CacheIROpArgLengths[] = {
#define ARGLENGTH(op, len, ...) len,
CACHE_IR_OPS(ARGLENGTH)
#undef ARGLENGTH
};
const uint32_t js::jit::CacheIROpHealth[] = {
#define OPHEALTH(op, len, health) health,
CACHE_IR_OPS(OPHEALTH)
#undef OPHEALTH
};
#ifdef DEBUG
size_t js::jit::NumInputsForCacheKind(CacheKind kind) {
switch (kind) {
case CacheKind::NewObject:
case CacheKind::GetIntrinsic:
return 0;
case CacheKind::GetProp:
case CacheKind::TypeOf:
case CacheKind::ToPropertyKey:
case CacheKind::GetIterator:
case CacheKind::ToBool:
case CacheKind::UnaryArith:
case CacheKind::GetName:
case CacheKind::BindName:
case CacheKind::Call:
return 1;
case CacheKind::Compare:
case CacheKind::GetElem:
case CacheKind::GetPropSuper:
case CacheKind::SetProp:
case CacheKind::In:
case CacheKind::HasOwn:
case CacheKind::CheckPrivateField:
case CacheKind::InstanceOf:
case CacheKind::BinaryArith:
return 2;
case CacheKind::GetElemSuper:
case CacheKind::SetElem:
return 3;
}
MOZ_CRASH("Invalid kind");
}
#endif
void CacheIRWriter::assertSameCompartment(JSObject* obj) {
cx_->debugOnlyCheck(obj);
}
StubField CacheIRWriter::readStubFieldForIon(uint32_t offset,
StubField::Type type) const {
size_t index = 0;
size_t currentOffset = 0;
// If we've seen an offset earlier than this before, we know we can start the
// search there at least, otherwise, we start the search from the beginning.
if (lastOffset_ < offset) {
currentOffset = lastOffset_;
index = lastIndex_;
}
while (currentOffset != offset) {
currentOffset += StubField::sizeInBytes(stubFields_[index].type());
index++;
MOZ_ASSERT(index < stubFields_.length());
}
MOZ_ASSERT(stubFields_[index].type() == type);
lastOffset_ = currentOffset;
lastIndex_ = index;
return stubFields_[index];
}
CacheIRCloner::CacheIRCloner(ICStub* stub)
: stubInfo_(stub->cacheIRStubInfo()), stubData_(stub->cacheIRStubData()) {}
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));
}
ObjectGroup* CacheIRCloner::getGroupField(uint32_t stubOffset) {
return reinterpret_cast<ObjectGroup*>(readStubWord(stubOffset));
}
JSObject* CacheIRCloner::getObjectField(uint32_t stubOffset) {
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));
}
PropertyName* CacheIRCloner::getPropertyNameField(uint32_t stubOffset) {
return reinterpret_cast<PropertyName*>(readStubWord(stubOffset));
}
JS::Symbol* CacheIRCloner::getSymbolField(uint32_t stubOffset) {
return reinterpret_cast<JS::Symbol*>(readStubWord(stubOffset));
}
uintptr_t CacheIRCloner::getRawWordField(uint32_t stubOffset) {
return reinterpret_cast<uintptr_t>(readStubWord(stubOffset));
}
const void* CacheIRCloner::getRawPointerField(uint32_t stubOffset) {
return reinterpret_cast<const void*>(readStubWord(stubOffset));
}
uint64_t CacheIRCloner::getDOMExpandoGenerationField(uint32_t stubOffset) {
return static_cast<uint64_t>(readStubInt64(stubOffset));
}
jsid CacheIRCloner::getIdField(uint32_t stubOffset) {
return jsid::fromRawBits(readStubWord(stubOffset));
}
const Value CacheIRCloner::getValueField(uint32_t stubOffset) {
return Value::fromRawBits(uint64_t(readStubInt64(stubOffset)));
}
IRGenerator::IRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc,
CacheKind cacheKind, ICState::Mode mode)
: writer(cx),
cx_(cx),
script_(script),
pc_(pc),
cacheKind_(cacheKind),
mode_(mode) {}
GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, HandleScript script,
jsbytecode* pc, ICState::Mode mode,
CacheKind cacheKind, HandleValue val,
HandleValue idVal, HandleValue receiver,
GetPropertyResultFlags resultFlags)
: IRGenerator(cx, script, pc, cacheKind, mode),
val_(val),
idVal_(idVal),
receiver_(receiver),
resultFlags_(resultFlags),
preliminaryObjectAction_(PreliminaryObjectAction::None) {}
static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderOp,
NativeObject* holder, Shape* shape) {
if (holder->isFixedSlot(shape->slot())) {
writer.loadFixedSlotResult(holderOp,
NativeObject::getFixedSlotOffset(shape->slot()));
} else {
size_t dynamicSlotOffset =
holder->dynamicSlotIndex(shape->slot()) * sizeof(Value);
writer.loadDynamicSlotResult(holderOp, dynamicSlotOffset);
}
}
// DOM proxies
// -----------
//
// DOM proxies are proxies that are used to implement various DOM objects like
// HTMLDocument and NodeList. DOM proxies may have an expando object - a native
// object that stores extra properties added to the object. The following
// CacheIR instructions are only used with DOM proxies:
//
// * LoadDOMExpandoValue: returns the Value in the proxy's expando slot. This
// returns either an UndefinedValue (no expando), ObjectValue (the expando
// object), or PrivateValue(ExpandoAndGeneration*).
//
// * LoadDOMExpandoValueGuardGeneration: guards the Value in the proxy's expando
// slot is the same PrivateValue(ExpandoAndGeneration*), then guards on its
// generation, then returns expandoAndGeneration->expando. This Value is
// either an UndefinedValue or ObjectValue.
//
// * LoadDOMExpandoValueIgnoreGeneration: assumes the Value in the proxy's
// expando slot is a PrivateValue(ExpandoAndGeneration*), unboxes it, and
// returns the expandoAndGeneration->expando Value.
//
// * GuardDOMExpandoMissingOrGuardShape: takes an expando Value as input, then
// guards it's either UndefinedValue or an object with the expected shape.
enum class ProxyStubType {
None,
DOMExpando,
DOMShadowed,
DOMUnshadowed,
Generic
};
static ProxyStubType GetProxyStubType(JSContext* cx, HandleObject obj,
HandleId id) {
if (!obj->is<ProxyObject>()) {
return ProxyStubType::None;
}
if (!IsCacheableDOMProxy(obj)) {
return ProxyStubType::Generic;
}
DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id);
if (shadows == ShadowCheckFailed) {
cx->clearPendingException();
return ProxyStubType::None;
}
if (DOMProxyIsShadowing(shadows)) {
if (shadows == ShadowsViaDirectExpando ||
shadows == ShadowsViaIndirectExpando) {
return ProxyStubType::DOMExpando;
}
return ProxyStubType::DOMShadowed;
}
MOZ_ASSERT(shadows == DoesntShadow || shadows == DoesntShadowUnique);
return ProxyStubType::DOMUnshadowed;
}
static bool ValueToNameOrSymbolId(JSContext* cx, HandleValue idval,
MutableHandleId id, bool* nameOrSymbol) {
*nameOrSymbol = false;
if (!idval.isString() && !idval.isSymbol()) {
return true;
}
if (!PrimitiveValueToId<CanGC>(cx, idval, id)) {
return false;
}
if (!JSID_IS_STRING(id) && !JSID_IS_SYMBOL(id)) {
id.set(JSID_VOID);
return true;
}
uint32_t dummy;
if (JSID_IS_STRING(id) && JSID_TO_ATOM(id)->isIndex(&dummy)) {
id.set(JSID_VOID);
return true;
}
*nameOrSymbol = true;
return true;
}
AttachDecision GetPropIRGenerator::tryAttachStub() {
// Idempotent ICs should call tryAttachIdempotentStub instead.
MOZ_ASSERT(!idempotent());
AutoAssertNoPendingException aanpe(cx_);
// Non-object receivers are a degenerate case, so don't try to attach
// stubs. The stubs we do emit will still perform runtime checks and
// fallback as needed.
if (isSuper() && !receiver_.isObject()) {
return AttachDecision::NoAction;
}
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;
}
if (val_.isObject()) {
RootedObject obj(cx_, &val_.toObject());
ObjOperandId objId = writer.guardToObject(valId);
if (nameOrSymbol) {
TRY_ATTACH(tryAttachObjectLength(obj, objId, id));
TRY_ATTACH(tryAttachTypedArrayLength(obj, objId, id));
TRY_ATTACH(tryAttachNative(obj, objId, id));
TRY_ATTACH(tryAttachTypedObject(obj, objId, id));
TRY_ATTACH(tryAttachModuleNamespace(obj, objId, id));
TRY_ATTACH(tryAttachWindowProxy(obj, objId, id));
TRY_ATTACH(tryAttachCrossCompartmentWrapper(obj, objId, id));
TRY_ATTACH(tryAttachXrayCrossCompartmentWrapper(obj, objId, id));
TRY_ATTACH(tryAttachFunction(obj, objId, id));
TRY_ATTACH(tryAttachProxy(obj, objId, id));
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(tryAttachTypedElement(obj, objId, index, indexId));
TRY_ATTACH(tryAttachDenseElement(obj, objId, index, indexId));
TRY_ATTACH(tryAttachDenseElementHole(obj, objId, index, indexId));
TRY_ATTACH(tryAttachSparseElement(obj, objId, index, indexId));
TRY_ATTACH(tryAttachArgumentsObjectArg(obj, objId, indexId));
TRY_ATTACH(tryAttachGenericElement(obj, objId, index, indexId));
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
TRY_ATTACH(tryAttachTypedArrayNonInt32Index(obj, objId));
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
if (nameOrSymbol) {
TRY_ATTACH(tryAttachPrimitive(valId, id));
TRY_ATTACH(tryAttachStringLength(valId, id));
TRY_ATTACH(tryAttachMagicArgumentsName(valId, id));
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
if (idVal_.isInt32()) {
ValOperandId indexId = getElemKeyValueId();
TRY_ATTACH(tryAttachStringChar(valId, indexId));
TRY_ATTACH(tryAttachMagicArgument(valId, indexId));
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
trackAttached(IRGenerator::NotAttached);
return AttachDecision::NoAction;
}
AttachDecision GetPropIRGenerator::tryAttachIdempotentStub() {
// For idempotent ICs, only attach stubs which we can be sure have no side
// effects and produce a result which the MIR in the calling code is able
// to handle, since we do not have a pc to explicitly monitor the result.
MOZ_ASSERT(idempotent());
RootedObject obj(cx_, &val_.toObject());
RootedId id(cx_, NameToId(idVal_.toString()->asAtom().asPropertyName()));
ValOperandId valId(writer.setInputOperandId(0));
ObjOperandId objId = writer.guardToObject(valId);
TRY_ATTACH(tryAttachNative(obj, objId, id));
// Object lengths are supported only if int32 results are allowed.
TRY_ATTACH(tryAttachObjectLength(obj, objId, id));
// Also support native data properties on DOMProxy prototypes.
if (GetProxyStubType(cx_, obj, id) == ProxyStubType::DOMUnshadowed) {
return tryAttachDOMProxyUnshadowed(obj, objId, id);
}
return AttachDecision::NoAction;
}
static bool IsCacheableProtoChain(JSObject* obj, JSObject* holder) {
while (obj != holder) {
/*
* We cannot assume that we find the holder object on the prototype
* chain and must check for null proto. The prototype chain can be
* altered during the lookupProperty call.
*/
JSObject* proto = obj->staticPrototype();
if (!proto || !proto->isNative()) {
return false;
}
obj = proto;
}
return true;
}
static bool IsCacheableGetPropReadSlot(JSObject* obj, JSObject* holder,
PropertyResult prop) {
if (!prop || !IsCacheableProtoChain(obj, holder)) {
return false;
}
Shape* shape = prop.shape();
if (!shape->isDataProperty()) {
return false;
}
return true;
}
enum NativeGetPropCacheability {
CanAttachNone,
CanAttachReadSlot,
CanAttachNativeGetter,
CanAttachScriptedGetter,
};
static NativeGetPropCacheability IsCacheableGetPropCall(JSObject* obj,
JSObject* holder,
Shape* shape) {
if (!shape || !IsCacheableProtoChain(obj, holder)) {
return CanAttachNone;
}
if (!shape->hasGetterValue() || !shape->getterValue().isObject()) {
return CanAttachNone;
}
if (!shape->getterValue().toObject().is<JSFunction>()) {
return CanAttachNone;
}
JSFunction& getter = shape->getterValue().toObject().as<JSFunction>();
if (getter.isClassConstructor()) {
return CanAttachNone;
}
// For getters that need the WindowProxy (instead of the Window) as this
// object, don't cache if obj is the Window, since our cache will pass that
// instead of the WindowProxy.
if (IsWindow(obj)) {
// Check for a getter that has jitinfo and whose jitinfo says it's
// OK with both inner and outer objects.
if (!getter.hasJitInfo() || getter.jitInfo()->needsOuterizedThisObject()) {
return CanAttachNone;
}
}
// Scripted functions and natives with JIT entry can use the scripted path.
if (getter.hasJitEntry()) {
return CanAttachScriptedGetter;
}
MOZ_ASSERT(getter.isNativeWithoutJitEntry());
return CanAttachNativeGetter;
}
static bool CheckHasNoSuchOwnProperty(JSContext* cx, JSObject* obj, jsid id) {
if (obj->isNative()) {
// Don't handle proto chains with resolve hooks.
if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj)) {
return false;
}
if (obj->as<NativeObject>().contains(cx, id)) {
return false;
}
} else if (obj->is<TypedObject>()) {
if (obj->as<TypedObject>().typeDescr().hasProperty(cx->names(), id)) {
return false;
}
} else {
return false;
}
return true;
}
static bool CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, jsid id) {
JSObject* curObj = obj;
do {
if (!CheckHasNoSuchOwnProperty(cx, curObj, id)) {
return false;
}
if (!curObj->isNative()) {
// Non-native objects are only handled as the original receiver.
if (curObj != obj) {
return false;
}
}
curObj = curObj->staticPrototype();
} while (curObj);
return true;
}
// Return whether obj is in some PreliminaryObjectArray and has a structure
// that might change in the future.
static bool IsPreliminaryObject(JSObject* obj) {
if (obj->isSingleton()) {
return false;
}
AutoSweepObjectGroup sweep(obj->group());
TypeNewScript* newScript = obj->group()->newScript(sweep);
if (newScript && !newScript->analyzed()) {
return true;
}
if (obj->group()->maybePreliminaryObjects(sweep)) {
return true;
}
return false;
}
static bool IsCacheableNoProperty(JSContext* cx, JSObject* obj,
JSObject* holder, Shape* shape, jsid id,
jsbytecode* pc,
GetPropertyResultFlags resultFlags) {
if (shape) {
return false;
}
MOZ_ASSERT(!holder);
// Idempotent ICs may only attach missing-property stubs if undefined
// results are explicitly allowed, since no monitoring is done of the
// cache result.
if (!pc && !(resultFlags & GetPropertyResultFlags::AllowUndefined)) {
return false;
}
// If we're doing a name lookup, we have to throw a ReferenceError.
// Note that Ion does not generate idempotent caches for JSOp::GetBoundName.
if (pc && JSOp(*pc) == JSOp::GetBoundName) {
return false;
}
return CheckHasNoSuchProperty(cx, obj, id);
}
static NativeGetPropCacheability CanAttachNativeGetProp(
JSContext* cx, HandleObject obj, HandleId id,
MutableHandleNativeObject holder, MutableHandleShape shape, jsbytecode* pc,
GetPropertyResultFlags resultFlags) {
MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
// The lookup needs to be universally pure, otherwise we risk calling hooks
// out of turn. We don't mind doing this even when purity isn't required,
// because we only miss out on shape hashification, which is only a temporary
// perf cost. The limits were arbitrarily set, anyways.
JSObject* baseHolder = nullptr;
PropertyResult prop;
if (!LookupPropertyPure(cx, obj, id, &baseHolder, &prop)) {
return CanAttachNone;
}
MOZ_ASSERT(!holder);
if (baseHolder) {
if (!baseHolder->isNative()) {
return CanAttachNone;
}
holder.set(&baseHolder->as<NativeObject>());
}
shape.set(prop.maybeShape());
if (IsCacheableGetPropReadSlot(obj, holder, prop)) {
return CanAttachReadSlot;
}
if (IsCacheableNoProperty(cx, obj, holder, shape, id, pc, resultFlags)) {
return CanAttachReadSlot;
}
// Idempotent ICs cannot call getters, see tryAttachIdempotentStub.
if (pc && (resultFlags & GetPropertyResultFlags::Monitored)) {
return IsCacheableGetPropCall(obj, holder, shape);
}
return CanAttachNone;
}
static void GuardGroupProto(CacheIRWriter& writer, JSObject* obj,
ObjOperandId objId) {
// Uses the group to determine if the prototype is unchanged. If the
// group's prototype is mutable, we must check the actual prototype,
// otherwise checking the group is sufficient. This can be used if object
// is not ShapedObject or if Shape has UNCACHEABLE_PROTO flag set.
ObjectGroup* group = obj->groupRaw();
if (group->hasUncacheableProto()) {
if (JSObject* proto = obj->staticPrototype()) {
writer.guardProto(objId, proto);
} else {
writer.guardNullProto(objId);
}
} else {
writer.guardGroupForProto(objId, group);
}
}
// Guard that a given object has same class and same OwnProperties (excluding
// dense elements and dynamic properties).
static void TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj,
ObjOperandId objId) {
if (obj->is<TypedObject>()) {
writer.guardGroupForLayout(objId, obj->group());
} else if (obj->is<ProxyObject>()) {
writer.guardShapeForClass(objId, obj->as<ProxyObject>().shape());
} else {
MOZ_ASSERT(obj->is<NativeObject>());
writer.guardShapeForOwnProperties(objId,
obj->as<NativeObject>().lastProperty());
}
}
// Similar to |TestMatchingReceiver|, but specialized for NativeObject.
static void TestMatchingNativeReceiver(CacheIRWriter& writer, NativeObject* obj,
ObjOperandId objId) {
writer.guardShapeForOwnProperties(objId, obj->lastProperty());
}
// Similar to |TestMatchingReceiver|, but specialized for ProxyObject.
static void TestMatchingProxyReceiver(CacheIRWriter& writer, ProxyObject* obj,
ObjOperandId objId) {
writer.guardShapeForClass(objId, obj->shape());
}
// Adds additional guards if TestMatchingReceiver* does not also imply the
// prototype.
static void GeneratePrototypeGuardsForReceiver(CacheIRWriter& writer,
JSObject* obj,
ObjOperandId objId) {
// If receiver was marked UNCACHEABLE_PROTO, the previous shape guard
// doesn't ensure the prototype is unchanged. In this case we must use the
// group to check the prototype.
if (obj->hasUncacheableProto()) {
MOZ_ASSERT(obj->is<NativeObject>());
GuardGroupProto(writer, obj, objId);
}
#ifdef DEBUG
// The following cases already guaranteed the prototype is unchanged.
if (obj->is<TypedObject>()) {
MOZ_ASSERT(!obj->group()->hasUncacheableProto());
} else if (obj->is<ProxyObject>()) {
MOZ_ASSERT(!obj->hasUncacheableProto());
}
#endif // DEBUG
}
static bool ProtoChainSupportsTeleporting(JSObject* obj, JSObject* holder) {
// Any non-delegate should already have been handled since its checks are
// always required.
MOZ_ASSERT(obj->isDelegate());
// Prototype chain must have cacheable prototypes to ensure the cached
// holder is the current holder.
for (JSObject* tmp = obj; tmp != holder; tmp = tmp->staticPrototype()) {
if (tmp->hasUncacheableProto()) {
return false;
}
}
// The holder itself only gets reshaped by teleportation if it is not
// marked UNCACHEABLE_PROTO. See: ReshapeForProtoMutation.
return !holder->hasUncacheableProto();
}
static void GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj,
JSObject* holder, ObjOperandId objId) {
// Assuming target property is on |holder|, generate appropriate guards to
// ensure |holder| is still on the prototype chain of |obj| and we haven't
// introduced any shadowing definitions.
//
// For each item in the proto chain before holder, we must ensure that
// [[GetPrototypeOf]] still has the expected result, and that
// [[GetOwnProperty]] has no definition of the target property.
//
//
// [SMDOC] Shape Teleporting Optimization
// ------------------------------
//
// Starting with the assumption (and guideline to developers) that mutating
// prototypes is an uncommon and fair-to-penalize operation we move cost
// from the access side to the mutation side.
//
// Consider the following proto chain, with B defining a property 'x':
//
// D -> C -> B{x: 3} -> A -> null
//
// When accessing |D.x| we refer to D as the "receiver", and B as the
// "holder". To optimize this access we need to ensure that neither D nor C
// has since defined a shadowing property 'x'. Since C is a prototype that
// we assume is rarely mutated we would like to avoid checking each time if
// new properties are added. To do this we require that everytime C is
// mutated that in addition to generating a new shape for itself, it will
// walk the proto chain and generate new shapes for those objects on the
// chain (B and A). As a result, checking the shape of D and B is
// sufficient. Note that we do not care if the shape or properties of A
// change since the lookup of 'x' will stop at B.
//
// The second condition we must verify is that the prototype chain was not
// mutated. The same mechanism as above is used. When the prototype link is
// changed, we generate a new shape for the object. If the object whose
// link we are mutating is itself a prototype, we regenerate shapes down
// the chain. This means the same two shape checks as above are sufficient.
//
// An additional wrinkle is the UNCACHEABLE_PROTO shape flag. This
// indicates that the shape no longer implies any specific prototype. As
// well, the shape will not be updated by the teleporting optimization.
// If any shape from receiver to holder (inclusive) is UNCACHEABLE_PROTO,
// we don't apply the optimization.
//
// See:
// - ReshapeForProtoMutation
// - ReshapeForShadowedProp
MOZ_ASSERT(holder);
MOZ_ASSERT(obj != holder);
// Only DELEGATE objects participate in teleporting so peel off the first
// object in the chain if needed and handle it directly.
JSObject* pobj = obj;
if (!obj->isDelegate()) {
// TestMatchingReceiver does not always ensure the prototype is
// unchanged, so generate extra guards as needed.
GeneratePrototypeGuardsForReceiver(writer, obj, objId);
pobj = obj->staticPrototype();
}
MOZ_ASSERT(pobj->isDelegate());
// If teleporting is supported for this prototype chain, we are done.
if (ProtoChainSupportsTeleporting(pobj, holder)) {
return;
}
// If already at the holder, no further proto checks are needed.
if (pobj == holder) {
return;
}
// NOTE: We could be clever and look for a middle prototype to shape check
// and elide some (but not all) of the group checks. Unless we have
// real-world examples, let's avoid the complexity.
// Synchronize pobj and protoId.
MOZ_ASSERT(pobj == obj || pobj == obj->staticPrototype());
ObjOperandId protoId = (pobj == obj) ? objId : writer.loadProto(objId);
// Guard prototype links from |pobj| to |holder|.
while (pobj != holder) {
pobj = pobj->staticPrototype();
// The object's proto could be nullptr so we must use GuardProto before
// LoadProto (LoadProto asserts the proto is non-null).
writer.guardProto(protoId, pobj);
protoId = writer.loadProto(protoId);
}
}
static void GeneratePrototypeHoleGuards(CacheIRWriter& writer, JSObject* obj,
ObjOperandId objId,
bool alwaysGuardFirstProto) {
if (alwaysGuardFirstProto || obj->hasUncacheableProto()) {
GuardGroupProto(writer, obj, objId);
}
JSObject* pobj = obj->staticPrototype();
while (pobj) {
ObjOperandId protoId = writer.loadObject(pobj);
// If shape doesn't imply proto, additional guards are needed.
if (pobj->hasUncacheableProto()) {
GuardGroupProto(writer, pobj, protoId);
}
// Make sure the shape matches, to avoid non-dense elements or anything
// else that is being checked by CanAttachDenseElementHole.
writer.guardShape(protoId, pobj->as<NativeObject>().lastProperty());
// Also make sure there are no dense elements.
writer.guardNoDenseElements(protoId);
pobj = pobj->staticPrototype();
}
}
// Similar to |TestMatchingReceiver|, but for the holder object (when it
// differs from the receiver). The holder may also be the expando of the
// receiver if it exists.
static void TestMatchingHolder(CacheIRWriter& writer, JSObject* obj,
ObjOperandId objId) {
// The GeneratePrototypeGuards + TestMatchingHolder checks only support
// prototype chains composed of NativeObject (excluding the receiver
// itself).
MOZ_ASSERT(obj->is<NativeObject>());
writer.guardShapeForOwnProperties(objId,
obj->as<NativeObject>().lastProperty());
}
static bool UncacheableProtoOnChain(JSObject* obj) {
while (true) {
if (obj->hasUncacheableProto()) {
return true;
}
obj = obj->staticPrototype();
if (!obj) {
return false;
}
}
}
static void ShapeGuardProtoChain(CacheIRWriter& writer, JSObject* obj,
ObjOperandId objId) {
while (true) {
JSObject* proto = obj->staticPrototype();
// Guard on the proto if the shape does not imply the proto.
if (obj->hasUncacheableProto()) {
if (proto) {
writer.guardProto(objId, proto);
} else {
writer.guardNullProto(objId);
}
}
if (!proto) {
return;
}
obj = proto;
objId = writer.loadProto(objId);
writer.guardShape(objId, obj->as<NativeObject>().shape());
}
}
// For cross compartment guards we shape-guard the prototype chain to avoid
// referencing the holder object.
//
// This peels off the first layer because it's guarded against obj == holder.
static void ShapeGuardProtoChainForCrossCompartmentHolder(
CacheIRWriter& writer, JSObject* obj, ObjOperandId objId, JSObject* holder,
Maybe<ObjOperandId>* holderId) {
MOZ_ASSERT(obj != holder);
MOZ_ASSERT(holder);
while (true) {
obj = obj->staticPrototype();
MOZ_ASSERT(obj);
objId = writer.loadProto(objId);
if (obj == holder) {
TestMatchingHolder(writer, obj, objId);
holderId->emplace(objId);
return;
} else {
writer.guardShapeForOwnProperties(objId, obj->as<NativeObject>().shape());
}
}
}
enum class SlotReadType { Normal, CrossCompartment };
template <SlotReadType MaybeCrossCompartment = SlotReadType::Normal>
static void EmitReadSlotGuard(CacheIRWriter& writer, JSObject* obj,
JSObject* holder, ObjOperandId objId,
Maybe<ObjOperandId>* holderId) {
TestMatchingReceiver(writer, obj, objId);
if (obj != holder) {
if (holder) {
if (MaybeCrossCompartment == SlotReadType::CrossCompartment) {
// Guard proto chain integrity.
// We use a variant of guards that avoid baking in any cross-compartment
// object pointers.
ShapeGuardProtoChainForCrossCompartmentHolder(writer, obj, objId,
holder, holderId);
} else {
// Guard proto chain integrity.
GeneratePrototypeGuards(writer, obj, holder, objId);
// Guard on the holder's shape.
holderId->emplace(writer.loadObject(holder));
TestMatchingHolder(writer, holder, holderId->ref());
}
} else {
// The property does not exist. Guard on everything in the prototype
// chain. This is guaranteed to see only Native objects because of
// CanAttachNativeGetProp().
ShapeGuardProtoChain(writer, obj, objId);
}
} else {
holderId->emplace(objId);
}
}
template <SlotReadType MaybeCrossCompartment = SlotReadType::Normal>
static void EmitReadSlotResult(CacheIRWriter& writer, JSObject* obj,
JSObject* holder, Shape* shape,
ObjOperandId objId) {
Maybe<ObjOperandId> holderId;
EmitReadSlotGuard<MaybeCrossCompartment>(writer, obj, holder, objId,
&holderId);
// Slot access.
if (holder) {
MOZ_ASSERT(holderId->valid());
EmitLoadSlotResult(writer, *holderId, &holder->as<NativeObject>(), shape);
} else {
MOZ_ASSERT(holderId.isNothing());
writer.loadUndefinedResult();
}
}
static void EmitReadSlotReturn(CacheIRWriter& writer, JSObject*,
JSObject* holder, Shape* shape,
bool wrapResult = false) {
// Slot access.
if (holder) {
MOZ_ASSERT(shape);
if (wrapResult) {
writer.wrapResult();
}
writer.typeMonitorResult();
} else {
// Normally for this op, the result would have to be monitored by TI.
// However, since this stub ALWAYS returns UndefinedValue(), and we can be
// sure that undefined is already registered with the type-set, this can be
// avoided.
writer.returnFromIC();
}
}
static void EmitCallGetterResultNoGuards(JSContext* cx, CacheIRWriter& writer,
JSObject* obj, JSObject* holder,
Shape* shape,
ObjOperandId receiverId) {
switch (IsCacheableGetPropCall(obj, holder, shape)) {
case CanAttachNativeGetter: {
JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
MOZ_ASSERT(target->isNativeWithoutJitEntry());
writer.callNativeGetterResult(receiverId, target);
writer.typeMonitorResult();
break;
}
case CanAttachScriptedGetter: {
JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
MOZ_ASSERT(target->hasJitEntry());
bool sameRealm = cx->realm() == target->realm();
writer.callScriptedGetterResult(receiverId, target, sameRealm);
writer.typeMonitorResult();
break;
}
default:
// CanAttachNativeGetProp guarantees that the getter is either a native or
// a scripted function.
MOZ_ASSERT_UNREACHABLE("Can't attach getter");
break;
}
}
static void EmitCallGetterResultGuards(CacheIRWriter& writer, JSObject* obj,
JSObject* holder, Shape* shape,
ObjOperandId objId, ICState::Mode mode) {
// Use the megamorphic guard if we're in megamorphic mode, except if |obj|
// is a Window as GuardHasGetterSetter doesn't support this yet (Window may
// require outerizing).
if (mode == ICState::Mode::Specialized || IsWindow(obj)) {
TestMatchingReceiver(writer, obj, objId);
if (obj != holder) {
GeneratePrototypeGuards(writer, obj, holder, objId);
// Guard on the holder's shape.
ObjOperandId holderId = writer.loadObject(holder);
TestMatchingHolder(writer, holder, holderId);
}
} else {
writer.guardHasGetterSetter(objId, shape);
}
}
static void EmitCallGetterResult(JSContext* cx, CacheIRWriter& writer,
JSObject* obj, JSObject* holder, Shape* shape,
ObjOperandId objId, ObjOperandId receiverId,
ICState::Mode mode) {
EmitCallGetterResultGuards(writer, obj, holder, shape, objId, mode);
EmitCallGetterResultNoGuards(cx, writer, obj, holder, shape, receiverId);
}
static void EmitCallGetterResult(JSContext* cx, CacheIRWriter& writer,
JSObject* obj, JSObject* holder, Shape* shape,
ObjOperandId objId, ICState::Mode mode) {
EmitCallGetterResult(cx, writer, obj, holder, shape, objId, objId, mode);
}
static void EmitCallGetterByValueResult(JSContext* cx, CacheIRWriter& writer,
JSObject* obj, JSObject* holder,
Shape* shape, ObjOperandId objId,
ValOperandId receiverId,
ICState::Mode mode) {
EmitCallGetterResultGuards(writer, obj, holder, shape, objId, mode);
switch (IsCacheableGetPropCall(obj, holder, shape)) {
case CanAttachNativeGetter: {
JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
MOZ_ASSERT(target->isNativeWithoutJitEntry());
writer.callNativeGetterByValueResult(receiverId, target);
writer.typeMonitorResult();
break;
}
case CanAttachScriptedGetter: {
JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
MOZ_ASSERT(target->hasJitEntry());
bool sameRealm = cx->realm() == target->realm();
writer.callScriptedGetterByValueResult(receiverId, target, sameRealm);
writer.typeMonitorResult();
break;
}
default:
// CanAttachNativeGetProp guarantees that the getter is either a native or
// a scripted function.
MOZ_ASSERT_UNREACHABLE("Can't attach getter");
break;
}
}
void GetPropIRGenerator::attachMegamorphicNativeSlot(ObjOperandId objId,
jsid id,
bool handleMissing) {
MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
// The stub handles the missing-properties case only if we're seeing one
// now, to make sure Ion ICs correctly monitor the undefined type.
if (cacheKind_ == CacheKind::GetProp ||
cacheKind_ == CacheKind::GetPropSuper) {
writer.megamorphicLoadSlotResult(objId, JSID_TO_ATOM(id)->asPropertyName(),
handleMissing);
} else {
MOZ_ASSERT(cacheKind_ == CacheKind::GetElem ||
cacheKind_ == CacheKind::GetElemSuper);
writer.megamorphicLoadSlotByValueResult(objId, getElemKeyValueId(),
handleMissing);
}
writer.typeMonitorResult();
trackAttached(handleMissing ? "MegamorphicMissingNativeSlot"
: "MegamorphicNativeSlot");
}
AttachDecision GetPropIRGenerator::tryAttachNative(HandleObject obj,
ObjOperandId objId,
HandleId id) {
RootedShape shape(cx_);
RootedNativeObject holder(cx_);
NativeGetPropCacheability type =
CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_, resultFlags_);
switch (type) {
case CanAttachNone:
return AttachDecision::NoAction;
case CanAttachReadSlot:
if (mode_ == ICState::Mode::Megamorphic) {
attachMegamorphicNativeSlot(objId, id, holder == nullptr);
return AttachDecision::Attach;
}
maybeEmitIdGuard(id);
if (holder) {
EnsureTrackPropertyTypes(cx_, holder, id);
// See the comment in StripPreliminaryObjectStubs.
if (IsPreliminaryObject(obj)) {
preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
} else {
preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
}
}
EmitReadSlotResult(writer, obj, holder, shape, objId);
EmitReadSlotReturn(writer, obj, holder, shape);
trackAttached("NativeSlot");
return AttachDecision::Attach;
case CanAttachScriptedGetter:
case CanAttachNativeGetter: {
// |super.prop| accesses use a |this| value that differs from lookup
// object
MOZ_ASSERT(!idempotent());
ObjOperandId receiverId =
isSuper() ? writer.guardToObject(getSuperReceiverValueId()) : objId;
maybeEmitIdGuard(id);
EmitCallGetterResult(cx_, writer, obj, holder, shape, objId, receiverId,
mode_);
trackAttached("NativeGetter");
return AttachDecision::Attach;
}
}
MOZ_CRASH("Bad NativeGetPropCacheability");
}
bool js::jit::IsWindowProxyForScriptGlobal(JSScript* script, JSObject* obj) {
if (!IsWindowProxy(obj)) {
return false;
}
MOZ_ASSERT(obj->getClass() ==
script->runtimeFromMainThread()->maybeWindowProxyClass());
JSObject* window = ToWindowIfWindowProxy(obj);
// Ion relies on the WindowProxy's group changing (and the group getting
// marked as having unknown properties) on navigation. If we ever stop
// transplanting same-compartment WindowProxies, this assert will fail and we
// need to fix that code.
MOZ_ASSERT(window == &obj->nonCCWGlobal());
// This must be a WindowProxy for a global in this compartment. Else it would
// be a cross-compartment wrapper and IsWindowProxy returns false for
// those.
MOZ_ASSERT(script->compartment() == obj->compartment());
// Only optimize lookups on the WindowProxy for the current global. Other
// WindowProxies in the compartment may require security checks (based on
// mutable document.domain). See bug 1516775.
return window == &script->global();
}
// Guards objId is a WindowProxy for windowObj. Returns the window's operand id.
static ObjOperandId GuardAndLoadWindowProxyWindow(CacheIRWriter& writer,
ObjOperandId objId,
GlobalObject* windowObj) {
// Note: update AddCacheIRGetPropFunction in BaselineInspector.cpp when making
// changes here.
writer.guardClass(objId, GuardClassKind::WindowProxy);
ObjOperandId windowObjId = writer.loadWrapperTarget(objId);
writer.guardSpecificObject(windowObjId, windowObj);
return windowObjId;
}
AttachDecision GetPropIRGenerator::tryAttachWindowProxy(HandleObject obj,
ObjOperandId objId,
HandleId id) {
// Attach a stub when the receiver is a WindowProxy and we can do the lookup
// on the Window (the global object).
if (!IsWindowProxyForScriptGlobal(script_, obj)) {
return AttachDecision::NoAction;
}
// If we're megamorphic prefer a generic proxy stub that handles a lot more
// cases.
if (mode_ == ICState::Mode::Megamorphic) {
return AttachDecision::NoAction;
}
// Now try to do the lookup on the Window (the current global).
Handle<GlobalObject*> windowObj = cx_->global();
RootedShape shape(cx_);
RootedNativeObject holder(cx_);
NativeGetPropCacheability type = CanAttachNativeGetProp(
cx_, windowObj, id, &holder, &shape, pc_, resultFlags_);
switch (type) {
case CanAttachNone:
return AttachDecision::NoAction;
case CanAttachReadSlot: {
maybeEmitIdGuard(id);
ObjOperandId windowObjId =
GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
EmitReadSlotResult(writer, windowObj, holder, shape, windowObjId);
EmitReadSlotReturn(writer, windowObj, holder, shape);
trackAttached("WindowProxySlot");
return AttachDecision::Attach;
}
case CanAttachNativeGetter: {
// Make sure the native getter is okay with the IC passing the Window
// instead of the WindowProxy as |this| value.
JSFunction* callee = &shape->getterObject()->as<JSFunction>();
MOZ_ASSERT(callee->isNativeWithoutJitEntry());
if (!callee->hasJitInfo() ||
callee->jitInfo()->needsOuterizedThisObject()) {
return AttachDecision::NoAction;
}
// If a |super| access, it is not worth the complexity to attach an IC.
if (isSuper()) {
return AttachDecision::NoAction;
}
// Guard the incoming object is a WindowProxy and inline a getter call
// based on the Window object.
maybeEmitIdGuard(id);
ObjOperandId windowObjId =
GuardAndLoadWindowProxyWindow(writer, objId, windowObj);
EmitCallGetterResult(cx_, writer, windowObj, holder, shape, windowObjId,
mode_);
trackAttached("WindowProxyGetter");
return AttachDecision::Attach;
}
case CanAttachScriptedGetter:
MOZ_ASSERT_UNREACHABLE("Not possible for window proxies");
}
MOZ_CRASH("Unreachable");
}
AttachDecision GetPropIRGenerator::tryAttachCrossCompartmentWrapper(
HandleObject obj, ObjOperandId objId, HandleId id) {
// We can only optimize this very wrapper-handler, because others might
// have a security policy.
if (!IsWrapper(obj) ||
Wrapper::wrapperHandler(obj) != &CrossCompartmentWrapper::singleton) {
return AttachDecision::NoAction;
}
// If we're megamorphic prefer a generic proxy stub that handles a lot more
// cases.
if (mode_ == ICState::Mode::Megamorphic) {
return AttachDecision::NoAction;
}
RootedObject unwrapped(cx_, Wrapper::wrappedObject(obj));
MOZ_ASSERT(unwrapped == UnwrapOneCheckedStatic(obj));
MOZ_ASSERT(!IsCrossCompartmentWrapper(unwrapped),
"CCWs must not wrap other CCWs");
// If we allowed different zones we would have to wrap strings.
if (unwrapped->compartment()->zone() != cx_->compartment()->zone()) {
return AttachDecision::NoAction;
}
// Take the unwrapped object's global, and wrap in a
// this-compartment wrapper. This is what will be stored in the IC
// keep the compartment alive.
RootedObject wrappedTargetGlobal(cx_, &unwrapped->nonCCWGlobal());
if (!cx_->compartment()->wrap(cx_, &wrappedTargetGlobal)) {
cx_->clearPendingException();
return AttachDecision::NoAction;
}
RootedShape shape(cx_);
RootedNativeObject holder(cx_);
// Enter realm of target since some checks have side-effects
// such as de-lazifying type info.
{
AutoRealm ar(cx_, unwrapped);
NativeGetPropCacheability canCache = CanAttachNativeGetProp(
cx_, unwrapped, id, &holder, &shape, pc_, resultFlags_);
if (canCache != CanAttachReadSlot) {
return AttachDecision::NoAction;
}
if (holder) {
// Need to be in the compartment of the holder to
// call EnsureTrackPropertyTypes
EnsureTrackPropertyTypes(cx_, holder, id);
if (unwrapped == holder) {
// See the comment in StripPreliminaryObjectStubs.
if (IsPreliminaryObject(unwrapped)) {
preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
} else {
preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
}
}
} else {
// UNCACHEABLE_PROTO may result in guards against specific
// (cross-compartment) prototype objects, so don't try to attach IC if we
// see the flag at all.
if (UncacheableProtoOnChain(unwrapped)) {
return AttachDecision::NoAction;
}
}
}
maybeEmitIdGuard(id);
writer.guardIsProxy(objId);
writer.guardHasProxyHandler(objId, Wrapper::wrapperHandler(obj));
// Load the object wrapped by the CCW
ObjOperandId wrapperTargetId = writer.loadWrapperTarget(objId);
// If the compartment of the wrapped object is different we should fail.
writer.guardCompartment(wrapperTargetId, wrappedTargetGlobal,
unwrapped->compartment());
ObjOperandId unwrappedId = wrapperTargetId;
EmitReadSlotResult<SlotReadType::CrossCompartment>(writer, unwrapped, holder,
shape, unwrappedId);
EmitReadSlotReturn(writer, unwrapped, holder, shape, /* wrapResult = */ true);
trackAttached("CCWSlot");
return AttachDecision::Attach;
}
static bool GetXrayExpandoShapeWrapper(JSContext* cx, HandleObject xray,
MutableHandleObject wrapper) {
Value v = GetProxyReservedSlot(xray, GetXrayJitInfo()->xrayHolderSlot);
if (v.isObject()) {
NativeObject* holder = &v.toObject().as<NativeObject>();
v = holder->getFixedSlot(GetXrayJitInfo()->holderExpandoSlot);
if (v.isObject()) {
RootedNativeObject 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) {
if (!IsProxy(obj)) {
return AttachDecision::NoAction;
}
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<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.object()) {
break;
}
if (!GetPrototype(cx_, holder, &holder)) {
cx_->clearPendingException();
return AttachDecision::NoAction;
}
if (!holder || !IsProxy(holder) ||
!info->isCrossCompartmentXray(GetProxyHandler(holder))) {
return AttachDecision::NoAction;
}
RootedObject prototypeExpandoShapeWrapper(cx_);
if (!GetXrayExpandoShapeWrapper(cx_, holder,
&prototypeExpandoShapeWrapper) ||
!prototypes.append(holder) ||
!prototypeExpandoShapeWrappers.append(prototypeExpandoShapeWrapper)) {
cx_->recoverFromOutOfMemory();
return AttachDecision::NoAction;
}
}
if (!desc.isAccessorDescriptor()) {
return AttachDecision::NoAction;
}
RootedObject getter(cx_, desc.getterObject());
if (!getter || !getter->is<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);
}
}
writer.callNativeGetterResult(objId, &getter->as<JSFunction>());
writer.typeMonitorResult();
trackAttached("XrayGetter");
return AttachDecision::Attach;
}
AttachDecision GetPropIRGenerator::tryAttachGenericProxy(
HandleObject obj, ObjOperandId objId, HandleId id, bool handleDOMProxies) {
MOZ_ASSERT(obj->is<ProxyObject>());
writer.guardIsProxy(objId);
if (!handleDOMProxies) {
// Ensure that the incoming object is not a DOM proxy, so that we can get to
// the specialized stubs
writer.guardNotDOMProxy(objId);
}
if (cacheKind_ == CacheKind::GetProp || mode_ == ICState::Mode::Specialized) {
MOZ_ASSERT(!isSuper());
maybeEmitIdGuard(id);
writer.callProxyGetResult(objId, id);
} else {
// Attach a stub that handles every id.
MOZ_ASSERT(cacheKind_ == CacheKind::GetElem);
MOZ_ASSERT(mode_ == ICState::Mode::Megamorphic);
MOZ_ASSERT(!isSuper());
writer.callProxyGetByValueResult(objId, getElemKeyValueId());
}
writer.typeMonitorResult();
trackAttached("GenericProxy");
return AttachDecision::Attach;
}
ObjOperandId IRGenerator::guardDOMProxyExpandoObjectAndShape(
JSObject* obj, ObjOperandId objId, const Value& expandoVal,
JSObject* expandoObj) {
MOZ_ASSERT(IsCacheableDOMProxy(obj));
TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
// Shape determines Class, so now it must be a DOM proxy.
ValOperandId expandoValId;
if (expandoVal.isObject()) {
expandoValId = writer.loadDOMExpandoValue(objId);
} else {
expandoValId = writer.loadDOMExpandoValueIgnoreGeneration(objId);
}
// Guard the expando is an object and shape guard.
ObjOperandId expandoObjId = writer.guardToObject(expandoValId);
TestMatchingHolder(writer, expandoObj, expandoObjId);
return expandoObjId;
}
AttachDecision GetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj,
ObjOperandId objId,
HandleId id) {
MOZ_ASSERT(IsCacheableDOMProxy(obj));
RootedValue expandoVal(cx_, GetProxyPrivate(obj));
RootedObject expandoObj(cx_);
if (expandoVal.isObject()) {
expandoObj = &expandoVal.toObject();
} else {
MOZ_ASSERT(!expandoVal.isUndefined(),
"How did a missing expando manage to shadow things?");
auto expandoAndGeneration =
static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
MOZ_ASSERT(expandoAndGeneration);
expandoObj = &expandoAndGeneration->expando.toObject();
}
// Try to do the lookup on the expando object.
RootedNativeObject holder(cx_);
RootedShape propShape(cx_);
NativeGetPropCacheability canCache = CanAttachNativeGetProp(
cx_, expandoObj, id, &holder, &propShape, pc_, resultFlags_);
if (canCache == CanAttachNone) {
return AttachDecision::NoAction;
}
if (!holder) {
return AttachDecision::NoAction;
}
MOZ_ASSERT(holder == expandoObj);
maybeEmitIdGuard(id);
ObjOperandId expandoObjId =
guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj);
if (canCache == CanAttachReadSlot) {
// Load from the expando's slots.
EmitLoadSlotResult(writer, expandoObjId, &expandoObj->as<NativeObject>(),
propShape);
writer.typeMonitorResult();
} else {
// Call the getter. Note that we pass objId, the DOM proxy, as |this|
// and not the expando object.
MOZ_ASSERT(canCache == CanAttachNativeGetter ||
canCache == CanAttachScriptedGetter);
EmitCallGetterResultNoGuards(cx_, writer, expandoObj, expandoObj, propShape,
objId);
}
trackAttached("DOMProxyExpando");
return AttachDecision::Attach;
}
AttachDecision GetPropIRGenerator::tryAttachDOMProxyShadowed(HandleObject obj,
ObjOperandId objId,
HandleId id) {
MOZ_ASSERT(!isSuper());
MOZ_ASSERT(IsCacheableDOMProxy(obj));
maybeEmitIdGuard(id);
TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
writer.callProxyGetResult(objId, id);
writer.typeMonitorResult();
trackAttached("DOMProxyShadowed");
return AttachDecision::Attach;
}
// Callers are expected to have already guarded on the shape of the
// object, which guarantees the object is a DOM proxy.
static void CheckDOMProxyExpandoDoesNotShadow(CacheIRWriter& writer,
JSObject* obj, jsid id,
ObjOperandId objId) {
MOZ_ASSERT(IsCacheableDOMProxy(obj));
Value expandoVal = GetProxyPrivate(obj);
ValOperandId expandoId;
if (!expandoVal.isObject() && !expandoVal.isUndefined()) {
auto expandoAndGeneration =
static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
uint64_t generation = expandoAndGeneration->generation;
expandoId = writer.loadDOMExpandoValueGuardGeneration(
objId, expandoAndGeneration, generation);
expandoVal = expandoAndGeneration->expando;
} else {
expandoId = writer.loadDOMExpandoValue(objId);
}
if (expandoVal.isUndefined()) {
// Guard there's no expando object.
writer.guardNonDoubleType(expandoId, ValueType::Undefined);
} else if (expandoVal.isObject()) {
// Guard the proxy either has no expando object or, if it has one, that
// the shape matches the current expando object.
NativeObject& expandoObj = expandoVal.toObject().as<NativeObject>();
MOZ_ASSERT(!expandoObj.containsPure(id));
writer.guardDOMExpandoMissingOrGuardShape(expandoId,
expandoObj.lastProperty());
} else {
MOZ_CRASH("Invalid expando value");
}
}
AttachDecision GetPropIRGenerator::tryAttachDOMProxyUnshadowed(
HandleObject obj, ObjOperandId objId, HandleId id) {
MOZ_ASSERT(IsCacheableDOMProxy(obj));
RootedObject checkObj(cx_, obj->staticPrototype());
if (!checkObj) {
return AttachDecision::NoAction;
}
RootedNativeObject holder(cx_);
RootedShape shape(cx_);
NativeGetPropCacheability canCache = CanAttachNativeGetProp(
cx_, checkObj, id, &holder, &shape, pc_, resultFlags_);
if (canCache == CanAttachNone) {
return AttachDecision::NoAction;
}
maybeEmitIdGuard(id);
// Guard that our expando object hasn't started shadowing this property.
TestMatchingProxyReceiver(writer, &obj->as<ProxyObject>(), objId);
CheckDOMProxyExpandoDoesNotShadow(writer, obj, id, objId);
if (holder) {
// Found the property on the prototype chain. Treat it like a native
// getprop.
GeneratePrototypeGuards(writer, obj, holder, objId);
// Guard on the holder of the property.
ObjOperandId holderId = writer.loadObject(holder);
TestMatchingHolder(writer, holder, holderId);
if (canCache == CanAttachReadSlot) {
EmitLoadSlotResult(writer, holderId, holder, shape);
writer.typeMonitorResult();
} else {
// EmitCallGetterResultNoGuards expects |obj| to be the object the
// property is on to do some checks. Since we actually looked at
// checkObj, and no extra guards will be generated, we can just
// pass that instead.
MOZ_ASSERT(canCache == CanAttachNativeGetter ||
canCache == CanAttachScriptedGetter);
MOZ_ASSERT(!isSuper());
EmitCallGetterResultNoGuards(cx_, writer, checkObj, holder, shape, objId);
}
} else {
// Property was not found on the prototype chain. Deoptimize down to
// proxy get call.
MOZ_ASSERT(!isSuper());
writer.callProxyGetResult(objId, id);
writer.typeMonitorResult();
}
trackAttached("DOMProxyUnshadowed");
return AttachDecision::Attach;
}
AttachDecision GetPropIRGenerator::tryAttachProxy(HandleObject obj,
ObjOperandId objId,
HandleId id) {
ProxyStubType type = GetProxyStubType(cx_, obj, id);
if (type == ProxyStubType::None) {
return AttachDecision::NoAction;
}
// The proxy stubs don't currently support |super| access.
if (isSuper()) {
return AttachDecision::NoAction;
}
if (mode_ == ICState::Mode::Megamorphic) {
return tryAttachGenericProxy(obj, objId, id, /* handleDOMProxies = */ true);
}
switch (type) {
case ProxyStubType::None:
break;
case ProxyStubType::DOMExpando:
TRY_ATTACH(tryAttachDOMProxyExpando(obj, objId, id));
[[fallthrough]]; // Fall through to the generic shadowed case.
case ProxyStubType::DOMShadowed:
return tryAttachDOMProxyShadowed(obj, objId, id);
case ProxyStubType::DOMUnshadowed:
TRY_ATTACH(tryAttachDOMProxyUnshadowed(obj, objId, id));
return tryAttachGenericProxy(obj, objId, id,
/* handleDOMProxies = */ true);
case ProxyStubType::Generic:
return tryAttachGenericProxy(obj, objId, id,
/* handleDOMProxies = */ false);
}
MOZ_CRASH("Unexpected ProxyStubType");
}
static TypedThingLayout GetTypedThingLayout(const JSClass* clasp) {
if (IsTypedArrayClass(clasp)) {
return TypedThingLayout::TypedArray;
}
if (IsOutlineTypedObjectClass(clasp)) {
return TypedThingLayout::OutlineTypedObject;
}
if (IsInlineTypedObjectClass(clasp)) {
return TypedThingLayout::InlineTypedObject;
}
MOZ_CRASH("Bad object class");
}
AttachDecision GetPropIRGenerator::tryAttachTypedObject(HandleObject obj,
ObjOperandId objId,
HandleId id) {
if (!obj->is<TypedObject>()) {
return AttachDecision::NoAction;
}
TypedObject* typedObj = &obj->as<TypedObject>();
if (!typedObj->typeDescr().is<StructTypeDescr>()) {
return AttachDecision::NoAction;
}
StructTypeDescr* structDescr = &typedObj->typeDescr().as<StructTypeDescr>();
size_t fieldIndex;
if (!structDescr->fieldIndex(id, &fieldIndex)) {
return AttachDecision::NoAction;
}
TypeDescr* fieldDescr = &structDescr->fieldDescr(fieldIndex);
if (!fieldDescr->is<SimpleTypeDescr>()) {
return AttachDecision::NoAction;
}
TypedThingLayout layout = GetTypedThingLayout(obj->getClass());
uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex);
uint32_t typeDescr = SimpleTypeDescrKey(&fieldDescr->as<SimpleTypeDescr>());