4642 lines
160 KiB
C++
4642 lines
160 KiB
C++
/* -*- 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 "vm/Stack.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];
|
|
}
|
|
|
|
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 void* const 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(ImmPtr(STUB_ADDR));
|
|
hasStubCodePatchOffset_ = true;
|
|
}
|
|
|
|
void patchRejoinJump(MacroAssembler& masm, JitCode* code) {
|
|
rejoinOffset_.fixup(&masm);
|
|
CodeLocationJump rejoinJump(code, rejoinOffset_);
|
|
PatchJump(rejoinJump, rejoinLabel_);
|
|
}
|
|
|
|
void patchStubCodePointer(JitCode* code) {
|
|
if (hasStubCodePatchOffset_) {
|
|
Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, stubCodePatchOffset_),
|
|
ImmPtr(code), ImmPtr(STUB_ADDR));
|
|
}
|
|
}
|
|
|
|
void patchNextStubJump(MacroAssembler& masm, JitCode* code) {
|
|
// If this path is not taken, we are producing an entry which can no
|
|
// longer go back into the update function.
|
|
if (hasNextStubOffset_) {
|
|
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 void* const IonCache::StubAttacher::STUB_ADDR = (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, CodeLocationJump lastJump,
|
|
Handle<JitCode*> code)
|
|
{
|
|
MOZ_ASSERT(canAttachStub());
|
|
incrementStubCount();
|
|
|
|
// Patch the previous nextStubJump of the last stub, or the jump from the
|
|
// codeGen, to jump into the newly allocated code.
|
|
PatchJump(lastJump, CodeLocationLabel(code), Reprotect);
|
|
}
|
|
|
|
IonCache::LinkStatus
|
|
IonCache::linkCode(JSContext* cx, MacroAssembler& masm, StubAttacher& attacher, 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;
|
|
|
|
// 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);
|
|
|
|
return LINK_GOOD;
|
|
}
|
|
|
|
bool
|
|
IonCache::linkAndAttachStub(JSContext* cx, MacroAssembler& masm, StubAttacher& attacher,
|
|
IonScript* ion, const char* attachKind,
|
|
JS::TrackedOutcome trackedOutcome)
|
|
{
|
|
CodeLocationJump lastJumpBefore = lastJump_;
|
|
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, attacher, 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, lastJumpBefore, 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);
|
|
}
|
|
|
|
void IonCache::trace(JSTracer* trc)
|
|
{
|
|
if (script_)
|
|
TraceManuallyBarrieredEdge(trc, &script_, "IonCache::script_");
|
|
}
|
|
|
|
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->staticPrototype()), failures);
|
|
}
|
|
|
|
JSObject* pobj = obj->staticPrototype();
|
|
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->staticPrototype()),
|
|
failures);
|
|
} else {
|
|
masm.branchPtr(Assembler::NotEqual, groupAddr, ImmGCPtr(pobj->group()), failures);
|
|
}
|
|
}
|
|
|
|
pobj = pobj->staticPrototype();
|
|
}
|
|
}
|
|
|
|
// Note: This differs from IsCacheableProtoChain in BaselineIC.cpp in that
|
|
// Ion caches can deal with objects on the proto chain that have uncacheable
|
|
// prototypes.
|
|
bool
|
|
jit::IsCacheableProtoChainForIonOrCacheIR(JSObject* obj, JSObject* holder)
|
|
{
|
|
while (obj != holder) {
|
|
/*
|
|
* We cannot assume that we find the holder object on the prototype
|
|
* chain and must check for null proto. The prototype chain can be
|
|
* altered during the lookupProperty call.
|
|
*/
|
|
JSObject* proto = obj->staticPrototype();
|
|
if (!proto || !proto->isNative())
|
|
return false;
|
|
obj = proto;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
jit::IsCacheableGetPropReadSlotForIonOrCacheIR(JSObject* obj, JSObject* holder, Shape* shape)
|
|
{
|
|
if (!shape || !IsCacheableProtoChainForIonOrCacheIR(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()->getGetProperty())
|
|
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->staticPrototype();
|
|
}
|
|
|
|
// 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, const Value& idval)
|
|
{
|
|
if (!IsOptimizableArgumentsObjectForLength(obj))
|
|
return false;
|
|
|
|
ArgumentsObject& argsObj = obj->as<ArgumentsObject>();
|
|
|
|
if (argsObj.isAnyElementDeleted())
|
|
return false;
|
|
|
|
if (argsObj.hasOverriddenElement())
|
|
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 || !IsCacheableProtoChainForIonOrCacheIR(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 || !IsCacheableProtoChainForIonOrCacheIR(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 || !IsCacheableProtoChainForIonOrCacheIR(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<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, ShapedObject::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(), ShapedObject::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, MaybeCheckTDZ checkTDZ,
|
|
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)
|
|
|| (checkTDZ && output.hasValue())
|
|
|| (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 ||
|
|
!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, ShapedObject::offsetOfShape()),
|
|
ImmGCPtr(holder->as<NativeObject>().lastProperty()),
|
|
&prototypeFailures);
|
|
} else {
|
|
// The property does not exist. Guard on everything in the
|
|
// prototype chain.
|
|
JSObject* proto = obj->staticPrototype();
|
|
Register lastReg = object;
|
|
MOZ_ASSERT(scratchReg != object);
|
|
while (proto) {
|
|
masm.loadObjProto(lastReg, scratchReg);
|
|
|
|
// Guard the shape of the current prototype.
|
|
MOZ_ASSERT(proto->hasStaticPrototype());
|
|
masm.branchPtr(Assembler::NotEqual,
|
|
Address(scratchReg, ShapedObject::offsetOfShape()),
|
|
ImmGCPtr(proto->as<NativeObject>().lastProperty()),
|
|
&prototypeFailures);
|
|
|
|
proto = proto->staticPrototype();
|
|
lastReg = scratchReg;
|
|
}
|
|
|
|
holderReg = InvalidReg;
|
|
}
|
|
} else {
|
|
holderReg = object;
|
|
}
|
|
|
|
// Slot access.
|
|
if (holder) {
|
|
EmitLoadSlot(masm, &holder->as<NativeObject>(), shape, holderReg, output, scratchReg);
|
|
if (checkTDZ && output.hasValue())
|
|
masm.branchTestMagic(Assembler::Equal, output.valueReg(), failures);
|
|
} 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 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,
|
|
IonAccessorICFrameLayout::Size());
|
|
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,
|
|
JitFrameLayout::Size());
|
|
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 env and manually push the global.
|
|
if (IsGlobalLexicalEnvironment(obj)) {
|
|
masm.extractObject(Address(object, EnvironmentObject::offsetOfEnclosingEnvironment()),
|
|
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, maybePopAndFail);
|
|
|
|
// Guard on the holder's shape.
|
|
Register holderReg = scratchReg;
|
|
masm.movePtr(ImmGCPtr(holder), holderReg);
|
|
masm.branchPtr(Assembler::NotEqual,
|
|
Address(holderReg, ShapedObject::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;
|
|
}
|
|
|
|
// 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 (IsCacheableGetPropReadSlotForIonOrCacheIR(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, DontCheckTDZ, 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::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
|
HandleObject obj, HandleId id, bool* emitted)
|
|
{
|
|
MOZ_ASSERT(canAttachStub());
|
|
MOZ_ASSERT(!*emitted);
|
|
|
|
if (!obj->is<TypedArrayObject>())
|
|
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(), ShapedObject::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->staticPrototype());
|
|
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(), ShapedObject::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, ShapedObject::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 && !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())
|
|
Invalidate(cx, outerScript);
|
|
|
|
return true;
|
|
}
|
|
|
|
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, const 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,
|
|
const 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->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 || !IsCacheableProtoChainForIonOrCacheIR(obj, holder))
|
|
return false;
|
|
|
|
if (!shape->hasSetterValue())
|
|
return false;
|
|
|
|
if (!shape->setterObject() || !shape->setterObject()->is<JSFunction>())
|
|
return false;
|
|
|
|
JSFunction& setter = shape->setterObject()->as<JSFunction>();
|
|
if (!setter.isNative())
|
|
return false;
|
|
|
|
if (setter.jitInfo() && !setter.jitInfo()->needsOuterizedThisObject())
|
|
return true;
|
|
|
|
return !IsWindow(obj);
|
|
}
|
|
|
|
static bool
|
|
IsCacheableSetPropCallScripted(HandleObject obj, HandleObject holder, HandleShape shape)
|
|
{
|
|
if (!shape || !IsCacheableProtoChainForIonOrCacheIR(obj, holder))
|
|
return false;
|
|
|
|
if (IsWindow(obj))
|
|
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 (!IsCacheableProtoChainForIonOrCacheIR(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,
|
|
const 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(), ShapedObject::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,
|
|
const 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, ShapedObject::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,
|
|
IonAccessorICFrameLayout::Size());
|
|
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,
|
|
JitFrameLayout::Size());
|
|
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->staticPrototype());
|
|
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(), ShapedObject::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, const 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);
|
|
}
|
|
|
|
Shape* newShape = obj->maybeShape();
|
|
|
|
// Guard that the incoming value is in the type set for the property
|
|
// if a type barrier is required.
|
|
if (newShape && checkTypeset)
|
|
CheckTypeSetForWrite(masm, obj, newShape->propid(), tempReg, value, failures);
|
|
|
|
// Guard shapes along prototype chain.
|
|
JSObject* proto = obj->staticPrototype();
|
|
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->staticPrototype();
|
|
}
|
|
|
|
// Call a stub to (re)allocate dynamic slots, if necessary.
|
|
uint32_t newNumDynamicSlots = 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();
|
|
|
|
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);
|
|
masm.PopRegsInMask(save);
|
|
masm.jump(failures);
|
|
|
|
masm.bind(&allocDone);
|
|
masm.setFramePushed(framePushedAfterCall);
|
|
masm.PopRegsInMask(save);
|
|
}
|
|
|
|
bool popObject = false;
|
|
|
|
// Write the object or expando object's new shape.
|
|
Address shapeAddr(object, ShapedObject::offsetOfShape());
|
|
if (cx->zone()->needsIncrementalBarrier())
|
|
masm.callPreBarrier(shapeAddr, MIRType::Shape);
|
|
masm.storePtr(ImmGCPtr(newShape), shapeAddr);
|
|
|
|
if (oldGroup != obj->group()) {
|
|
// 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, const 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,
|
|
const 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->staticPrototype(); proto; proto = proto->staticPrototype()) {
|
|
// 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,
|
|
const 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()->getAddProperty())
|
|
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, const 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;
|
|
}
|
|
|
|
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::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())
|
|
return true;
|
|
|
|
// Fail cache emission if the object is frozen
|
|
if (obj->is<NativeObject>() && obj->as<NativeObject>().getElementsHeader()->isFrozen())
|
|
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 (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;
|
|
|
|
// Fail cache emission if the object is frozen
|
|
if (obj->is<NativeObject>() && obj->as<NativeObject>().getElementsHeader()->isFrozen())
|
|
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::AutoAssertNoGC 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;
|
|
}
|
|
|
|
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 = JSObject::getGroup(cx, obj);
|
|
if (!oldGroup)
|
|
return false;
|
|
|
|
oldShape = obj->maybeShape();
|
|
}
|
|
|
|
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()->lexicalEnvironment(), 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;
|
|
|
|
if (uint32_t(idval.toInt32()) >= obj->as<NativeObject>().getDenseInitializedLength())
|
|
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;
|
|
|
|
do {
|
|
if (obj->isIndexed())
|
|
return false;
|
|
|
|
if (ClassCanHaveExtraProperties(obj->getClass()))
|
|
return false;
|
|
|
|
JSObject* proto = obj->staticPrototype();
|
|
if (!proto)
|
|
break;
|
|
|
|
if (!proto->isNative())
|
|
return false;
|
|
|
|
// Make sure objects on the prototype don't have dense elements.
|
|
if (proto->as<NativeObject>().getDenseInitializedLength() != 0)
|
|
return false;
|
|
|
|
obj = proto;
|
|
} while (obj);
|
|
|
|
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, ShapedObject::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->staticPrototype()), &failures);
|
|
}
|
|
|
|
JSObject* pobj = obj->staticPrototype();
|
|
while (pobj) {
|
|
MOZ_ASSERT(pobj->as<NativeObject>().lastProperty());
|
|
|
|
masm.movePtr(ImmGCPtr(pobj), scratchReg);
|
|
|
|
// Non-singletons with uncacheable protos can change their proto
|
|
// without a shape change, so also guard on the group (which determines
|
|
// the proto) in this case.
|
|
if (pobj->hasUncacheableProto() && !pobj->isSingleton()) {
|
|
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, ShapedObject::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->staticPrototype();
|
|
}
|
|
|
|
// 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 (!obj->is<TypedArrayObject>())
|
|
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 (obj->is<TypedArrayObject>()) {
|
|
if (index >= obj->as<TypedArrayObject>().length())
|
|
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 = obj->as<TypedArrayObject>().type();
|
|
if (arrayType == Scalar::Float32 || arrayType == Scalar::Float64)
|
|
return output.hasValue();
|
|
|
|
return output.hasValue() || !output.typedReg().isFloat();
|
|
}
|
|
|
|
return output.hasValue() || !output.typedReg().isFloat();
|
|
}
|
|
|
|
static void
|
|
GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm,
|
|
IonCache::StubAttacher& attacher,
|
|
HandleObject array, const Value& idval, Register object,
|
|
const 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;
|
|
|
|
// 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 = array->as<TypedArrayObject>().type();
|
|
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);
|
|
}
|
|
|
|
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 or any element have
|
|
// been overridden.
|
|
masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
|
|
masm.branchTest32(Assembler::NonZero, tmpReg,
|
|
Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT |
|
|
ArgumentsObject::ELEMENT_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);
|
|
}
|
|
|
|
// Fail if we have a RareArgumentsData (elements were deleted).
|
|
masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
|
|
masm.branchPtr(Assembler::NotEqual,
|
|
Address(tmpReg, offsetof(ArgumentsData, rareData)),
|
|
ImmWord(0),
|
|
&failureRestoreIndex);
|
|
|
|
// Get the address to load from into tmpReg
|
|
masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
|
|
masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), tmpReg);
|
|
|
|
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.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, const ConstantOrRegister& val,
|
|
bool needsTypeBarrier, bool* checkTypeset)
|
|
{
|
|
if (!obj->is<ArrayObject>())
|
|
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. (This guarantees static prototype below.)
|
|
if (!curObj->isNative())
|
|
return false;
|
|
|
|
// Ensure all indexed properties are stored in dense elements.
|
|
if (curObj->isIndexed())
|
|
return false;
|
|
|
|
curObj = curObj->staticPrototype();
|
|
}
|
|
|
|
*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 obj->is<TypedArrayObject>() && idval.isInt32() &&
|
|
!value.isString() && !value.isObject() && !value.isSymbol();
|
|
}
|
|
|
|
static void
|
|
StoreDenseElement(MacroAssembler& masm, const 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, const 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.
|
|
Register newLength = indexReg;
|
|
masm.add32(Imm32(1), newLength);
|
|
masm.store32(newLength, initLength);
|
|
|
|
// Increase length if needed.
|
|
Label bumpedLength;
|
|
Address length(elements, ObjectElements::offsetOfLength());
|
|
masm.branch32(Assembler::AboveOrEqual, length, indexReg, &bumpedLength);
|
|
masm.store32(newLength, length);
|
|
masm.bind(&bumpedLength);
|
|
|
|
// Restore the index.
|
|
masm.add32(Imm32(-1), newLength);
|
|
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,
|
|
const ConstantOrRegister& value, Register tempUnbox, Register temp,
|
|
FloatRegister tempDouble, FloatRegister tempFloat32)
|
|
{
|
|
Label failures, done, popObjectAndFail;
|
|
|
|
// Guard on the shape.
|
|
Shape* shape = tarr->as<TypedArrayObject>().lastProperty();
|
|
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 = tarr->as<TypedArrayObject>().type();
|
|
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 envChain)
|
|
{
|
|
MOZ_ASSERT(envChain->is<GlobalObject>());
|
|
|
|
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
|
|
StubAttacher attacher(*this);
|
|
|
|
// Guard on the env chain.
|
|
attacher.branchNextStub(masm, Assembler::NotEqual, environmentChainReg(),
|
|
ImmGCPtr(envChain));
|
|
masm.movePtr(ImmGCPtr(envChain), outputReg());
|
|
|
|
attacher.jumpRejoin(masm);
|
|
|
|
return linkAndAttachStub(cx, masm, attacher, ion, "global");
|
|
}
|
|
|
|
static inline void
|
|
GenerateEnvironmentChainGuard(MacroAssembler& masm, JSObject* envObj,
|
|
Register envObjReg, Shape* shape, Label* failures)
|
|
{
|
|
if (envObj->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 = &envObj->as<CallObject>();
|
|
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 (envObj->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(envObjReg, ShapedObject::offsetOfShape());
|
|
masm.branchPtr(Assembler::NotEqual, shapeAddr,
|
|
ImmGCPtr(envObj->as<NativeObject>().lastProperty()), failures);
|
|
}
|
|
|
|
static void
|
|
GenerateEnvironmentChainGuards(MacroAssembler& masm, JSObject* envChain, JSObject* holder,
|
|
Register outputReg, Label* failures, bool skipLastGuard = false)
|
|
{
|
|
JSObject* tobj = envChain;
|
|
|
|
// Walk up the env chain. Note that IsCacheableEnvironmentChain guarantees the
|
|
// |tobj == holder| condition terminates the loop.
|
|
while (true) {
|
|
MOZ_ASSERT(IsCacheableEnvironment(tobj) || tobj->is<GlobalObject>());
|
|
|
|
if (skipLastGuard && tobj == holder)
|
|
break;
|
|
|
|
GenerateEnvironmentChainGuard(masm, tobj, outputReg, nullptr, failures);
|
|
|
|
if (tobj == holder)
|
|
break;
|
|
|
|
// Load the next link.
|
|
tobj = &tobj->as<EnvironmentObject>().enclosingEnvironment();
|
|
masm.extractObject(Address(outputReg, EnvironmentObject::offsetOfEnclosingEnvironment()),
|
|
outputReg);
|
|
}
|
|
}
|
|
|
|
bool
|
|
BindNameIC::attachNonGlobal(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
|
HandleObject envChain, HandleObject holder)
|
|
{
|
|
MOZ_ASSERT(IsCacheableEnvironment(envChain));
|
|
|
|
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
|
|
StubAttacher attacher(*this);
|
|
|
|
// Guard on the shape of the env chain.
|
|
Label failures;
|
|
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
|
|
Address(environmentChainReg(), ShapedObject::offsetOfShape()),
|
|
ImmGCPtr(envChain->as<NativeObject>().lastProperty()),
|
|
holder != envChain ? &failures : nullptr);
|
|
|
|
if (holder != envChain) {
|
|
JSObject* parent = &envChain->as<EnvironmentObject>().enclosingEnvironment();
|
|
masm.extractObject(Address(environmentChainReg(),
|
|
EnvironmentObject::offsetOfEnclosingEnvironment()),
|
|
outputReg());
|
|
|
|
GenerateEnvironmentChainGuards(masm, parent, holder, outputReg(), &failures);
|
|
} else {
|
|
masm.movePtr(environmentChainReg(), 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 != envChain) {
|
|
masm.bind(&failures);
|
|
attacher.jumpNextStub(masm);
|
|
}
|
|
|
|
return linkAndAttachStub(cx, masm, attacher, ion, "non-global");
|
|
}
|
|
|
|
static bool
|
|
IsCacheableNonGlobalEnvironmentChain(JSObject* envChain, JSObject* holder)
|
|
{
|
|
while (true) {
|
|
if (!IsCacheableEnvironment(envChain)) {
|
|
JitSpew(JitSpew_IonIC, "Non-cacheable object on env chain");
|
|
return false;
|
|
}
|
|
|
|
if (envChain == holder)
|
|
return true;
|
|
|
|
envChain = &envChain->as<EnvironmentObject>().enclosingEnvironment();
|
|
if (!envChain) {
|
|
JitSpew(JitSpew_IonIC, "env chain indirect hit");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
MOZ_CRASH("Invalid env chain");
|
|
}
|
|
|
|
JSObject*
|
|
BindNameIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex,
|
|
HandleObject envChain)
|
|
{
|
|
IonScript* ion = outerScript->ionScript();
|
|
BindNameIC& cache = ion->getCache(cacheIndex).toBindName();
|
|
HandlePropertyName name = cache.name();
|
|
|
|
RootedObject holder(cx);
|
|
if (!LookupNameUnqualified(cx, name, envChain, &holder))
|
|
return nullptr;
|
|
|
|
// Stop generating new stubs once we hit the stub count limit, see
|
|
// GetPropertyCache.
|
|
if (cache.canAttachStub()) {
|
|
if (envChain->is<GlobalObject>()) {
|
|
if (!cache.attachGlobal(cx, outerScript, ion, envChain))
|
|
return nullptr;
|
|
} else if (IsCacheableNonGlobalEnvironmentChain(envChain, holder)) {
|
|
if (!cache.attachNonGlobal(cx, outerScript, ion, envChain, holder))
|
|
return nullptr;
|
|
} else {
|
|
JitSpew(JitSpew_IonIC, "BINDNAME uncacheable env chain");
|
|
}
|
|
}
|
|
|
|
return holder;
|
|
}
|
|
|
|
bool
|
|
NameIC::attachReadSlot(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
|
HandleObject envChain, 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(environmentChainReg(), scratchReg);
|
|
GenerateEnvironmentChainGuards(masm, envChain, holderBase, scratchReg, &failures,
|
|
/* skipLastGuard = */true);
|
|
|
|
// GenerateEnvironmentChain leaves the last env chain in scratchReg, even though it
|
|
// doesn't generate the extra guard.
|
|
//
|
|
// NAME ops must do their own TDZ checks.
|
|
GenerateReadSlot(cx, ion, masm, attacher, CheckTDZ, holderBase, holder, shape, scratchReg,
|
|
outputReg(), failures.used() ? &failures : nullptr);
|
|
|
|
return linkAndAttachStub(cx, masm, attacher, ion, "generic",
|
|
JS::TrackedOutcome::ICNameStub_ReadSlot);
|
|
}
|
|
|
|
static bool
|
|
IsCacheableEnvironmentChain(JSObject* envChain, JSObject* obj)
|
|
{
|
|
JSObject* obj2 = envChain;
|
|
while (obj2) {
|
|
if (!IsCacheableEnvironment(obj2) && !obj2->is<GlobalObject>())
|
|
return false;
|
|
|
|
// Stop once we hit the global or target obj.
|
|
if (obj2->is<GlobalObject>() || obj2 == obj)
|
|
break;
|
|
|
|
obj2 = obj2->enclosingEnvironment();
|
|
}
|
|
|
|
return obj == obj2;
|
|
}
|
|
|
|
static bool
|
|
IsCacheableNameReadSlot(HandleObject envChain, 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 (!IsCacheableGetPropReadSlotForIonOrCacheIR(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 IsCacheableEnvironmentChain(envChain, obj);
|
|
}
|
|
|
|
bool
|
|
NameIC::attachCallGetter(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
|
HandleObject envChain, 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(environmentChainReg(), scratchReg);
|
|
GenerateEnvironmentChainGuards(masm, envChain, obj, scratchReg, &failures,
|
|
/* skipLastGuard = */true);
|
|
|
|
// GenerateEnvironmentChain leaves the last env 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 envChain, HandleObject obj, HandleObject holder,
|
|
HandleShape shape)
|
|
{
|
|
if (!shape)
|
|
return false;
|
|
if (!obj->is<GlobalObject>())
|
|
return false;
|
|
|
|
if (!IsCacheableEnvironmentChain(envChain, obj))
|
|
return false;
|
|
|
|
return IsCacheableGetPropCallNative(obj, holder, shape) ||
|
|
IsCacheableGetPropCallPropertyOp(obj, holder, shape) ||
|
|
IsCacheableGetPropCallScripted(obj, holder, shape);
|
|
}
|
|
|
|
bool
|
|
NameIC::attachTypeOfNoProperty(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
|
HandleObject envChain)
|
|
{
|
|
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
|
|
Label failures;
|
|
StubAttacher attacher(*this);
|
|
|
|
Register scratchReg = outputReg().valueReg().scratchReg();
|
|
|
|
masm.movePtr(environmentChainReg(), scratchReg);
|
|
|
|
// Generate env chain guards.
|
|
// Since the property was not defined on any object, iterate until reaching the global.
|
|
JSObject* tobj = envChain;
|
|
while (true) {
|
|
GenerateEnvironmentChainGuard(masm, tobj, scratchReg, nullptr, &failures);
|
|
|
|
if (tobj->is<GlobalObject>())
|
|
break;
|
|
|
|
// Load the next link.
|
|
tobj = &tobj->as<EnvironmentObject>().enclosingEnvironment();
|
|
masm.extractObject(Address(scratchReg, EnvironmentObject::offsetOfEnclosingEnvironment()),
|
|
scratchReg);
|
|
}
|
|
|
|
masm.moveValue(UndefinedValue(), outputReg().valueReg());
|
|
attacher.jumpRejoin(masm);
|
|
|
|
masm.bind(&failures);
|
|
attacher.jumpNextStub(masm);
|
|
|
|
return linkAndAttachStub(cx, masm, attacher, ion, "generic",
|
|
JS::TrackedOutcome::ICNameStub_TypeOfNoProperty);
|
|
}
|
|
|
|
static bool
|
|
IsCacheableNameNoProperty(HandleObject envChain, HandleObject obj,
|
|
HandleObject holder, HandleShape shape, jsbytecode* pc,
|
|
NameIC& cache)
|
|
{
|
|
if (cache.isTypeOf() && !shape) {
|
|
MOZ_ASSERT(!obj);
|
|
MOZ_ASSERT(!holder);
|
|
MOZ_ASSERT(envChain);
|
|
|
|
// Assert those extra things checked by IsCacheableNoProperty().
|
|
MOZ_ASSERT(cache.outputReg().hasValue());
|
|
MOZ_ASSERT(pc != nullptr);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
NameIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, HandleObject envChain,
|
|
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, envChain, &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(envChain, obj, holder, shape, pc, cache.outputReg())) {
|
|
if (!cache.attachReadSlot(cx, outerScript, ion, envChain, obj,
|
|
holder.as<NativeObject>(), shape))
|
|
{
|
|
return false;
|
|
}
|
|
} else if (IsCacheableNameCallGetter(envChain, obj, holder, shape)) {
|
|
void* returnAddr = GetReturnAddressToIonCode(cx);
|
|
if (!cache.attachCallGetter(cx, outerScript, ion, envChain, obj, holder, shape,
|
|
returnAddr))
|
|
{
|
|
return false;
|
|
}
|
|
} else if (IsCacheableNameNoProperty(envChain, obj, holder, shape, pc, cache)) {
|
|
if (!cache.attachTypeOfNoProperty(cx, outerScript, ion, envChain))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Monitor changes to cache entry.
|
|
TypeScript::Monitor(cx, script, pc, vp);
|
|
|
|
return true;
|
|
}
|