diff options
Diffstat (limited to 'js/src/jit/BaselineIC.cpp')
-rw-r--r-- | js/src/jit/BaselineIC.cpp | 2744 |
1 files changed, 2744 insertions, 0 deletions
diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp new file mode 100644 index 0000000000..8bf52b7fd5 --- /dev/null +++ b/js/src/jit/BaselineIC.cpp @@ -0,0 +1,2744 @@ +/* -*- 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/Casting.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Sprintf.h" +#include "mozilla/TemplateLib.h" +#include "mozilla/Unused.h" + +#include "jsfriendapi.h" +#include "jslibmath.h" +#include "jstypes.h" + +#include "builtin/Eval.h" +#include "gc/Policy.h" +#include "jit/BaselineCacheIRCompiler.h" +#include "jit/BaselineDebugModeOSR.h" +#include "jit/BaselineJIT.h" +#include "jit/CacheIRHealth.h" +#include "jit/InlinableNatives.h" +#include "jit/JitFrames.h" +#include "jit/JitRealm.h" +#include "jit/JitRuntime.h" +#include "jit/JitSpewer.h" +#include "jit/JitZone.h" +#include "jit/Linker.h" +#include "jit/Lowering.h" +#ifdef JS_ION_PERF +# include "jit/PerfSpewer.h" +#endif +#include "jit/SharedICHelpers.h" +#include "jit/SharedICRegisters.h" +#include "jit/VMFunctions.h" +#include "js/Conversions.h" +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "js/GCVector.h" +#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" +#include "vm/SelfHosting.h" +#include "vm/TypedArrayObject.h" +#ifdef MOZ_VTUNE +# include "vtune/VTuneWrapper.h" +#endif + +#include "builtin/Boolean-inl.h" + +#include "jit/MacroAssembler-inl.h" +#include "jit/shared/Lowering-shared-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" +#include "vm/StringObject-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, bool calledIntoIon = false); +}; + +AllocatableGeneralRegisterSet BaselineICAvailableGeneralRegs(size_t numInputs) { + AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); +#if defined(JS_CODEGEN_ARM) + MOZ_ASSERT(!regs.has(BaselineStackReg)); + MOZ_ASSERT(!regs.has(ICTailCallReg)); + regs.take(BaselineSecondScratchReg); +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + MOZ_ASSERT(!regs.has(BaselineStackReg)); + 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)); +#else + MOZ_ASSERT(!regs.has(BaselineStackReg)); +#endif + regs.take(BaselineFrameReg); + regs.take(ICStubReg); +#ifdef JS_CODEGEN_X64 + regs.take(ExtractTemp0); + regs.take(ExtractTemp1); +#endif + + 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; +} + +#ifdef JS_JITSPEW +void FallbackICSpew(JSContext* cx, ICFallbackStub* stub, const char* fmt, ...) { + if (JitSpewEnabled(JitSpew_BaselineICFallback)) { + RootedScript script(cx, GetTopJitJSScript(cx)); + jsbytecode* pc = stub->icEntry()->pc(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 + +ICFallbackStub* ICEntry::fallbackStub() const { + return firstStub()->getChainFallback(); +} + +void ICEntry::trace(JSTracer* trc) { +#ifdef JS_64BIT + // If we have filled our padding with a magic value, check it now. + MOZ_DIAGNOSTIC_ASSERT(traceMagic_ == EXPECTED_TRACE_MAGIC); +#endif + ICStub* stub = firstStub(); + while (!stub->isFallback()) { + stub->toCacheIRStub()->trace(trc); + stub = stub->toCacheIRStub()->next(); + } + stub->toFallbackStub()->trace(trc); +} + +// Allocator for Baseline IC fallback stubs. These stubs use trampoline code +// stored in JitRuntime. +class MOZ_RAII FallbackStubAllocator { + JSContext* cx_; + ICStubSpace& stubSpace_; + const BaselineICFallbackCode& code_; + + public: + FallbackStubAllocator(JSContext* cx, ICStubSpace& stubSpace) + : cx_(cx), + stubSpace_(stubSpace), + code_(cx->runtime()->jitRuntime()->baselineICFallbackCode()) {} + + template <typename T, typename... Args> + T* newStub(BaselineICFallbackKind kind, Args&&... args) { + TrampolinePtr addr = code_.addr(kind); + return ICStub::NewFallback<T>(cx_, &stubSpace_, addr, + std::forward<Args>(args)...); + } +}; + +bool ICScript::initICEntries(JSContext* cx, JSScript* script) { + MOZ_ASSERT(cx->realm()->jitRealm()); + MOZ_ASSERT(jit::IsBaselineInterpreterEnabled()); + + MOZ_ASSERT(numICEntries() == script->numICEntries()); + + FallbackStubAllocator alloc(cx, *fallbackStubSpace()); + + // Index of the next ICEntry to initialize. + uint32_t icEntryIndex = 0; + + using Kind = BaselineICFallbackKind; + + auto addIC = [cx, this, script, &icEntryIndex](BytecodeLocation loc, + ICFallbackStub* stub) { + if (MOZ_UNLIKELY(!stub)) { + MOZ_ASSERT(cx->isExceptionPending()); + mozilla::Unused << cx; // Silence -Wunused-lambda-capture in opt builds. + return false; + } + + // Initialize the ICEntry. + uint32_t offset = loc.bytecodeToOffset(script); + ICEntry& entryRef = this->icEntry(icEntryIndex); + icEntryIndex++; + new (&entryRef) ICEntry(stub, offset); + + // Fix up pointers from fallback stubs to the ICEntry. + stub->fixupICEntry(&entryRef); + return true; + }; + + // 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); + + if (!BytecodeOpHasIC(op)) { + continue; + } + + switch (op) { + case JSOp::Not: + case JSOp::And: + case JSOp::Or: + case JSOp::IfEq: + case JSOp::IfNe: { + auto* stub = alloc.newStub<ICToBool_Fallback>(Kind::ToBool); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::BitNot: + case JSOp::Pos: + case JSOp::Neg: + case JSOp::Inc: + case JSOp::Dec: + case JSOp::ToNumeric: { + auto* stub = alloc.newStub<ICUnaryArith_Fallback>(Kind::UnaryArith); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::BitOr: + case JSOp::BitXor: + case JSOp::BitAnd: + case JSOp::Lsh: + case JSOp::Rsh: + case JSOp::Ursh: + case JSOp::Add: + case JSOp::Sub: + case JSOp::Mul: + case JSOp::Div: + case JSOp::Mod: + case JSOp::Pow: { + auto* stub = alloc.newStub<ICBinaryArith_Fallback>(Kind::BinaryArith); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::Eq: + case JSOp::Ne: + case JSOp::Lt: + case JSOp::Le: + case JSOp::Gt: + case JSOp::Ge: + case JSOp::StrictEq: + case JSOp::StrictNe: { + auto* stub = alloc.newStub<ICCompare_Fallback>(Kind::Compare); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::NewArray: { + auto* stub = alloc.newStub<ICNewArray_Fallback>(Kind::NewArray); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::NewObject: + case JSOp::NewInit: { + auto* stub = alloc.newStub<ICNewObject_Fallback>(Kind::NewObject); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::InitElem: + case JSOp::InitHiddenElem: + case JSOp::InitLockedElem: + case JSOp::InitElemInc: + case JSOp::SetElem: + case JSOp::StrictSetElem: { + auto* stub = alloc.newStub<ICSetElem_Fallback>(Kind::SetElem); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::InitProp: + case JSOp::InitLockedProp: + case JSOp::InitHiddenProp: + case JSOp::InitGLexical: + case JSOp::SetProp: + case JSOp::StrictSetProp: + case JSOp::SetName: + case JSOp::StrictSetName: + case JSOp::SetGName: + case JSOp::StrictSetGName: { + auto* stub = alloc.newStub<ICSetProp_Fallback>(Kind::SetProp); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::GetProp: + case JSOp::GetBoundName: { + auto* stub = alloc.newStub<ICGetProp_Fallback>(Kind::GetProp); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::GetPropSuper: { + auto* stub = alloc.newStub<ICGetProp_Fallback>(Kind::GetPropSuper); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::GetElem: { + auto* stub = alloc.newStub<ICGetElem_Fallback>(Kind::GetElem); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::GetElemSuper: { + auto* stub = alloc.newStub<ICGetElem_Fallback>(Kind::GetElemSuper); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::In: { + auto* stub = alloc.newStub<ICIn_Fallback>(Kind::In); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::HasOwn: { + auto* stub = alloc.newStub<ICHasOwn_Fallback>(Kind::HasOwn); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::CheckPrivateField: { + auto* stub = alloc.newStub<ICCheckPrivateField_Fallback>( + Kind::CheckPrivateField); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::GetName: + case JSOp::GetGName: { + auto* stub = alloc.newStub<ICGetName_Fallback>(Kind::GetName); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::BindName: + case JSOp::BindGName: { + auto* stub = alloc.newStub<ICBindName_Fallback>(Kind::BindName); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::GetIntrinsic: { + auto* stub = alloc.newStub<ICGetIntrinsic_Fallback>(Kind::GetIntrinsic); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::Call: + case JSOp::CallIgnoresRv: + case JSOp::CallIter: + case JSOp::FunCall: + case JSOp::FunApply: + case JSOp::Eval: + case JSOp::StrictEval: { + auto* stub = alloc.newStub<ICCall_Fallback>(Kind::Call); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::SuperCall: + case JSOp::New: { + auto* stub = alloc.newStub<ICCall_Fallback>(Kind::CallConstructing); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::SpreadCall: + case JSOp::SpreadEval: + case JSOp::StrictSpreadEval: { + auto* stub = alloc.newStub<ICCall_Fallback>(Kind::SpreadCall); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::SpreadSuperCall: + case JSOp::SpreadNew: { + auto* stub = + alloc.newStub<ICCall_Fallback>(Kind::SpreadCallConstructing); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::Instanceof: { + auto* stub = alloc.newStub<ICInstanceOf_Fallback>(Kind::InstanceOf); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::Typeof: + case JSOp::TypeofExpr: { + auto* stub = alloc.newStub<ICTypeOf_Fallback>(Kind::TypeOf); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::ToPropertyKey: { + auto* stub = + alloc.newStub<ICToPropertyKey_Fallback>(Kind::ToPropertyKey); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::Iter: { + auto* stub = alloc.newStub<ICGetIterator_Fallback>(Kind::GetIterator); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::OptimizeSpreadCall: { + auto* stub = alloc.newStub<ICOptimizeSpreadCall_Fallback>( + Kind::OptimizeSpreadCall); + if (!addIC(loc, stub)) { + return false; + } + break; + } + case JSOp::Rest: { + ArrayObject* templateObject = NewTenuredDenseEmptyArray(cx); + if (!templateObject) { + return false; + } + auto* stub = alloc.newStub<ICRest_Fallback>(Kind::Rest, templateObject); + if (!addIC(loc, stub)) { + return false; + } + break; + } + default: + MOZ_CRASH("JOF_IC op not handled"); + } + } + + // Assert all ICEntries have been initialized. + MOZ_ASSERT(icEntryIndex == numICEntries()); + return true; +} + +ICStubConstIterator& ICStubConstIterator::operator++() { + MOZ_ASSERT(currentStub_ != nullptr); + currentStub_ = currentStub_->toCacheIRStub()->next(); + return *this; +} + +ICStubIterator::ICStubIterator(ICFallbackStub* fallbackStub, bool end) + : icEntry_(fallbackStub->icEntry()), + fallbackStub_(fallbackStub), + previousStub_(nullptr), + currentStub_(end ? fallbackStub : icEntry_->firstStub()), + unlinked_(false) {} + +ICStubIterator& ICStubIterator::operator++() { + MOZ_ASSERT(!currentStub_->isFallback()); + if (!unlinked_) { + previousStub_ = currentStub_->toCacheIRStub(); + } + currentStub_ = currentStub_->toCacheIRStub()->next(); + unlinked_ = false; + return *this; +} + +void ICStubIterator::unlink(JSContext* cx) { + MOZ_ASSERT(currentStub_ != fallbackStub_); + MOZ_ASSERT(currentStub_->maybeNext() != nullptr); + MOZ_ASSERT(!unlinked_); + + fallbackStub_->unlinkStub(cx->zone(), previousStub_, + currentStub_->toCacheIRStub()); + + // Mark the current iterator position as unlinked, so operator++ works + // properly. + unlinked_ = true; +} + +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()); +} + +void ICFallbackStub::trace(JSTracer* trc) { + // Fallback stubs use runtime-wide trampoline code we don't need to trace. + MOZ_ASSERT(usesTrampolineCode()); + + switch (kind()) { + case ICStub::NewArray_Fallback: { + ICNewArray_Fallback* stub = toNewArray_Fallback(); + TraceNullableEdge(trc, &stub->templateObject(), + "baseline-newarray-template"); + break; + } + case ICStub::NewObject_Fallback: { + ICNewObject_Fallback* stub = toNewObject_Fallback(); + TraceNullableEdge(trc, &stub->templateObject(), + "baseline-newobject-template"); + break; + } + case ICStub::Rest_Fallback: { + ICRest_Fallback* stub = toRest_Fallback(); + TraceEdge(trc, &stub->templateObject(), "baseline-rest-template"); + break; + } + default: + break; + } +} + +static void MaybeTransition(JSContext* cx, BaselineFrame* frame, + ICFallbackStub* stub) { + if (stub->state().maybeTransition()) { +#ifdef JS_CACHEIR_SPEW + if (cx->spewer().enabled(cx, frame->script(), SpewChannel::RateMyCacheIR)) { + CacheIRHealth cih; + RootedScript script(cx, frame->script()); + cih.rateIC(cx, stub->icEntry(), script, SpewContext::Transition); + } +#endif + stub->discardStubs(cx); + } +} + +// 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 = stub->icEntry()->pc(script); + + bool attached = false; + IRGenerator gen(cx, script, pc, stub->state().mode(), + std::forward<Args>(args)...); + switch (gen.tryAttachStub()) { + case AttachDecision::Attach: { + ICStub* newStub = + AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), + script, icScript, stub, &attached); + if (newStub) { + 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, 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(); + + if (zone->needsIncrementalBarrier()) { + // We are removing edges from ICStub to gcthings. Perform one final trace + // of the stub for incremental GC, as it must know about those edges. + stub->trace(zone->barrierTracer()); + } + +#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) { + for (ICStubIterator iter = beginChain(); !iter.atEnd(); iter++) { + iter.unlink(cx); + } +} + +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 STUB_FRAME_SIZE so that assertions don't fail in leaveStubFrame. + framePushedAtEnterStubFrame_ = STUB_FRAME_SIZE; +#endif +} + +void FallbackICCodeCompiler::leaveStubFrame(MacroAssembler& masm, + bool calledIntoIon) { + MOZ_ASSERT(entersStubFrame_ && inStubFrame_); + inStubFrame_ = false; + +#ifdef DEBUG + masm.setFramePushed(framePushedAtEnterStubFrame_); + if (calledIntoIon) { + masm.adjustFrame(sizeof(intptr_t)); // Calls into ion have this extra. + } +#endif + EmitBaselineLeaveStubFrame(masm, calledIntoIon); +} + +void FallbackICCodeCompiler::pushStubPayload(MacroAssembler& masm, + Register scratch) { + if (inStubFrame_) { + masm.loadPtr(Address(BaselineFrameReg, 0), scratch); + masm.pushBaselineFramePtr(scratch, scratch); + } else { + masm.pushBaselineFramePtr(BaselineFrameReg, scratch); + } +} + +void FallbackICCodeCompiler::PushStubPayload(MacroAssembler& masm, + Register scratch) { + pushStubPayload(masm, scratch); + masm.adjustFrame(sizeof(intptr_t)); +} + +// +// ToBool_Fallback +// + +bool DoToBoolFallback(JSContext* cx, BaselineFrame* frame, + ICToBool_Fallback* stub, HandleValue arg, + MutableHandleValue ret) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + FallbackICSpew(cx, stub, "ToBool"); + + MOZ_ASSERT(!arg.isBoolean()); + + 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*, ICToBool_Fallback*, + HandleValue, MutableHandleValue); + return tailCallVM<Fn, DoToBoolFallback>(masm); +} + +// +// GetElem_Fallback +// + +bool DoGetElemFallback(JSContext* cx, BaselineFrame* frame, + ICGetElem_Fallback* stub, HandleValue lhs, + HandleValue rhs, MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + FallbackICSpew(cx, stub, "GetElem"); + +#ifdef DEBUG + jsbytecode* pc = stub->icEntry()->pc(frame->script()); + MOZ_ASSERT(JSOp(*pc) == JSOp::GetElem); +#endif + + // Don't pass lhs directly, we need it when generating stubs. + RootedValue lhsCopy(cx, lhs); + + bool isOptimizedArgs = false; + if (lhs.isMagic(JS_OPTIMIZED_ARGUMENTS)) { + // Handle optimized arguments[i] access. + isOptimizedArgs = + MaybeGetElemOptimizedArguments(cx, frame, &lhsCopy, rhs, res); + } + + TryAttachStub<GetPropIRGenerator>("GetElem", cx, frame, stub, + CacheKind::GetElem, lhs, rhs); + + if (!isOptimizedArgs) { + if (!GetElementOperation(cx, lhsCopy, rhs, res)) { + return false; + } + } + + return true; +} + +bool DoGetElemSuperFallback(JSContext* cx, BaselineFrame* frame, + ICGetElem_Fallback* stub, HandleValue lhs, + HandleValue rhs, HandleValue receiver, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(frame->script()); + + JSOp op = JSOp(*pc); + FallbackICSpew(cx, stub, "GetElemSuper(%s)", CodeName(op)); + + MOZ_ASSERT(op == JSOp::GetElemSuper); + + TryAttachStub<GetPropIRGenerator>("GetElemSuper", cx, frame, stub, + CacheKind::GetElemSuper, lhs, rhs); + + // |lhs| is [[HomeObject]].[[Prototype]] which must be Object + RootedObject lhsObj(cx, &lhs.toObject()); + 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(BaselineFrameReg, R0.scratchReg()); + + using Fn = + bool (*)(JSContext*, BaselineFrame*, ICGetElem_Fallback*, 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(BaselineFrameReg, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICGetElem_Fallback*, + 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, true); + + 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, + ICSetElem_Fallback* 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 = stub->icEntry()->pc(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; + } + + RootedShape oldShape(cx, obj->shape()); + + // We cannot attach a stub if the operation executed after the stub + // is attached may throw. + bool mayThrow = false; + + DeferType deferType = DeferType::None; + bool attached = false; + + MaybeTransition(cx, frame, stub); + + if (stub->state().canAttachStub() && !mayThrow) { + ICScript* icScript = frame->icScript(); + SetPropIRGenerator gen(cx, script, pc, CacheKind::SetElem, + stub->state().mode(), objv, index, rhs); + switch (gen.tryAttachStub()) { + case AttachDecision::Attach: { + ICStub* newStub = AttachBaselineCacheIRStub( + cx, gen.writerRef(), gen.cacheKind(), frame->script(), icScript, + stub, &attached); + if (newStub) { + 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; + } + } + + // Don't try to attach stubs that wish to be hidden. We don't know how to + // have different enumerability in the stubs for the moment. + if (op == JSOp::InitHiddenElem) { + return true; + } + + // 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().mode(), objv, index, rhs); + + MOZ_ASSERT(deferType == DeferType::AddSlot); + AttachDecision decision = gen.tryAttachAddSlotStub(oldShape); + + switch (decision) { + case AttachDecision::Attach: { + ICScript* icScript = frame->icScript(); + ICStub* newStub = AttachBaselineCacheIRStub( + cx, gen.writerRef(), gen.cacheKind(), frame->script(), icScript, + stub, &attached); + if (newStub) { + 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*, ICSetElem_Fallback*, Value*, + HandleValue, HandleValue, HandleValue); + return tailCallVM<Fn, DoSetElemFallback>(masm); +} + +// +// In_Fallback +// + +bool DoInFallback(JSContext* cx, BaselineFrame* frame, ICIn_Fallback* stub, + HandleValue key, HandleValue objValue, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + FallbackICSpew(cx, stub, "In"); + + if (!objValue.isObject()) { + ReportInNotObjectError(cx, key, -2, objValue, -1); + 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*, ICIn_Fallback*, HandleValue, + HandleValue, MutableHandleValue); + return tailCallVM<Fn, DoInFallback>(masm); +} + +// +// HasOwn_Fallback +// + +bool DoHasOwnFallback(JSContext* cx, BaselineFrame* frame, + ICHasOwn_Fallback* 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*, ICHasOwn_Fallback*, + HandleValue, HandleValue, MutableHandleValue); + return tailCallVM<Fn, DoHasOwnFallback>(masm); +} + +// +// CheckPrivate_Fallback +// + +bool DoCheckPrivateFieldFallback(JSContext* cx, BaselineFrame* frame, + ICCheckPrivateField_Fallback* stub, + HandleValue objValue, HandleValue keyValue, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(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*, ICCheckPrivateField_Fallback*, + HandleValue, HandleValue, MutableHandleValue); + return tailCallVM<Fn, DoCheckPrivateFieldFallback>(masm); +} + +// +// GetName_Fallback +// + +bool DoGetNameFallback(JSContext* cx, BaselineFrame* frame, + ICGetName_Fallback* stub, HandleObject envChain, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(script); + mozilla::DebugOnly<JSOp> op = JSOp(*pc); + FallbackICSpew(cx, stub, "GetName(%s)", CodeName(JSOp(*pc))); + + MOZ_ASSERT(op == JSOp::GetName || op == JSOp::GetGName); + + RootedPropertyName 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*, ICGetName_Fallback*, + HandleObject, MutableHandleValue); + return tailCallVM<Fn, DoGetNameFallback>(masm); +} + +// +// BindName_Fallback +// + +bool DoBindNameFallback(JSContext* cx, BaselineFrame* frame, + ICBindName_Fallback* stub, HandleObject envChain, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + jsbytecode* pc = stub->icEntry()->pc(frame->script()); + mozilla::DebugOnly<JSOp> op = JSOp(*pc); + FallbackICSpew(cx, stub, "BindName(%s)", CodeName(JSOp(*pc))); + + MOZ_ASSERT(op == JSOp::BindName || op == JSOp::BindGName); + + RootedPropertyName 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*, ICBindName_Fallback*, + HandleObject, MutableHandleValue); + return tailCallVM<Fn, DoBindNameFallback>(masm); +} + +// +// GetIntrinsic_Fallback +// + +bool DoGetIntrinsicFallback(JSContext* cx, BaselineFrame* frame, + ICGetIntrinsic_Fallback* stub, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(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*, ICGetIntrinsic_Fallback*, + MutableHandleValue); + return tailCallVM<Fn, DoGetIntrinsicFallback>(masm); +} + +// +// GetProp_Fallback +// + +static bool ComputeGetPropResult(JSContext* cx, BaselineFrame* frame, JSOp op, + HandlePropertyName name, + MutableHandleValue val, + MutableHandleValue res) { + // Handle arguments.length and arguments.callee on optimized arguments, as + // it is not an object. + if (val.isMagic(JS_OPTIMIZED_ARGUMENTS) && IsOptimizedArguments(frame, val)) { + if (name == cx->names().length) { + res.setInt32(frame->numActualArgs()); + } else { + MOZ_ASSERT(name == cx->names().callee); + MOZ_ASSERT(frame->script()->hasMappedArgsObj()); + res.setObject(*frame->callee()); + } + } else { + if (op == JSOp::GetBoundName) { + RootedObject env(cx, &val.toObject()); + RootedId id(cx, NameToId(name)); + if (!GetNameBoundInEnvironment(cx, env, id, res)) { + return false; + } + } else { + MOZ_ASSERT(op == JSOp::GetProp); + if (!GetProperty(cx, val, name, res)) { + return false; + } + } + } + + return true; +} + +bool DoGetPropFallback(JSContext* cx, BaselineFrame* frame, + ICGetProp_Fallback* stub, MutableHandleValue val, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(script); + JSOp op = JSOp(*pc); + FallbackICSpew(cx, stub, "GetProp(%s)", CodeName(op)); + + MOZ_ASSERT(op == JSOp::GetProp || op == JSOp::GetBoundName); + + RootedPropertyName name(cx, script->getName(pc)); + RootedValue idVal(cx, StringValue(name)); + + TryAttachStub<GetPropIRGenerator>("GetProp", cx, frame, stub, + CacheKind::GetProp, val, idVal); + + return ComputeGetPropResult(cx, frame, op, name, val, res); +} + +bool DoGetPropSuperFallback(JSContext* cx, BaselineFrame* frame, + ICGetProp_Fallback* stub, HandleValue receiver, + MutableHandleValue val, MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(script); + FallbackICSpew(cx, stub, "GetPropSuper(%s)", CodeName(JSOp(*pc))); + + MOZ_ASSERT(JSOp(*pc) == JSOp::GetPropSuper); + + RootedPropertyName name(cx, script->getName(pc)); + RootedValue idVal(cx, StringValue(name)); + + TryAttachStub<GetPropIRGenerator>("GetPropSuper", cx, frame, stub, + CacheKind::GetPropSuper, val, idVal); + + // |val| is [[HomeObject]].[[Prototype]] which must be Object + RootedObject valObj(cx, &val.toObject()); + 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(BaselineFrameReg, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICGetProp_Fallback*, + 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(BaselineFrameReg, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICGetProp_Fallback*, + 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, true); + + 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, + ICSetProp_Fallback* stub, Value* stack, HandleValue lhs, + HandleValue rhs) { + using DeferType = SetPropIRGenerator::DeferType; + + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(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); + + RootedPropertyName 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; + } + RootedShape 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().mode(), lhs, idVal, rhs); + switch (gen.tryAttachStub()) { + case AttachDecision::Attach: { + ICScript* icScript = frame->icScript(); + ICStub* newStub = AttachBaselineCacheIRStub( + cx, gen.writerRef(), gen.cacheKind(), frame->script(), icScript, + stub, &attached); + if (newStub) { + 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, op, 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) { + RootedValue v(cx, rhs); + LexicalEnvironmentObject* lexicalEnv; + if (script->hasNonSyntacticScope()) { + lexicalEnv = &NearestEnclosingExtensibleLexicalEnvironment( + frame->environmentChain()); + } else { + lexicalEnv = &cx->global()->lexicalEnvironment(); + } + InitGlobalLexicalOperation(cx, lexicalEnv, script, pc, v); + } 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(); + + if (deferType != DeferType::None && canAttachStub) { + RootedValue idVal(cx, StringValue(name)); + SetPropIRGenerator gen(cx, script, pc, CacheKind::SetProp, + stub->state().mode(), lhs, idVal, rhs); + + MOZ_ASSERT(deferType == DeferType::AddSlot); + AttachDecision decision = gen.tryAttachAddSlotStub(oldShape); + + switch (decision) { + case AttachDecision::Attach: { + ICScript* icScript = frame->icScript(); + ICStub* newStub = AttachBaselineCacheIRStub( + cx, gen.writerRef(), gen.cacheKind(), frame->script(), icScript, + stub, &attached); + if (newStub) { + 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_SetProp() { + static_assert(R0 == JSReturnOperand); + + EmitRestoreTailCallReg(masm); + + // Ensure stack is fully synced for the expression decompiler. + // Overwrite the RHS value on top of the stack with the object, then push + // the RHS in R1 on top of that. + masm.storeValue(R0, Address(masm.getStackPointer(), 0)); + masm.pushValue(R1); + + // Push arguments. + masm.pushValue(R1); + masm.pushValue(R0); + + // 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(), 2 * sizeof(Value)), R0.scratchReg()); + masm.push(R0.scratchReg()); + + masm.push(ICStubReg); + pushStubPayload(masm, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICSetProp_Fallback*, Value*, + HandleValue, HandleValue); + if (!tailCallVM<Fn, DoSetPropFallback>(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(); + code.initBailoutReturnOffset(BailoutReturnKind::SetProp, + masm.currentOffset()); + + leaveStubFrame(masm, true); + EmitReturnFromIC(masm); + + return true; +} + +// +// Call_Fallback +// + +bool DoCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub, + uint32_t argc, Value* vp, MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(script); + JSOp op = JSOp(*pc); + FallbackICSpew(cx, stub, "Call(%s)", CodeName(op)); + + MOZ_ASSERT(argc == GET_ARGC(pc)); + bool constructing = (op == JSOp::New || op == JSOp::SuperCall); + bool ignoresReturnValue = (op == JSOp::CallIgnoresRv); + + // Ensure vp array is rooted - we may GC in here. + size_t numValues = argc + 2 + constructing; + RootedExternalValueArray vpRoot(cx, numValues, vp); + + CallArgs callArgs = CallArgsFromSp(argc + constructing, vp + numValues, + constructing, ignoresReturnValue); + RootedValue callee(cx, vp[0]); + RootedValue newTarget(cx, constructing ? callArgs.newTarget() : NullValue()); + + // Handle funapply with JSOp::Arguments + if (op == JSOp::FunApply && argc == 2 && + callArgs[1].isMagic(JS_OPTIMIZED_ARGUMENTS)) { + GuardFunApplyArgumentsOptimization(cx, frame, callArgs); + } + + // Transition stub state to megamorphic or generic if warranted. + MaybeTransition(cx, frame, stub); + + bool canAttachStub = stub->state().canAttachStub(); + bool handled = false; + + // Only bother to try optimizing JSOp::Call with CacheIR if the chain is still + // allowed to attach stubs. + if (canAttachStub) { + HandleValueArray args = HandleValueArray::fromMarkedLocation(argc, vp + 2); + bool isFirstStub = stub->newStubIsFirstStub(); + CallIRGenerator gen(cx, script, pc, op, stub->state().mode(), isFirstStub, + argc, callee, callArgs.thisv(), newTarget, args); + switch (gen.tryAttachStub()) { + case AttachDecision::NoAction: + break; + case AttachDecision::Attach: { + ICScript* icScript = frame->icScript(); + ICStub* newStub = + AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), + script, icScript, stub, &handled); + if (newStub) { + JitSpew(JitSpew_BaselineIC, " Attached Call CacheIR stub"); + } + } break; + case AttachDecision::TemporarilyUnoptimizable: + handled = true; + break; + case AttachDecision::Deferred: + MOZ_CRASH("No deferred Call stubs"); + } + if (!handled) { + stub->trackNotAttached(); + } + } + + if (constructing) { + if (!ConstructFromStack(cx, callArgs)) { + return false; + } + res.set(callArgs.rval()); + } else if ((op == JSOp::Eval || op == JSOp::StrictEval) && + cx->global()->valueIsEval(callee)) { + if (!DirectEval(cx, callArgs.get(0), res)) { + return false; + } + } else { + MOZ_ASSERT(op == JSOp::Call || op == JSOp::CallIgnoresRv || + op == JSOp::CallIter || op == JSOp::FunCall || + op == JSOp::FunApply || op == JSOp::Eval || + op == JSOp::StrictEval); + if (op == JSOp::CallIter && callee.isPrimitive()) { + MOZ_ASSERT(argc == 0, "thisv must be on top of the stack"); + ReportValueError(cx, JSMSG_NOT_ITERABLE, -1, callArgs.thisv(), nullptr); + return false; + } + + if (!CallFromStack(cx, callArgs)) { + return false; + } + + res.set(callArgs.rval()); + } + + return true; +} + +bool DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame, + ICCall_Fallback* stub, Value* vp, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(script); + JSOp op = JSOp(*pc); + bool constructing = (op == JSOp::SpreadNew || op == JSOp::SpreadSuperCall); + FallbackICSpew(cx, stub, "SpreadCall(%s)", CodeName(op)); + + // Ensure vp array is rooted - we may GC in here. + RootedExternalValueArray vpRoot(cx, 3 + constructing, vp); + + RootedValue callee(cx, vp[0]); + RootedValue thisv(cx, vp[1]); + RootedValue arr(cx, vp[2]); + RootedValue newTarget(cx, constructing ? vp[3] : NullValue()); + + // Transition stub state to megamorphic or generic if warranted. + MaybeTransition(cx, frame, stub); + + // Try attaching a call stub. + bool handled = false; + if (op != JSOp::SpreadEval && op != JSOp::StrictSpreadEval && + stub->state().canAttachStub()) { + // Try CacheIR first: + RootedArrayObject aobj(cx, &arr.toObject().as<ArrayObject>()); + MOZ_ASSERT(aobj->length() == aobj->getDenseInitializedLength()); + + HandleValueArray args = HandleValueArray::fromMarkedLocation( + aobj->length(), aobj->getDenseElements()); + bool isFirstStub = stub->newStubIsFirstStub(); + CallIRGenerator gen(cx, script, pc, op, stub->state().mode(), isFirstStub, + 1, callee, thisv, newTarget, args); + switch (gen.tryAttachStub()) { + case AttachDecision::NoAction: + break; + case AttachDecision::Attach: { + ICScript* icScript = frame->icScript(); + ICStub* newStub = + AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), + script, icScript, stub, &handled); + + if (newStub) { + JitSpew(JitSpew_BaselineIC, " Attached Spread Call CacheIR stub"); + } + } break; + case AttachDecision::TemporarilyUnoptimizable: + handled = true; + break; + case AttachDecision::Deferred: + MOZ_ASSERT_UNREACHABLE("No deferred optimizations for spread calls"); + break; + } + if (!handled) { + stub->trackNotAttached(); + } + } + + return SpreadCallOperation(cx, script, pc, thisv, callee, arr, newTarget, + res); +} + +void FallbackICCodeCompiler::pushCallArguments( + MacroAssembler& masm, AllocatableGeneralRegisterSet regs, Register argcReg, + bool isConstructing) { + MOZ_ASSERT(!regs.has(argcReg)); + + // argPtr initially points to the last argument. + Register argPtr = regs.takeAny(); + masm.moveStackPtrTo(argPtr); + + // Skip 4 pointers pushed on top of the arguments: the frame descriptor, + // return address, old frame pointer and stub reg. + size_t valueOffset = STUB_FRAME_SIZE; + + // We have to push |this|, callee, new.target (if constructing) and argc + // arguments. Handle the number of Values we know statically first. + + size_t numNonArgValues = 2 + isConstructing; + for (size_t i = 0; i < numNonArgValues; i++) { + masm.pushValue(Address(argPtr, valueOffset)); + valueOffset += sizeof(Value); + } + + // If there are no arguments we're done. + Label done; + masm.branchTest32(Assembler::Zero, argcReg, argcReg, &done); + + // Push argc Values. + Label loop; + Register count = regs.takeAny(); + masm.addPtr(Imm32(valueOffset), argPtr); + masm.move32(argcReg, count); + masm.bind(&loop); + { + masm.pushValue(Address(argPtr, 0)); + masm.addPtr(Imm32(sizeof(Value)), argPtr); + + masm.branchSub32(Assembler::NonZero, Imm32(1), count, &loop); + } + masm.bind(&done); +} + +bool FallbackICCodeCompiler::emitCall(bool isSpread, bool isConstructing) { + static_assert(R0 == JSReturnOperand); + + // Values are on the stack left-to-right. Calling convention wants them + // right-to-left so duplicate them on the stack in reverse order. + // |this| and callee are pushed last. + + AllocatableGeneralRegisterSet regs = BaselineICAvailableGeneralRegs(0); + + if (MOZ_UNLIKELY(isSpread)) { + // Push a stub frame so that we can perform a non-tail call. + enterStubFrame(masm, R1.scratchReg()); + + // Use BaselineFrameReg instead of BaselineStackReg, because + // BaselineFrameReg and BaselineStackReg hold the same value just after + // calling enterStubFrame. + + // newTarget + uint32_t valueOffset = 0; + if (isConstructing) { + masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE)); + valueOffset++; + } + + // array + masm.pushValue(Address(BaselineFrameReg, + valueOffset * sizeof(Value) + STUB_FRAME_SIZE)); + valueOffset++; + + // this + masm.pushValue(Address(BaselineFrameReg, + valueOffset * sizeof(Value) + STUB_FRAME_SIZE)); + valueOffset++; + + // callee + masm.pushValue(Address(BaselineFrameReg, + valueOffset * sizeof(Value) + STUB_FRAME_SIZE)); + valueOffset++; + + masm.push(masm.getStackPointer()); + masm.push(ICStubReg); + + PushStubPayload(masm, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICCall_Fallback*, Value*, + MutableHandleValue); + if (!callVM<Fn, DoSpreadCallFallback>(masm)) { + return false; + } + + leaveStubFrame(masm); + EmitReturnFromIC(masm); + + // SpreadCall is not yet supported in Ion, so do not generate asmcode for + // bailout. + return true; + } + + // Push a stub frame so that we can perform a non-tail call. + enterStubFrame(masm, R1.scratchReg()); + + regs.take(R0.scratchReg()); // argc. + + pushCallArguments(masm, regs, R0.scratchReg(), isConstructing); + + masm.push(masm.getStackPointer()); + masm.push(R0.scratchReg()); + masm.push(ICStubReg); + + PushStubPayload(masm, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICCall_Fallback*, uint32_t, + Value*, MutableHandleValue); + if (!callVM<Fn, DoCallFallback>(masm)) { + return false; + } + + leaveStubFrame(masm); + EmitReturnFromIC(masm); + + // 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(); + + MOZ_ASSERT(!isSpread); + + if (isConstructing) { + code.initBailoutReturnOffset(BailoutReturnKind::New, masm.currentOffset()); + } else { + code.initBailoutReturnOffset(BailoutReturnKind::Call, masm.currentOffset()); + } + + // Load passed-in ThisV into R1 just in case it's needed. Need to do this + // before we leave the stub frame since that info will be lost. + // Current stack: [...., ThisV, ActualArgc, CalleeToken, Descriptor ] + masm.loadValue(Address(masm.getStackPointer(), 3 * sizeof(size_t)), R1); + + leaveStubFrame(masm, true); + + // If this is a |constructing| call, if the callee returns a non-object, we + // replace it with the |this| object passed in. + if (isConstructing) { + static_assert(JSReturnOperand == R0); + Label skipThisReplace; + + masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); + masm.moveValue(R1, R0); +#ifdef DEBUG + masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); + masm.assumeUnreachable("Failed to return object in constructing call."); +#endif + masm.bind(&skipThisReplace); + } + + EmitReturnFromIC(masm); + return true; +} + +bool FallbackICCodeCompiler::emit_Call() { + return emitCall(/* isSpread = */ false, /* isConstructing = */ false); +} + +bool FallbackICCodeCompiler::emit_CallConstructing() { + return emitCall(/* isSpread = */ false, /* isConstructing = */ true); +} + +bool FallbackICCodeCompiler::emit_SpreadCall() { + return emitCall(/* isSpread = */ true, /* isConstructing = */ false); +} + +bool FallbackICCodeCompiler::emit_SpreadCallConstructing() { + return emitCall(/* isSpread = */ true, /* isConstructing = */ true); +} + +// +// GetIterator_Fallback +// + +bool DoGetIteratorFallback(JSContext* cx, BaselineFrame* frame, + ICGetIterator_Fallback* stub, HandleValue value, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + FallbackICSpew(cx, stub, "GetIterator"); + + TryAttachStub<GetIteratorIRGenerator>("GetIterator", cx, frame, stub, value); + + JSObject* iterobj = ValueToIterator(cx, value); + if (!iterobj) { + return false; + } + + res.setObject(*iterobj); + return true; +} + +bool FallbackICCodeCompiler::emit_GetIterator() { + EmitRestoreTailCallReg(masm); + + // Sync stack for the decompiler. + masm.pushValue(R0); + + masm.pushValue(R0); + masm.push(ICStubReg); + pushStubPayload(masm, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICGetIterator_Fallback*, + HandleValue, MutableHandleValue); + return tailCallVM<Fn, DoGetIteratorFallback>(masm); +} + +// +// OptimizeSpreadCall_Fallback +// + +bool DoOptimizeSpreadCallFallback(JSContext* cx, BaselineFrame* frame, + ICOptimizeSpreadCall_Fallback* stub, + HandleValue value, MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + FallbackICSpew(cx, stub, "OptimizeSpreadCall"); + + TryAttachStub<OptimizeSpreadCallIRGenerator>("OptimizeSpreadCall", cx, frame, + stub, value); + + bool optimized; + if (!OptimizeSpreadCall(cx, value, &optimized)) { + return false; + } + + res.setBoolean(optimized); + return true; +} + +bool FallbackICCodeCompiler::emit_OptimizeSpreadCall() { + EmitRestoreTailCallReg(masm); + + masm.pushValue(R0); + masm.push(ICStubReg); + pushStubPayload(masm, R0.scratchReg()); + + using Fn = + bool (*)(JSContext*, BaselineFrame*, ICOptimizeSpreadCall_Fallback*, + HandleValue, MutableHandleValue); + return tailCallVM<Fn, DoOptimizeSpreadCallFallback>(masm); +} + +// +// InstanceOf_Fallback +// + +bool DoInstanceOfFallback(JSContext* cx, BaselineFrame* frame, + ICInstanceOf_Fallback* stub, HandleValue lhs, + HandleValue rhs, MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + FallbackICSpew(cx, stub, "InstanceOf"); + + if (!rhs.isObject()) { + ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS, -1, rhs, nullptr); + return false; + } + + RootedObject obj(cx, &rhs.toObject()); + bool cond = false; + if (!HasInstance(cx, obj, lhs, &cond)) { + return false; + } + + res.setBoolean(cond); + + if (!obj->is<JSFunction>()) { + // ensure we've recorded at least one failure, so we can detect there was a + // non-optimizable case + if (!stub->state().hasFailures()) { + stub->trackNotAttached(); + } + return true; + } + + TryAttachStub<InstanceOfIRGenerator>("InstanceOf", cx, frame, stub, lhs, obj); + return true; +} + +bool FallbackICCodeCompiler::emit_InstanceOf() { + EmitRestoreTailCallReg(masm); + + // Sync stack for the decompiler. + masm.pushValue(R0); + masm.pushValue(R1); + + masm.pushValue(R1); + masm.pushValue(R0); + masm.push(ICStubReg); + pushStubPayload(masm, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICInstanceOf_Fallback*, + HandleValue, HandleValue, MutableHandleValue); + return tailCallVM<Fn, DoInstanceOfFallback>(masm); +} + +// +// TypeOf_Fallback +// + +bool DoTypeOfFallback(JSContext* cx, BaselineFrame* frame, + ICTypeOf_Fallback* stub, HandleValue val, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + FallbackICSpew(cx, stub, "TypeOf"); + + TryAttachStub<TypeOfIRGenerator>("TypeOf", cx, frame, stub, val); + + JSType type = js::TypeOfValue(val); + RootedString string(cx, TypeName(type, cx->names())); + res.setString(string); + return true; +} + +bool FallbackICCodeCompiler::emit_TypeOf() { + EmitRestoreTailCallReg(masm); + + masm.pushValue(R0); + masm.push(ICStubReg); + pushStubPayload(masm, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICTypeOf_Fallback*, + HandleValue, MutableHandleValue); + return tailCallVM<Fn, DoTypeOfFallback>(masm); +} + +// +// ToPropertyKey_Fallback +// + +bool DoToPropertyKeyFallback(JSContext* cx, BaselineFrame* frame, + ICToPropertyKey_Fallback* stub, HandleValue val, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + FallbackICSpew(cx, stub, "ToPropertyKey"); + + TryAttachStub<ToPropertyKeyIRGenerator>("ToPropertyKey", cx, frame, stub, + val); + + return ToPropertyKeyOperation(cx, val, res); +} + +bool FallbackICCodeCompiler::emit_ToPropertyKey() { + EmitRestoreTailCallReg(masm); + + masm.pushValue(R0); + masm.push(ICStubReg); + pushStubPayload(masm, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICToPropertyKey_Fallback*, + HandleValue, MutableHandleValue); + return tailCallVM<Fn, DoToPropertyKeyFallback>(masm); +} + +// +// Rest_Fallback +// + +bool DoRestFallback(JSContext* cx, BaselineFrame* frame, ICRest_Fallback* stub, + MutableHandleValue res) { + unsigned numFormals = frame->numFormalArgs() - 1; + unsigned numActuals = frame->numActualArgs(); + unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0; + Value* rest = frame->argv() + numFormals; + + ArrayObject* obj = NewDenseCopiedArray(cx, numRest, rest); + if (!obj) { + return false; + } + res.setObject(*obj); + return true; +} + +bool FallbackICCodeCompiler::emit_Rest() { + EmitRestoreTailCallReg(masm); + + masm.push(ICStubReg); + pushStubPayload(masm, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICRest_Fallback*, + MutableHandleValue); + return tailCallVM<Fn, DoRestFallback>(masm); +} + +// +// UnaryArith_Fallback +// + +bool DoUnaryArithFallback(JSContext* cx, BaselineFrame* frame, + ICUnaryArith_Fallback* stub, HandleValue val, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(script); + JSOp op = JSOp(*pc); + FallbackICSpew(cx, stub, "UnaryArith(%s)", CodeName(op)); + + switch (op) { + case JSOp::BitNot: { + res.set(val); + if (!BitNot(cx, res, res)) { + return false; + } + break; + } + case JSOp::Pos: { + res.set(val); + if (!ToNumber(cx, res)) { + return false; + } + break; + } + case JSOp::Neg: { + res.set(val); + if (!NegOperation(cx, res, res)) { + return false; + } + break; + } + case JSOp::Inc: { + if (!IncOperation(cx, val, res)) { + return false; + } + break; + } + case JSOp::Dec: { + if (!DecOperation(cx, val, res)) { + return false; + } + break; + } + case JSOp::ToNumeric: { + res.set(val); + if (!ToNumeric(cx, res)) { + return false; + } + break; + } + default: + MOZ_CRASH("Unexpected op"); + } + MOZ_ASSERT(res.isNumeric()); + + TryAttachStub<UnaryArithIRGenerator>("UnaryArith", cx, frame, stub, op, val, + res); + return true; +} + +bool FallbackICCodeCompiler::emit_UnaryArith() { + static_assert(R0 == JSReturnOperand); + + // Restore the tail call register. + EmitRestoreTailCallReg(masm); + + // Ensure stack is fully synced for the expression decompiler. + masm.pushValue(R0); + + // Push arguments. + masm.pushValue(R0); + masm.push(ICStubReg); + pushStubPayload(masm, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICUnaryArith_Fallback*, + HandleValue, MutableHandleValue); + return tailCallVM<Fn, DoUnaryArithFallback>(masm); +} + +// +// BinaryArith_Fallback +// + +bool DoBinaryArithFallback(JSContext* cx, BaselineFrame* frame, + ICBinaryArith_Fallback* stub, HandleValue lhs, + HandleValue rhs, MutableHandleValue ret) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(script); + JSOp op = JSOp(*pc); + FallbackICSpew( + cx, stub, "CacheIRBinaryArith(%s,%d,%d)", CodeName(op), + int(lhs.isDouble() ? JSVAL_TYPE_DOUBLE : lhs.extractNonDoubleType()), + int(rhs.isDouble() ? JSVAL_TYPE_DOUBLE : rhs.extractNonDoubleType())); + + // Don't pass lhs/rhs directly, we need the original values when + // generating stubs. + RootedValue lhsCopy(cx, lhs); + RootedValue rhsCopy(cx, rhs); + + // Perform the arith operation. + switch (op) { + case JSOp::Add: + // Do an add. + if (!AddValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + case JSOp::Sub: + if (!SubValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + case JSOp::Mul: + if (!MulValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + case JSOp::Div: + if (!DivValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + case JSOp::Mod: + if (!ModValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + case JSOp::Pow: + if (!PowValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + case JSOp::BitOr: { + if (!BitOr(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + } + case JSOp::BitXor: { + if (!BitXor(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + } + case JSOp::BitAnd: { + if (!BitAnd(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + } + case JSOp::Lsh: { + if (!BitLsh(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + } + case JSOp::Rsh: { + if (!BitRsh(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + } + case JSOp::Ursh: { + if (!UrshValues(cx, &lhsCopy, &rhsCopy, ret)) { + return false; + } + break; + } + default: + MOZ_CRASH("Unhandled baseline arith op"); + } + + TryAttachStub<BinaryArithIRGenerator>("BinaryArith", cx, frame, stub, op, lhs, + rhs, ret); + return true; +} + +bool FallbackICCodeCompiler::emit_BinaryArith() { + static_assert(R0 == JSReturnOperand); + + // Restore the tail call register. + EmitRestoreTailCallReg(masm); + + // 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); + pushStubPayload(masm, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICBinaryArith_Fallback*, + HandleValue, HandleValue, MutableHandleValue); + return tailCallVM<Fn, DoBinaryArithFallback>(masm); +} + +// +// Compare_Fallback +// +bool DoCompareFallback(JSContext* cx, BaselineFrame* frame, + ICCompare_Fallback* stub, HandleValue lhs, + HandleValue rhs, MutableHandleValue ret) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(script); + JSOp op = JSOp(*pc); + + FallbackICSpew(cx, stub, "Compare(%s)", CodeName(op)); + + // Don't pass lhs/rhs directly, we need the original values when + // generating stubs. + RootedValue lhsCopy(cx, lhs); + RootedValue rhsCopy(cx, rhs); + + // Perform the compare operation. + bool out; + switch (op) { + case JSOp::Lt: + if (!LessThan(cx, &lhsCopy, &rhsCopy, &out)) { + return false; + } + break; + case JSOp::Le: + if (!LessThanOrEqual(cx, &lhsCopy, &rhsCopy, &out)) { + return false; + } + break; + case JSOp::Gt: + if (!GreaterThan(cx, &lhsCopy, &rhsCopy, &out)) { + return false; + } + break; + case JSOp::Ge: + if (!GreaterThanOrEqual(cx, &lhsCopy, &rhsCopy, &out)) { + return false; + } + break; + case JSOp::Eq: + if (!js::LooselyEqual(cx, lhsCopy, rhsCopy, &out)) { + return false; + } + break; + case JSOp::Ne: + if (!js::LooselyEqual(cx, lhsCopy, rhsCopy, &out)) { + return false; + } + out = !out; + break; + case JSOp::StrictEq: + if (!js::StrictlyEqual(cx, lhsCopy, rhsCopy, &out)) { + return false; + } + break; + case JSOp::StrictNe: + if (!js::StrictlyEqual(cx, lhsCopy, rhsCopy, &out)) { + return false; + } + out = !out; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unhandled baseline compare op"); + return false; + } + + ret.setBoolean(out); + + TryAttachStub<CompareIRGenerator>("Compare", cx, frame, stub, op, lhs, rhs); + return true; +} + +bool FallbackICCodeCompiler::emit_Compare() { + static_assert(R0 == JSReturnOperand); + + // Restore the tail call register. + EmitRestoreTailCallReg(masm); + + // 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); + pushStubPayload(masm, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICCompare_Fallback*, + HandleValue, HandleValue, MutableHandleValue); + return tailCallVM<Fn, DoCompareFallback>(masm); +} + +// +// NewArray_Fallback +// + +bool DoNewArrayFallback(JSContext* cx, BaselineFrame* frame, + ICNewArray_Fallback* stub, uint32_t length, + MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + FallbackICSpew(cx, stub, "NewArray"); + + if (!stub->templateObject()) { + ArrayObject* templateObject = NewArrayOperation(cx, length, TenuredObject); + if (!templateObject) { + return false; + } + stub->setTemplateObject(templateObject); + } + + ArrayObject* arr = NewArrayOperation(cx, length); + if (!arr) { + return false; + } + + res.setObject(*arr); + return true; +} + +bool FallbackICCodeCompiler::emit_NewArray() { + EmitRestoreTailCallReg(masm); + + masm.push(R0.scratchReg()); // length + masm.push(ICStubReg); // stub. + masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICNewArray_Fallback*, + uint32_t, MutableHandleValue); + return tailCallVM<Fn, DoNewArrayFallback>(masm); +} + +// +// NewObject_Fallback +// +bool DoNewObjectFallback(JSContext* cx, BaselineFrame* frame, + ICNewObject_Fallback* stub, MutableHandleValue res) { + stub->incrementEnteredCount(); + MaybeNotifyWarp(frame->outerScript(), stub); + FallbackICSpew(cx, stub, "NewObject"); + + RootedObject obj(cx); + + RootedObject templateObject(cx, stub->templateObject()); + if (templateObject) { + obj = NewObjectOperationWithTemplate(cx, templateObject); + } else { + RootedScript script(cx, frame->script()); + jsbytecode* pc = stub->icEntry()->pc(script); + obj = NewObjectOperation(cx, script, pc); + + if (obj) { + templateObject = NewObjectOperation(cx, script, pc, TenuredObject); + if (!templateObject) { + return false; + } + + TryAttachStub<NewObjectIRGenerator>("NewObject", cx, frame, stub, + JSOp(*pc), templateObject); + + stub->setTemplateObject(templateObject); + } + } + + if (!obj) { + return false; + } + + res.setObject(*obj); + return true; +} + +bool FallbackICCodeCompiler::emit_NewObject() { + EmitRestoreTailCallReg(masm); + + masm.push(ICStubReg); // stub. + pushStubPayload(masm, R0.scratchReg()); + + using Fn = bool (*)(JSContext*, BaselineFrame*, ICNewObject_Fallback*, + MutableHandleValue); + return tailCallVM<Fn, DoNewObjectFallback>(masm); +} + +bool JitRuntime::generateBaselineICFallbackCode(JSContext* cx) { + StackMacroAssembler masm; + + BaselineICFallbackCode& fallbackCode = baselineICFallbackCode_.ref(); + FallbackICCodeCompiler compiler(cx, fallbackCode, masm); + + JitSpew(JitSpew_Codegen, "# Emitting Baseline IC fallback code"); + +#define EMIT_CODE(kind) \ + { \ + uint32_t offset = startTrampolineCode(masm); \ + InitMacroAssemblerForICStub(masm); \ + if (!compiler.emit_##kind()) { \ + return false; \ + } \ + fallbackCode.initOffset(BaselineICFallbackKind::kind, offset); \ + } + IC_BASELINE_FALLBACK_CODE_KIND_LIST(EMIT_CODE) +#undef EMIT_CODE + + Linker linker(masm); + JitCode* code = linker.newCode(cx, CodeKind::Other); + if (!code) { + return false; + } + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "BaselineICFallback"); +#endif +#ifdef MOZ_VTUNE + vtune::MarkStub(code, "BaselineICFallback"); +#endif + + fallbackCode.initCode(code); + return true; +} + +} // namespace jit +} // namespace js |