Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/BaselineIC.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Sprintf.h"
#include "jstypes.h"
#include "builtin/Eval.h"
#include "jit/BaselineCacheIRCompiler.h"
#include "jit/CacheIRGenerator.h"
#include "jit/CacheIRHealth.h"
#include "jit/JitFrames.h"
#include "jit/JitRuntime.h"
#include "jit/JitSpewer.h"
#include "jit/Linker.h"
#include "jit/PerfSpewer.h"
#include "jit/SharedICHelpers.h"
#include "jit/SharedICRegisters.h"
#include "jit/VMFunctions.h"
#include "js/Conversions.h"
#include "js/friend/ErrorMessages.h" // JSMSG_*
#include "vm/BytecodeIterator.h"
#include "vm/BytecodeLocation.h"
#include "vm/BytecodeUtil.h"
#include "vm/EqualityOperations.h"
#include "vm/JSFunction.h"
#include "vm/JSScript.h"
#include "vm/Opcodes.h"
#ifdef MOZ_VTUNE
# include "vtune/VTuneWrapper.h"
#endif
#include "jit/MacroAssembler-inl.h"
#include "jit/SharedICHelpers-inl.h"
#include "jit/VMFunctionList-inl.h"
#include "vm/BytecodeIterator-inl.h"
#include "vm/BytecodeLocation-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/JSScript-inl.h"
using mozilla::DebugOnly;
namespace js {
namespace jit {
// Class used to emit all Baseline IC fallback code when initializing the
// JitRuntime.
class MOZ_RAII FallbackICCodeCompiler final {
BaselineICFallbackCode& code;
MacroAssembler& masm;
JSContext* cx;
bool inStubFrame_ = false;
#ifdef DEBUG
bool entersStubFrame_ = false;
uint32_t framePushedAtEnterStubFrame_ = 0;
#endif
[[nodiscard]] bool emitCall(bool isSpread, bool isConstructing);
[[nodiscard]] bool emitGetElem(bool hasReceiver);
[[nodiscard]] bool emitGetProp(bool hasReceiver);
public:
FallbackICCodeCompiler(JSContext* cx, BaselineICFallbackCode& code,
MacroAssembler& masm)
: code(code), masm(masm), cx(cx) {}
#define DEF_METHOD(kind) [[nodiscard]] bool emit_##kind();
IC_BASELINE_FALLBACK_CODE_KIND_LIST(DEF_METHOD)
#undef DEF_METHOD
void pushCallArguments(MacroAssembler& masm,
AllocatableGeneralRegisterSet regs, Register argcReg,
bool isConstructing);
// Push a payload specialized per compiler needed to execute stubs.
void PushStubPayload(MacroAssembler& masm, Register scratch);
void pushStubPayload(MacroAssembler& masm, Register scratch);
// Emits a tail call to a VMFunction wrapper.
[[nodiscard]] bool tailCallVMInternal(MacroAssembler& masm,
TailCallVMFunctionId id);
template <typename Fn, Fn fn>
[[nodiscard]] bool tailCallVM(MacroAssembler& masm);
// Emits a normal (non-tail) call to a VMFunction wrapper.
[[nodiscard]] bool callVMInternal(MacroAssembler& masm, VMFunctionId id);
template <typename Fn, Fn fn>
[[nodiscard]] bool callVM(MacroAssembler& masm);
// A stub frame is used when a stub wants to call into the VM without
// performing a tail call. This is required for the return address
// to pc mapping to work.
void enterStubFrame(MacroAssembler& masm, Register scratch);
void assumeStubFrame();
void leaveStubFrame(MacroAssembler& masm);
};
AllocatableGeneralRegisterSet BaselineICAvailableGeneralRegs(size_t numInputs) {
AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
MOZ_ASSERT(!regs.has(FramePointer));
#if defined(JS_CODEGEN_ARM)
MOZ_ASSERT(!regs.has(ICTailCallReg));
regs.take(BaselineSecondScratchReg);
#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
MOZ_ASSERT(!regs.has(ICTailCallReg));
MOZ_ASSERT(!regs.has(BaselineSecondScratchReg));
#elif defined(JS_CODEGEN_ARM64)
MOZ_ASSERT(!regs.has(PseudoStackPointer));
MOZ_ASSERT(!regs.has(RealStackPointer));
MOZ_ASSERT(!regs.has(ICTailCallReg));
#endif
regs.take(ICStubReg);
switch (numInputs) {
case 0:
break;
case 1:
regs.take(R0);
break;
case 2:
regs.take(R0);
regs.take(R1);
break;
default:
MOZ_CRASH("Invalid numInputs");
}
return regs;
}
static jsbytecode* StubOffsetToPc(const ICFallbackStub* stub,
const JSScript* script) {
return script->offsetToPC(stub->pcOffset());
}
#ifdef JS_JITSPEW
void FallbackICSpew(JSContext* cx, ICFallbackStub* stub, const char* fmt, ...) {
if (JitSpewEnabled(JitSpew_BaselineICFallback)) {
RootedScript script(cx, GetTopJitJSScript(cx));
jsbytecode* pc = StubOffsetToPc(stub, script);
char fmtbuf[100];
va_list args;
va_start(args, fmt);
(void)VsprintfLiteral(fmtbuf, fmt, args);
va_end(args);
JitSpew(
JitSpew_BaselineICFallback,
"Fallback hit for (%s:%u:%u) (pc=%zu,line=%u,uses=%u,stubs=%zu): %s",
script->filename(), script->lineno(), script->column(),
script->pcToOffset(pc), PCToLineNumber(script, pc),
script->getWarmUpCount(), stub->numOptimizedStubs(), fmtbuf);
}
}
#endif // JS_JITSPEW
void ICEntry::trace(JSTracer* trc) {
ICStub* stub = firstStub();
// Trace CacheIR stubs.
while (!stub->isFallback()) {
stub->toCacheIRStub()->trace(trc);
stub = stub->toCacheIRStub()->next();
}
// Fallback stubs use runtime-wide trampoline code we don't need to trace.
MOZ_ASSERT(stub->usesTrampolineCode());
}
// constexpr table mapping JSOp to BaselineICFallbackKind. Each value in the
// table is either a fallback kind or a sentinel value (NoICValue) indicating
// the JSOp is not a JOF_IC op.
class MOZ_STATIC_CLASS OpToFallbackKindTable {
static_assert(sizeof(BaselineICFallbackKind) == sizeof(uint8_t));
uint8_t table_[JSOP_LIMIT] = {};
constexpr void setKind(JSOp op, BaselineICFallbackKind kind) {
MOZ_ASSERT(uint8_t(kind) != NoICValue);
table_[size_t(op)] = uint8_t(kind);
}
public:
static constexpr uint8_t NoICValue = uint8_t(BaselineICFallbackKind::Count);
uint8_t lookup(JSOp op) const { return table_[size_t(op)]; }
constexpr OpToFallbackKindTable() {
for (size_t i = 0; i < JSOP_LIMIT; i++) {
table_[i] = NoICValue;
}
setKind(JSOp::Not, BaselineICFallbackKind::ToBool);
setKind(JSOp::And, BaselineICFallbackKind::ToBool);
setKind(JSOp::Or, BaselineICFallbackKind::ToBool);
setKind(JSOp::JumpIfTrue, BaselineICFallbackKind::ToBool);
setKind(JSOp::JumpIfFalse, BaselineICFallbackKind::ToBool);
setKind(JSOp::BitNot, BaselineICFallbackKind::UnaryArith);
setKind(JSOp::Pos, BaselineICFallbackKind::UnaryArith);
setKind(JSOp::Neg, BaselineICFallbackKind::UnaryArith);
setKind(JSOp::Inc, BaselineICFallbackKind::UnaryArith);
setKind(JSOp::Dec, BaselineICFallbackKind::UnaryArith);
setKind(JSOp::ToNumeric, BaselineICFallbackKind::UnaryArith);
setKind(JSOp::BitOr, BaselineICFallbackKind::BinaryArith);
setKind(JSOp::BitXor, BaselineICFallbackKind::BinaryArith);
setKind(JSOp::BitAnd, BaselineICFallbackKind::BinaryArith);
setKind(JSOp::Lsh, BaselineICFallbackKind::BinaryArith);
setKind(JSOp::Rsh, BaselineICFallbackKind::BinaryArith);
setKind(JSOp::Ursh, BaselineICFallbackKind::BinaryArith);
setKind(JSOp::Add, BaselineICFallbackKind::BinaryArith);
setKind(JSOp::Sub, BaselineICFallbackKind::BinaryArith);
setKind(JSOp::Mul, BaselineICFallbackKind::BinaryArith);
setKind(JSOp::Div, BaselineICFallbackKind::BinaryArith);
setKind(JSOp::Mod, BaselineICFallbackKind::BinaryArith);
setKind(JSOp::Pow, BaselineICFallbackKind::BinaryArith);
setKind(JSOp::Eq, BaselineICFallbackKind::Compare);
setKind(JSOp::Ne, BaselineICFallbackKind::Compare);
setKind(JSOp::Lt, BaselineICFallbackKind::Compare);
setKind(JSOp::Le, BaselineICFallbackKind::Compare);
setKind(JSOp::Gt, BaselineICFallbackKind::Compare);
setKind(JSOp::Ge, BaselineICFallbackKind::Compare);
setKind(JSOp::StrictEq, BaselineICFallbackKind::Compare);
setKind(JSOp::StrictNe, BaselineICFallbackKind::Compare);
setKind(JSOp::NewArray, BaselineICFallbackKind::NewArray);
setKind(JSOp::NewObject, BaselineICFallbackKind::NewObject);
setKind(JSOp::NewInit, BaselineICFallbackKind::NewObject);
setKind(JSOp::InitElem, BaselineICFallbackKind::SetElem);
setKind(JSOp::InitHiddenElem, BaselineICFallbackKind::SetElem);
setKind(JSOp::InitLockedElem, BaselineICFallbackKind::SetElem);
setKind(JSOp::InitElemInc, BaselineICFallbackKind::SetElem);
setKind(JSOp::SetElem, BaselineICFallbackKind::SetElem);
setKind(JSOp::StrictSetElem, BaselineICFallbackKind::SetElem);
setKind(JSOp::InitProp, BaselineICFallbackKind::SetProp);
setKind(JSOp::InitLockedProp, BaselineICFallbackKind::SetProp);
setKind(JSOp::InitHiddenProp, BaselineICFallbackKind::SetProp);
setKind(JSOp::InitGLexical, BaselineICFallbackKind::SetProp);
setKind(JSOp::SetProp, BaselineICFallbackKind::SetProp);
setKind(JSOp::StrictSetProp, BaselineICFallbackKind::SetProp);
setKind(JSOp::SetName, BaselineICFallbackKind::SetProp);
setKind(JSOp::StrictSetName, BaselineICFallbackKind::SetProp);
setKind(JSOp::SetGName, BaselineICFallbackKind::SetProp);
setKind(JSOp::StrictSetGName, BaselineICFallbackKind::SetProp);
setKind(JSOp::GetProp, BaselineICFallbackKind::GetProp);
setKind(JSOp::GetBoundName, BaselineICFallbackKind::GetProp);
setKind(JSOp::GetPropSuper, BaselineICFallbackKind::GetPropSuper);
setKind(JSOp::GetElem, BaselineICFallbackKind::GetElem);
setKind(JSOp::GetElemSuper, BaselineICFallbackKind::GetElemSuper);
setKind(JSOp::In, BaselineICFallbackKind::In);
setKind(JSOp::HasOwn, BaselineICFallbackKind::HasOwn);
setKind(JSOp::CheckPrivateField, BaselineICFallbackKind::CheckPrivateField);
setKind(JSOp::GetName, BaselineICFallbackKind::GetName);
setKind(JSOp::GetGName, BaselineICFallbackKind::GetName);
setKind(JSOp::BindName, BaselineICFallbackKind::BindName);
setKind(JSOp::BindGName, BaselineICFallbackKind::BindName);
setKind(JSOp::GetIntrinsic, BaselineICFallbackKind::GetIntrinsic);
setKind(JSOp::Call, BaselineICFallbackKind::Call);
setKind(JSOp::CallContent, BaselineICFallbackKind::Call);
setKind(JSOp::CallIgnoresRv, BaselineICFallbackKind::Call);
setKind(JSOp::CallIter, BaselineICFallbackKind::Call);
setKind(JSOp::CallContentIter, BaselineICFallbackKind::Call);
setKind(JSOp::Eval, BaselineICFallbackKind::Call);
setKind(JSOp::StrictEval, BaselineICFallbackKind::Call);
setKind(JSOp::SuperCall, BaselineICFallbackKind::CallConstructing);
setKind(JSOp::New, BaselineICFallbackKind::CallConstructing);
setKind(JSOp::NewContent, BaselineICFallbackKind::CallConstructing);
setKind(JSOp::SpreadCall, BaselineICFallbackKind::SpreadCall);
setKind(JSOp::SpreadEval, BaselineICFallbackKind::SpreadCall);
setKind(JSOp::StrictSpreadEval, BaselineICFallbackKind::SpreadCall);
setKind(JSOp::SpreadSuperCall,
BaselineICFallbackKind::SpreadCallConstructing);
setKind(JSOp::SpreadNew, BaselineICFallbackKind::SpreadCallConstructing);
setKind(JSOp::Instanceof, BaselineICFallbackKind::InstanceOf);
setKind(JSOp::Typeof, BaselineICFallbackKind::TypeOf);
setKind(JSOp::TypeofExpr, BaselineICFallbackKind::TypeOf);
setKind(JSOp::ToPropertyKey, BaselineICFallbackKind::ToPropertyKey);
setKind(JSOp::Iter, BaselineICFallbackKind::GetIterator);
setKind(JSOp::OptimizeSpreadCall,
BaselineICFallbackKind::OptimizeSpreadCall);
setKind(JSOp::Rest, BaselineICFallbackKind::Rest);
setKind(JSOp::CloseIter, BaselineICFallbackKind::CloseIter);
}
};
static constexpr OpToFallbackKindTable FallbackKindTable;
void ICScript::initICEntries(JSContext* cx, JSScript* script) {
MOZ_ASSERT(cx->realm()->jitRealm());
MOZ_ASSERT(jit::IsBaselineInterpreterEnabled());
MOZ_ASSERT(numICEntries() == script->numICEntries());
// Index of the next ICEntry to initialize.
uint32_t icEntryIndex = 0;
const BaselineICFallbackCode& fallbackCode =
cx->runtime()->jitRuntime()->baselineICFallbackCode();
// For JOF_IC ops: initialize ICEntries and fallback stubs.
for (BytecodeLocation loc : js::AllBytecodesIterable(script)) {
JSOp op = loc.getOp();
// Assert the frontend stored the correct IC index in jump target ops.
MOZ_ASSERT_IF(BytecodeIsJumpTarget(op), loc.icIndex() == icEntryIndex);
uint8_t tableValue = FallbackKindTable.lookup(op);
if (tableValue == OpToFallbackKindTable::NoICValue) {
MOZ_ASSERT(!BytecodeOpHasIC(op),
"Missing entry in OpToFallbackKindTable for JOF_IC op");
continue;
}
MOZ_ASSERT(BytecodeOpHasIC(op),
"Unexpected fallback kind for non-JOF_IC op");
BaselineICFallbackKind kind = BaselineICFallbackKind(tableValue);
TrampolinePtr stubCode = fallbackCode.addr(kind);
// Initialize the ICEntry and ICFallbackStub.
uint32_t offset = loc.bytecodeToOffset(script);
ICEntry& entryRef = this->icEntry(icEntryIndex);
ICFallbackStub* stub = fallbackStub(icEntryIndex);
icEntryIndex++;
new (&entryRef) ICEntry(stub);
new (stub) ICFallbackStub(offset, stubCode);
}
// Assert all ICEntries have been initialized.
MOZ_ASSERT(icEntryIndex == numICEntries());
}
bool ICSupportsPolymorphicTypeData(JSOp op) {
MOZ_ASSERT(BytecodeOpHasIC(op));
BaselineICFallbackKind kind =
BaselineICFallbackKind(FallbackKindTable.lookup(op));
switch (kind) {
case BaselineICFallbackKind::ToBool:
case BaselineICFallbackKind::TypeOf:
return true;
default:
return false;
}
}
bool ICCacheIRStub::makesGCCalls() const { return stubInfo()->makesGCCalls(); }
void ICFallbackStub::trackNotAttached() { state().trackNotAttached(); }
// When we enter a baseline fallback stub, if a Warp compilation
// exists that transpiled that IC, we notify that compilation. This
// helps the bailout code tell whether a bailing instruction hoisted
// by LICM would have been executed anyway.
static void MaybeNotifyWarp(JSScript* script, ICFallbackStub* stub) {
if (stub->state().usedByTranspiler() && script->hasIonScript()) {
script->ionScript()->noteBaselineFallback();
}
}
void ICCacheIRStub::trace(JSTracer* trc) {
JitCode* stubJitCode = jitCode();
TraceManuallyBarrieredEdge(trc, &stubJitCode, "baseline-ic-stub-code");
TraceCacheIRStub(trc, this, stubInfo());
}
static void MaybeTransition(JSContext* cx, BaselineFrame* frame,
ICFallbackStub* stub) {
if (stub->state().shouldTransition()) {
if (!TryFoldingStubs(cx, stub, frame->script(), frame->icScript())) {
cx->recoverFromOutOfMemory();
}
if (stub->state().maybeTransition()) {
ICEntry* icEntry = frame->icScript()->icEntryForStub(stub);
#ifdef JS_CACHEIR_SPEW
if (cx->spewer().enabled(cx, frame->script(),
SpewChannel::CacheIRHealthReport)) {
CacheIRHealth cih;
RootedScript script(cx, frame->script());
cih.healthReportForIC(cx, icEntry, stub, script,
SpewContext::Transition);
}
#endif
stub->discardStubs(cx, icEntry);
}
}
}
// This helper handles ICState updates/transitions while attaching CacheIR
// stubs.
template <typename IRGenerator, typename... Args>
static void TryAttachStub(const char* name, JSContext* cx, BaselineFrame* frame,
ICFallbackStub* stub, Args&&... args) {
MaybeTransition(cx, frame, stub);
if (stub->state().canAttachStub()) {
RootedScript script(cx, frame->script());
ICScript* icScript = frame->icScript();
jsbytecode* pc = StubOffsetToPc(stub, script);
bool attached = false;
IRGenerator gen(cx, script, pc, stub->state(), std::forward<Args>(args)...);
switch (gen.tryAttachStub()) {
case AttachDecision::Attach: {
ICAttachResult result =
AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
script, icScript, stub, gen.stubName());
if (result == ICAttachResult::Attached) {
attached = true;
JitSpew(JitSpew_BaselineIC, " Attached %s CacheIR stub", name);
}
} break;
case AttachDecision::NoAction:
break;
case AttachDecision::TemporarilyUnoptimizable:
case AttachDecision::Deferred:
MOZ_ASSERT_UNREACHABLE("Not expected in generic TryAttachStub");
break;
}
if (!attached) {
stub->trackNotAttached();
}
}
}
void ICFallbackStub::unlinkStub(Zone* zone, ICEntry* icEntry,
ICCacheIRStub* prev, ICCacheIRStub* stub) {
if (prev) {
MOZ_ASSERT(prev->next() == stub);
prev->setNext(stub->next());
} else {
MOZ_ASSERT(icEntry->firstStub() == stub);
icEntry->setFirstStub(stub->next());
}
state_.trackUnlinkedStub();
// We are removing edges from ICStub to gcthings. Perform a barrier to let the
// GC know about those edges.
PreWriteBarrier(zone, stub);
#ifdef DEBUG
// Poison stub code to ensure we don't call this stub again. However, if
// this stub can make calls, a pointer to it may be stored in a stub frame
// on the stack, so we can't touch the stubCode_ or GC will crash when
// tracing this pointer.
if (!stub->makesGCCalls()) {
stub->stubCode_ = (uint8_t*)0xbad;
}
#endif
}
void ICFallbackStub::discardStubs(JSContext* cx, ICEntry* icEntry) {
ICStub* stub = icEntry->firstStub();
while (stub != this) {
unlinkStub(cx->zone(), icEntry, /* prev = */ nullptr,
stub->toCacheIRStub());
stub = stub->toCacheIRStub()->next();
}
clearHasFoldedStub();
}
static void InitMacroAssemblerForICStub(StackMacroAssembler& masm) {
#ifndef JS_USE_LINK_REGISTER
// The first value contains the return addres,
// which we pull into ICTailCallReg for tail calls.
masm.adjustFrame(sizeof(intptr_t));
#endif
#ifdef JS_CODEGEN_ARM
masm.setSecondScratchReg(BaselineSecondScratchReg);
#endif
}
bool FallbackICCodeCompiler::tailCallVMInternal(MacroAssembler& masm,
TailCallVMFunctionId id) {
TrampolinePtr code = cx->runtime()->jitRuntime()->getVMWrapper(id);
const VMFunctionData& fun = GetVMFunction(id);
MOZ_ASSERT(fun.expectTailCall == TailCall);
uint32_t argSize = fun.explicitStackSlots() * sizeof(void*);
EmitBaselineTailCallVM(code, masm, argSize);
return true;
}
bool FallbackICCodeCompiler::callVMInternal(MacroAssembler& masm,
VMFunctionId id) {
MOZ_ASSERT(inStubFrame_);
TrampolinePtr code = cx->runtime()->jitRuntime()->getVMWrapper(id);
MOZ_ASSERT(GetVMFunction(id).expectTailCall == NonTailCall);
EmitBaselineCallVM(code, masm);
return true;
}
template <typename Fn, Fn fn>
bool FallbackICCodeCompiler::callVM(MacroAssembler& masm) {
VMFunctionId id = VMFunctionToId<Fn, fn>::id;
return callVMInternal(masm, id);
}
template <typename Fn, Fn fn>
bool FallbackICCodeCompiler::tailCallVM(MacroAssembler& masm) {
TailCallVMFunctionId id = TailCallVMFunctionToId<Fn, fn>::id;
return tailCallVMInternal(masm, id);
}
void FallbackICCodeCompiler::enterStubFrame(MacroAssembler& masm,
Register scratch) {
EmitBaselineEnterStubFrame(masm, scratch);
#ifdef DEBUG
framePushedAtEnterStubFrame_ = masm.framePushed();
#endif
MOZ_ASSERT(!inStubFrame_);
inStubFrame_ = true;
#ifdef DEBUG
entersStubFrame_ = true;
#endif
}
void FallbackICCodeCompiler::assumeStubFrame() {
MOZ_ASSERT(!inStubFrame_);
inStubFrame_ = true;
#ifdef DEBUG
entersStubFrame_ = true;
// |framePushed| isn't tracked precisely in ICStubs, so simply assume it to
// be the stub frame layout and the pushed ICStub* so that assertions don't
// fail in leaveStubFrame
framePushedAtEnterStubFrame_ =
BaselineStubFrameLayout::Size() + sizeof(ICStub*);
#endif
}
void FallbackICCodeCompiler::leaveStubFrame(MacroAssembler& masm) {
MOZ_ASSERT(entersStubFrame_ && inStubFrame_);
inStubFrame_ = false;
#ifdef DEBUG
masm.setFramePushed(framePushedAtEnterStubFrame_);
#endif
EmitBaselineLeaveStubFrame(masm);
}
void FallbackICCodeCompiler::pushStubPayload(MacroAssembler& masm,
Register scratch) {
if (inStubFrame_) {
masm.loadPtr(Address(FramePointer, 0), scratch);
masm.pushBaselineFramePtr(scratch, scratch);
} else {
masm.pushBaselineFramePtr(FramePointer, scratch);
}
}
void FallbackICCodeCompiler::PushStubPayload(MacroAssembler& masm,
Register scratch) {
pushStubPayload(masm, scratch);
masm.adjustFrame(sizeof(intptr_t));
}
//
// ToBool_Fallback
//
bool DoToBoolFallback(JSContext* cx, BaselineFrame* frame, ICFallbackStub* stub,
HandleValue arg, MutableHandleValue ret) {
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
FallbackICSpew(cx, stub, "ToBool");
TryAttachStub<ToBoolIRGenerator>("ToBool", cx, frame, stub, arg);
bool cond = ToBoolean(arg);
ret.setBoolean(cond);
return true;
}
bool FallbackICCodeCompiler::emit_ToBool() {
static_assert(R0 == JSReturnOperand);
// Restore the tail call register.
EmitRestoreTailCallReg(masm);
// Push arguments.
masm.pushValue(R0);
masm.push(ICStubReg);
pushStubPayload(masm, R0.scratchReg());
using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, HandleValue,
MutableHandleValue);
return tailCallVM<Fn, DoToBoolFallback>(masm);
}
//
// GetElem_Fallback
//
bool DoGetElemFallback(JSContext* cx, BaselineFrame* frame,
ICFallbackStub* stub, HandleValue lhs, HandleValue rhs,
MutableHandleValue res) {
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
FallbackICSpew(cx, stub, "GetElem");
#ifdef DEBUG
jsbytecode* pc = StubOffsetToPc(stub, frame->script());
MOZ_ASSERT(JSOp(*pc) == JSOp::GetElem);
#endif
TryAttachStub<GetPropIRGenerator>("GetElem", cx, frame, stub,
CacheKind::GetElem, lhs, rhs);
if (!GetElementOperation(cx, lhs, rhs, res)) {
return false;
}
return true;
}
bool DoGetElemSuperFallback(JSContext* cx, BaselineFrame* frame,
ICFallbackStub* stub, HandleValue lhs,
HandleValue rhs, HandleValue receiver,
MutableHandleValue res) {
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
jsbytecode* pc = StubOffsetToPc(stub, frame->script());
JSOp op = JSOp(*pc);
FallbackICSpew(cx, stub, "GetElemSuper(%s)", CodeName(op));
MOZ_ASSERT(op == JSOp::GetElemSuper);
// |lhs| is [[HomeObject]].[[Prototype]] which must be an Object or null.
MOZ_ASSERT(lhs.isObjectOrNull());
int lhsIndex = -1;
RootedObject lhsObj(
cx, ToObjectFromStackForPropertyAccess(cx, lhs, lhsIndex, rhs));
if (!lhsObj) {
return false;
}
TryAttachStub<GetPropIRGenerator>("GetElemSuper", cx, frame, stub,
CacheKind::GetElemSuper, lhs, rhs);
return GetObjectElementOperation(cx, op, lhsObj, receiver, rhs, res);
}
bool FallbackICCodeCompiler::emitGetElem(bool hasReceiver) {
static_assert(R0 == JSReturnOperand);
// Restore the tail call register.
EmitRestoreTailCallReg(masm);
// Super property getters use a |this| that differs from base object
if (hasReceiver) {
// State: receiver in R0, index in R1, obj on the stack
// Ensure stack is fully synced for the expression decompiler.
// We need: receiver, index, obj
masm.pushValue(R0);
masm.pushValue(R1);
masm.pushValue(Address(masm.getStackPointer(), sizeof(Value) * 2));
// Push arguments.
masm.pushValue(R0); // Receiver
masm.pushValue(R1); // Index
masm.pushValue(Address(masm.getStackPointer(), sizeof(Value) * 5)); // Obj
masm.push(ICStubReg);
masm.pushBaselineFramePtr(FramePointer, R0.scratchReg());
using Fn =
bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, HandleValue,
HandleValue, HandleValue, MutableHandleValue);
if (!tailCallVM<Fn, DoGetElemSuperFallback>(masm)) {
return false;
}
} else {
// Ensure stack is fully synced for the expression decompiler.
masm.pushValue(R0);
masm.pushValue(R1);
// Push arguments.
masm.pushValue(R1);
masm.pushValue(R0);
masm.push(ICStubReg);
masm.pushBaselineFramePtr(FramePointer, R0.scratchReg());
using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*,
HandleValue, HandleValue, MutableHandleValue);
if (!tailCallVM<Fn, DoGetElemFallback>(masm)) {
return false;
}
}
// This is the resume point used when bailout rewrites call stack to undo
// Ion inlined frames. The return address pushed onto reconstructed stack
// will point here.
assumeStubFrame();
if (hasReceiver) {
code.initBailoutReturnOffset(BailoutReturnKind::GetElemSuper,
masm.currentOffset());
} else {
code.initBailoutReturnOffset(BailoutReturnKind::GetElem,
masm.currentOffset());
}
leaveStubFrame(masm);
EmitReturnFromIC(masm);
return true;
}
bool FallbackICCodeCompiler::emit_GetElem() {
return emitGetElem(/* hasReceiver = */ false);
}
bool FallbackICCodeCompiler::emit_GetElemSuper() {
return emitGetElem(/* hasReceiver = */ true);
}
bool DoSetElemFallback(JSContext* cx, BaselineFrame* frame,
ICFallbackStub* stub, Value* stack, HandleValue objv,
HandleValue index, HandleValue rhs) {
using DeferType = SetPropIRGenerator::DeferType;
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
RootedScript script(cx, frame->script());
RootedScript outerScript(cx, script);
jsbytecode* pc = StubOffsetToPc(stub, script);
JSOp op = JSOp(*pc);
FallbackICSpew(cx, stub, "SetElem(%s)", CodeName(JSOp(*pc)));
MOZ_ASSERT(op == JSOp::SetElem || op == JSOp::StrictSetElem ||
op == JSOp::InitElem || op == JSOp::InitHiddenElem ||
op == JSOp::InitLockedElem || op == JSOp::InitElemInc);
int objvIndex = -3;
RootedObject obj(
cx, ToObjectFromStackForPropertyAccess(cx, objv, objvIndex, index));
if (!obj) {
return false;
}
Rooted<Shape*> oldShape(cx, obj->shape());
DeferType deferType = DeferType::None;
bool attached = false;
MaybeTransition(cx, frame, stub);
if (stub->state().canAttachStub()) {
ICScript* icScript = frame->icScript();
SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, stub->state(),
objv, index, rhs);
switch (gen.tryAttachStub()) {
case AttachDecision::Attach: {
ICAttachResult result = AttachBaselineCacheIRStub(
cx, gen.writerRef(), gen.cacheKind(), frame->script(), icScript,
stub, gen.stubName());
if (result == ICAttachResult::Attached) {
attached = true;
JitSpew(JitSpew_BaselineIC, " Attached SetElem CacheIR stub");
}
} break;
case AttachDecision::NoAction:
break;
case AttachDecision::TemporarilyUnoptimizable:
attached = true;
break;
case AttachDecision::Deferred:
deferType = gen.deferType();
MOZ_ASSERT(deferType != DeferType::None);
break;
}
}
if (op == JSOp::InitElem || op == JSOp::InitHiddenElem ||
op == JSOp::InitLockedElem) {
if (!InitElemOperation(cx, pc, obj, index, rhs)) {
return false;
}
} else if (op == JSOp::InitElemInc) {
if (!InitElemIncOperation(cx, obj.as<ArrayObject>(), index.toInt32(),
rhs)) {
return false;
}
} else {
if (!SetObjectElementWithReceiver(cx, obj, index, rhs, objv,
JSOp(*pc) == JSOp::StrictSetElem)) {
return false;
}
}
// Overwrite the object on the stack (pushed for the decompiler) with the rhs.
MOZ_ASSERT(stack[2] == objv);
stack[2] = rhs;
if (attached) {
return true;
}
// The SetObjectElement call might have entered this IC recursively, so try
// to transition.
MaybeTransition(cx, frame, stub);
bool canAttachStub = stub->state().canAttachStub();
if (deferType != DeferType::None && canAttachStub) {
SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, stub->state(),
objv, index, rhs);
MOZ_ASSERT(deferType == DeferType::AddSlot);
AttachDecision decision = gen.tryAttachAddSlotStub(oldShape);
switch (decision) {
case AttachDecision::Attach: {
ICScript* icScript = frame->icScript();
ICAttachResult result = AttachBaselineCacheIRStub(
cx, gen.writerRef(), gen.cacheKind(), frame->script(), icScript,
stub, gen.stubName());
if (result == ICAttachResult::Attached) {
attached = true;
JitSpew(JitSpew_BaselineIC, " Attached SetElem CacheIR stub");
}
} break;
case AttachDecision::NoAction:
gen.trackAttached(IRGenerator::NotAttached);
break;
case AttachDecision::TemporarilyUnoptimizable:
case AttachDecision::Deferred:
MOZ_ASSERT_UNREACHABLE("Invalid attach result");
break;
}
}
if (!attached && canAttachStub) {
stub->trackNotAttached();
}
return true;
}
bool FallbackICCodeCompiler::emit_SetElem() {
static_assert(R0 == JSReturnOperand);
EmitRestoreTailCallReg(masm);
// State: R0: object, R1: index, stack: rhs.
// For the decompiler, the stack has to be: object, index, rhs,
// so we push the index, then overwrite the rhs Value with R0
// and push the rhs value.
masm.pushValue(R1);
masm.loadValue(Address(masm.getStackPointer(), sizeof(Value)), R1);
masm.storeValue(R0, Address(masm.getStackPointer(), sizeof(Value)));
masm.pushValue(R1);
// Push arguments.
masm.pushValue(R1); // RHS
// Push index. On x86 and ARM two push instructions are emitted so use a
// separate register to store the old stack pointer.
masm.moveStackPtrTo(R1.scratchReg());
masm.pushValue(Address(R1.scratchReg(), 2 * sizeof(Value)));
masm.pushValue(R0); // Object.
// Push pointer to stack values, so that the stub can overwrite the object
// (pushed for the decompiler) with the rhs.
masm.computeEffectiveAddress(
Address(masm.getStackPointer(), 3 * sizeof(Value)), R0.scratchReg());
masm.push(R0.scratchReg());
masm.push(ICStubReg);
pushStubPayload(masm, R0.scratchReg());
using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, Value*,
HandleValue, HandleValue, HandleValue);
return tailCallVM<Fn, DoSetElemFallback>(masm);
}
//
// In_Fallback
//
bool DoInFallback(JSContext* cx, BaselineFrame* frame, ICFallbackStub* stub,
HandleValue key, HandleValue objValue,
MutableHandleValue res) {
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
FallbackICSpew(cx, stub, "In");
if (!objValue.isObject()) {
ReportInNotObjectError(cx, key, objValue);
return false;
}
TryAttachStub<HasPropIRGenerator>("In", cx, frame, stub, CacheKind::In, key,
objValue);
RootedObject obj(cx, &objValue.toObject());
bool cond = false;
if (!OperatorIn(cx, key, obj, &cond)) {
return false;
}
res.setBoolean(cond);
return true;
}
bool FallbackICCodeCompiler::emit_In() {
EmitRestoreTailCallReg(masm);
// Sync for the decompiler.
masm.pushValue(R0);
masm.pushValue(R1);
// Push arguments.
masm.pushValue(R1);
masm.pushValue(R0);
masm.push(ICStubReg);
pushStubPayload(masm, R0.scratchReg());
using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, HandleValue,
HandleValue, MutableHandleValue);
return tailCallVM<Fn, DoInFallback>(masm);
}
//
// HasOwn_Fallback
//
bool DoHasOwnFallback(JSContext* cx, BaselineFrame* frame, ICFallbackStub* stub,
HandleValue keyValue, HandleValue objValue,
MutableHandleValue res) {
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
FallbackICSpew(cx, stub, "HasOwn");
TryAttachStub<HasPropIRGenerator>("HasOwn", cx, frame, stub,
CacheKind::HasOwn, keyValue, objValue);
bool found;
if (!HasOwnProperty(cx, objValue, keyValue, &found)) {
return false;
}
res.setBoolean(found);
return true;
}
bool FallbackICCodeCompiler::emit_HasOwn() {
EmitRestoreTailCallReg(masm);
// Sync for the decompiler.
masm.pushValue(R0);
masm.pushValue(R1);
// Push arguments.
masm.pushValue(R1);
masm.pushValue(R0);
masm.push(ICStubReg);
pushStubPayload(masm, R0.scratchReg());
using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, HandleValue,
HandleValue, MutableHandleValue);
return tailCallVM<Fn, DoHasOwnFallback>(masm);
}
//
// CheckPrivate_Fallback
//
bool DoCheckPrivateFieldFallback(JSContext* cx, BaselineFrame* frame,
ICFallbackStub* stub, HandleValue objValue,
HandleValue keyValue, MutableHandleValue res) {
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
jsbytecode* pc = StubOffsetToPc(stub, frame->script());
FallbackICSpew(cx, stub, "CheckPrivateField");
MOZ_ASSERT(keyValue.isSymbol() && keyValue.toSymbol()->isPrivateName());
TryAttachStub<CheckPrivateFieldIRGenerator>("CheckPrivate", cx, frame, stub,
CacheKind::CheckPrivateField,
keyValue, objValue);
bool result;
if (!CheckPrivateFieldOperation(cx, pc, objValue, keyValue, &result)) {
return false;
}
res.setBoolean(result);
return true;
}
bool FallbackICCodeCompiler::emit_CheckPrivateField() {
EmitRestoreTailCallReg(masm);
// Sync for the decompiler.
masm.pushValue(R0);
masm.pushValue(R1);
// Push arguments.
masm.pushValue(R1);
masm.pushValue(R0);
masm.push(ICStubReg);
pushStubPayload(masm, R0.scratchReg());
using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, HandleValue,
HandleValue, MutableHandleValue);
return tailCallVM<Fn, DoCheckPrivateFieldFallback>(masm);
}
//
// GetName_Fallback
//
bool DoGetNameFallback(JSContext* cx, BaselineFrame* frame,
ICFallbackStub* stub, HandleObject envChain,
MutableHandleValue res) {
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
RootedScript script(cx, frame->script());
jsbytecode* pc = StubOffsetToPc(stub, script);
mozilla::DebugOnly<JSOp> op = JSOp(*pc);
FallbackICSpew(cx, stub, "GetName(%s)", CodeName(JSOp(*pc)));
MOZ_ASSERT(op == JSOp::GetName || op == JSOp::GetGName);
Rooted<PropertyName*> name(cx, script->getName(pc));
TryAttachStub<GetNameIRGenerator>("GetName", cx, frame, stub, envChain, name);
static_assert(JSOpLength_GetGName == JSOpLength_GetName,
"Otherwise our check for JSOp::Typeof isn't ok");
if (JSOp(pc[JSOpLength_GetGName]) == JSOp::Typeof) {
if (!GetEnvironmentName<GetNameMode::TypeOf>(cx, envChain, name, res)) {
return false;
}
} else {
if (!GetEnvironmentName<GetNameMode::Normal>(cx, envChain, name, res)) {
return false;
}
}
return true;
}
bool FallbackICCodeCompiler::emit_GetName() {
static_assert(R0 == JSReturnOperand);
EmitRestoreTailCallReg(masm);
masm.push(R0.scratchReg());
masm.push(ICStubReg);
pushStubPayload(masm, R0.scratchReg());
using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, HandleObject,
MutableHandleValue);
return tailCallVM<Fn, DoGetNameFallback>(masm);
}
//
// BindName_Fallback
//
bool DoBindNameFallback(JSContext* cx, BaselineFrame* frame,
ICFallbackStub* stub, HandleObject envChain,
MutableHandleValue res) {
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
jsbytecode* pc = StubOffsetToPc(stub, frame->script());
mozilla::DebugOnly<JSOp> op = JSOp(*pc);
FallbackICSpew(cx, stub, "BindName(%s)", CodeName(JSOp(*pc)));
MOZ_ASSERT(op == JSOp::BindName || op == JSOp::BindGName);
Rooted<PropertyName*> name(cx, frame->script()->getName(pc));
TryAttachStub<BindNameIRGenerator>("BindName", cx, frame, stub, envChain,
name);
RootedObject scope(cx);
if (!LookupNameUnqualified(cx, name, envChain, &scope)) {
return false;
}
res.setObject(*scope);
return true;
}
bool FallbackICCodeCompiler::emit_BindName() {
static_assert(R0 == JSReturnOperand);
EmitRestoreTailCallReg(masm);
masm.push(R0.scratchReg());
masm.push(ICStubReg);
pushStubPayload(masm, R0.scratchReg());
using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, HandleObject,
MutableHandleValue);
return tailCallVM<Fn, DoBindNameFallback>(masm);
}
//
// GetIntrinsic_Fallback
//
bool DoGetIntrinsicFallback(JSContext* cx, BaselineFrame* frame,
ICFallbackStub* stub, MutableHandleValue res) {
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
RootedScript script(cx, frame->script());
jsbytecode* pc = StubOffsetToPc(stub, script);
mozilla::DebugOnly<JSOp> op = JSOp(*pc);
FallbackICSpew(cx, stub, "GetIntrinsic(%s)", CodeName(JSOp(*pc)));
MOZ_ASSERT(op == JSOp::GetIntrinsic);
if (!GetIntrinsicOperation(cx, script, pc, res)) {
return false;
}
TryAttachStub<GetIntrinsicIRGenerator>("GetIntrinsic", cx, frame, stub, res);
return true;
}
bool FallbackICCodeCompiler::emit_GetIntrinsic() {
EmitRestoreTailCallReg(masm);
masm.push(ICStubReg);
pushStubPayload(masm, R0.scratchReg());
using Fn =
bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, MutableHandleValue);
return tailCallVM<Fn, DoGetIntrinsicFallback>(masm);
}
//
// GetProp_Fallback
//
bool DoGetPropFallback(JSContext* cx, BaselineFrame* frame,
ICFallbackStub* stub, MutableHandleValue val,
MutableHandleValue res) {
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
RootedScript script(cx, frame->script());
jsbytecode* pc = StubOffsetToPc(stub, script);
JSOp op = JSOp(*pc);
FallbackICSpew(cx, stub, "GetProp(%s)", CodeName(op));
MOZ_ASSERT(op == JSOp::GetProp || op == JSOp::GetBoundName);
Rooted<PropertyName*> name(cx, script->getName(pc));
RootedValue idVal(cx, StringValue(name));
TryAttachStub<GetPropIRGenerator>("GetProp", cx, frame, stub,
CacheKind::GetProp, val, idVal);
if (op == JSOp::GetBoundName) {
RootedObject env(cx, &val.toObject());
RootedId id(cx, NameToId(name));
return GetNameBoundInEnvironment(cx, env, id, res);
}
MOZ_ASSERT(op == JSOp::GetProp);
if (!GetProperty(cx, val, name, res)) {
return false;
}
return true;
}
bool DoGetPropSuperFallback(JSContext* cx, BaselineFrame* frame,
ICFallbackStub* stub, HandleValue receiver,
MutableHandleValue val, MutableHandleValue res) {
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
RootedScript script(cx, frame->script());
jsbytecode* pc = StubOffsetToPc(stub, script);
FallbackICSpew(cx, stub, "GetPropSuper(%s)", CodeName(JSOp(*pc)));
MOZ_ASSERT(JSOp(*pc) == JSOp::GetPropSuper);
Rooted<PropertyName*> name(cx, script->getName(pc));
RootedValue idVal(cx, StringValue(name));
// |val| is [[HomeObject]].[[Prototype]] which must be an Object or null.
MOZ_ASSERT(val.isObjectOrNull());
int valIndex = -1;
RootedObject valObj(
cx, ToObjectFromStackForPropertyAccess(cx, val, valIndex, name));
if (!valObj) {
return false;
}
TryAttachStub<GetPropIRGenerator>("GetPropSuper", cx, frame, stub,
CacheKind::GetPropSuper, val, idVal);
if (!GetProperty(cx, valObj, receiver, name, res)) {
return false;
}
return true;
}
bool FallbackICCodeCompiler::emitGetProp(bool hasReceiver) {
static_assert(R0 == JSReturnOperand);
EmitRestoreTailCallReg(masm);
// Super property getters use a |this| that differs from base object
if (hasReceiver) {
// Push arguments.
masm.pushValue(R0);
masm.pushValue(R1);
masm.push(ICStubReg);
masm.pushBaselineFramePtr(FramePointer, R0.scratchReg());
using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*,
HandleValue, MutableHandleValue, MutableHandleValue);
if (!tailCallVM<Fn, DoGetPropSuperFallback>(masm)) {
return false;
}
} else {
// Ensure stack is fully synced for the expression decompiler.
masm.pushValue(R0);
// Push arguments.
masm.pushValue(R0);
masm.push(ICStubReg);
masm.pushBaselineFramePtr(FramePointer, R0.scratchReg());
using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*,
MutableHandleValue, MutableHandleValue);
if (!tailCallVM<Fn, DoGetPropFallback>(masm)) {
return false;
}
}
// This is the resume point used when bailout rewrites call stack to undo
// Ion inlined frames. The return address pushed onto reconstructed stack
// will point here.
assumeStubFrame();
if (hasReceiver) {
code.initBailoutReturnOffset(BailoutReturnKind::GetPropSuper,
masm.currentOffset());
} else {
code.initBailoutReturnOffset(BailoutReturnKind::GetProp,
masm.currentOffset());
}
leaveStubFrame(masm);
EmitReturnFromIC(masm);
return true;
}
bool FallbackICCodeCompiler::emit_GetProp() {
return emitGetProp(/* hasReceiver = */ false);
}
bool FallbackICCodeCompiler::emit_GetPropSuper() {
return emitGetProp(/* hasReceiver = */ true);
}
//
// SetProp_Fallback
//
bool DoSetPropFallback(JSContext* cx, BaselineFrame* frame,
ICFallbackStub* stub, Value* stack, HandleValue lhs,
HandleValue rhs) {
using DeferType = SetPropIRGenerator::DeferType;
stub->incrementEnteredCount();
MaybeNotifyWarp(frame->outerScript(), stub);
RootedScript script(cx, frame->script());
jsbytecode* pc = StubOffsetToPc(stub, script);
JSOp op = JSOp(*pc);
FallbackICSpew(cx, stub, "SetProp(%s)", CodeName(op));
MOZ_ASSERT(op == JSOp::SetProp || op == JSOp::StrictSetProp ||
op == JSOp::SetName || op == JSOp::StrictSetName ||
op == JSOp::SetGName || op == JSOp::StrictSetGName ||
op == JSOp::InitProp || op == JSOp::InitLockedProp ||
op == JSOp::InitHiddenProp || op == JSOp::InitGLexical);
Rooted<PropertyName*> name(cx, script->getName(pc));
RootedId id(cx, NameToId(name));
int lhsIndex = -2;
RootedObject obj(cx,
ToObjectFromStackForPropertyAccess(cx, lhs, lhsIndex, id));
if (!obj) {
return false;
}
Rooted<Shape*> oldShape(cx, obj->shape());
DeferType deferType = DeferType::None;
bool attached = false;
MaybeTransition(cx, frame, stub);
if (stub->state().canAttachStub()) {
RootedValue idVal(cx, StringValue(name));
SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, stub->state(),
lhs, idVal, rhs);
switch (gen.tryAttachStub()) {
case AttachDecision::Attach: {
ICScript* icScript = frame->icScript();
ICAttachResult result = AttachBaselineCacheIRStub(
cx, gen.writerRef(), gen.cacheKind(), frame->script(), icScript,
stub, gen.stubName());
if (result == ICAttachResult::Attached) {
attached = true;
JitSpew(JitSpew_BaselineIC, " Attached SetProp CacheIR stub");
}
} break;
case AttachDecision::NoAction:
break;
case AttachDecision::TemporarilyUnoptimizable:
attached = true;
break;
case AttachDecision::Deferred:
deferType = gen.deferType();
MOZ_ASSERT(deferType != DeferType::None);
break;
}
}
if (op == JSOp::InitProp || op == JSOp::InitLockedProp ||
op == JSOp::InitHiddenProp) {
if (!InitPropertyOperation(cx, pc, obj, name, rhs)) {
return false;
}
} else if (op == JSOp::SetName || op == JSOp::StrictSetName ||
op == JSOp::SetGName || op == JSOp::StrictSetGName) {
if (!SetNameOperation(cx, script, pc, obj, rhs)) {
return false;
}
} else if (op == JSOp::InitGLexical) {
ExtensibleLexicalEnvironmentObject* lexicalEnv;
if (script->hasNonSyntacticScope()) {
lexicalEnv = &NearestEnclosingExtensibleLexicalEnvironment(
frame->environmentChain());
} else {
lexicalEnv = &cx->global()->lexicalEnvironment();
}
InitGlobalLexicalOperation(cx, lexicalEnv, script, pc, rhs);
} else {
MOZ_ASSERT(op == JSOp::SetProp || op == JSOp::StrictSetProp);
ObjectOpResult result;
if (!SetProperty(cx, obj, id, rhs, lhs, result) ||
!result.checkStrictModeError(cx, obj, id, op == JSOp::StrictSetProp)) {
return false;
}
}
// Overwrite the LHS on the stack (pushed for the decompiler) with the RHS.
MOZ_ASSERT(stack[1] == lhs);
stack[1] = rhs;
if (attached) {
return true;
}
// The SetProperty call might have entered this IC recursively, so try
// to transition.
MaybeTransition(cx, frame, stub);
bool canAttachStub = stub->state().canAttachStub();