blob: b5fe6dec072cd82ffc0d111bfe74365e3baa63c9 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/IonCaches.h"
#include "mozilla/SizePrintfMacros.h"
#include "mozilla/TemplateLib.h"
#include "jstypes.h"
#include "builtin/TypedObject.h"
#include "jit/BaselineIC.h"
#include "jit/Ion.h"
#include "jit/JitcodeMap.h"
#include "jit/JitSpewer.h"
#include "jit/Linker.h"
#include "jit/Lowering.h"
#ifdef JS_ION_PERF
# include "jit/PerfSpewer.h"
#endif
#include "jit/VMFunctions.h"
#include "js/Proxy.h"
#include "vm/Shape.h"
#include "jit/JitFrames-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "jit/shared/Lowering-shared-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/Shape-inl.h"
using namespace js;
using namespace js::jit;
using mozilla::tl::FloorLog2;
typedef Rooted<TypedArrayObject*> RootedTypedArrayObject;
void
CodeLocationJump::repoint(JitCode* code, MacroAssembler* masm)
{
MOZ_ASSERT(state_ == Relative);
size_t new_off = (size_t)raw_;
#ifdef JS_SMALL_BRANCH
size_t jumpTableEntryOffset = reinterpret_cast<size_t>(jumpTableEntry_);
#endif
if (masm != nullptr) {
#ifdef JS_CODEGEN_X64
MOZ_ASSERT((uint64_t)raw_ <= UINT32_MAX);
#endif
new_off = (uintptr_t)raw_;
#ifdef JS_SMALL_BRANCH
jumpTableEntryOffset = masm->actualIndex(jumpTableEntryOffset);
#endif
}
raw_ = code->raw() + new_off;
#ifdef JS_SMALL_BRANCH
jumpTableEntry_ = Assembler::PatchableJumpAddress(code, (size_t) jumpTableEntryOffset);
#endif
setAbsolute();
}
void
CodeLocationLabel::repoint(JitCode* code, MacroAssembler* masm)
{
MOZ_ASSERT(state_ == Relative);
size_t new_off = (size_t)raw_;
if (masm != nullptr) {
#ifdef JS_CODEGEN_X64
MOZ_ASSERT((uint64_t)raw_ <= UINT32_MAX);
#endif
new_off = (uintptr_t)raw_;
}
MOZ_ASSERT(new_off < code->instructionsSize());
raw_ = code->raw() + new_off;
setAbsolute();
}
void
CodeOffsetJump::fixup(MacroAssembler* masm)
{
#ifdef JS_SMALL_BRANCH
jumpTableIndex_ = masm->actualIndex(jumpTableIndex_);
#endif
}
const char*
IonCache::CacheName(IonCache::Kind kind)
{
static const char * const names[] =
{
#define NAME(x) #x,
IONCACHE_KIND_LIST(NAME)
#undef NAME
};
return names[kind];
}
IonCache::LinkStatus
IonCache::linkCode(JSContext* cx, MacroAssembler& masm, IonScript* ion, JitCode** code)
{
Linker linker(masm);
*code = linker.newCode<CanGC>(cx, ION_CODE);
if (!*code)
return LINK_ERROR;
if (ion->invalidated())
return CACHE_FLUSHED;
return LINK_GOOD;
}
const size_t IonCache::MAX_STUBS = 16;
// Helper class which encapsulates logic to attach a stub to an IC by hooking
// up rejoins and next stub jumps.
//
// The simplest stubs have a single jump to the next stub and look like the
// following:
//
// branch guard NEXTSTUB
// ... IC-specific code ...
// jump REJOIN
//
// This corresponds to:
//
// attacher.branchNextStub(masm, ...);
// ... emit IC-specific code ...
// attacher.jumpRejoin(masm);
//
// Whether the stub needs multiple next stub jumps look like:
//
// branch guard FAILURES
// ... IC-specific code ...
// branch another-guard FAILURES
// ... IC-specific code ...
// jump REJOIN
// FAILURES:
// jump NEXTSTUB
//
// This corresponds to:
//
// Label failures;
// masm.branchX(..., &failures);
// ... emit IC-specific code ...
// masm.branchY(..., failures);
// ... emit more IC-specific code ...
// attacher.jumpRejoin(masm);
// masm.bind(&failures);
// attacher.jumpNextStub(masm);
//
// A convenience function |branchNextStubOrLabel| is provided in the case that
// the stub sometimes has multiple next stub jumps and sometimes a single
// one. If a non-nullptr label is passed in, a |branchPtr| will be made to
// that label instead of a |branchPtrWithPatch| to the next stub.
class IonCache::StubAttacher
{
protected:
bool hasNextStubOffset_ : 1;
bool hasStubCodePatchOffset_ : 1;
IonCache& cache_;
CodeLocationLabel rejoinLabel_;
CodeOffsetJump nextStubOffset_;
CodeOffsetJump rejoinOffset_;
CodeOffset stubCodePatchOffset_;
public:
explicit StubAttacher(IonCache& cache)
: hasNextStubOffset_(false),
hasStubCodePatchOffset_(false),
cache_(cache),
rejoinLabel_(cache.rejoinLabel_),
nextStubOffset_(),
rejoinOffset_(),
stubCodePatchOffset_()
{ }
// Value used instead of the JitCode self-reference of generated
// stubs. This value is needed for marking calls made inside stubs. This
// value would be replaced by the attachStub function after the allocation
// of the JitCode. The self-reference is used to keep the stub path alive
// even if the IonScript is invalidated or if the IC is flushed.
static const ImmPtr STUB_ADDR;
template <class T1, class T2>
void branchNextStub(MacroAssembler& masm, Assembler::Condition cond, T1 op1, T2 op2) {
MOZ_ASSERT(!hasNextStubOffset_);
RepatchLabel nextStub;
nextStubOffset_ = masm.branchPtrWithPatch(cond, op1, op2, &nextStub);
hasNextStubOffset_ = true;
masm.bind(&nextStub);
}
template <class T1, class T2>
void branchNextStubOrLabel(MacroAssembler& masm, Assembler::Condition cond, T1 op1, T2 op2,
Label* label)
{
if (label != nullptr)
masm.branchPtr(cond, op1, op2, label);
else
branchNextStub(masm, cond, op1, op2);
}
void jumpRejoin(MacroAssembler& masm) {
RepatchLabel rejoin;
rejoinOffset_ = masm.jumpWithPatch(&rejoin);
masm.bind(&rejoin);
}
void jumpNextStub(MacroAssembler& masm) {
MOZ_ASSERT(!hasNextStubOffset_);
RepatchLabel nextStub;
nextStubOffset_ = masm.jumpWithPatch(&nextStub);
hasNextStubOffset_ = true;
masm.bind(&nextStub);
}
void pushStubCodePointer(MacroAssembler& masm) {
// Push the JitCode pointer for the stub we're generating.
// WARNING:
// WARNING: If JitCode ever becomes relocatable, the following code is incorrect.
// WARNING: Note that we're not marking the pointer being pushed as an ImmGCPtr.
// WARNING: This location will be patched with the pointer of the generated stub,
// WARNING: such as it can be marked when a call is made with this stub. Be aware
// WARNING: that ICs are not marked and so this stub will only be kept alive iff
// WARNING: it is on the stack at the time of the GC. No ImmGCPtr is needed as the
// WARNING: stubs are flushed on GC.
// WARNING:
MOZ_ASSERT(!hasStubCodePatchOffset_);
stubCodePatchOffset_ = masm.PushWithPatch(STUB_ADDR);
hasStubCodePatchOffset_ = true;
}
void patchRejoinJump(MacroAssembler& masm, JitCode* code) {
rejoinOffset_.fixup(&masm);
CodeLocationJump rejoinJump(code, rejoinOffset_);
AutoWritableJitCode awjc(code);
PatchJump(rejoinJump, rejoinLabel_);
}
void patchStubCodePointer(JitCode* code) {
if (hasStubCodePatchOffset_) {
AutoWritableJitCode awjc(code);
Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, stubCodePatchOffset_),
ImmPtr(code), STUB_ADDR);
}
}
void patchNextStubJump(MacroAssembler& masm, JitCode* code) {
// Patch the previous nextStubJump of the last stub, or the jump from the
// codeGen, to jump into the newly allocated code.
PatchJump(cache_.lastJump_, CodeLocationLabel(code), Reprotect);
// If this path is not taken, we are producing an entry which can no
// longer go back into the update function.
if (hasNextStubOffset_) {
AutoWritableJitCode awjc(code);
nextStubOffset_.fixup(&masm);
CodeLocationJump nextStubJump(code, nextStubOffset_);
PatchJump(nextStubJump, cache_.fallbackLabel_);
// When the last stub fails, it fallback to the ool call which can
// produce a stub. Next time we generate a stub, we will patch the
// nextStub jump to try the new stub.
cache_.lastJump_ = nextStubJump;
}
}
};
const ImmPtr IonCache::StubAttacher::STUB_ADDR = ImmPtr((void*)0xdeadc0de);
void
IonCache::emitInitialJump(MacroAssembler& masm, RepatchLabel& entry)
{
initialJump_ = masm.jumpWithPatch(&entry);
lastJump_ = initialJump_;
Label label;
masm.bind(&label);
rejoinLabel_ = CodeOffset(label.offset());
}
void
IonCache::attachStub(MacroAssembler& masm, StubAttacher& attacher, Handle<JitCode*> code)
{
MOZ_ASSERT(canAttachStub());
incrementStubCount();
// Update the success path to continue after the IC initial jump.
attacher.patchRejoinJump(masm, code);
// Replace the STUB_ADDR constant by the address of the generated stub, such
// as it can be kept alive even if the cache is flushed (see
// MarkJitExitFrame).
attacher.patchStubCodePointer(code);
// Update the failure path.
attacher.patchNextStubJump(masm, code);
}
bool
IonCache::linkAndAttachStub(JSContext* cx, MacroAssembler& masm, StubAttacher& attacher,
IonScript* ion, const char* attachKind,
JS::TrackedOutcome trackedOutcome)
{
Rooted<JitCode*> code(cx);
{
// Need to exit the AutoFlushICache context to flush the cache
// before attaching the stub below.
AutoFlushICache afc("IonCache");
LinkStatus status = linkCode(cx, masm, ion, code.address());
if (status != LINK_GOOD)
return status != LINK_ERROR;
}
if (pc_) {
JitSpew(JitSpew_IonIC, "Cache %p(%s:%" PRIuSIZE "/%" PRIuSIZE ") generated %s %s stub at %p",
this, script_->filename(), script_->lineno(), script_->pcToOffset(pc_),
attachKind, CacheName(kind()), code->raw());
} else {
JitSpew(JitSpew_IonIC, "Cache %p generated %s %s stub at %p",
this, attachKind, CacheName(kind()), code->raw());
}
#ifdef JS_ION_PERF
writePerfSpewerJitCodeProfile(code, "IonCache");
#endif
attachStub(masm, attacher, code);
// Add entry to native => bytecode mapping for this stub if needed.
if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) {
JitcodeGlobalEntry::IonCacheEntry entry;
entry.init(code, code->raw(), code->rawEnd(), rejoinAddress(), trackedOutcome);
// Add entry to the global table.
JitcodeGlobalTable* globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable();
if (!globalTable->addEntry(entry, cx->runtime())) {
entry.destroy();
ReportOutOfMemory(cx);
return false;
}
// Mark the jitcode as having a bytecode map.
code->setHasBytecodeMap();
} else {
JitcodeGlobalEntry::DummyEntry entry;
entry.init(code, code->raw(), code->rawEnd());
// Add entry to the global table.
JitcodeGlobalTable* globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable();
if (!globalTable->addEntry(entry, cx->runtime())) {
entry.destroy();
ReportOutOfMemory(cx);
return false;
}
// Mark the jitcode as having a bytecode map.
code->setHasBytecodeMap();
}
// Report masm OOM errors here, so all our callers can:
// return linkAndAttachStub(...);
if (masm.oom()) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
void
IonCache::updateBaseAddress(JitCode* code, MacroAssembler& masm)
{
fallbackLabel_.repoint(code, &masm);
initialJump_.repoint(code, &masm);
lastJump_.repoint(code, &masm);
rejoinLabel_.repoint(code, &masm);
}
static void*
GetReturnAddressToIonCode(JSContext* cx)
{
JitFrameIterator iter(cx);
MOZ_ASSERT(iter.type() == JitFrame_Exit,
"An exit frame is expected as update functions are called with a VMFunction.");
void* returnAddr = iter.returnAddress();
#ifdef DEBUG
++iter;
MOZ_ASSERT(iter.isIonJS());
#endif
return returnAddr;
}
static void
GeneratePrototypeGuards(JSContext* cx, IonScript* ion, MacroAssembler& masm, JSObject* obj,
JSObject* holder, Register objectReg, Register scratchReg,
Label* failures)
{
/*
* The guards here protect against the effects of JSObject::swap(). If the prototype chain
* is directly altered, then TI will toss the jitcode, so we don't have to worry about
* it, and any other change to the holder, or adding a shadowing property will result
* in reshaping the holder, and thus the failure of the shape guard.
*/
MOZ_ASSERT(obj != holder);
if (obj->hasUncacheableProto()) {
// Note: objectReg and scratchReg may be the same register, so we cannot
// use objectReg in the rest of this function.
masm.loadPtr(Address(objectReg, JSObject::offsetOfGroup()), scratchReg);
Address proto(scratchReg, ObjectGroup::offsetOfProto());
masm.branchPtr(Assembler::NotEqual, proto, ImmGCPtr(obj->getProto()), failures);
}
JSObject* pobj = IsCacheableDOMProxy(obj)
? obj->getTaggedProto().toObjectOrNull()
: obj->getProto();
if (!pobj)
return;
while (pobj != holder) {
if (pobj->hasUncacheableProto()) {
masm.movePtr(ImmGCPtr(pobj), scratchReg);
Address groupAddr(scratchReg, JSObject::offsetOfGroup());
if (pobj->isSingleton()) {
// Singletons can have their group's |proto| mutated directly.
masm.loadPtr(groupAddr, scratchReg);
Address protoAddr(scratchReg, ObjectGroup::offsetOfProto());
masm.branchPtr(Assembler::NotEqual, protoAddr, ImmGCPtr(pobj->getProto()), failures);
} else {
masm.branchPtr(Assembler::NotEqual, groupAddr, ImmGCPtr(pobj->group()), failures);
}
}
pobj = pobj->getProto();
}
}
// Note: This differs from IsCacheableProtoChain in BaselineIC.cpp in that
// Ion caches can deal with objects on the proto chain that have uncacheable
// prototypes.
static bool
IsCacheableProtoChainForIon(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->getProto();
if (!proto || !proto->isNative())
return false;
obj = proto;
}
return true;
}
static bool
IsCacheableGetPropReadSlotForIon(JSObject* obj, JSObject* holder, Shape* shape)
{
if (!shape || !IsCacheableProtoChainForIon(obj, holder))
return false;
if (!shape->hasSlot() || !shape->hasDefaultGetter())
return false;
return true;
}
static bool
IsCacheableNoProperty(JSObject* obj, JSObject* holder, Shape* shape, jsbytecode* pc,
const TypedOrValueRegister& output)
{
if (shape)
return false;
MOZ_ASSERT(!holder);
// Just because we didn't find the property on the object doesn't mean it
// won't magically appear through various engine hacks:
if (obj->getClass()->getProperty)
return false;
// Don't generate missing property ICs if we skipped a non-native object, as
// lookups may extend beyond the prototype chain (e.g. for DOMProxy
// proxies).
JSObject* obj2 = obj;
while (obj2) {
if (!obj2->isNative())
return false;
obj2 = obj2->getProto();
}
// The pc is nullptr if the cache is idempotent. We cannot share missing
// properties between caches because TI can only try to prove that a type is
// contained, but does not attempts to check if something does not exists.
// So the infered type of getprop would be missing and would not contain
// undefined, as expected for missing properties.
if (!pc)
return false;
// TI has not yet monitored an Undefined value. The fallback path will
// monitor and invalidate the script.
if (!output.hasValue())
return false;
return true;
}
static bool
IsOptimizableArgumentsObjectForLength(JSObject* obj)
{
if (!obj->is<ArgumentsObject>())
return false;
if (obj->as<ArgumentsObject>().hasOverriddenLength())
return false;
return true;
}
static bool
IsOptimizableArgumentsObjectForGetElem(JSObject* obj, Value idval)
{
if (!IsOptimizableArgumentsObjectForLength(obj))
return false;
ArgumentsObject& argsObj = obj->as<ArgumentsObject>();
if (argsObj.isAnyElementDeleted())
return false;
if (!idval.isInt32())
return false;
int32_t idint = idval.toInt32();
if (idint < 0 || static_cast<uint32_t>(idint) >= argsObj.initialLength())
return false;
return true;
}
static bool
IsCacheableGetPropCallNative(JSObject* obj, JSObject* holder, Shape* shape)
{
if (!shape || !IsCacheableProtoChainForIon(obj, holder))
return false;
if (!shape->hasGetterValue() || !shape->getterValue().isObject())
return false;
if (!shape->getterValue().toObject().is<JSFunction>())
return false;
JSFunction& getter = shape->getterValue().toObject().as<JSFunction>();
if (!getter.isNative())
return false;
// Check for a getter that has jitinfo and whose jitinfo says it's
// OK with both inner and outer objects.
if (getter.jitInfo() && !getter.jitInfo()->needsOuterizedThisObject())
return true;
// For getters that need the WindowProxy (instead of the Window) as this
// object, don't cache if obj is the Window, since our cache will pass that
// instead of the WindowProxy.
return !IsWindow(obj);
}
static bool
IsCacheableGetPropCallScripted(JSObject* obj, JSObject* holder, Shape* shape)
{
if (!shape || !IsCacheableProtoChainForIon(obj, holder))
return false;
if (!shape->hasGetterValue() || !shape->getterValue().isObject())
return false;
if (!shape->getterValue().toObject().is<JSFunction>())
return false;
JSFunction& getter = shape->getterValue().toObject().as<JSFunction>();
if (!getter.hasJITCode())
return false;
// See IsCacheableGetPropCallNative.
return !IsWindow(obj);
}
static bool
IsCacheableGetPropCallPropertyOp(JSObject* obj, JSObject* holder, Shape* shape)
{
if (!shape || !IsCacheableProtoChainForIon(obj, holder))
return false;
if (shape->hasSlot() || shape->hasGetterValue() || shape->hasDefaultGetter())
return false;
return true;
}
static void
TestMatchingReceiver(MacroAssembler& masm, IonCache::StubAttacher& attacher,
Register object, JSObject* obj, Label* failure,
bool alwaysCheckGroup = false)
{
if (obj->is<UnboxedPlainObject>()) {
MOZ_ASSERT(failure);
masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
Label success;
masm.push(object);
masm.loadPtr(expandoAddress, object);
masm.branchTestObjShape(Assembler::Equal, object, expando->lastProperty(),
&success);
masm.pop(object);
masm.jump(failure);
masm.bind(&success);
masm.pop(object);
} else {
masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure);
}
} else if (obj->is<UnboxedArrayObject>()) {
MOZ_ASSERT(failure);
masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
} else if (obj->is<TypedObject>()) {
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object, JSObject::offsetOfGroup()),
ImmGCPtr(obj->group()), failure);
} else {
Shape* shape = obj->maybeShape();
MOZ_ASSERT(shape);
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object, JSObject::offsetOfShape()),
ImmGCPtr(shape), failure);
if (alwaysCheckGroup)
masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
}
}
static inline void
EmitLoadSlot(MacroAssembler& masm, NativeObject* holder, Shape* shape, Register holderReg,
TypedOrValueRegister output, Register scratchReg)
{
MOZ_ASSERT(holder);
NativeObject::slotsSizeMustNotOverflow();
if (holder->isFixedSlot(shape->slot())) {
Address addr(holderReg, NativeObject::getFixedSlotOffset(shape->slot()));
masm.loadTypedOrValue(addr, output);
} else {
masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), scratchReg);
Address addr(scratchReg, holder->dynamicSlotIndex(shape->slot()) * sizeof(Value));
masm.loadTypedOrValue(addr, output);
}
}
// Callers are expected to have already guarded on the shape of the
// object, which guarantees the object is a DOM proxy.
static void
CheckDOMProxyExpandoDoesNotShadow(JSContext* cx, MacroAssembler& masm, JSObject* obj,
jsid id, Register object, Label* stubFailure)
{
MOZ_ASSERT(IsCacheableDOMProxy(obj));
// Guard that the object does not have expando properties, or has an expando
// which is known to not have the desired property.
// For the remaining code, we need to reserve some registers to load a value.
// This is ugly, but unvaoidable.
AllocatableRegisterSet domProxyRegSet(RegisterSet::All());
domProxyRegSet.take(AnyRegister(object));
ValueOperand tempVal = domProxyRegSet.takeAnyValue();
masm.pushValue(tempVal);
Label failDOMProxyCheck;
Label domProxyOk;
Value expandoVal = GetProxyExtra(obj, GetDOMProxyExpandoSlot());
masm.loadPtr(Address(object, ProxyObject::offsetOfValues()), tempVal.scratchReg());
masm.loadValue(Address(tempVal.scratchReg(),
ProxyObject::offsetOfExtraSlotInValues(GetDOMProxyExpandoSlot())),
tempVal);
if (!expandoVal.isObject() && !expandoVal.isUndefined()) {
masm.branchTestValue(Assembler::NotEqual, tempVal, expandoVal, &failDOMProxyCheck);
ExpandoAndGeneration* expandoAndGeneration = (ExpandoAndGeneration*)expandoVal.toPrivate();
masm.movePtr(ImmPtr(expandoAndGeneration), tempVal.scratchReg());
masm.branch64(Assembler::NotEqual,
Address(tempVal.scratchReg(),
ExpandoAndGeneration::offsetOfGeneration()),
Imm64(expandoAndGeneration->generation),
&failDOMProxyCheck);
expandoVal = expandoAndGeneration->expando;
masm.loadValue(Address(tempVal.scratchReg(),
ExpandoAndGeneration::offsetOfExpando()),
tempVal);
}
// If the incoming object does not have an expando object then we're sure we're not
// shadowing.
masm.branchTestUndefined(Assembler::Equal, tempVal, &domProxyOk);
if (expandoVal.isObject()) {
MOZ_ASSERT(!expandoVal.toObject().as<NativeObject>().contains(cx, id));
// Reference object has an expando object that doesn't define the name. Check that
// the incoming object has an expando object with the same shape.
masm.branchTestObject(Assembler::NotEqual, tempVal, &failDOMProxyCheck);
masm.extractObject(tempVal, tempVal.scratchReg());
masm.branchPtr(Assembler::Equal,
Address(tempVal.scratchReg(), JSObject::offsetOfShape()),
ImmGCPtr(expandoVal.toObject().as<NativeObject>().lastProperty()),
&domProxyOk);
}
// Failure case: restore the tempVal registers and jump to failures.
masm.bind(&failDOMProxyCheck);
masm.popValue(tempVal);
masm.jump(stubFailure);
// Success case: restore the tempval and proceed.
masm.bind(&domProxyOk);
masm.popValue(tempVal);
}
static void
GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm,
IonCache::StubAttacher& attacher, JSObject* obj, JSObject* holder,
Shape* shape, Register object, TypedOrValueRegister output,
Label* failures = nullptr)
{
// If there's a single jump to |failures|, we can patch the shape guard
// jump directly. Otherwise, jump to the end of the stub, so there's a
// common point to patch.
bool multipleFailureJumps = (obj != holder)
|| obj->is<UnboxedPlainObject>()
|| (failures != nullptr && failures->used());
// If we have multiple failure jumps but didn't get a label from the
// outside, make one ourselves.
Label failures_;
if (multipleFailureJumps && !failures)
failures = &failures_;
TestMatchingReceiver(masm, attacher, object, obj, failures);
// If we need a scratch register, use either an output register or the
// object register. After this point, we cannot jump directly to
// |failures| since we may still have to pop the object register.
bool restoreScratch = false;
Register scratchReg = Register::FromCode(0); // Quell compiler warning.
if (obj != holder ||
obj->is<UnboxedPlainObject>() ||
!holder->as<NativeObject>().isFixedSlot(shape->slot()))
{
if (output.hasValue()) {
scratchReg = output.valueReg().scratchReg();
} else if (output.type() == MIRType_Double) {
scratchReg = object;
masm.push(scratchReg);
restoreScratch = true;
} else {
scratchReg = output.typedReg().gpr();
}
}
// Fast path: single failure jump, no prototype guards.
if (!multipleFailureJumps) {
EmitLoadSlot(masm, &holder->as<NativeObject>(), shape, object, output, scratchReg);
if (restoreScratch)
masm.pop(scratchReg);
attacher.jumpRejoin(masm);
return;
}
// Slow path: multiple jumps; generate prototype guards.
Label prototypeFailures;
Register holderReg;
if (obj != holder) {
// Note: this may clobber the object register if it's used as scratch.
GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg,
&prototypeFailures);
if (holder) {
// Guard on the holder's shape.
holderReg = scratchReg;
masm.movePtr(ImmGCPtr(holder), holderReg);
masm.branchPtr(Assembler::NotEqual,
Address(holderReg, JSObject::offsetOfShape()),
ImmGCPtr(holder->as<NativeObject>().lastProperty()),
&prototypeFailures);
} else {
// The property does not exist. Guard on everything in the
// prototype chain.
JSObject* proto = obj->getTaggedProto().toObjectOrNull();
Register lastReg = object;
MOZ_ASSERT(scratchReg != object);
while (proto) {
masm.loadObjProto(lastReg, scratchReg);
// Guard the shape of the current prototype.
masm.branchPtr(Assembler::NotEqual,
Address(scratchReg, JSObject::offsetOfShape()),
ImmGCPtr(proto->as<NativeObject>().lastProperty()),
&prototypeFailures);
proto = proto->getProto();
lastReg = scratchReg;
}
holderReg = InvalidReg;
}
} else if (obj->is<UnboxedPlainObject>()) {
holder = obj->as<UnboxedPlainObject>().maybeExpando();
holderReg = scratchReg;
masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), holderReg);
} else {
holderReg = object;
}
// Slot access.
if (holder)
EmitLoadSlot(masm, &holder->as<NativeObject>(), shape, holderReg, output, scratchReg);
else
masm.moveValue(UndefinedValue(), output.valueReg());
// Restore scratch on success.
if (restoreScratch)
masm.pop(scratchReg);
attacher.jumpRejoin(masm);
masm.bind(&prototypeFailures);
if (restoreScratch)
masm.pop(scratchReg);
masm.bind(failures);
attacher.jumpNextStub(masm);
}
static void
GenerateReadUnboxed(JSContext* cx, IonScript* ion, MacroAssembler& masm,
IonCache::StubAttacher& attacher, JSObject* obj,
const UnboxedLayout::Property* property,
Register object, TypedOrValueRegister output,
Label* failures = nullptr)
{
// Guard on the group of the object.
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object, JSObject::offsetOfGroup()),
ImmGCPtr(obj->group()), failures);
Address address(object, UnboxedPlainObject::offsetOfData() + property->offset);
masm.loadUnboxedProperty(address, property->type, output);
attacher.jumpRejoin(masm);
if (failures) {
masm.bind(failures);
attacher.jumpNextStub(masm);
}
}
static bool
EmitGetterCall(JSContext* cx, MacroAssembler& masm,
IonCache::StubAttacher& attacher, JSObject* obj,
JSObject* holder, HandleShape shape, bool holderIsReceiver,
LiveRegisterSet liveRegs, Register object,
TypedOrValueRegister output,
void* returnAddr)
{
MOZ_ASSERT(output.hasValue());
MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);
MOZ_ASSERT_IF(obj != holder, !holderIsReceiver);
// Remaining registers should basically be free, but we need to use |object| still
// so leave it alone.
AllocatableRegisterSet regSet(RegisterSet::All());
regSet.take(AnyRegister(object));
// This is a slower stub path, and we're going to be doing a call anyway. Don't need
// to try so hard to not use the stack. Scratch regs are just taken from the register
// set not including the input, current value saved on the stack, and restored when
// we're done with it.
Register scratchReg = regSet.takeAnyGeneral();
// Shape has a JSNative, PropertyOp or scripted getter function.
if (IsCacheableGetPropCallNative(obj, holder, shape)) {
Register argJSContextReg = regSet.takeAnyGeneral();
Register argUintNReg = regSet.takeAnyGeneral();
Register argVpReg = regSet.takeAnyGeneral();
JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
MOZ_ASSERT(target);
MOZ_ASSERT(target->isNative());
// Native functions have the signature:
// bool (*)(JSContext*, unsigned, Value* vp)
// Where vp[0] is space for an outparam, vp[1] is |this|, and vp[2] onward
// are the function arguments.
// Construct vp array:
// Push object value for |this|
masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object)));
// Push callee/outparam.
masm.Push(ObjectValue(*target));
// Preload arguments into registers.
masm.loadJSContext(argJSContextReg);
masm.move32(Imm32(0), argUintNReg);
masm.moveStackPtrTo(argVpReg);
// Push marking data for later use.
masm.Push(argUintNReg);
attacher.pushStubCodePointer(masm);
if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
return false;
masm.enterFakeExitFrame(IonOOLNativeExitFrameLayoutToken);
// Construct and execute call.
masm.setupUnalignedABICall(scratchReg);
masm.passABIArg(argJSContextReg);
masm.passABIArg(argUintNReg);
masm.passABIArg(argVpReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->native()));
// Test for failure.
masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
// Load the outparam vp[0] into output register(s).
Address outparam(masm.getStackPointer(), IonOOLNativeExitFrameLayout::offsetOfResult());
masm.loadTypedOrValue(outparam, output);
// masm.leaveExitFrame & pop locals
masm.adjustStack(IonOOLNativeExitFrameLayout::Size(0));
} else if (IsCacheableGetPropCallPropertyOp(obj, holder, shape)) {
Register argJSContextReg = regSet.takeAnyGeneral();
Register argObjReg = regSet.takeAnyGeneral();
Register argIdReg = regSet.takeAnyGeneral();
Register argVpReg = regSet.takeAnyGeneral();
GetterOp target = shape->getterOp();
MOZ_ASSERT(target);
// Push stubCode for marking.
attacher.pushStubCodePointer(masm);
// JSGetterOp: bool fn(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
// Push args on stack first so we can take pointers to make handles.
masm.Push(UndefinedValue());
masm.moveStackPtrTo(argVpReg);
// Push canonical jsid from shape instead of propertyname.
masm.Push(shape->propid(), scratchReg);
masm.moveStackPtrTo(argIdReg);
// Push the holder.
if (holderIsReceiver) {
// When the holder is also the current receiver, we just have a shape guard,
// so we might end up with a random object which is also guaranteed to have
// this JSGetterOp.
masm.Push(object);
} else {
// If the holder is on the prototype chain, the prototype-guarding
// only allows objects with the same holder.
masm.movePtr(ImmGCPtr(holder), scratchReg);
masm.Push(scratchReg);
}
masm.moveStackPtrTo(argObjReg);
masm.loadJSContext(argJSContextReg);
if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
return false;
masm.enterFakeExitFrame(IonOOLPropertyOpExitFrameLayoutToken);
// Make the call.
masm.setupUnalignedABICall(scratchReg);
masm.passABIArg(argJSContextReg);
masm.passABIArg(argObjReg);
masm.passABIArg(argIdReg);
masm.passABIArg(argVpReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target));
// Test for failure.
masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
// Load the outparam vp[0] into output register(s).
Address outparam(masm.getStackPointer(), IonOOLPropertyOpExitFrameLayout::offsetOfResult());
masm.loadTypedOrValue(outparam, output);
// masm.leaveExitFrame & pop locals.
masm.adjustStack(IonOOLPropertyOpExitFrameLayout::Size());
} else {
MOZ_ASSERT(IsCacheableGetPropCallScripted(obj, holder, shape));
JSFunction* target = &shape->getterValue().toObject().as<JSFunction>();
uint32_t framePushedBefore = masm.framePushed();
// Construct IonAccessorICFrameLayout.
uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), JitFrame_IonJS);
attacher.pushStubCodePointer(masm);
masm.Push(Imm32(descriptor));
masm.Push(ImmPtr(returnAddr));
// The JitFrameLayout pushed below will be aligned to JitStackAlignment,
// so we just have to make sure the stack is aligned after we push the
// |this| + argument Values.
uint32_t argSize = (target->nargs() + 1) * sizeof(Value);
uint32_t padding = ComputeByteAlignment(masm.framePushed() + argSize, JitStackAlignment);
MOZ_ASSERT(padding % sizeof(uintptr_t) == 0);
MOZ_ASSERT(padding < JitStackAlignment);
masm.reserveStack(padding);
for (size_t i = 0; i < target->nargs(); i++)
masm.Push(UndefinedValue());
masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object)));
masm.movePtr(ImmGCPtr(target), scratchReg);
descriptor = MakeFrameDescriptor(argSize + padding, JitFrame_IonAccessorIC);
masm.Push(Imm32(0)); // argc
masm.Push(scratchReg);
masm.Push(Imm32(descriptor));
// Check stack alignment. Add sizeof(uintptr_t) for the return address.
MOZ_ASSERT(((masm.framePushed() + sizeof(uintptr_t)) % JitStackAlignment) == 0);
// The getter has JIT code now and we will only discard the getter's JIT
// code when discarding all JIT code in the Zone, so we can assume it'll
// still have JIT code.
MOZ_ASSERT(target->hasJITCode());
masm.loadPtr(Address(scratchReg, JSFunction::offsetOfNativeOrScript()), scratchReg);
masm.loadBaselineOrIonRaw(scratchReg, scratchReg, nullptr);
masm.callJit(scratchReg);
masm.storeCallResultValue(output);
masm.freeStack(masm.framePushed() - framePushedBefore);
}
masm.icRestoreLive(liveRegs, aic);
return true;
}
static bool
GenerateCallGetter(JSContext* cx, IonScript* ion, MacroAssembler& masm,
IonCache::StubAttacher& attacher, JSObject* obj,
JSObject* holder, HandleShape shape, LiveRegisterSet& liveRegs, Register object,
TypedOrValueRegister output, void* returnAddr, Label* failures = nullptr)
{
MOZ_ASSERT(output.hasValue());
// Use the passed in label if there was one. Otherwise, we'll have to make our own.
Label stubFailure;
failures = failures ? failures : &stubFailure;
TestMatchingReceiver(masm, attacher, object, obj, failures);
Register scratchReg = output.valueReg().scratchReg();
bool spillObjReg = scratchReg == object;
Label pop1AndFail;
Label* maybePopAndFail = failures;
// If we're calling a getter on the global, inline the logic for the
// 'this' hook on the global lexical scope and manually push the global.
if (IsGlobalLexicalScope(obj))
masm.extractObject(Address(object, ScopeObject::offsetOfEnclosingScope()), object);
// Save off the object register if it aliases the scratchReg
if (spillObjReg) {
masm.push(object);
maybePopAndFail = &pop1AndFail;
}
// Note: this may clobber the object register if it's used as scratch.
if (obj != holder)
GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg, failures);
// Guard on the holder's shape.
Register holderReg = scratchReg;
masm.movePtr(ImmGCPtr(holder), holderReg);
masm.branchPtr(Assembler::NotEqual,
Address(holderReg, JSObject::offsetOfShape()),
ImmGCPtr(holder->as<NativeObject>().lastProperty()),
maybePopAndFail);
if (spillObjReg)
masm.pop(object);
// Now we're good to go to invoke the native call.
bool holderIsReceiver = (obj == holder);
if (!EmitGetterCall(cx, masm, attacher, obj, holder, shape, holderIsReceiver, liveRegs, object,
output, returnAddr))
return false;
// Rejoin jump.
attacher.jumpRejoin(masm);
// Jump to next stub.
if (spillObjReg) {
masm.bind(&pop1AndFail);
masm.pop(object);
}
masm.bind(failures);
attacher.jumpNextStub(masm);
return true;
}
static bool
GenerateArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
JSObject* obj, Register object, TypedOrValueRegister output, Label* failures)
{
MOZ_ASSERT(obj->is<ArrayObject>());
// Guard object is a dense array.
RootedShape shape(cx, obj->as<ArrayObject>().lastProperty());
if (!shape)
return false;
masm.branchTestObjShape(Assembler::NotEqual, object, shape, failures);
// Load length.
Register outReg;
if (output.hasValue()) {
outReg = output.valueReg().scratchReg();
} else {
MOZ_ASSERT(output.type() == MIRType_Int32);
outReg = output.typedReg().gpr();
}
masm.loadPtr(Address(object, NativeObject::offsetOfElements()), outReg);
masm.load32(Address(outReg, ObjectElements::offsetOfLength()), outReg);
// The length is an unsigned int, but the value encodes a signed int.
MOZ_ASSERT(object != outReg);
masm.branchTest32(Assembler::Signed, outReg, outReg, failures);
if (output.hasValue())
masm.tagValue(JSVAL_TYPE_INT32, outReg, output.valueReg());
/* Success. */
attacher.jumpRejoin(masm);
/* Failure. */
masm.bind(failures);
attacher.jumpNextStub(masm);
return true;
}
static void
GenerateUnboxedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
JSObject* array, Register object, TypedOrValueRegister output,
Label* failures)
{
Register outReg;
if (output.hasValue()) {
outReg = output.valueReg().scratchReg();
} else {
MOZ_ASSERT(output.type() == MIRType_Int32);
outReg = output.typedReg().gpr();
}
MOZ_ASSERT(object != outReg);
TestMatchingReceiver(masm, attacher, object, array, failures);
// Load length.
masm.load32(Address(object, UnboxedArrayObject::offsetOfLength()), outReg);
// Check for a length that fits in an int32.
masm.branchTest32(Assembler::Signed, outReg, outReg, failures);
if (output.hasValue())
masm.tagValue(JSVAL_TYPE_INT32, outReg, output.valueReg());
// Success.
attacher.jumpRejoin(masm);
// Failure.
masm.bind(failures);
attacher.jumpNextStub(masm);
}
// In this case, the code for TypedArray and SharedTypedArray is not the same,
// because the code embeds pointers to the respective class arrays. Code that
// caches the stub code must distinguish between the two cases.
static void
GenerateTypedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
Register object, TypedOrValueRegister output, Label* failures)
{
Register tmpReg;
if (output.hasValue()) {
tmpReg = output.valueReg().scratchReg();
} else {
MOZ_ASSERT(output.type() == MIRType_Int32);
tmpReg = output.typedReg().gpr();
}
MOZ_ASSERT(object != tmpReg);
// Implement the negated version of JSObject::isTypedArray predicate.
masm.loadObjClass(object, tmpReg);
masm.branchPtr(Assembler::Below, tmpReg, ImmPtr(&TypedArrayObject::classes[0]),
failures);
masm.branchPtr(Assembler::AboveOrEqual, tmpReg,
ImmPtr(&TypedArrayObject::classes[Scalar::MaxTypedArrayViewType]),
failures);
// Load length.
masm.loadTypedOrValue(Address(object, TypedArrayObject::lengthOffset()), output);
/* Success. */
attacher.jumpRejoin(masm);
/* Failure. */
masm.bind(failures);
attacher.jumpNextStub(masm);
}
static bool
IsCacheableArrayLength(JSContext* cx, HandleObject obj, TypedOrValueRegister output)
{
if (!obj->is<ArrayObject>())
return false;
if (output.type() != MIRType_Value && output.type() != MIRType_Int32) {
// The stub assumes that we always output Int32, so make sure our output
// is equipped to handle that.
return false;
}
// The emitted stub can only handle int32 lengths. If the length of the
// actual object does not fit in an int32 then don't attach a stub, as if
// the cache is idempotent we won't end up invalidating the compiled script
// otherwise.
if (obj->as<ArrayObject>().length() > INT32_MAX)
return false;
return true;
}
template <class GetPropCache>
static GetPropertyIC::NativeGetPropCacheability
CanAttachNativeGetProp(JSContext* cx, const GetPropCache& cache,
HandleObject obj, HandleId id,
MutableHandleNativeObject holder, MutableHandleShape shape,
bool skipArrayLen = false)
{
MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
if (!obj)
return GetPropertyIC::CanAttachNone;
// 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;
if (!LookupPropertyPure(cx, obj, id, &baseHolder, shape.address()))
return GetPropertyIC::CanAttachNone;
MOZ_ASSERT(!holder);
if (baseHolder) {
if (!baseHolder->isNative())
return GetPropertyIC::CanAttachNone;
holder.set(&baseHolder->as<NativeObject>());
}
RootedScript script(cx);
jsbytecode* pc;
cache.getScriptedLocation(&script, &pc);
if (IsCacheableGetPropReadSlotForIon(obj, holder, shape) ||
IsCacheableNoProperty(obj, holder, shape, pc, cache.output()))
{
return GetPropertyIC::CanAttachReadSlot;
}
// |length| is a non-configurable getter property on ArrayObjects. Any time this
// check would have passed, we can install a getter stub instead. Allow people to
// make that decision themselves with skipArrayLen
if (!skipArrayLen && JSID_IS_ATOM(id, cx->names().length) && cache.allowArrayLength(cx) &&
IsCacheableArrayLength(cx, obj, cache.output()))
{
// The array length property is non-configurable, which means both that
// checking the class of the object and the name of the property is enough
// and that we don't need to worry about monitoring, since we know the
// return type statically.
return GetPropertyIC::CanAttachArrayLength;
}
// IonBuilder guarantees that it's impossible to generate a GetPropertyIC with
// allowGetters() true and cache.output().hasValue() false. If this isn't true,
// we will quickly assert during stub generation.
//
// Be careful when adding support for other getters here: for outer window
// proxies, IonBuilder can innerize and pass us the inner window (the global),
// see IonBuilder::getPropTryInnerize. This is fine for native/scripted getters
// because IsCacheableGetPropCallNative and IsCacheableGetPropCallScripted
// handle this.
if (cache.allowGetters() &&
(IsCacheableGetPropCallNative(obj, holder, shape) ||
IsCacheableGetPropCallPropertyOp(obj, holder, shape) ||
IsCacheableGetPropCallScripted(obj, holder, shape)))
{
// Don't enable getter call if cache is idempotent, since they can be
// effectful. This is handled by allowGetters()
return GetPropertyIC::CanAttachCallGetter;
}
return GetPropertyIC::CanAttachNone;
}
static bool
EqualStringsHelper(JSString* str1, JSString* str2)
{
MOZ_ASSERT(str1->isAtom());
MOZ_ASSERT(!str2->isAtom());
MOZ_ASSERT(str1->length() == str2->length());
JSLinearString* str2Linear = str2->ensureLinear(nullptr);
if (!str2Linear)
return false;
return EqualChars(&str1->asLinear(), str2Linear);
}
static void
EmitIdGuard(MacroAssembler& masm, jsid id, TypedOrValueRegister idReg, Register objReg,
Register scratchReg, Label* failures)
{
MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
MOZ_ASSERT(idReg.type() == MIRType_String ||
idReg.type() == MIRType_Symbol ||
idReg.type() == MIRType_Value);
Register payloadReg;
if (idReg.type() == MIRType_Value) {
ValueOperand val = idReg.valueReg();
if (JSID_IS_SYMBOL(id)) {
masm.branchTestSymbol(Assembler::NotEqual, val, failures);
} else {
MOZ_ASSERT(JSID_IS_STRING(id));
masm.branchTestString(Assembler::NotEqual, val, failures);
}
masm.unboxNonDouble(val, scratchReg);
payloadReg = scratchReg;
} else {
payloadReg = idReg.typedReg().gpr();
}
if (JSID_IS_SYMBOL(id)) {
// For symbols, we can just do a pointer comparison.
masm.branchPtr(Assembler::NotEqual, payloadReg, ImmGCPtr(JSID_TO_SYMBOL(id)), failures);
} else {
PropertyName* name = JSID_TO_ATOM(id)->asPropertyName();
Label equal;
masm.branchPtr(Assembler::Equal, payloadReg, ImmGCPtr(name), &equal);
// The pointers are not equal, so if the input string is also an atom it
// must be a different string.
masm.branchTest32(Assembler::NonZero, Address(payloadReg, JSString::offsetOfFlags()),
Imm32(JSString::ATOM_BIT), failures);
// Check the length.
masm.branch32(Assembler::NotEqual, Address(payloadReg, JSString::offsetOfLength()),
Imm32(name->length()), failures);
// We have a non-atomized string with the same length. For now call a helper
// function to do the comparison.
LiveRegisterSet volatileRegs(RegisterSet::Volatile());
masm.PushRegsInMask(volatileRegs);
if (!volatileRegs.has(objReg))
masm.push(objReg);
masm.setupUnalignedABICall(objReg);
masm.movePtr(ImmGCPtr(name), objReg);
masm.passABIArg(objReg);
masm.passABIArg(payloadReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, EqualStringsHelper));
masm.mov(ReturnReg, scratchReg);
if (!volatileRegs.has(objReg))
masm.pop(objReg);
LiveRegisterSet ignore;
ignore.add(scratchReg);
masm.PopRegsInMaskIgnore(volatileRegs, ignore);
masm.branchIfFalseBool(scratchReg, failures);
masm.bind(&equal);
}
}
void
GetPropertyIC::emitIdGuard(MacroAssembler& masm, jsid id, Label* fail)
{
if (this->id().constant())
return;
Register scratch = output().valueReg().scratchReg();
EmitIdGuard(masm, id, this->id().reg(), object(), scratch, fail);
}
void
SetPropertyIC::emitIdGuard(MacroAssembler& masm, jsid id, Label* fail)
{
if (this->id().constant())
return;
EmitIdGuard(masm, id, this->id().reg(), object(), temp(), fail);
}
bool
GetPropertyIC::allowArrayLength(JSContext* cx) const
{
if (!idempotent())
return true;
uint32_t locationIndex, numLocations;
getLocationInfo(&locationIndex, &numLocations);
IonScript* ion = GetTopJitJSScript(cx)->ionScript();
CacheLocation* locs = ion->getCacheLocs(locationIndex);
for (size_t i = 0; i < numLocations; i++) {
CacheLocation& curLoc = locs[i];
StackTypeSet* bcTypes = TypeScript::BytecodeTypes(curLoc.script, curLoc.pc);
if (!bcTypes->hasType(TypeSet::Int32Type()))
return false;
}
return true;
}
bool
GetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(outerScript->ionScript() == ion);
RootedShape shape(cx);
RootedNativeObject holder(cx);
NativeGetPropCacheability type =
CanAttachNativeGetProp(cx, *this, obj, id, &holder, &shape);
if (type == CanAttachNone)
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
const char* attachKind;
JS::TrackedOutcome outcome = JS::TrackedOutcome::ICOptStub_GenericSuccess;
Label failures;
emitIdGuard(masm, id, &failures);
Label* maybeFailures = failures.used() ? &failures : nullptr;
switch (type) {
case CanAttachReadSlot:
GenerateReadSlot(cx, ion, masm, attacher, obj, holder,
shape, object(), output(), maybeFailures);
attachKind = idempotent() ? "idempotent reading"
: "non idempotent reading";
outcome = JS::TrackedOutcome::ICGetPropStub_ReadSlot;
break;
case CanAttachCallGetter:
if (!GenerateCallGetter(cx, ion, masm, attacher, obj, holder, shape,
liveRegs_, object(), output(), returnAddr, maybeFailures))
{
return false;
}
attachKind = "getter call";
outcome = JS::TrackedOutcome::ICGetPropStub_CallGetter;
break;
case CanAttachArrayLength:
if (!GenerateArrayLength(cx, masm, attacher, obj, object(), output(), &failures))
return false;
attachKind = "array length";
outcome = JS::TrackedOutcome::ICGetPropStub_ArrayLength;
break;
default:
MOZ_CRASH("Bad NativeGetPropCacheability");
}
return linkAndAttachStub(cx, masm, attacher, ion, attachKind, outcome);
}
bool
GetPropertyIC::tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(outerScript->ionScript() == ion);
if (!obj->is<UnboxedPlainObject>())
return true;
const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
if (!property)
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
Label failures;
emitIdGuard(masm, id, &failures);
Label* maybeFailures = failures.used() ? &failures : nullptr;
StubAttacher attacher(*this);
GenerateReadUnboxed(cx, ion, masm, attacher, obj, property, object(), output(), maybeFailures);
return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed",
JS::TrackedOutcome::ICGetPropStub_UnboxedRead);
}
bool
GetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(outerScript->ionScript() == ion);
if (!obj->is<UnboxedPlainObject>())
return true;
Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
if (!expando)
return true;
Shape* shape = expando->lookup(cx, id);
if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot())
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
Label failures;
emitIdGuard(masm, id, &failures);
Label* maybeFailures = failures.used() ? &failures : nullptr;
StubAttacher attacher(*this);
GenerateReadSlot(cx, ion, masm, attacher, obj, obj,
shape, object(), output(), maybeFailures);
return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed expando",
JS::TrackedOutcome::ICGetPropStub_UnboxedReadExpando);
}
bool
GetPropertyIC::tryAttachUnboxedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, void* returnAddr,
bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(outerScript->ionScript() == ion);
if (!obj->is<UnboxedArrayObject>())
return true;
if (!JSID_IS_ATOM(id, cx->names().length))
return true;
if (obj->as<UnboxedArrayObject>().length() > INT32_MAX)
return true;
if (!allowArrayLength(cx))
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
Label failures;
emitIdGuard(masm, id, &failures);
StubAttacher attacher(*this);
GenerateUnboxedArrayLength(cx, masm, attacher, obj, object(), output(), &failures);
return linkAndAttachStub(cx, masm, attacher, ion, "unboxed array length",
JS::TrackedOutcome::ICGetPropStub_UnboxedArrayLength);
}
bool
GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
if (!IsAnyTypedArray(obj))
return true;
if (!JSID_IS_ATOM(id, cx->names().length))
return true;
if (hasTypedArrayLengthStub(obj))
return true;
if (output().type() != MIRType_Value && output().type() != MIRType_Int32) {
// The next execution should cause an invalidation because the type
// does not fit.
return true;
}
if (idempotent())
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
Label failures;
emitIdGuard(masm, id, &failures);
GenerateTypedArrayLength(cx, masm, attacher, object(), output(), &failures);
setHasTypedArrayLengthStub(obj);
return linkAndAttachStub(cx, masm, attacher, ion, "typed array length",
JS::TrackedOutcome::ICGetPropStub_TypedArrayLength);
}
static void
PushObjectOpResult(MacroAssembler& masm)
{
static_assert(sizeof(ObjectOpResult) == sizeof(uintptr_t),
"ObjectOpResult size must match size reserved by masm.Push() here");
masm.Push(ImmWord(ObjectOpResult::Uninitialized));
}
static bool
ProxyGetProperty(JSContext* cx, HandleObject proxy, HandleId id, MutableHandleValue vp)
{
RootedValue receiver(cx, ObjectValue(*proxy));
return Proxy::get(cx, proxy, receiver, id, vp);
}
static bool
EmitCallProxyGet(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
jsid id, LiveRegisterSet liveRegs, Register object, TypedOrValueRegister output,
jsbytecode* pc, void* returnAddr)
{
MOZ_ASSERT(output.hasValue());
MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);
// Remaining registers should be free, but we need to use |object| still
// so leave it alone.
AllocatableRegisterSet regSet(RegisterSet::All());
regSet.take(AnyRegister(object));
// ProxyGetProperty(JSContext* cx, HandleObject proxy, HandleId id,
// MutableHandleValue vp)
Register argJSContextReg = regSet.takeAnyGeneral();
Register argProxyReg = regSet.takeAnyGeneral();
Register argIdReg = regSet.takeAnyGeneral();
Register argVpReg = regSet.takeAnyGeneral();
Register scratch = regSet.takeAnyGeneral();
// Push stubCode for marking.
attacher.pushStubCodePointer(masm);
// Push args on stack first so we can take pointers to make handles.
masm.Push(UndefinedValue());
masm.moveStackPtrTo(argVpReg);
masm.Push(id, scratch);
masm.moveStackPtrTo(argIdReg);
// Push the proxy. Also used as receiver.
masm.Push(object);
masm.moveStackPtrTo(argProxyReg);
masm.loadJSContext(argJSContextReg);
if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
return false;
masm.enterFakeExitFrame(IonOOLProxyExitFrameLayoutToken);
// Make the call.
masm.setupUnalignedABICall(scratch);
masm.passABIArg(argJSContextReg);
masm.passABIArg(argProxyReg);
masm.passABIArg(argIdReg);
masm.passABIArg(argVpReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ProxyGetProperty));
// Test for failure.
masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
// Load the outparam vp[0] into output register(s).
Address outparam(masm.getStackPointer(), IonOOLProxyExitFrameLayout::offsetOfResult());
masm.loadTypedOrValue(outparam, output);
// masm.leaveExitFrame & pop locals
masm.adjustStack(IonOOLProxyExitFrameLayout::Size());
masm.icRestoreLive(liveRegs, aic);
return true;
}
bool
GetPropertyIC::tryAttachDOMProxyShadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, void* returnAddr,
bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(IsCacheableDOMProxy(obj));
MOZ_ASSERT(monitoredResult());
MOZ_ASSERT(output().hasValue());
if (idempotent())
return true;
*emitted = true;
Label failures;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
emitIdGuard(masm, id, &failures);
// Guard on the shape of the object.
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object(), JSObject::offsetOfShape()),
ImmGCPtr(obj->maybeShape()),
&failures);
// No need for more guards: we know this is a DOM proxy, since the shape
// guard enforces a given JSClass, so just go ahead and emit the call to
// ProxyGet.
if (!EmitCallProxyGet(cx, masm, attacher, id, liveRegs_, object(), output(),
pc(), returnAddr))
{
return false;
}
// Success.
attacher.jumpRejoin(masm);
// Failure.
masm.bind(&failures);
attacher.jumpNextStub(masm);
return linkAndAttachStub(cx, masm, attacher, ion, "list base shadowed get",
JS::TrackedOutcome::ICGetPropStub_DOMProxyShadowed);
}
bool
GetPropertyIC::tryAttachDOMProxyUnshadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, bool resetNeeded,
void* returnAddr, bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(IsCacheableDOMProxy(obj));
MOZ_ASSERT(monitoredResult());
MOZ_ASSERT(output().hasValue());
RootedObject checkObj(cx, obj->getTaggedProto().toObjectOrNull());
RootedNativeObject holder(cx);
RootedShape shape(cx);
NativeGetPropCacheability canCache =
CanAttachNativeGetProp(cx, *this, checkObj, id, &holder, &shape,
/* skipArrayLen = */true);
MOZ_ASSERT(canCache != CanAttachArrayLength);
if (canCache == CanAttachNone)
return true;
// Make sure we observe our invariants if we're gonna deoptimize.
if (!holder && idempotent())
return true;
*emitted = true;
if (resetNeeded) {
// If we know that we have a DoesntShadowUnique object, then
// we reset the cache to clear out an existing IC for the object
// (if there is one). The generation is a constant in the generated
// code and we will not have the same generation again for this
// object, so the generation check in the existing IC would always
// fail anyway.
reset(Reprotect);
}
Label failures;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
emitIdGuard(masm, id, &failures);
// Guard on the shape of the object.
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object(), JSObject::offsetOfShape()),
ImmGCPtr(obj->maybeShape()),
&failures);
// Guard that our expando object hasn't started shadowing this property.
CheckDOMProxyExpandoDoesNotShadow(cx, masm, obj, id, object(), &failures);
if (holder) {
// Found the property on the prototype chain. Treat it like a native
// getprop.
Register scratchReg = output().valueReg().scratchReg();
GeneratePrototypeGuards(cx, ion, masm, obj, holder, object(), scratchReg, &failures);
// Rename scratch for clarity.
Register holderReg = scratchReg;
// Guard on the holder of the property
masm.movePtr(ImmGCPtr(holder), holderReg);
masm.branchPtr(Assembler::NotEqual,
Address(holderReg, JSObject::offsetOfShape()),
ImmGCPtr(holder->lastProperty()),
&failures);
if (canCache == CanAttachReadSlot) {
EmitLoadSlot(masm, holder, shape, holderReg, output(), scratchReg);
} else {
// EmitGetterCall() 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.
// The holderIsReceiver check needs to use |obj| though.
MOZ_ASSERT(canCache == CanAttachCallGetter);
MOZ_ASSERT(!idempotent());
bool holderIsReceiver = (obj == holder);
if (!EmitGetterCall(cx, masm, attacher, checkObj, holder, shape, holderIsReceiver,
liveRegs_, object(), output(), returnAddr))
{
return false;
}
}
} else {
// Property was not found on the prototype chain. Deoptimize down to
// proxy get call
MOZ_ASSERT(!idempotent());
if (!EmitCallProxyGet(cx, masm, attacher, id, liveRegs_, object(), output(),
pc(), returnAddr))
{
return false;
}
}
attacher.jumpRejoin(masm);
masm.bind(&failures);
attacher.jumpNextStub(masm);
return linkAndAttachStub(cx, masm, attacher, ion, "unshadowed proxy get",
JS::TrackedOutcome::ICGetPropStub_DOMProxyUnshadowed);
}
bool
GetPropertyIC::tryAttachProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
if (!obj->is<ProxyObject>())
return true;
// TI can't be sure about our properties, so make sure anything
// we return can be monitored directly.
if (!monitoredResult())
return true;
// Skim off DOM proxies.
if (IsCacheableDOMProxy(obj)) {
DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id);
if (shadows == ShadowCheckFailed)
return false;
if (DOMProxyIsShadowing(shadows))
return tryAttachDOMProxyShadowed(cx, outerScript, ion, obj, id, returnAddr, emitted);
MOZ_ASSERT(shadows == DoesntShadow || shadows == DoesntShadowUnique);
return tryAttachDOMProxyUnshadowed(cx, outerScript, ion, obj, id,
shadows == DoesntShadowUnique, returnAddr, emitted);
}
return tryAttachGenericProxy(cx, outerScript, ion, obj, id, returnAddr, emitted);
}
bool
GetPropertyIC::tryAttachGenericProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, void* returnAddr,
bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(obj->is<ProxyObject>());
MOZ_ASSERT(monitoredResult());
MOZ_ASSERT(output().hasValue());
if (hasGenericProxyStub())
return true;
if (idempotent())
return true;
*emitted = true;
Label failures;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
emitIdGuard(masm, id, &failures);
Register scratchReg = output().valueReg().scratchReg();
masm.branchTestObjectIsProxy(false, object(), scratchReg, &failures);
// Ensure that the incoming object is not a DOM proxy, so that we can get to
// the specialized stubs
masm.branchTestProxyHandlerFamily(Assembler::Equal, object(), scratchReg,
GetDOMProxyHandlerFamily(), &failures);
if (!EmitCallProxyGet(cx, masm, attacher, id, liveRegs_, object(), output(),
pc(), returnAddr))
{
return false;
}
attacher.jumpRejoin(masm);
masm.bind(&failures);
attacher.jumpNextStub(masm);
MOZ_ASSERT(!hasGenericProxyStub_);
hasGenericProxyStub_ = true;
return linkAndAttachStub(cx, masm, attacher, ion, "Generic Proxy get",
JS::TrackedOutcome::ICGetPropStub_GenericProxy);
}
bool
GetPropertyIC::tryAttachArgumentsLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
if (!JSID_IS_ATOM(id, cx->names().length))
return true;
if (!IsOptimizableArgumentsObjectForLength(obj))
return true;
MIRType outputType = output().type();
if (!(outputType == MIRType_Value || outputType == MIRType_Int32))
return true;
if (hasArgumentsLengthStub(obj->is<MappedArgumentsObject>()))
return true;
*emitted = true;
MOZ_ASSERT(!idempotent());
Label failures;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
emitIdGuard(masm, id, &failures);
Register tmpReg;
if (output().hasValue()) {
tmpReg = output().valueReg().scratchReg();
} else {
MOZ_ASSERT(output().type() == MIRType_Int32);
tmpReg = output().typedReg().gpr();
}
MOZ_ASSERT(object() != tmpReg);
masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, obj->getClass(), &failures);
// Get initial ArgsObj length value, test if length has been overridden.
masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
&failures);
masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg);
// If output is Int32, result is already in right place, otherwise box it into output.
if (output().hasValue())
masm.tagValue(JSVAL_TYPE_INT32, tmpReg, output().valueReg());
// Success.
attacher.jumpRejoin(masm);
// Failure.
masm.bind(&failures);
attacher.jumpNextStub(masm);
if (obj->is<UnmappedArgumentsObject>()) {
MOZ_ASSERT(!hasUnmappedArgumentsLengthStub_);
hasUnmappedArgumentsLengthStub_ = true;
return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (unmapped)",
JS::TrackedOutcome::ICGetPropStub_ArgumentsLength);
}
MOZ_ASSERT(!hasMappedArgumentsLengthStub_);
hasMappedArgumentsLengthStub_ = true;
return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (mapped)",
JS::TrackedOutcome::ICGetPropStub_ArgumentsLength);
}
static void
GenerateReadModuleNamespace(JSContext* cx, IonScript* ion, MacroAssembler& masm,
IonCache::StubAttacher& attacher, ModuleNamespaceObject* ns,
ModuleEnvironmentObject* env, Shape* shape, Register object,
TypedOrValueRegister output, Label* failures)
{
MOZ_ASSERT(ns);
MOZ_ASSERT(env);
// If we have multiple failure jumps but didn't get a label from the
// outside, make one ourselves.
Label failures_;
if (!failures)
failures = &failures_;
// Check for the specific namespace object.
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, object, ImmGCPtr(ns), failures);
// If we need a scratch register, use either an output register or the
// object register.
bool restoreScratch = false;
Register scratchReg = InvalidReg; // Quell compiler warning.
if (output.hasValue()) {
scratchReg = output.valueReg().scratchReg();
} else if (output.type() == MIRType_Double) {
masm.push(object);
scratchReg = object;
restoreScratch = true;
} else {
scratchReg = output.typedReg().gpr();
}
// Slot access.
Register envReg = scratchReg;
masm.movePtr(ImmGCPtr(env), envReg);
EmitLoadSlot(masm, &env->as<NativeObject>(), shape, envReg, output, scratchReg);
// Restore scratch on success.
if (restoreScratch)
masm.pop(object);
attacher.jumpRejoin(masm);
masm.bind(failures);
attacher.jumpNextStub(masm);
}
bool
GetPropertyIC::tryAttachModuleNamespace(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, void* returnAddr,
bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(outerScript->ionScript() == ion);
if (!obj->is<ModuleNamespaceObject>())
return true;
Rooted<ModuleNamespaceObject*> ns(cx, &obj->as<ModuleNamespaceObject>());
RootedModuleEnvironmentObject env(cx);
RootedShape shape(cx);
if (!ns->bindings().lookup(id, env.address(), shape.address()))
return true;
// Don't emit a stub until the target binding has been initialized.
if (env->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL))
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
Label failures;
emitIdGuard(masm, id, &failures);
Label* maybeFailures = failures.used() ? &failures : nullptr;
GenerateReadModuleNamespace(cx, ion, masm, attacher, ns, env,
shape, object(), output(), maybeFailures);
return linkAndAttachStub(cx, masm, attacher, ion, "module namespace",
JS::TrackedOutcome::ICGetPropStub_ReadSlot);
}
static bool
ValueToNameOrSymbolId(JSContext* cx, HandleValue idval, MutableHandleId id, bool* nameOrSymbol)
{
*nameOrSymbol = false;
if (!idval.isString() && !idval.isSymbol())
return true;
if (!ValueToId<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;
}
bool
GetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleValue idval, bool* emitted)
{
MOZ_ASSERT(!*emitted);
if (!canAttachStub())
return true;
RootedId id(cx);
bool nameOrSymbol;
if (!ValueToNameOrSymbolId(cx, idval, &id, &nameOrSymbol))
return false;
if (nameOrSymbol) {
if (!*emitted && !tryAttachArgumentsLength(cx, outerScript, ion, obj, id, emitted))
return false;
void* returnAddr = GetReturnAddressToIonCode(cx);
if (!*emitted && !tryAttachModuleNamespace(cx, outerScript, ion, obj, id, returnAddr, emitted))
return false;
if (!*emitted && !tryAttachProxy(cx, outerScript, ion, obj, id, returnAddr, emitted))
return false;
if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, returnAddr, emitted))
return false;
if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, returnAddr, emitted))
return false;
if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, returnAddr, emitted))
return false;
if (!*emitted && !tryAttachUnboxedArrayLength(cx, outerScript, ion, obj, id, returnAddr, emitted))
return false;
if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, id, emitted))
return false;
}
if (idval.isInt32()) {
if (!*emitted && !tryAttachArgumentsElement(cx, outerScript, ion, obj, idval, emitted))
return false;
if (!*emitted && !tryAttachDenseElement(cx, outerScript, ion, obj, idval, emitted))
return false;
if (!*emitted && !tryAttachDenseElementHole(cx, outerScript, ion, obj, idval, emitted))
return false;
}
if (idval.isInt32() || idval.isString()) {
if (!*emitted && !tryAttachTypedOrUnboxedArrayElement(cx, outerScript, ion, obj, idval, emitted))
return false;
}
if (!*emitted)
JitSpew(JitSpew_IonIC, "Failed to attach GETPROP cache");
return true;
}
/* static */ bool
GetPropertyIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex,
HandleObject obj, HandleValue idval, MutableHandleValue vp)
{
IonScript* ion = outerScript->ionScript();
GetPropertyIC& cache = ion->getCache(cacheIndex).toGetProperty();
// Override the return value if we are invalidated (bug 728188).
AutoDetectInvalidation adi(cx, vp, ion);
// If the cache is idempotent, we will redo the op in the interpreter.
if (cache.idempotent())
adi.disable();
// For now, just stop generating new stubs once we hit the stub count
// limit. Once we can make calls from within generated stubs, a new call
// stub will be generated instead and the previous stubs unlinked.
bool emitted = false;
if (!cache.isDisabled()) {
if (!cache.tryAttachStub(cx, outerScript, ion, obj, idval, &emitted))
return false;
cache.maybeDisable(emitted);
}
if (cache.idempotent() && !emitted) {
// Invalidate the cache if the property was not found, or was found on
// a non-native object. This ensures:
// 1) The property read has no observable side-effects.
// 2) There's no need to dynamically monitor the return type. This would
// be complicated since (due to GVN) there can be multiple pc's
// associated with a single idempotent cache.
JitSpew(JitSpew_IonIC, "Invalidating from idempotent cache %s:%" PRIuSIZE,
outerScript->filename(), outerScript->lineno());
outerScript->setInvalidatedIdempotentCache();
// Do not re-invalidate if the lookup already caused invalidation.
if (!outerScript->hasIonScript())
return true;
return Invalidate(cx, outerScript);
}
jsbytecode* pc = cache.idempotent() ? nullptr : cache.pc();
if (!pc || *pc == JSOP_GETPROP || *pc == JSOP_CALLPROP || *pc == JSOP_LENGTH) {
if (!GetProperty(cx, obj, obj, idval.toString()->asAtom().asPropertyName(), vp))
return false;
} else {
MOZ_ASSERT(*pc == JSOP_GETELEM || *pc == JSOP_CALLELEM);
if (!GetObjectElementOperation(cx, JSOp(*pc), obj, obj, idval, vp))
return false;
}
if (!cache.idempotent()) {
RootedScript script(cx);
jsbytecode* pc;
cache.getScriptedLocation(&script, &pc);
// Monitor changes to cache entry.
if (!cache.monitoredResult())
TypeScript::Monitor(cx, script, pc, vp);
}
return true;
}
void
GetPropertyIC::reset(ReprotectCode reprotect)
{
IonCache::reset(reprotect);
hasTypedArrayLengthStub_ = false;
hasMappedArgumentsLengthStub_ = false;
hasUnmappedArgumentsLengthStub_ = false;
hasMappedArgumentsElementStub_ = false;
hasUnmappedArgumentsElementStub_ = false;
hasGenericProxyStub_ = false;
hasDenseStub_ = false;
}
void
IonCache::disable()
{
reset(Reprotect);
this->disabled_ = 1;
}
void
GetPropertyIC::maybeDisable(bool emitted)
{
if (emitted) {
failedUpdates_ = 0;
return;
}
if (!canAttachStub() && id().constant()) {
// Don't disable the cache (and discard stubs) if we have a GETPROP and
// attached the maximum number of stubs. This can happen when JS code
// uses an AST-like data structure and accesses a field of a "base
// class", like node.nodeType. This should be temporary until we handle
// this case better, see bug 1107515.
return;
}
if (++failedUpdates_ > MAX_FAILED_UPDATES) {
JitSpew(JitSpew_IonIC, "Disable inline cache");
disable();
}
}
void
IonCache::reset(ReprotectCode reprotect)
{
this->stubCount_ = 0;
PatchJump(initialJump_, fallbackLabel_, reprotect);
lastJump_ = initialJump_;
}
// Jump to failure if a value being written is not a property for obj/id.
static void
CheckTypeSetForWrite(MacroAssembler& masm, JSObject* obj, jsid id,
Register scratch, ConstantOrRegister value, Label* failure)
{
TypedOrValueRegister valReg = value.reg();
ObjectGroup* group = obj->group();
MOZ_ASSERT(!group->unknownProperties());
HeapTypeSet* propTypes = group->maybeGetProperty(id);
MOZ_ASSERT(propTypes);
// guardTypeSet can read from type sets without triggering read barriers.
TypeSet::readBarrier(propTypes);
masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratch, failure);
}
static void
GenerateSetSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
JSObject* obj, Shape* shape, Register object, Register tempReg,
ConstantOrRegister value, bool needsTypeBarrier, bool checkTypeset,
Label* failures)
{
TestMatchingReceiver(masm, attacher, object, obj, failures, needsTypeBarrier);
// Guard that the incoming value is in the type set for the property
// if a type barrier is required.
if (checkTypeset) {
MOZ_ASSERT(needsTypeBarrier);
CheckTypeSetForWrite(masm, obj, shape->propid(), tempReg, value, failures);
}
NativeObject::slotsSizeMustNotOverflow();
if (obj->is<UnboxedPlainObject>()) {
obj = obj->as<UnboxedPlainObject>().maybeExpando();
masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), tempReg);
object = tempReg;
}
if (obj->as<NativeObject>().isFixedSlot(shape->slot())) {
Address addr(object, NativeObject::getFixedSlotOffset(shape->slot()));
if (cx->zone()->needsIncrementalBarrier())
masm.callPreBarrier(addr, MIRType_Value);
masm.storeConstantOrRegister(value, addr);
} else {
masm.loadPtr(Address(object, NativeObject::offsetOfSlots()), tempReg);
Address addr(tempReg, obj->as<NativeObject>().dynamicSlotIndex(shape->slot()) * sizeof(Value));
if (cx->zone()->needsIncrementalBarrier())
masm.callPreBarrier(addr, MIRType_Value);
masm.storeConstantOrRegister(value, addr);
}
attacher.jumpRejoin(masm);
masm.bind(failures);
attacher.jumpNextStub(masm);
}
bool
SetPropertyIC::attachSetSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleShape shape, bool checkTypeset)
{
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
Label failures;
emitIdGuard(masm, shape->propid(), &failures);
GenerateSetSlot(cx, masm, attacher, obj, shape, object(), temp(), value(), needsTypeBarrier(),
checkTypeset, &failures);
return linkAndAttachStub(cx, masm, attacher, ion, "setting",
JS::TrackedOutcome::ICSetPropStub_Slot);
}
static bool
IsCacheableSetPropCallNative(HandleObject obj, HandleObject holder, HandleShape shape)
{
if (!shape || !IsCacheableProtoChainForIon(obj, holder))
return false;
return shape->hasSetterValue() && shape->setterObject() &&
shape->setterObject()->is<JSFunction>() &&
shape->setterObject()->as<JSFunction>().isNative();
}
static bool
IsCacheableSetPropCallScripted(HandleObject obj, HandleObject holder, HandleShape shape)
{
if (!shape || !IsCacheableProtoChainForIon(obj, holder))
return false;
return shape->hasSetterValue() && shape->setterObject() &&
shape->setterObject()->is<JSFunction>() &&
shape->setterObject()->as<JSFunction>().hasJITCode();
}
static bool
IsCacheableSetPropCallPropertyOp(HandleObject obj, HandleObject holder, HandleShape shape)
{
if (!shape)
return false;
if (!IsCacheableProtoChainForIon(obj, holder))
return false;
if (shape->hasSlot())
return false;
if (shape->hasDefaultSetter())
return false;
if (shape->hasSetterValue())
return false;
// Despite the vehement claims of Shape.h that writable() is only relevant
// for data descriptors, some SetterOps care desperately about its
// value. The flag should be always true, apart from these rare instances.
if (!shape->writable())
return false;
return true;
}
static bool
ReportStrictErrorOrWarning(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool strict,
JS::ObjectOpResult& result)
{
return result.reportStrictErrorOrWarning(cx, obj, id, strict);
}
template <class FrameLayout>
void
EmitObjectOpResultCheck(MacroAssembler& masm, Label* failure, bool strict,
Register scratchReg,
Register argJSContextReg,
Register argObjReg,
Register argIdReg,
Register argStrictReg,
Register argResultReg)
{
// if (!result) {
Label noStrictError;
masm.branch32(Assembler::Equal,
Address(masm.getStackPointer(),
FrameLayout::offsetOfObjectOpResult()),
Imm32(ObjectOpResult::OkCode),
&noStrictError);
// if (!ReportStrictErrorOrWarning(cx, obj, id, strict, &result))
// goto failure;
masm.loadJSContext(argJSContextReg);
masm.computeEffectiveAddress(
Address(masm.getStackPointer(), FrameLayout::offsetOfObject()),
argObjReg);
masm.computeEffectiveAddress(
Address(masm.getStackPointer(), FrameLayout::offsetOfId()),
argIdReg);
masm.move32(Imm32(strict), argStrictReg);
masm.computeEffectiveAddress(
Address(masm.getStackPointer(), FrameLayout::offsetOfObjectOpResult()),
argResultReg);
masm.setupUnalignedABICall(scratchReg);
masm.passABIArg(argJSContextReg);
masm.passABIArg(argObjReg);
masm.passABIArg(argIdReg);
masm.passABIArg(argStrictReg);
masm.passABIArg(argResultReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ReportStrictErrorOrWarning));
masm.branchIfFalseBool(ReturnReg, failure);
// }
masm.bind(&noStrictError);
}
static bool
ProxySetProperty(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, bool strict)
{
RootedValue receiver(cx, ObjectValue(*proxy));
ObjectOpResult result;
return Proxy::set(cx, proxy, id, v, receiver, result)
&& result.checkStrictErrorOrWarning(cx, proxy, id, strict);
}
static bool
EmitCallProxySet(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
HandleId propId, LiveRegisterSet liveRegs, Register object,
ConstantOrRegister value, void* returnAddr, bool strict)
{
MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);
// Remaining registers should be free, but we still need to use |object| so
// leave it alone.
//
// WARNING: We do not take() the register used by |value|, if any, so
// regSet is going to re-allocate it. Hence the emitted code must not touch
// any of the registers allocated from regSet until after the last use of
// |value|. (We can't afford to take it, either, because x86.)
AllocatableRegisterSet regSet(RegisterSet::All());
regSet.take(AnyRegister(object));
// ProxySetProperty(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
// bool strict);
Register argJSContextReg = regSet.takeAnyGeneral();
Register argProxyReg = regSet.takeAnyGeneral();
Register argIdReg = regSet.takeAnyGeneral();
Register argValueReg = regSet.takeAnyGeneral();
Register argStrictReg = regSet.takeAnyGeneral();
Register scratch = regSet.takeAnyGeneral();
// Push stubCode for marking.
attacher.pushStubCodePointer(masm);
// Push args on stack so we can take pointers to make handles.
// Push value before touching any other registers (see WARNING above).
masm.Push(value);
masm.moveStackPtrTo(argValueReg);
masm.move32(Imm32(strict), argStrictReg);
masm.Push(propId, scratch);
masm.moveStackPtrTo(argIdReg);
// Push object.
masm.Push(object);
masm.moveStackPtrTo(argProxyReg);
masm.loadJSContext(argJSContextReg);
if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
return false;
masm.enterFakeExitFrame(IonOOLProxyExitFrameLayoutToken);
// Make the call.
masm.setupUnalignedABICall(scratch);
masm.passABIArg(argJSContextReg);
masm.passABIArg(argProxyReg);
masm.passABIArg(argIdReg);
masm.passABIArg(argValueReg);
masm.passABIArg(argStrictReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ProxySetProperty));
// Test for error.
masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
// masm.leaveExitFrame & pop locals
masm.adjustStack(IonOOLProxyExitFrameLayout::Size());
masm.icRestoreLive(liveRegs, aic);
return true;
}
bool
SetPropertyIC::attachGenericProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleId id, void* returnAddr)
{
MOZ_ASSERT(!hasGenericProxyStub());
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
Label failures;
emitIdGuard(masm, id, &failures);
{
masm.branchTestObjectIsProxy(false, object(), temp(), &failures);
// Remove the DOM proxies. They'll take care of themselves so this stub doesn't
// catch too much. The failure case is actually Equal. Fall through to the failure code.
masm.branchTestProxyHandlerFamily(Assembler::Equal, object(), temp(),
GetDOMProxyHandlerFamily(), &failures);
}
if (!EmitCallProxySet(cx, masm, attacher, id, liveRegs_, object(), value(),
returnAddr, strict()))
{
return false;
}
attacher.jumpRejoin(masm);
masm.bind(&failures);
attacher.jumpNextStub(masm);
MOZ_ASSERT(!hasGenericProxyStub_);
hasGenericProxyStub_ = true;
return linkAndAttachStub(cx, masm, attacher, ion, "generic proxy set",
JS::TrackedOutcome::ICSetPropStub_GenericProxy);
}
bool
SetPropertyIC::attachDOMProxyShadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, void* returnAddr)
{
MOZ_ASSERT(IsCacheableDOMProxy(obj));
Label failures;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
emitIdGuard(masm, id, &failures);
// Guard on the shape of the object.
masm.branchPtr(Assembler::NotEqual,
Address(object(), JSObject::offsetOfShape()),
ImmGCPtr(obj->maybeShape()), &failures);
// No need for more guards: we know this is a DOM proxy, since the shape
// guard enforces a given JSClass, so just go ahead and emit the call to
// ProxySet.
if (!EmitCallProxySet(cx, masm, attacher, id, liveRegs_, object(),
value(), returnAddr, strict()))
{
return false;
}
// Success.
attacher.jumpRejoin(masm);
// Failure.
masm.bind(&failures);
attacher.jumpNextStub(masm);
return linkAndAttachStub(cx, masm, attacher, ion, "DOM proxy shadowed set",
JS::TrackedOutcome::ICSetPropStub_DOMProxyShadowed);
}
static bool
GenerateCallSetter(JSContext* cx, IonScript* ion, MacroAssembler& masm,
IonCache::StubAttacher& attacher, HandleObject obj, HandleObject holder,
HandleShape shape, bool strict, Register object, Register tempReg,
ConstantOrRegister value, Label* failure, LiveRegisterSet liveRegs,
void* returnAddr)
{
// Generate prototype guards if needed.
{
// Generate prototype/shape guards.
if (obj != holder)
GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, tempReg, failure);
masm.movePtr(ImmGCPtr(holder), tempReg);
masm.branchPtr(Assembler::NotEqual,
Address(tempReg, JSObject::offsetOfShape()),
ImmGCPtr(holder->as<NativeObject>().lastProperty()),
failure);
}
// Good to go for invoking setter.
MacroAssembler::AfterICSaveLive aic = masm.icSaveLive(liveRegs);
// Remaining registers should basically be free, but we need to use |object| still
// so leave it alone. And of course we need our value, if it's not a constant.
AllocatableRegisterSet regSet(RegisterSet::All());
if (!value.constant())
regSet.take(value.reg());
bool valueAliasesObject = !regSet.has(object);
if (!valueAliasesObject)
regSet.take(object);
regSet.take(tempReg);
// This is a slower stub path, and we're going to be doing a call anyway. Don't need
// to try so hard to not use the stack. Scratch regs are just taken from the register
// set not including the input, current value saved on the stack, and restored when
// we're done with it.
//
// Be very careful not to use any of these before value is pushed, since they
// might shadow.
if (IsCacheableSetPropCallNative(obj, holder, shape)) {
Register argJSContextReg = regSet.takeAnyGeneral();
Register argVpReg = regSet.takeAnyGeneral();
MOZ_ASSERT(shape->hasSetterValue() && shape->setterObject() &&
shape->setterObject()->is<JSFunction>());
JSFunction* target = &shape->setterObject()->as<JSFunction>();
MOZ_ASSERT(target->isNative());
Register argUintNReg = regSet.takeAnyGeneral();
// Set up the call:
// bool (*)(JSContext*, unsigned, Value* vp)
// vp[0] is callee/outparam
// vp[1] is |this|
// vp[2] is the value
// Build vp and move the base into argVpReg.
masm.Push(value);
masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object)));
masm.Push(ObjectValue(*target));
masm.moveStackPtrTo(argVpReg);
// Preload other regs
masm.loadJSContext(argJSContextReg);
masm.move32(Imm32(1), argUintNReg);
// Push data for GC marking
masm.Push(argUintNReg);
attacher.pushStubCodePointer(masm);
if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
return false;
masm.enterFakeExitFrame(IonOOLNativeExitFrameLayoutToken);
// Make the call
masm.setupUnalignedABICall(tempReg);
masm.passABIArg(argJSContextReg);
masm.passABIArg(argUintNReg);
masm.passABIArg(argVpReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->native()));
// Test for failure.
masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
// masm.leaveExitFrame & pop locals.
masm.adjustStack(IonOOLNativeExitFrameLayout::Size(1));
} else if (IsCacheableSetPropCallPropertyOp(obj, holder, shape)) {
// We can't take all our registers up front, because on x86 we need 2
// for the value, one for scratch, 5 for the arguments, which makes 8,
// but we only have 7 to work with. So only grab the ones we need
// before we push value and release its reg back into the set.
Register argResultReg = regSet.takeAnyGeneral();
SetterOp target = shape->setterOp();
MOZ_ASSERT(target);
// JSSetterOp: bool fn(JSContext* cx, HandleObject obj,
// HandleId id, HandleValue value, ObjectOpResult& result);
// First, allocate an ObjectOpResult on the stack. We push this before
// the stubCode pointer in order to match the layout of
// IonOOLSetterOpExitFrameLayout.
PushObjectOpResult(masm);
masm.moveStackPtrTo(argResultReg);
attacher.pushStubCodePointer(masm);
// Push args on stack so we can take pointers to make handles.
if (value.constant()) {
masm.Push(value.value());
} else {
masm.Push(value.reg());
if (!valueAliasesObject)
regSet.add(value.reg());
}
// OK, now we can grab our remaining registers and grab the pointer to
// what we just pushed into one of them.
Register argJSContextReg = regSet.takeAnyGeneral();
Register argValueReg = regSet.takeAnyGeneral();
// We can just reuse the "object" register for argObjReg
Register argObjReg = object;
Register argIdReg = regSet.takeAnyGeneral();
masm.moveStackPtrTo(argValueReg);
// push canonical jsid from shape instead of propertyname.
masm.Push(shape->propid(), argIdReg);
masm.moveStackPtrTo(argIdReg);
masm.Push(object);
masm.moveStackPtrTo(argObjReg);
masm.loadJSContext(argJSContextReg);
if (!masm.icBuildOOLFakeExitFrame(returnAddr, aic))
return false;
masm.enterFakeExitFrame(IonOOLSetterOpExitFrameLayoutToken);
// Make the call.
masm.setupUnalignedABICall(tempReg);
masm.passABIArg(argJSContextReg);
masm.passABIArg(argObjReg);
masm.passABIArg(argIdReg);
masm.passABIArg(argValueReg);
masm.passABIArg(argResultReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target));
// Test for error.
masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
// Test for strict failure. We emit the check even in non-strict mode
// in order to pick up the warning if extraWarnings is enabled.
EmitObjectOpResultCheck<IonOOLSetterOpExitFrameLayout>(masm, masm.exceptionLabel(),
strict, tempReg,
argJSContextReg, argObjReg,
argIdReg, argValueReg,
argResultReg);
// masm.leaveExitFrame & pop locals.
masm.adjustStack(IonOOLSetterOpExitFrameLayout::Size());
} else {
MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, shape));
JSFunction* target = &shape->setterValue().toObject().as<JSFunction>();
uint32_t framePushedBefore = masm.framePushed();
// Construct IonAccessorICFrameLayout.
uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), JitFrame_IonJS);
attacher.pushStubCodePointer(masm);
masm.Push(Imm32(descriptor));
masm.Push(ImmPtr(returnAddr));
// The JitFrameLayout pushed below will be aligned to JitStackAlignment,
// so we just have to make sure the stack is aligned after we push the
// |this| + argument Values.
uint32_t numArgs = Max(size_t(1), target->nargs());
uint32_t argSize = (numArgs + 1) * sizeof(Value);
uint32_t padding = ComputeByteAlignment(masm.framePushed() + argSize, JitStackAlignment);
MOZ_ASSERT(padding % sizeof(uintptr_t) == 0);
MOZ_ASSERT(padding < JitStackAlignment);
masm.reserveStack(padding);
for (size_t i = 1; i < target->nargs(); i++)
masm.Push(UndefinedValue());
masm.Push(value);
masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object)));
masm.movePtr(ImmGCPtr(target), tempReg);
descriptor = MakeFrameDescriptor(argSize + padding, JitFrame_IonAccessorIC);
masm.Push(Imm32(1)); // argc
masm.Push(tempReg);
masm.Push(Imm32(descriptor));
// Check stack alignment. Add sizeof(uintptr_t) for the return address.
MOZ_ASSERT(((masm.framePushed() + sizeof(uintptr_t)) % JitStackAlignment) == 0);
// The setter has JIT code now and we will only discard the setter's JIT
// code when discarding all JIT code in the Zone, so we can assume it'll
// still have JIT code.
MOZ_ASSERT(target->hasJITCode());
masm.loadPtr(Address(tempReg, JSFunction::offsetOfNativeOrScript()), tempReg);
masm.loadBaselineOrIonRaw(tempReg, tempReg, nullptr);
masm.callJit(tempReg);
masm.freeStack(masm.framePushed() - framePushedBefore);
}
masm.icRestoreLive(liveRegs, aic);
return true;
}
static bool
IsCacheableDOMProxyUnshadowedSetterCall(JSContext* cx, HandleObject obj, HandleId id,
MutableHandleObject holder, MutableHandleShape shape)
{
MOZ_ASSERT(IsCacheableDOMProxy(obj));
RootedObject checkObj(cx, obj->getTaggedProto().toObjectOrNull());
if (!checkObj)
return false;
if (!LookupPropertyPure(cx, obj, id, holder.address(), shape.address()))
return false;
if (!holder)
return false;
return IsCacheableSetPropCallNative(checkObj, holder, shape) ||
IsCacheableSetPropCallPropertyOp(checkObj, holder, shape) ||
IsCacheableSetPropCallScripted(checkObj, holder, shape);
}
bool
SetPropertyIC::attachDOMProxyUnshadowed(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, void* returnAddr)
{
MOZ_ASSERT(IsCacheableDOMProxy(obj));
Label failures;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
emitIdGuard(masm, id, &failures);
// Guard on the shape of the object.
masm.branchPtr(Assembler::NotEqual,
Address(object(), JSObject::offsetOfShape()),
ImmGCPtr(obj->maybeShape()), &failures);
// Guard that our expando object hasn't started shadowing this property.
CheckDOMProxyExpandoDoesNotShadow(cx, masm, obj, id, object(), &failures);
RootedObject holder(cx);
RootedShape shape(cx);
if (IsCacheableDOMProxyUnshadowedSetterCall(cx, obj, id, &holder, &shape)) {
if (!GenerateCallSetter(cx, ion, masm, attacher, obj, holder, shape, strict(),
object(), temp(), value(), &failures, liveRegs_, returnAddr))
{
return false;
}
} else {
// Either there was no proto, or the property wasn't appropriately found on it.
// Drop back to just a call to Proxy::set().
if (!EmitCallProxySet(cx, masm, attacher, id, liveRegs_, object(),
value(), returnAddr, strict()))
{
return false;
}
}
// Success.
attacher.jumpRejoin(masm);
// Failure.
masm.bind(&failures);
attacher.jumpNextStub(masm);
return linkAndAttachStub(cx, masm, attacher, ion, "DOM proxy unshadowed set",
JS::TrackedOutcome::ICSetPropStub_DOMProxyUnshadowed);
}
bool
SetPropertyIC::attachCallSetter(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleObject holder, HandleShape shape,
void* returnAddr)
{
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
Label failure;
emitIdGuard(masm, shape->propid(), &failure);
TestMatchingReceiver(masm, attacher, object(), obj, &failure);
if (!GenerateCallSetter(cx, ion, masm, attacher, obj, holder, shape, strict(),
object(), temp(), value(), &failure, liveRegs_, returnAddr))
{
return false;
}
// Rejoin jump.
attacher.jumpRejoin(masm);
// Jump to next stub.
masm.bind(&failure);
attacher.jumpNextStub(masm);
return linkAndAttachStub(cx, masm, attacher, ion, "setter call",
JS::TrackedOutcome::ICSetPropStub_CallSetter);
}
static void
GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
JSObject* obj, Shape* oldShape, ObjectGroup* oldGroup,
Register object, Register tempReg, ConstantOrRegister value,
bool checkTypeset, Label* failures)
{
// Use a modified version of TestMatchingReceiver that uses the old shape and group.
masm.branchTestObjGroup(Assembler::NotEqual, object, oldGroup, failures);
if (obj->maybeShape()) {
masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, failures);
} else {
MOZ_ASSERT(obj->is<UnboxedPlainObject>());
Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failures);
masm.loadPtr(expandoAddress, tempReg);
masm.branchTestObjShape(Assembler::NotEqual, tempReg, oldShape, failures);
}
Shape* newShape = obj->maybeShape();
if (!newShape)
newShape = obj->as<UnboxedPlainObject>().maybeExpando()->lastProperty();
// Guard that the incoming value is in the type set for the property
// if a type barrier is required.
if (checkTypeset)
CheckTypeSetForWrite(masm, obj, newShape->propid(), tempReg, value, failures);
// Guard shapes along prototype chain.
JSObject* proto = obj->getProto();
Register protoReg = tempReg;
bool first = true;
while (proto) {
Shape* protoShape = proto->as<NativeObject>().lastProperty();
// Load next prototype.
masm.loadObjProto(first ? object : protoReg, protoReg);
first = false;
// Ensure that its shape matches.
masm.branchTestObjShape(Assembler::NotEqual, protoReg, protoShape, failures);
proto = proto->getProto();
}
// Call a stub to (re)allocate dynamic slots, if necessary.
uint32_t newNumDynamicSlots = obj->is<UnboxedPlainObject>()
? obj->as<UnboxedPlainObject>().maybeExpando()->numDynamicSlots()
: obj->as<NativeObject>().numDynamicSlots();
if (NativeObject::dynamicSlotsCount(oldShape) != newNumDynamicSlots) {
AllocatableRegisterSet regs(RegisterSet::Volatile());
LiveRegisterSet save(regs.asLiveSet());
masm.PushRegsInMask(save);
// Get 2 temp registers, without clobbering the object register.
regs.takeUnchecked(object);
Register temp1 = regs.takeAnyGeneral();
Register temp2 = regs.takeAnyGeneral();
if (obj->is<UnboxedPlainObject>()) {
// Pass the expando object to the stub.
masm.Push(object);
masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object);
}
masm.setupUnalignedABICall(temp1);
masm.loadJSContext(temp1);
masm.passABIArg(temp1);
masm.passABIArg(object);
masm.move32(Imm32(newNumDynamicSlots), temp2);
masm.passABIArg(temp2);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, NativeObject::growSlotsDontReportOOM));
// Branch on ReturnReg before restoring volatile registers, so
// ReturnReg isn't clobbered.
uint32_t framePushedAfterCall = masm.framePushed();
Label allocFailed, allocDone;
masm.branchIfFalseBool(ReturnReg, &allocFailed);
masm.jump(&allocDone);
masm.bind(&allocFailed);
if (obj->is<UnboxedPlainObject>())
masm.Pop(object);
masm.PopRegsInMask(save);
masm.jump(failures);
masm.bind(&allocDone);
masm.setFramePushed(framePushedAfterCall);
if (obj->is<UnboxedPlainObject>())
masm.Pop(object);
masm.PopRegsInMask(save);
}
bool popObject = false;
if (obj->is<UnboxedPlainObject>()) {
masm.push(object);
popObject = true;
obj = obj->as<UnboxedPlainObject>().maybeExpando();
masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object);
}
// Write the object or expando object's new shape.
Address shapeAddr(object, JSObject::offsetOfShape());
if (cx->zone()->needsIncrementalBarrier())
masm.callPreBarrier(shapeAddr, MIRType_Shape);
masm.storePtr(ImmGCPtr(newShape), shapeAddr);
if (oldGroup != obj->group()) {
MOZ_ASSERT(!obj->is<UnboxedPlainObject>());
// Changing object's group from a partially to fully initialized group,
// per the acquired properties analysis. Only change the group if the
// old group still has a newScript.
Label noTypeChange, skipPop;
masm.loadPtr(Address(object, JSObject::offsetOfGroup()), tempReg);
masm.branchPtr(Assembler::Equal,
Address(tempReg, ObjectGroup::offsetOfAddendum()),
ImmWord(0),
&noTypeChange);
Address groupAddr(object, JSObject::offsetOfGroup());
if (cx->zone()->needsIncrementalBarrier())
masm.callPreBarrier(groupAddr, MIRType_ObjectGroup);
masm.storePtr(ImmGCPtr(obj->group()), groupAddr);
masm.bind(&noTypeChange);
}
// Set the value on the object. Since this is an add, obj->lastProperty()
// must be the shape of the property we are adding.
NativeObject::slotsSizeMustNotOverflow();
if (obj->as<NativeObject>().isFixedSlot(newShape->slot())) {
Address addr(object, NativeObject::getFixedSlotOffset(newShape->slot()));
masm.storeConstantOrRegister(value, addr);
} else {
masm.loadPtr(Address(object, NativeObject::offsetOfSlots()), tempReg);
Address addr(tempReg, obj->as<NativeObject>().dynamicSlotIndex(newShape->slot()) * sizeof(Value));
masm.storeConstantOrRegister(value, addr);
}
if (popObject)
masm.pop(object);
// Success.
attacher.jumpRejoin(masm);
// Failure.
masm.bind(failures);
attacher.jumpNextStub(masm);
}
bool
SetPropertyIC::attachAddSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, HandleShape oldShape,
HandleObjectGroup oldGroup, bool checkTypeset)
{
MOZ_ASSERT_IF(!needsTypeBarrier(), !checkTypeset);
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
Label failures;
emitIdGuard(masm, id, &failures);
GenerateAddSlot(cx, masm, attacher, obj, oldShape, oldGroup, object(), temp(), value(),
checkTypeset, &failures);
return linkAndAttachStub(cx, masm, attacher, ion, "adding",
JS::TrackedOutcome::ICSetPropStub_AddSlot);
}
static bool
CanInlineSetPropTypeCheck(JSObject* obj, jsid id, ConstantOrRegister val, bool* checkTypeset)
{
bool shouldCheck = false;
ObjectGroup* group = obj->group();
if (!group->unknownProperties()) {
HeapTypeSet* propTypes = group->maybeGetProperty(id);
if (!propTypes)
return false;
if (!propTypes->unknown()) {
if (obj->isSingleton() && !propTypes->nonConstantProperty())
return false;
shouldCheck = true;
if (val.constant()) {
// If the input is a constant, then don't bother if the barrier will always fail.
if (!propTypes->hasType(TypeSet::GetValueType(val.value())))
return false;
shouldCheck = false;
} else {
TypedOrValueRegister reg = val.reg();
// We can do the same trick as above for primitive types of specialized registers.
// TIs handling of objects is complicated enough to warrant a runtime
// check, as we can't statically handle the case where the typeset
// contains the specific object, but doesn't have ANYOBJECT set.
if (reg.hasTyped() && reg.type() != MIRType_Object) {
JSValueType valType = ValueTypeFromMIRType(reg.type());
if (!propTypes->hasType(TypeSet::PrimitiveType(valType)))
return false;
shouldCheck = false;
}
}
}
}
*checkTypeset = shouldCheck;
return true;
}
static bool
IsPropertySetInlineable(NativeObject* obj, HandleId id, MutableHandleShape pshape,
ConstantOrRegister val, bool needsTypeBarrier, bool* checkTypeset)
{
// CanInlineSetPropTypeCheck assumes obj has a non-lazy group.
MOZ_ASSERT(!obj->hasLazyGroup());
// Do a pure non-proto chain climbing lookup. See note in
// CanAttachNativeGetProp.
pshape.set(obj->lookupPure(id));
if (!pshape)
return false;
if (!pshape->hasSlot())
return false;
if (!pshape->hasDefaultSetter())
return false;
if (!pshape->writable())
return false;
*checkTypeset = false;
if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
return false;
return true;
}
static bool
PrototypeChainShadowsPropertyAdd(JSContext* cx, JSObject* obj, jsid id)
{
// Walk up the object prototype chain and ensure that all prototypes
// are native, and that all prototypes have no getter or setter
// defined on the property
for (JSObject* proto = obj->getProto(); proto; proto = proto->getProto()) {
// If prototype is non-native, don't optimize
if (!proto->isNative())
return true;
// If prototype defines this property in a non-plain way, don't optimize
Shape* protoShape = proto->as<NativeObject>().lookupPure(id);
if (protoShape && !protoShape->hasDefaultSetter())
return true;
// Otherwise, if there's no such property, watch out for a resolve
// hook that would need to be invoked and thus prevent inlining of
// property addition.
if (ClassMayResolveId(cx->names(), proto->getClass(), id, proto))
return true;
}
return false;
}
static bool
IsPropertyAddInlineable(JSContext* cx, NativeObject* obj, HandleId id, ConstantOrRegister val,
HandleShape oldShape, bool needsTypeBarrier, bool* checkTypeset)
{
// If the shape of the object did not change, then this was not an add.
if (obj->lastProperty() == oldShape)
return false;
Shape* shape = obj->lookupPure(id);
if (!shape || shape->inDictionary() || !shape->hasSlot() || !shape->hasDefaultSetter())
return false;
// If we have a shape at this point and the object's shape changed, then
// the shape must be the one we just added.
MOZ_ASSERT(shape == obj->lastProperty());
// Watch out for resolve hooks.
if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj))
return false;
// Likewise for an addProperty hook, since we'll need to invoke it.
if (obj->getClass()->addProperty)
return false;
if (!obj->nonProxyIsExtensible() || !shape->writable())
return false;
if (PrototypeChainShadowsPropertyAdd(cx, obj, id))
return false;
// Don't attach if we are adding a property to an object which the new
// script properties analysis hasn't been performed for yet, as there
// may be a group change required here afterwards.
if (obj->group()->newScript() && !obj->group()->newScript()->analyzed())
return false;
*checkTypeset = false;
if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
return false;
return true;
}
static SetPropertyIC::NativeSetPropCacheability
CanAttachNativeSetProp(JSContext* cx, HandleObject obj, HandleId id, ConstantOrRegister val,
bool needsTypeBarrier, MutableHandleObject holder,
MutableHandleShape shape, bool* checkTypeset)
{
// See if the property exists on the object.
if (obj->isNative() && IsPropertySetInlineable(&obj->as<NativeObject>(), id, shape, val,
needsTypeBarrier, checkTypeset))
{
return SetPropertyIC::CanAttachSetSlot;
}
// If we couldn't find the property on the object itself, do a full, but
// still pure lookup for setters.
if (!LookupPropertyPure(cx, obj, id, holder.address(), shape.address()))
return SetPropertyIC::CanAttachNone;
// If the object doesn't have the property, we don't know if we can attach
// a stub to add the property until we do the VM call to add. If the
// property exists as a data property on the prototype, we should add
// a new, shadowing property.
if (obj->isNative() && (!shape || (obj != holder && holder->isNative() &&
shape->hasDefaultSetter() && shape->hasSlot())))
{
return SetPropertyIC::MaybeCanAttachAddSlot;
}
if (IsImplicitNonNativeProperty(shape))
return SetPropertyIC::CanAttachNone;
if (IsCacheableSetPropCallPropertyOp(obj, holder, shape) ||
IsCacheableSetPropCallNative(obj, holder, shape) ||
IsCacheableSetPropCallScripted(obj, holder, shape))
{
return SetPropertyIC::CanAttachCallSetter;
}
return SetPropertyIC::CanAttachNone;
}
static void
GenerateSetUnboxed(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
JSObject* obj, jsid id, uint32_t unboxedOffset, JSValueType unboxedType,
Register object, Register tempReg, ConstantOrRegister value, bool checkTypeset,
Label* failures)
{
// Guard on the type of the object.
masm.branchPtr(Assembler::NotEqual,
Address(object, JSObject::offsetOfGroup()),
ImmGCPtr(obj->group()), failures);
if (checkTypeset)
CheckTypeSetForWrite(masm, obj, id, tempReg, value, failures);
Address address(object, UnboxedPlainObject::offsetOfData() + unboxedOffset);
if (cx->zone()->needsIncrementalBarrier()) {
if (unboxedType == JSVAL_TYPE_OBJECT)
masm.callPreBarrier(address, MIRType_Object);
else if (unboxedType == JSVAL_TYPE_STRING)
masm.callPreBarrier(address, MIRType_String);
else
MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(unboxedType));
}
masm.storeUnboxedProperty(address, unboxedType, value, failures);
attacher.jumpRejoin(masm);
masm.bind(failures);
attacher.jumpNextStub(masm);
}
static bool
CanAttachSetUnboxed(JSContext* cx, HandleObject obj, HandleId id, ConstantOrRegister val,
bool needsTypeBarrier, bool* checkTypeset,
uint32_t* unboxedOffset, JSValueType* unboxedType)
{
if (!obj->is<UnboxedPlainObject>())
return false;
const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
if (property) {
*checkTypeset = false;
if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
return false;
*unboxedOffset = property->offset;
*unboxedType = property->type;
return true;
}
return false;
}
static bool
CanAttachSetUnboxedExpando(JSContext* cx, HandleObject obj, HandleId id, ConstantOrRegister val,
bool needsTypeBarrier, bool* checkTypeset, Shape** pshape)
{
if (!obj->is<UnboxedPlainObject>())
return false;
Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
if (!expando)
return false;
Shape* shape = expando->lookupPure(id);
if (!shape || !shape->hasDefaultSetter() || !shape->hasSlot() || !shape->writable())
return false;
*checkTypeset = false;
if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
return false;
*pshape = shape;
return true;
}
static bool
CanAttachAddUnboxedExpando(JSContext* cx, HandleObject obj, HandleShape oldShape,
HandleId id, ConstantOrRegister val,
bool needsTypeBarrier, bool* checkTypeset)
{
if (!obj->is<UnboxedPlainObject>())
return false;
Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
if (!expando || expando->inDictionaryMode())
return false;
Shape* newShape = expando->lastProperty();
if (newShape->isEmptyShape() || newShape->propid() != id || newShape->previous() != oldShape)
return false;
MOZ_ASSERT(newShape->hasDefaultSetter() && newShape->hasSlot() && newShape->writable());
if (PrototypeChainShadowsPropertyAdd(cx, obj, id))
return false;
*checkTypeset = false;
if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
return false;
return true;
}
bool
SetPropertyIC::tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, bool* emitted)
{
MOZ_ASSERT(!*emitted);
bool checkTypeset = false;
uint32_t unboxedOffset;
JSValueType unboxedType;
if (!CanAttachSetUnboxed(cx, obj, id, value(), needsTypeBarrier(), &checkTypeset,
&unboxedOffset, &unboxedType))
{
return true;
}
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
Label failures;
emitIdGuard(masm, id, &failures);
GenerateSetUnboxed(cx, masm, attacher, obj, id, unboxedOffset, unboxedType,
object(), temp(), value(), checkTypeset, &failures);
return linkAndAttachStub(cx, masm, attacher, ion, "set_unboxed",
JS::TrackedOutcome::ICSetPropStub_SetUnboxed);
}
bool
SetPropertyIC::tryAttachProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, bool* emitted)
{
MOZ_ASSERT(!*emitted);
if (!obj->is<ProxyObject>())
return true;
void* returnAddr = GetReturnAddressToIonCode(cx);
if (IsCacheableDOMProxy(obj)) {
DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id);
if (shadows == ShadowCheckFailed)
return false;
if (DOMProxyIsShadowing(shadows)) {
if (!attachDOMProxyShadowed(cx, outerScript, ion, obj, id, returnAddr))
return false;
*emitted = true;
return true;
}
MOZ_ASSERT(shadows == DoesntShadow || shadows == DoesntShadowUnique);
if (shadows == DoesntShadowUnique)
reset(Reprotect);
if (!attachDOMProxyUnshadowed(cx, outerScript, ion, obj, id, returnAddr))
return false;
*emitted = true;
return true;
}
if (hasGenericProxyStub())
return true;
if (!attachGenericProxy(cx, outerScript, ion, id, returnAddr))
return false;
*emitted = true;
return true;
}
bool
SetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, bool* emitted, bool* tryNativeAddSlot)
{
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(!*tryNativeAddSlot);
RootedShape shape(cx);
RootedObject holder(cx);
bool checkTypeset = false;
NativeSetPropCacheability canCache = CanAttachNativeSetProp(cx, obj, id, value(), needsTypeBarrier(),
&holder, &shape, &checkTypeset);
switch (canCache) {
case CanAttachNone:
return true;
case CanAttachSetSlot: {
RootedNativeObject nobj(cx, &obj->as<NativeObject>());
if (!attachSetSlot(cx, outerScript, ion, nobj, shape, checkTypeset))
return false;
*emitted = true;
return true;
}
case CanAttachCallSetter: {
void* returnAddr = GetReturnAddressToIonCode(cx);
if (!attachCallSetter(cx, outerScript, ion, obj, holder, shape, returnAddr))
return false;
*emitted = true;
return true;
}
case MaybeCanAttachAddSlot:
*tryNativeAddSlot = true;
return true;
}
MOZ_CRASH("Unreachable");
}
bool
SetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, bool* emitted)
{
MOZ_ASSERT(!*emitted);
RootedShape shape(cx);
bool checkTypeset = false;
if (!CanAttachSetUnboxedExpando(cx, obj, id, value(), needsTypeBarrier(),
&checkTypeset, shape.address()))
{
return true;
}
if (!attachSetSlot(cx, outerScript, ion, obj, shape, checkTypeset))
return false;
*emitted = true;
return true;
}
bool
SetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleValue idval, HandleValue value,
MutableHandleId id, bool* emitted, bool* tryNativeAddSlot)
{
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(!*tryNativeAddSlot);
if (!canAttachStub() || obj->watched())
return true;
bool nameOrSymbol;
if (!ValueToNameOrSymbolId(cx, idval, id, &nameOrSymbol))
return false;
if (nameOrSymbol) {
if (!*emitted && !tryAttachProxy(cx, outerScript, ion, obj, id, emitted))
return false;
if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, emitted, tryNativeAddSlot))
return false;
if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, emitted))
return false;
if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, emitted))
return false;
}
if (idval.isInt32()) {
if (!*emitted && !tryAttachDenseElement(cx, outerScript, ion, obj, idval, emitted))
return false;
if (!*emitted &&
!tryAttachTypedArrayElement(cx, outerScript, ion, obj, idval, value, emitted))
{
return false;
}
}
return true;
}
bool
SetPropertyIC::tryAttachAddSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, HandleObjectGroup oldGroup,
HandleShape oldShape, bool tryNativeAddSlot, bool* emitted)
{
MOZ_ASSERT(!*emitted);
if (!canAttachStub())
return true;
if (!JSID_IS_STRING(id) && !JSID_IS_SYMBOL(id))
return true;
// A GC may have caused cache.value() to become stale as it is not traced.
// In this case the IonScript will have been invalidated, so check for that.
// Assert no further GC is possible past this point.
JS::AutoAssertNoAlloc nogc;
if (ion->invalidated())
return true;
// The property did not exist before, now we can try to inline the property add.
bool checkTypeset = false;
if (tryNativeAddSlot &&
IsPropertyAddInlineable(cx, &obj->as<NativeObject>(), id, value(), oldShape,
needsTypeBarrier(), &checkTypeset))
{
if (!attachAddSlot(cx, outerScript, ion, obj, id, oldShape, oldGroup, checkTypeset))
return false;
*emitted = true;
return true;
}
checkTypeset = false;
if (CanAttachAddUnboxedExpando(cx, obj, oldShape, id, value(), needsTypeBarrier(),
&checkTypeset))
{
if (!attachAddSlot(cx, outerScript, ion, obj, id, oldShape, oldGroup, checkTypeset))
return false;
*emitted = true;
return true;
}
return true;
}
bool
SetPropertyIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, HandleObject obj,
HandleValue idval, HandleValue value)
{
IonScript* ion = outerScript->ionScript();
SetPropertyIC& cache = ion->getCache(cacheIndex).toSetProperty();
// Remember the old group and shape if we may attach an add-property stub.
// Also, some code under tryAttachStub depends on obj having a non-lazy
// group, see for instance CanInlineSetPropTypeCheck.
RootedObjectGroup oldGroup(cx);
RootedShape oldShape(cx);
if (cache.canAttachStub()) {
oldGroup = obj->getGroup(cx);
if (!oldGroup)
return false;
oldShape = obj->maybeShape();
if (obj->is<UnboxedPlainObject>()) {
MOZ_ASSERT(!oldShape);
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
oldShape = expando->lastProperty();
}
}
RootedId id(cx);
bool emitted = false;
bool tryNativeAddSlot = false;
if (!cache.tryAttachStub(cx, outerScript, ion, obj, idval, value, &id, &emitted,
&tryNativeAddSlot))
{
return false;
}
// Set/Add the property on the object, the inlined cache are setup for the next execution.
if (JSOp(*cache.pc()) == JSOP_INITGLEXICAL) {
RootedScript script(cx);
jsbytecode* pc;
cache.getScriptedLocation(&script, &pc);
MOZ_ASSERT(!script->hasNonSyntacticScope());
InitGlobalLexicalOperation(cx, &cx->global()->lexicalScope(), script, pc, value);
} else if (*cache.pc() == JSOP_SETELEM || *cache.pc() == JSOP_STRICTSETELEM) {
if (!SetObjectElement(cx, obj, idval, value, cache.strict()))
return false;
} else {
RootedPropertyName name(cx, idval.toString()->asAtom().asPropertyName());
if (!SetProperty(cx, obj, name, value, cache.strict(), cache.pc()))
return false;
}
if (!emitted &&
!cache.tryAttachAddSlot(cx, outerScript, ion, obj, id, oldGroup, oldShape,
tryNativeAddSlot, &emitted))
{
return false;
}
if (!emitted)
JitSpew(JitSpew_IonIC, "Failed to attach SETPROP cache");
return true;
}
void
SetPropertyIC::reset(ReprotectCode reprotect)
{
IonCache::reset(reprotect);
hasGenericProxyStub_ = false;
hasDenseStub_ = false;
}
static bool
GenerateDenseElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
JSObject* obj, const Value& idval, Register object,
TypedOrValueRegister index, TypedOrValueRegister output)
{
Label failures;
// Guard object's shape.
RootedShape shape(cx, obj->as<NativeObject>().lastProperty());
if (!shape)
return false;
masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
// Ensure the index is an int32 value.
Register indexReg = InvalidReg;
if (index.hasValue()) {
indexReg = output.scratchReg().gpr();
MOZ_ASSERT(indexReg != InvalidReg);
ValueOperand val = index.valueReg();
masm.branchTestInt32(Assembler::NotEqual, val, &failures);
// Unbox the index.
masm.unboxInt32(val, indexReg);
} else {
MOZ_ASSERT(!index.typedReg().isFloat());
indexReg = index.typedReg().gpr();
}
// Load elements vector.
masm.push(object);
masm.loadPtr(Address(object, NativeObject::offsetOfElements()), object);
Label hole;
// Guard on the initialized length.
Address initLength(object, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &hole);
// Check for holes & load the value.
masm.loadElementTypedOrValue(BaseObjectElementIndex(object, indexReg),
output, true, &hole);
masm.pop(object);
attacher.jumpRejoin(masm);
// All failures flow to here.
masm.bind(&hole);
masm.pop(object);
masm.bind(&failures);
attacher.jumpNextStub(masm);
return true;
}
bool
GetPropertyIC::tryAttachDenseElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleValue idval, bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
if (hasDenseStub())
return true;
if (!obj->isNative() || !idval.isInt32())
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
if (!GenerateDenseElement(cx, masm, attacher, obj, idval, object(), id().reg(), output()))
return false;
setHasDenseStub();
return linkAndAttachStub(cx, masm, attacher, ion, "dense array",
JS::TrackedOutcome::ICGetElemStub_Dense);
}
/* static */ bool
GetPropertyIC::canAttachDenseElementHole(JSObject* obj, HandleValue idval, TypedOrValueRegister output)
{
if (!idval.isInt32() || idval.toInt32() < 0)
return false;
if (!output.hasValue())
return false;
if (!obj->isNative())
return false;
if (obj->as<NativeObject>().getDenseInitializedLength() == 0)
return false;
while (obj) {
if (obj->isIndexed())
return false;
if (ClassCanHaveExtraProperties(obj->getClass()))
return false;
JSObject* proto = obj->getProto();
if (!proto)
break;
if (!proto->isNative())
return false;
// Make sure objects on the prototype don't have dense elements.
if (proto->as<NativeObject>().getDenseInitializedLength() != 0)
return false;
obj = proto;
}
return true;
}
static bool
GenerateDenseElementHole(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
IonScript* ion, JSObject* obj, HandleValue idval,
Register object, TypedOrValueRegister index, TypedOrValueRegister output)
{
MOZ_ASSERT(GetPropertyIC::canAttachDenseElementHole(obj, idval, output));
Register scratchReg = output.valueReg().scratchReg();
// Guard on the shape and group, to prevent non-dense elements from appearing.
Label failures;
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object, JSObject::offsetOfShape()),
ImmGCPtr(obj->as<NativeObject>().lastProperty()), &failures);
if (obj->hasUncacheableProto()) {
masm.loadPtr(Address(object, JSObject::offsetOfGroup()), scratchReg);
Address proto(scratchReg, ObjectGroup::offsetOfProto());
masm.branchPtr(Assembler::NotEqual, proto, ImmGCPtr(obj->getProto()), &failures);
}
JSObject* pobj = obj->getProto();
while (pobj) {
MOZ_ASSERT(pobj->as<NativeObject>().lastProperty());
masm.movePtr(ImmGCPtr(pobj), scratchReg);
if (pobj->hasUncacheableProto()) {
MOZ_ASSERT(!pobj->isSingleton());
Address groupAddr(scratchReg, JSObject::offsetOfGroup());
masm.branchPtr(Assembler::NotEqual, groupAddr, ImmGCPtr(pobj->group()), &failures);
}
// Make sure the shape matches, to avoid non-dense elements.
masm.branchPtr(Assembler::NotEqual, Address(scratchReg, JSObject::offsetOfShape()),
ImmGCPtr(pobj->as<NativeObject>().lastProperty()), &failures);
// Load elements vector.
masm.loadPtr(Address(scratchReg, NativeObject::offsetOfElements()), scratchReg);
// Also make sure there are no dense elements.
Label hole;
Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::NotEqual, initLength, Imm32(0), &failures);
pobj = pobj->getProto();
}
// Ensure the index is an int32 value.
Register indexReg;
if (index.hasValue()) {
// Unbox the index.
ValueOperand val = index.valueReg();
masm.branchTestInt32(Assembler::NotEqual, val, &failures);
indexReg = scratchReg;
masm.unboxInt32(val, indexReg);
} else {
MOZ_ASSERT(index.type() == MIRType_Int32);
indexReg = index.typedReg().gpr();
}
// Make sure index is nonnegative.
masm.branch32(Assembler::LessThan, indexReg, Imm32(0), &failures);
// Save the object register.
Register elementsReg = object;
masm.push(object);
// Load elements vector.
masm.loadPtr(Address(object, NativeObject::offsetOfElements()), elementsReg);
// Guard on the initialized length.
Label hole;
Address initLength(elementsReg, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &hole);
// Load the value.
Label done;
masm.loadValue(BaseObjectElementIndex(elementsReg, indexReg), output.valueReg());
masm.branchTestMagic(Assembler::NotEqual, output.valueReg(), &done);
// Load undefined for the hole.
masm.bind(&hole);
masm.moveValue(UndefinedValue(), output.valueReg());
masm.bind(&done);
// Restore the object register.
if (elementsReg == object)
masm.pop(object);
attacher.jumpRejoin(masm);
// All failure flows through here.
masm.bind(&failures);
attacher.jumpNextStub(masm);
return true;
}
bool
GetPropertyIC::tryAttachDenseElementHole(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleValue idval, bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
if (!monitoredResult())
return true;
if (!canAttachDenseElementHole(obj, idval, output()))
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
GenerateDenseElementHole(cx, masm, attacher, ion, obj, idval, object(), id().reg(), output());
return linkAndAttachStub(cx, masm, attacher, ion, "dense hole",
JS::TrackedOutcome::ICGetElemStub_DenseHole);
}
/* static */ bool
GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& idval,
TypedOrValueRegister output)
{
if (!IsAnyTypedArray(obj) && !obj->is<UnboxedArrayObject>())
return false;
MOZ_ASSERT(idval.isInt32() || idval.isString());
// Don't emit a stub if the access is out of bounds. We make to make
// certain that we monitor the type coming out of the typed array when
// we generate the stub. Out of bounds accesses will hit the fallback
// path.
uint32_t index;
if (idval.isInt32()) {
index = idval.toInt32();
} else {
index = GetIndexFromString(idval.toString());
if (index == UINT32_MAX)
return false;
}
if (IsAnyTypedArray(obj)) {
if (index >= AnyTypedArrayLength(obj))
return false;
// The output register is not yet specialized as a float register, the only
// way to accept float typed arrays for now is to return a Value type.
uint32_t arrayType = AnyTypedArrayType(obj);
if (arrayType == Scalar::Float32 || arrayType == Scalar::Float64)
return output.hasValue();
return output.hasValue() || !output.typedReg().isFloat();
}
if (index >= obj->as<UnboxedArrayObject>().initializedLength())
return false;
JSValueType elementType = obj->as<UnboxedArrayObject>().elementType();
if (elementType == JSVAL_TYPE_DOUBLE)
return output.hasValue();
return output.hasValue() || !output.typedReg().isFloat();
}
static void
GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm,
IonCache::StubAttacher& attacher,
HandleObject array, const Value& idval, Register object,
ConstantOrRegister index, TypedOrValueRegister output,
bool allowDoubleResult)
{
MOZ_ASSERT(GetPropertyIC::canAttachTypedOrUnboxedArrayElement(array, idval, output));
Label failures;
TestMatchingReceiver(masm, attacher, object, array, &failures);
// Decide to what type index the stub should be optimized
Register tmpReg = output.scratchReg().gpr();
MOZ_ASSERT(tmpReg != InvalidReg);
Register indexReg = tmpReg;
if (idval.isString()) {
MOZ_ASSERT(GetIndexFromString(idval.toString()) != UINT32_MAX);
if (index.constant()) {
MOZ_ASSERT(idval == index.value());
masm.move32(Imm32(GetIndexFromString(idval.toString())), indexReg);
} else {
// Part 1: Get the string into a register
Register str;
if (index.reg().hasValue()) {
ValueOperand val = index.reg().valueReg();
masm.branchTestString(Assembler::NotEqual, val, &failures);
str = masm.extractString(val, indexReg);
} else {
MOZ_ASSERT(!index.reg().typedReg().isFloat());
str = index.reg().typedReg().gpr();
}
// Part 2: Call to translate the str into index
AllocatableRegisterSet regs(RegisterSet::Volatile());
LiveRegisterSet save(regs.asLiveSet());
masm.PushRegsInMask(save);
regs.takeUnchecked(str);
Register temp = regs.takeAnyGeneral();
masm.setupUnalignedABICall(temp);
masm.passABIArg(str);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, GetIndexFromString));
masm.mov(ReturnReg, indexReg);
LiveRegisterSet ignore;
ignore.add(indexReg);
masm.PopRegsInMaskIgnore(save, ignore);
masm.branch32(Assembler::Equal, indexReg, Imm32(UINT32_MAX), &failures);
}
} else {
MOZ_ASSERT(idval.isInt32());
MOZ_ASSERT(!index.constant());
if (index.reg().hasValue()) {
ValueOperand val = index.reg().valueReg();
masm.branchTestInt32(Assembler::NotEqual, val, &failures);
// Unbox the index.
masm.unboxInt32(val, indexReg);
} else {
MOZ_ASSERT(!index.reg().typedReg().isFloat());
indexReg = index.reg().typedReg().gpr();
}
}
Label popObjectAndFail;
if (IsAnyTypedArray(array)) {
// Guard on the initialized length.
Address length(object, TypedArrayObject::lengthOffset());
masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures);
// Save the object register on the stack in case of failure.
Register elementReg = object;
masm.push(object);
// Load elements vector.
masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elementReg);
// Load the value. We use an invalid register because the destination
// register is necessary a non double register.
Scalar::Type arrayType = AnyTypedArrayType(array);
int width = Scalar::byteSize(arrayType);
BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(width));
if (output.hasValue()) {
masm.loadFromTypedArray(arrayType, source, output.valueReg(), allowDoubleResult,
elementReg, &popObjectAndFail);
} else {
masm.loadFromTypedArray(arrayType, source, output.typedReg(), elementReg, &popObjectAndFail);
}
} else {
// Save the object register on the stack in case of failure.
masm.push(object);
// Guard on the initialized length.
masm.load32(Address(object, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), object);
masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), object);
masm.branch32(Assembler::BelowOrEqual, object, indexReg, &popObjectAndFail);
// Load elements vector.
Register elementReg = object;
masm.loadPtr(Address(masm.getStackPointer(), 0), object);
masm.loadPtr(Address(object, UnboxedArrayObject::offsetOfElements()), elementReg);
JSValueType elementType = array->as<UnboxedArrayObject>().elementType();
BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(UnboxedTypeSize(elementType)));
masm.loadUnboxedProperty(source, elementType, output);
}
masm.pop(object);
attacher.jumpRejoin(masm);
// Restore the object before continuing to the next stub.
masm.bind(&popObjectAndFail);
masm.pop(object);
masm.bind(&failures);
attacher.jumpNextStub(masm);
}
bool
GetPropertyIC::tryAttachTypedOrUnboxedArrayElement(JSContext* cx, HandleScript outerScript,
IonScript* ion, HandleObject obj,
HandleValue idval, bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
if (!canAttachTypedOrUnboxedArrayElement(obj, idval, output()))
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
GenerateGetTypedOrUnboxedArrayElement(cx, masm, attacher, obj, idval, object(), id(),
output(), allowDoubleResult_);
return linkAndAttachStub(cx, masm, attacher, ion, "typed array",
JS::TrackedOutcome::ICGetElemStub_TypedArray);
}
bool
GetPropertyIC::tryAttachArgumentsElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleValue idval, bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
if (!IsOptimizableArgumentsObjectForGetElem(obj, idval))
return true;
MOZ_ASSERT(obj->is<ArgumentsObject>());
if (hasArgumentsElementStub(obj->is<MappedArgumentsObject>()))
return true;
TypedOrValueRegister index = id().reg();
if (index.type() != MIRType_Value && index.type() != MIRType_Int32)
return true;
MOZ_ASSERT(output().hasValue());
*emitted = true;
Label failures;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
Register tmpReg = output().scratchReg().gpr();
MOZ_ASSERT(tmpReg != InvalidReg);
masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, obj->getClass(), &failures);
// Get initial ArgsObj length value, test if length has been overridden.
masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
&failures);
masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg);
// Decide to what type index the stub should be optimized
Register indexReg;
// Check index against length.
Label failureRestoreIndex;
if (index.hasValue()) {
ValueOperand val = index.valueReg();
masm.branchTestInt32(Assembler::NotEqual, val, &failures);
indexReg = val.scratchReg();
masm.unboxInt32(val, indexReg);
masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failureRestoreIndex);
} else {
MOZ_ASSERT(index.type() == MIRType_Int32);
indexReg = index.typedReg().gpr();
masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failures);
}
// Save indexReg because it needs to be clobbered to check deleted bit.
Label failurePopIndex;
masm.push(indexReg);
// Check if property was deleted on arguments object.
masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
masm.loadPtr(Address(tmpReg, offsetof(ArgumentsData, deletedBits)), tmpReg);
// In tempReg, calculate index of word containing bit: (idx >> logBitsPerWord)
const uint32_t shift = FloorLog2<(sizeof(size_t) * JS_BITS_PER_BYTE)>::value;
MOZ_ASSERT(shift == 5 || shift == 6);
masm.rshiftPtr(Imm32(shift), indexReg);
masm.loadPtr(BaseIndex(tmpReg, indexReg, ScaleFromElemWidth(sizeof(size_t))), tmpReg);
// Don't bother testing specific bit, if any bit is set in the word, fail.
masm.branchPtr(Assembler::NotEqual, tmpReg, ImmPtr(nullptr), &failurePopIndex);
// Get the address to load from into tmpReg
masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), tmpReg);
// Restore original index register value, to use for indexing element.
masm.pop(indexReg);
BaseValueIndex elemIdx(tmpReg, indexReg);
// Ensure result is not magic value, and type-check result.
masm.branchTestMagic(Assembler::Equal, elemIdx, &failureRestoreIndex);
masm.loadTypedOrValue(elemIdx, output());
// indexReg may need to be reconstructed if it was originally a value.
if (index.hasValue())
masm.tagValue(JSVAL_TYPE_INT32, indexReg, index.valueReg());
// Success.
attacher.jumpRejoin(masm);
// Restore the object before continuing to the next stub.
masm.bind(&failurePopIndex);
masm.pop(indexReg);
masm.bind(&failureRestoreIndex);
if (index.hasValue())
masm.tagValue(JSVAL_TYPE_INT32, indexReg, index.valueReg());
masm.bind(&failures);
attacher.jumpNextStub(masm);
if (obj->is<UnmappedArgumentsObject>()) {
MOZ_ASSERT(!hasUnmappedArgumentsElementStub_);
hasUnmappedArgumentsElementStub_ = true;
return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (unmapped)",
JS::TrackedOutcome::ICGetElemStub_ArgsElementUnmapped);
}
MOZ_ASSERT(!hasMappedArgumentsElementStub_);
hasMappedArgumentsElementStub_ = true;
return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (mapped)",
JS::TrackedOutcome::ICGetElemStub_ArgsElementMapped);
}
static bool
IsDenseElementSetInlineable(JSObject* obj, const Value& idval, ConstantOrRegister val,
bool needsTypeBarrier, bool* checkTypeset)
{
if (!obj->is<ArrayObject>())
return false;
if (obj->watched())
return false;
if (!idval.isInt32())
return false;
// The object may have a setter definition,
// either directly, or via a prototype, or via the target object for a prototype
// which is a proxy, that handles a particular integer write.
// Scan the prototype and shape chain to make sure that this is not the case.
JSObject* curObj = obj;
while (curObj) {
// Ensure object is native.
if (!curObj->isNative())
return false;
// Ensure all indexed properties are stored in dense elements.
if (curObj->isIndexed())
return false;
curObj = curObj->getProto();
}
*checkTypeset = false;
if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, JSID_VOID, val, checkTypeset))
return false;
return true;
}
static bool
IsTypedArrayElementSetInlineable(JSObject* obj, const Value& idval, const Value& value)
{
// Don't bother attaching stubs for assigning strings, objects or symbols.
return IsAnyTypedArray(obj) && idval.isInt32() &&
!value.isString() && !value.isObject() && !value.isSymbol();
}
static void
StoreDenseElement(MacroAssembler& masm, ConstantOrRegister value, Register elements,
BaseObjectElementIndex target)
{
// If the ObjectElements::CONVERT_DOUBLE_ELEMENTS flag is set, int32 values
// have to be converted to double first. If the value is not int32, it can
// always be stored directly.
Address elementsFlags(elements, ObjectElements::offsetOfFlags());
if (value.constant()) {
Value v = value.value();
Label done;
if (v.isInt32()) {
Label dontConvert;
masm.branchTest32(Assembler::Zero, elementsFlags,
Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
&dontConvert);
masm.storeValue(DoubleValue(v.toInt32()), target);
masm.jump(&done);
masm.bind(&dontConvert);
}
masm.storeValue(v, target);
masm.bind(&done);
return;
}
TypedOrValueRegister reg = value.reg();
if (reg.hasTyped() && reg.type() != MIRType_Int32) {
masm.storeTypedOrValue(reg, target);
return;
}
Label convert, storeValue, done;
masm.branchTest32(Assembler::NonZero, elementsFlags,
Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
&convert);
masm.bind(&storeValue);
masm.storeTypedOrValue(reg, target);
masm.jump(&done);
masm.bind(&convert);
if (reg.hasValue()) {
masm.branchTestInt32(Assembler::NotEqual, reg.valueReg(), &storeValue);
masm.int32ValueToDouble(reg.valueReg(), ScratchDoubleReg);
masm.storeDouble(ScratchDoubleReg, target);
} else {
MOZ_ASSERT(reg.type() == MIRType_Int32);
masm.convertInt32ToDouble(reg.typedReg().gpr(), ScratchDoubleReg);
masm.storeDouble(ScratchDoubleReg, target);
}
masm.bind(&done);
}
static bool
GenerateSetDenseElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
JSObject* obj, const Value& idval, bool guardHoles, Register object,
TypedOrValueRegister index, ConstantOrRegister value, Register tempToUnboxIndex,
Register temp, bool needsTypeBarrier, bool checkTypeset)
{
MOZ_ASSERT(obj->isNative());
MOZ_ASSERT(idval.isInt32());
Label failures;
// Guard object is a dense array.
Shape* shape = obj->as<NativeObject>().lastProperty();
if (!shape)
return false;
masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
// Guard that the incoming value is in the type set for the property
// if a type barrier is required.
if (needsTypeBarrier) {
masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), &failures);
if (checkTypeset)
CheckTypeSetForWrite(masm, obj, JSID_VOID, temp, value, &failures);
}
// Ensure the index is an int32 value.
Register indexReg;
if (index.hasValue()) {
ValueOperand val = index.valueReg();
masm.branchTestInt32(Assembler::NotEqual, val, &failures);
indexReg = masm.extractInt32(val, tempToUnboxIndex);
} else {
MOZ_ASSERT(!index.typedReg().isFloat());
indexReg = index.typedReg().gpr();
}
{
// Load obj->elements.
Register elements = temp;
masm.loadPtr(Address(object, NativeObject::offsetOfElements()), elements);
// Compute the location of the element.
BaseObjectElementIndex target(elements, indexReg);
Label storeElement;
// If TI cannot help us deal with HOLES by preventing indexed properties
// on the prototype chain, we have to be very careful to check for ourselves
// to avoid stomping on what should be a setter call. Start by only allowing things
// within the initialized length.
if (guardHoles) {
Address initLength(elements, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &failures);
} else {
// Guard that we can increase the initialized length.
Address capacity(elements, ObjectElements::offsetOfCapacity());
masm.branch32(Assembler::BelowOrEqual, capacity, indexReg, &failures);
// Guard on the initialized length.
Address initLength(elements, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::Below, initLength, indexReg, &failures);
// if (initLength == index)
Label inBounds;
masm.branch32(Assembler::NotEqual, initLength, indexReg, &inBounds);
{
// Increase initialize length.
Int32Key newLength(indexReg);
masm.bumpKey(&newLength, 1);
masm.storeKey(newLength, initLength);
// Increase length if needed.
Label bumpedLength;
Address length(elements, ObjectElements::offsetOfLength());
masm.branch32(Assembler::AboveOrEqual, length, indexReg, &bumpedLength);
masm.storeKey(newLength, length);
masm.bind(&bumpedLength);
// Restore the index.
masm.bumpKey(&newLength, -1);
masm.jump(&storeElement);
}
// else
masm.bind(&inBounds);
}
if (cx->zone()->needsIncrementalBarrier())
masm.callPreBarrier(target, MIRType_Value);
// Store the value.
if (guardHoles)
masm.branchTestMagic(Assembler::Equal, target, &failures);
else
masm.bind(&storeElement);
StoreDenseElement(masm, value, elements, target);
}
attacher.jumpRejoin(masm);
masm.bind(&failures);
attacher.jumpNextStub(masm);
return true;
}
bool
SetPropertyIC::tryAttachDenseElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, const Value& idval, bool* emitted)
{
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(canAttachStub());
if (hasDenseStub())
return true;
bool checkTypeset = false;
if (!IsDenseElementSetInlineable(obj, idval, value(), needsTypeBarrier(), &checkTypeset))
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
if (!GenerateSetDenseElement(cx, masm, attacher, obj, idval,
guardHoles(), object(), id().reg(),
value(), tempToUnboxIndex(), temp(),
needsTypeBarrier(), checkTypeset))
{
return false;
}
setHasDenseStub();
const char* message = guardHoles() ? "dense array (holes)" : "dense array";
return linkAndAttachStub(cx, masm, attacher, ion, message,
JS::TrackedOutcome::ICSetElemStub_Dense);
}
static bool
GenerateSetTypedArrayElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
HandleObject tarr, Register object, TypedOrValueRegister index,
ConstantOrRegister value, Register tempUnbox, Register temp,
FloatRegister tempDouble, FloatRegister tempFloat32)
{
Label failures, done, popObjectAndFail;
// Guard on the shape.
Shape* shape = AnyTypedArrayShape(tarr);
if (!shape)
return false;
masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
// Ensure the index is an int32.
Register indexReg;
if (index.hasValue()) {
ValueOperand val = index.valueReg();
masm.branchTestInt32(Assembler::NotEqual, val, &failures);
indexReg = masm.extractInt32(val, tempUnbox);
} else {
MOZ_ASSERT(!index.typedReg().isFloat());
indexReg = index.typedReg().gpr();
}
// Guard on the length.
Address length(object, TypedArrayObject::lengthOffset());
masm.unboxInt32(length, temp);
masm.branch32(Assembler::BelowOrEqual, temp, indexReg, &done);
// Load the elements vector.
Register elements = temp;
masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elements);
// Set the value.
Scalar::Type arrayType = AnyTypedArrayType(tarr);
int width = Scalar::byteSize(arrayType);
BaseIndex target(elements, indexReg, ScaleFromElemWidth(width));
if (arrayType == Scalar::Float32) {
MOZ_ASSERT_IF(hasUnaliasedDouble(), tempFloat32 != InvalidFloatReg);
FloatRegister tempFloat = hasUnaliasedDouble() ? tempFloat32 : tempDouble;
if (!masm.convertConstantOrRegisterToFloat(cx, value, tempFloat, &failures))
return false;
masm.storeToTypedFloatArray(arrayType, tempFloat, target);
} else if (arrayType == Scalar::Float64) {
if (!masm.convertConstantOrRegisterToDouble(cx, value, tempDouble, &failures))
return false;
masm.storeToTypedFloatArray(arrayType, tempDouble, target);
} else {
// On x86 we only have 6 registers available to use, so reuse the object
// register to compute the intermediate value to store and restore it
// afterwards.
masm.push(object);
if (arrayType == Scalar::Uint8Clamped) {
if (!masm.clampConstantOrRegisterToUint8(cx, value, tempDouble, object,
&popObjectAndFail))
{
return false;
}
} else {
if (!masm.truncateConstantOrRegisterToInt32(cx, value, tempDouble, object,
&popObjectAndFail))
{
return false;
}
}
masm.storeToTypedIntArray(arrayType, object, target);
masm.pop(object);
}
// Out-of-bound writes jump here as they are no-ops.
masm.bind(&done);
attacher.jumpRejoin(masm);
if (popObjectAndFail.used()) {
masm.bind(&popObjectAndFail);
masm.pop(object);
}
masm.bind(&failures);
attacher.jumpNextStub(masm);
return true;
}
bool
SetPropertyIC::tryAttachTypedArrayElement(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleValue idval, HandleValue val,
bool* emitted)
{
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(canAttachStub());
if (!IsTypedArrayElementSetInlineable(obj, idval, val))
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
if (!GenerateSetTypedArrayElement(cx, masm, attacher, obj,
object(), id().reg(), value(),
tempToUnboxIndex(), temp(), tempDouble(), tempFloat32()))
{
return false;
}
return linkAndAttachStub(cx, masm, attacher, ion, "typed array",
JS::TrackedOutcome::ICSetElemStub_TypedArray);
}
bool
BindNameIC::attachGlobal(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject scopeChain)
{
MOZ_ASSERT(scopeChain->is<GlobalObject>());
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
// Guard on the scope chain.
attacher.branchNextStub(masm, Assembler::NotEqual, scopeChainReg(),
ImmGCPtr(scopeChain));
masm.movePtr(ImmGCPtr(scopeChain), outputReg());
attacher.jumpRejoin(masm);
return linkAndAttachStub(cx, masm, attacher, ion, "global");
}
static inline void
GenerateScopeChainGuard(MacroAssembler& masm, JSObject* scopeObj,
Register scopeObjReg, Shape* shape, Label* failures)
{
if (scopeObj->is<CallObject>()) {
// We can skip a guard on the call object if the script's bindings are
// guaranteed to be immutable (and thus cannot introduce shadowing
// variables).
CallObject* callObj = &scopeObj->as<CallObject>();
if (!callObj->isForEval()) {
JSFunction* fun = &callObj->callee();
// The function might have been relazified under rare conditions.
// In that case, we pessimistically create the guard, as we'd
// need to root various pointers to delazify,
if (fun->hasScript()) {
JSScript* script = fun->nonLazyScript();
if (!script->funHasExtensibleScope())
return;
}
}
} else if (scopeObj->is<GlobalObject>()) {
// If this is the last object on the scope walk, and the property we've
// found is not configurable, then we don't need a shape guard because
// the shape cannot be removed.
if (shape && !shape->configurable())
return;
}
Address shapeAddr(scopeObjReg, JSObject::offsetOfShape());
masm.branchPtr(Assembler::NotEqual, shapeAddr,
ImmGCPtr(scopeObj->as<NativeObject>().lastProperty()), failures);
}
static void
GenerateScopeChainGuards(MacroAssembler& masm, JSObject* scopeChain, JSObject* holder,
Register outputReg, Label* failures, bool skipLastGuard = false)
{
JSObject* tobj = scopeChain;
// Walk up the scope chain. Note that IsCacheableScopeChain guarantees the
// |tobj == holder| condition terminates the loop.
while (true) {
MOZ_ASSERT(IsCacheableNonGlobalScope(tobj) || tobj->is<GlobalObject>());
if (skipLastGuard && tobj == holder)
break;
GenerateScopeChainGuard(masm, tobj, outputReg, nullptr, failures);
if (tobj == holder)
break;
// Load the next link.
tobj = &tobj->as<ScopeObject>().enclosingScope();
masm.extractObject(Address(outputReg, ScopeObject::offsetOfEnclosingScope()), outputReg);
}
}
bool
BindNameIC::attachNonGlobal(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject scopeChain, HandleObject holder)
{
MOZ_ASSERT(IsCacheableNonGlobalScope(scopeChain));
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
// Guard on the shape of the scope chain.
Label failures;
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(scopeChainReg(), JSObject::offsetOfShape()),
ImmGCPtr(scopeChain->as<NativeObject>().lastProperty()),
holder != scopeChain ? &failures : nullptr);
if (holder != scopeChain) {
JSObject* parent = &scopeChain->as<ScopeObject>().enclosingScope();
masm.extractObject(Address(scopeChainReg(), ScopeObject::offsetOfEnclosingScope()), outputReg());
GenerateScopeChainGuards(masm, parent, holder, outputReg(), &failures);
} else {
masm.movePtr(scopeChainReg(), outputReg());
}
// At this point outputReg holds the object on which the property
// was found, so we're done.
attacher.jumpRejoin(masm);
// All failures flow to here, so there is a common point to patch.
if (holder != scopeChain) {
masm.bind(&failures);
attacher.jumpNextStub(masm);
}
return linkAndAttachStub(cx, masm, attacher, ion, "non-global");
}
static bool
IsCacheableNonGlobalScopeChain(JSObject* scopeChain, JSObject* holder)
{
while (true) {
if (!IsCacheableNonGlobalScope(scopeChain)) {
JitSpew(JitSpew_IonIC, "Non-cacheable object on scope chain");
return false;
}
if (scopeChain == holder)
return true;
scopeChain = &scopeChain->as<ScopeObject>().enclosingScope();
if (!scopeChain) {
JitSpew(JitSpew_IonIC, "Scope chain indirect hit");
return false;
}
}
MOZ_CRASH("Invalid scope chain");
}
JSObject*
BindNameIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex,
HandleObject scopeChain)
{
IonScript* ion = outerScript->ionScript();
BindNameIC& cache = ion->getCache(cacheIndex).toBindName();
HandlePropertyName name = cache.name();
RootedObject holder(cx);
if (!LookupNameUnqualified(cx, name, scopeChain, &holder))
return nullptr;
// Stop generating new stubs once we hit the stub count limit, see
// GetPropertyCache.
if (cache.canAttachStub()) {
if (scopeChain->is<GlobalObject>()) {
if (!cache.attachGlobal(cx, outerScript, ion, scopeChain))
return nullptr;
} else if (IsCacheableNonGlobalScopeChain(scopeChain, holder)) {
if (!cache.attachNonGlobal(cx, outerScript, ion, scopeChain, holder))
return nullptr;
} else {
JitSpew(JitSpew_IonIC, "BINDNAME uncacheable scope chain");
}
}
return holder;
}
bool
NameIC::attachReadSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject scopeChain, HandleObject holderBase,
HandleNativeObject holder, HandleShape shape)
{
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
Label failures;
StubAttacher attacher(*this);
Register scratchReg = outputReg().valueReg().scratchReg();
// Don't guard the base of the proto chain the name was found on. It will be guarded
// by GenerateReadSlot().
masm.mov(scopeChainReg(), scratchReg);
GenerateScopeChainGuards(masm, scopeChain, holderBase, scratchReg, &failures,
/* skipLastGuard = */true);
// GenerateScopeChain leaves the last scope chain in scratchReg, even though it
// doesn't generate the extra guard.
GenerateReadSlot(cx, ion, masm, attacher, holderBase, holder, shape, scratchReg,
outputReg(), failures.used() ? &failures : nullptr);
return linkAndAttachStub(cx, masm, attacher, ion, "generic",
JS::TrackedOutcome::ICNameStub_ReadSlot);
}
static bool
IsCacheableScopeChain(JSObject* scopeChain, JSObject* obj)
{
JSObject* obj2 = scopeChain;
while (obj2) {
if (!IsCacheableNonGlobalScope(obj2) && !obj2->is<GlobalObject>())
return false;
// Stop once we hit the global or target obj.
if (obj2->is<GlobalObject>() || obj2 == obj)
break;
obj2 = obj2->enclosingScope();
}
return obj == obj2;
}
static bool
IsCacheableNameReadSlot(HandleObject scopeChain, HandleObject obj,
HandleObject holder, HandleShape shape, jsbytecode* pc,
const TypedOrValueRegister& output)
{
if (!shape)
return false;
if (!obj->isNative())
return false;
if (obj->is<GlobalObject>()) {
// Support only simple property lookups.
if (!IsCacheableGetPropReadSlotForIon(obj, holder, shape) &&
!IsCacheableNoProperty(obj, holder, shape, pc, output))
return false;
} else if (obj->is<ModuleEnvironmentObject>()) {
// We don't yet support lookups in a module environment.
return false;
} else if (obj->is<CallObject>()) {
MOZ_ASSERT(obj == holder);
if (!shape->hasDefaultGetter())
return false;
} else {
// We don't yet support lookups on Block or DeclEnv objects.
return false;
}
return IsCacheableScopeChain(scopeChain, obj);
}
bool
NameIC::attachCallGetter(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject scopeChain, HandleObject obj, HandleObject holder,
HandleShape shape, void* returnAddr)
{
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
StubAttacher attacher(*this);
Label failures;
Register scratchReg = outputReg().valueReg().scratchReg();
// Don't guard the base of the proto chain the name was found on. It will be guarded
// by GenerateCallGetter().
masm.mov(scopeChainReg(), scratchReg);
GenerateScopeChainGuards(masm, scopeChain, obj, scratchReg, &failures,
/* skipLastGuard = */true);
// GenerateScopeChain leaves the last scope chain in scratchReg, even though it
// doesn't generate the extra guard.
if (!GenerateCallGetter(cx, ion, masm, attacher, obj, holder, shape, liveRegs_,
scratchReg, outputReg(), returnAddr,
failures.used() ? &failures : nullptr))
{
return false;
}
const char* attachKind = "name getter";
return linkAndAttachStub(cx, masm, attacher, ion, attachKind,
JS::TrackedOutcome::ICNameStub_CallGetter);
}
static bool
IsCacheableNameCallGetter(HandleObject scopeChain, HandleObject obj, HandleObject holder,
HandleShape shape)
{
if (!shape)
return false;
if (!obj->is<GlobalObject>())
return false;
if (!IsCacheableScopeChain(scopeChain, obj))
return false;
return IsCacheableGetPropCallNative(obj, holder, shape) ||
IsCacheableGetPropCallPropertyOp(obj, holder, shape) ||
IsCacheableGetPropCallScripted(obj, holder, shape);
}
bool
NameIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, HandleObject scopeChain,
MutableHandleValue vp)
{
IonScript* ion = outerScript->ionScript();
NameIC& cache = ion->getCache(cacheIndex).toName();
RootedPropertyName name(cx, cache.name());
RootedScript script(cx);
jsbytecode* pc;
cache.getScriptedLocation(&script, &pc);
RootedObject obj(cx);
RootedObject holder(cx);
RootedShape shape(cx);
if (!LookupName(cx, name, scopeChain, &obj, &holder, &shape))
return false;
// Look first. Don't generate cache entries if the lookup fails.
if (cache.isTypeOf()) {
if (!FetchName<true>(cx, obj, holder, name, shape, vp))
return false;
} else {
if (!FetchName<false>(cx, obj, holder, name, shape, vp))
return false;
}
if (cache.canAttachStub()) {
if (IsCacheableNameReadSlot(scopeChain, obj, holder, shape, pc, cache.outputReg())) {
if (!cache.attachReadSlot(cx, outerScript, ion, scopeChain, obj,
holder.as<NativeObject>(), shape))
{
return false;
}
} else if (IsCacheableNameCallGetter(scopeChain, obj, holder, shape)) {
void* returnAddr = GetReturnAddressToIonCode(cx);
if (!cache.attachCallGetter(cx, outerScript, ion, scopeChain, obj, holder, shape,
returnAddr))
{
return false;
}
}
}
// Monitor changes to cache entry.
TypeScript::Monitor(cx, script, pc, vp);
return true;
}