/* -*- 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"
#include "vm/PortableBaselineInterpret.h"
#include "vm/TypeofEqOperand.h"  // TypeofEqOperand
#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, VMFunctionId 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_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().oneOriginValue(),
        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());
}

inline ICFallbackStub* GetFallbackStub(ICEntry* entry) {
  ICStub* stub = entry->firstStub();
  while (!stub->isFallback()) {
    stub = stub->toCacheIRStub()->next();
  }
  return stub->toFallbackStub();
}

bool ICEntry::traceWeak(JSTracer* trc) {
  // Trace CacheIR stubs and remove those containing weak pointers to dead GC
  // things.  Prebarriers are not necessary because this happens as part of GC.

  ICFallbackStub* fallbackStub = GetFallbackStub(this);

  ICStub* stub = firstStub();
  ICCacheIRStub* prev = nullptr;
  bool allSurvived = true;
  while (!stub->isFallback()) {
    ICCacheIRStub* cacheIRStub = stub->toCacheIRStub();
    if (!cacheIRStub->traceWeak(trc)) {
      fallbackStub->unlinkStubUnbarriered(this, prev, cacheIRStub);
      allSurvived = false;
    } else {
      prev = cacheIRStub;
    }

    stub = cacheIRStub->next();
    MOZ_ASSERT_IF(prev, prev->next() == stub);
  }

  // Clear the folded stubs flag if we know for sure that there are none
  // left. The flag will remain set if we have removed all folded stubs but
  // other stubs remain.
  if (fallbackStub->numOptimizedStubs() == 0 &&
      fallbackStub->mayHaveFoldedStub()) {
    fallbackStub->clearMayHaveFoldedStub();
  }

#ifdef DEBUG
  size_t count = 0;
  for (ICStub* stub = firstStub(); stub != fallbackStub;
       stub = stub->toCacheIRStub()->next()) {
    count++;
  }
  MOZ_ASSERT(count == fallbackStub->state().numOptimizedStubs());
#endif

  return allSurvived;
}

// 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::Lambda, BaselineICFallbackKind::Lambda);

    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::BindUnqualifiedName, BaselineICFallbackKind::BindName);
    setKind(JSOp::BindUnqualifiedGName, BaselineICFallbackKind::BindName);

    setKind(JSOp::GetIntrinsic, BaselineICFallbackKind::LazyConstant);
    setKind(JSOp::BuiltinObject, BaselineICFallbackKind::LazyConstant);
    setKind(JSOp::ImportMeta, BaselineICFallbackKind::LazyConstant);

    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::TypeofEq, BaselineICFallbackKind::TypeOfEq);

    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);
    setKind(JSOp::OptimizeGetIterator,
            BaselineICFallbackKind::OptimizeGetIterator);

    setKind(JSOp::GetImport, BaselineICFallbackKind::GetImport);
  }
};

static constexpr OpToFallbackKindTable FallbackKindTable;

void ICScript::initICEntries(JSContext* cx, JSScript* script) {
  MOZ_ASSERT(cx->zone()->jitZone());
  MOZ_ASSERT(jit::IsBaselineInterpreterEnabled() ||
             jit::IsPortableBaselineInterpreterEnabled());

  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 =
#ifdef ENABLE_PORTABLE_BASELINE_INTERP
        !jit::IsPortableBaselineInterpreterEnabled()
            ? fallbackCode.addr(kind)
            : TrampolinePtr(js::pbl::GetPortableFallbackStub(kind));
#else
        fallbackCode.addr(kind);
#endif

    // 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:
    case BaselineICFallbackKind::TypeOfEq:
      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) {
  if (hasJitCode()) {
    JitCode* stubJitCode = jitCode();
    TraceManuallyBarrieredEdge(trc, &stubJitCode, "baseline-ic-stub-code");
  }

  TraceCacheIRStub(trc, this, stubInfo());
}

bool ICCacheIRStub::traceWeak(JSTracer* trc) {
  return TraceWeakCacheIRStub(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->zone(), 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) {
  // We are removing edges from ICStub to gcthings. Perform a barrier to let the
  // GC know about those edges.
  PreWriteBarrier(zone, stub);

  unlinkStubUnbarriered(icEntry, prev, stub);
}

void ICFallbackStub::unlinkStubUnbarriered(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();

#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(Zone* zone, ICEntry* icEntry) {
  ICStub* stub = icEntry->firstStub();
  while (stub != this) {
    unlinkStub(zone, icEntry, /* prev = */ nullptr, stub->toCacheIRStub());
    stub = stub->toCacheIRStub()->next();
  }
  clearMayHaveFoldedStub();
}

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,
                                                VMFunctionId id) {
  TrampolinePtr code = cx->runtime()->jitRuntime()->getVMWrapper(id);
  const VMFunctionData& fun = GetVMFunction(id);
  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);

  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) {
  VMFunctionId id = VMFunctionToId<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 (deferType == DeferType::None && !attached) {
      stub->trackNotAttached();
    }
  }

  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;
    }
  }

  if (stack) {
    // 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) {
      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 (IsTypeOfNameOp(JSOp(pc[JSOpLength_GetGName]))) {
    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());
  JSOp op = JSOp(*pc);
  FallbackICSpew(cx, stub, "BindName(%s)", CodeName(JSOp(*pc)));

  MOZ_ASSERT(op == JSOp::BindName || op == JSOp::BindUnqualifiedName ||
             op == JSOp::BindUnqualifiedGName);

  Rooted<PropertyName*> name(cx, frame->script()->getName(pc));

  TryAttachStub<BindNameIRGenerator>("BindName", cx, frame, stub, envChain,
                                     name);

  JSObject* env;
  if (op == JSOp::BindName) {
    env = LookupNameWithGlobalDefault(cx, name, envChain);
  } else {
    env = LookupNameUnqualified(cx, name, envChain);
  }
  if (!env) {
    return false;
  }

  res.setObject(*env);
  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);
}

//
// LazyConstant_Fallback
//

bool DoLazyConstantFallback(JSContext* cx, BaselineFrame* frame,
                            ICFallbackStub* stub, 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, "LazyConstant(%s)", CodeName(JSOp(*pc)));

  MOZ_ASSERT(op == JSOp::GetIntrinsic || op == JSOp::BuiltinObject ||
             op == JSOp::ImportMeta);

  if (op == JSOp::GetIntrinsic) {
    if (!GetIntrinsicOperation(cx, script, pc, res)) {
      return false;
    }
  } else if (op == JSOp::BuiltinObject) {
    auto kind = BuiltinObjectKind(GET_UINT8(pc));
    JSObject* builtinObject = BuiltinObjectOperation(cx, kind);
    if (!builtinObject) {
      return false;
    }
    res.setObject(*builtinObject);
  } else {
    JSObject* metaObject = ImportMetaOperation(cx, script);
    if (!metaObject) {
      return false;
    }
    res.setObject(*metaObject);
  }

  TryAttachStub<LazyConstantIRGenerator>("LazyConstant", cx, frame, stub, res);

  return true;
}

bool FallbackICCodeCompiler::emit_LazyConstant() {
  EmitRestoreTailCallReg(masm);

  masm.push(ICStubReg);
  pushStubPayload(masm, R0.scratchReg());

  using Fn =
      bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, MutableHandleValue);
  return tailCallVM<Fn, DoLazyConstantFallback>(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 = stack ? -2 : JSDVG_IGNORE_STACK;
  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 (deferType == DeferType::None && !attached) {
      stub->trackNotAttached();
    }
  }

  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;
    }
  }

  if (stack) {
    // 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(),
                           lhs, idVal, 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) {
      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*, ICFallbackStub*, 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);
  EmitReturnFromIC(masm);

  return true;
}

//
// Call_Fallback
//

bool DoCallFallback(JSContext* cx, BaselineFrame* frame, ICFallbackStub* stub,
                    uint32_t argc, Value* vp, 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, "Call(%s)", CodeName(op));

  MOZ_ASSERT(argc == GET_ARGC(pc));
  bool constructing =
      (op == JSOp::New || op == JSOp::NewContent || 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());

  // 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);
    CallIRGenerator gen(cx, script, pc, op, stub->state(), frame, argc, callee,
                        callArgs.thisv(), newTarget, args);
    switch (gen.tryAttachStub()) {
      case AttachDecision::NoAction:
        break;
      case AttachDecision::Attach: {
        ICScript* icScript = frame->icScript();
        ICAttachResult result =
            AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                      script, icScript, stub, gen.stubName());
        if (result == ICAttachResult::Attached) {
          handled = true;
          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::CallContent ||
               op == JSOp::CallIgnoresRv || op == JSOp::CallIter ||
               op == JSOp::CallContentIter || op == JSOp::Eval ||
               op == JSOp::StrictEval);
    if ((op == JSOp::CallIter || op == JSOp::CallContentIter) &&
        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,
                          ICFallbackStub* stub, Value* vp,
                          MutableHandleValue res) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);

  RootedScript script(cx, frame->script());
  jsbytecode* pc = StubOffsetToPc(stub, 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:
    Rooted<ArrayObject*> aobj(cx, &arr.toObject().as<ArrayObject>());
    MOZ_ASSERT(IsPackedArray(aobj));

    HandleValueArray args = HandleValueArray::fromMarkedLocation(
        aobj->length(), aobj->getDenseElements());
    CallIRGenerator gen(cx, script, pc, op, stub->state(), frame, 1, callee,
                        thisv, newTarget, args);
    switch (gen.tryAttachStub()) {
      case AttachDecision::NoAction:
        break;
      case AttachDecision::Attach: {
        ICScript* icScript = frame->icScript();
        ICAttachResult result =
            AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
                                      script, icScript, stub, gen.stubName());

        if (result == ICAttachResult::Attached) {
          handled = true;
          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.mov(FramePointer, argPtr);

  // Skip 3 pointers pushed on top of the arguments: the frame descriptor,
  // return address, and old frame pointer.
  size_t valueOffset = BaselineStubFrameLayout::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 FramePointer instead of StackPointer because it's not affected by
    // the stack pushes below.

    // newTarget
    uint32_t valueOffset = BaselineStubFrameLayout::Size();
    if (isConstructing) {
      masm.pushValue(Address(FramePointer, valueOffset));
      valueOffset += sizeof(Value);
    }

    // array
    masm.pushValue(Address(FramePointer, valueOffset));
    valueOffset += sizeof(Value);

    // this
    masm.pushValue(Address(FramePointer, valueOffset));
    valueOffset += sizeof(Value);

    // callee
    masm.pushValue(Address(FramePointer, valueOffset));
    valueOffset += sizeof(Value);

    masm.push(masm.getStackPointer());
    masm.push(ICStubReg);

    PushStubPayload(masm, R0.scratchReg());

    using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, 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*, ICFallbackStub*, 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, CalleeToken, Descriptor ]
  size_t thisvOffset =
      JitFrameLayout::offsetOfThis() - JitFrameLayout::bytesPoppedAfterCall();
  masm.loadValue(Address(masm.getStackPointer(), thisvOffset), R1);

  leaveStubFrame(masm);

  // 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,
                           ICFallbackStub* stub, HandleValue value,
                           MutableHandleValue res) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);
  FallbackICSpew(cx, stub, "GetIterator");

  TryAttachStub<GetIteratorIRGenerator>("GetIterator", cx, frame, stub, value);

  PropertyIteratorObject* 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*, ICFallbackStub*, HandleValue,
                      MutableHandleValue);
  return tailCallVM<Fn, DoGetIteratorFallback>(masm);
}

//
// OptimizeSpreadCall_Fallback
//

bool DoOptimizeSpreadCallFallback(JSContext* cx, BaselineFrame* frame,
                                  ICFallbackStub* stub, HandleValue value,
                                  MutableHandleValue res) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);
  FallbackICSpew(cx, stub, "OptimizeSpreadCall");

  TryAttachStub<OptimizeSpreadCallIRGenerator>("OptimizeSpreadCall", cx, frame,
                                               stub, value);

  return OptimizeSpreadCall(cx, value, res);
}

bool FallbackICCodeCompiler::emit_OptimizeSpreadCall() {
  EmitRestoreTailCallReg(masm);

  masm.pushValue(R0);
  masm.push(ICStubReg);
  pushStubPayload(masm, R0.scratchReg());

  using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, HandleValue,
                      MutableHandleValue);
  return tailCallVM<Fn, DoOptimizeSpreadCallFallback>(masm);
}

//
// InstanceOf_Fallback
//

bool DoInstanceOfFallback(JSContext* cx, BaselineFrame* frame,
                          ICFallbackStub* 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 (!InstanceofOperator(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*, ICFallbackStub*, HandleValue,
                      HandleValue, MutableHandleValue);
  return tailCallVM<Fn, DoInstanceOfFallback>(masm);
}

//
// TypeOf_Fallback
//

bool DoTypeOfFallback(JSContext* cx, BaselineFrame* frame, ICFallbackStub* 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*, ICFallbackStub*, HandleValue,
                      MutableHandleValue);
  return tailCallVM<Fn, DoTypeOfFallback>(masm);
}

//
// TypeOfEq_Fallback
//

bool DoTypeOfEqFallback(JSContext* cx, BaselineFrame* frame,
                        ICFallbackStub* stub, HandleValue val,
                        MutableHandleValue res) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);
  FallbackICSpew(cx, stub, "TypeOfEq");

  jsbytecode* pc = StubOffsetToPc(stub, frame->script());
  auto operand = TypeofEqOperand::fromRawValue(GET_UINT8(pc));
  JSType type = operand.type();
  JSOp compareOp = operand.compareOp();

  TryAttachStub<TypeOfEqIRGenerator>("TypeOfEq", cx, frame, stub, val, type,
                                     compareOp);

  bool result = js::TypeOfValue(val) == type;
  if (compareOp == JSOp::Ne) {
    result = !result;
  }
  res.setBoolean(result);
  return true;
}

bool FallbackICCodeCompiler::emit_TypeOfEq() {
  EmitRestoreTailCallReg(masm);

  masm.pushValue(R0);
  masm.push(ICStubReg);
  pushStubPayload(masm, R0.scratchReg());

  using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, HandleValue,
                      MutableHandleValue);
  return tailCallVM<Fn, DoTypeOfEqFallback>(masm);
}

//
// ToPropertyKey_Fallback
//

bool DoToPropertyKeyFallback(JSContext* cx, BaselineFrame* frame,
                             ICFallbackStub* 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*, ICFallbackStub*, HandleValue,
                      MutableHandleValue);
  return tailCallVM<Fn, DoToPropertyKeyFallback>(masm);
}

//
// Rest_Fallback
//

bool DoRestFallback(JSContext* cx, BaselineFrame* frame, ICFallbackStub* 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*, ICFallbackStub*, MutableHandleValue);
  return tailCallVM<Fn, DoRestFallback>(masm);
}

//
// UnaryArith_Fallback
//

bool DoUnaryArithFallback(JSContext* cx, BaselineFrame* frame,
                          ICFallbackStub* stub, HandleValue val,
                          MutableHandleValue res) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);

  jsbytecode* pc = StubOffsetToPc(stub, frame->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*, ICFallbackStub*, HandleValue,
                      MutableHandleValue);
  return tailCallVM<Fn, DoUnaryArithFallback>(masm);
}

//
// BinaryArith_Fallback
//

bool DoBinaryArithFallback(JSContext* cx, BaselineFrame* frame,
                           ICFallbackStub* stub, HandleValue lhs,
                           HandleValue rhs, MutableHandleValue ret) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);

  jsbytecode* pc = StubOffsetToPc(stub, frame->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*, ICFallbackStub*, HandleValue,
                      HandleValue, MutableHandleValue);
  return tailCallVM<Fn, DoBinaryArithFallback>(masm);
}

//
// Compare_Fallback
//
bool DoCompareFallback(JSContext* cx, BaselineFrame* frame,
                       ICFallbackStub* stub, HandleValue lhs, HandleValue rhs,
                       MutableHandleValue ret) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);

  jsbytecode* pc = StubOffsetToPc(stub, frame->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*, ICFallbackStub*, HandleValue,
                      HandleValue, MutableHandleValue);
  return tailCallVM<Fn, DoCompareFallback>(masm);
}

//
// NewArray_Fallback
//

bool DoNewArrayFallback(JSContext* cx, BaselineFrame* frame,
                        ICFallbackStub* stub, MutableHandleValue res) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);
  FallbackICSpew(cx, stub, "NewArray");

  jsbytecode* pc = StubOffsetToPc(stub, frame->script());

  uint32_t length = GET_UINT32(pc);
  MOZ_ASSERT(length <= INT32_MAX,
             "the bytecode emitter must fail to compile code that would "
             "produce a length exceeding int32_t range");

  Rooted<ArrayObject*> array(cx, NewArrayOperation(cx, length));
  if (!array) {
    return false;
  }

  TryAttachStub<NewArrayIRGenerator>("NewArray", cx, frame, stub, JSOp(*pc),
                                     array, frame);

  res.setObject(*array);
  return true;
}

bool FallbackICCodeCompiler::emit_NewArray() {
  EmitRestoreTailCallReg(masm);

  masm.push(ICStubReg);  // stub.
  masm.pushBaselineFramePtr(FramePointer, R0.scratchReg());

  using Fn =
      bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, MutableHandleValue);
  return tailCallVM<Fn, DoNewArrayFallback>(masm);
}

//
// NewObject_Fallback
//
bool DoNewObjectFallback(JSContext* cx, BaselineFrame* frame,
                         ICFallbackStub* stub, MutableHandleValue res) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);
  FallbackICSpew(cx, stub, "NewObject");

  RootedScript script(cx, frame->script());
  jsbytecode* pc = StubOffsetToPc(stub, script);

  RootedObject obj(cx, NewObjectOperation(cx, script, pc));
  if (!obj) {
    return false;
  }

  TryAttachStub<NewObjectIRGenerator>("NewObject", cx, frame, stub, JSOp(*pc),
                                      obj, frame);

  res.setObject(*obj);
  return true;
}

bool FallbackICCodeCompiler::emit_NewObject() {
  EmitRestoreTailCallReg(masm);

  masm.push(ICStubReg);  // stub.
  pushStubPayload(masm, R0.scratchReg());

  using Fn =
      bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, MutableHandleValue);
  return tailCallVM<Fn, DoNewObjectFallback>(masm);
}

//
// Lambda_Fallback
//

bool DoLambdaFallback(JSContext* cx, BaselineFrame* frame, ICFallbackStub* stub,
                      MutableHandleValue res) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);
  FallbackICSpew(cx, stub, "Lambda");

  jsbytecode* pc = StubOffsetToPc(stub, frame->script());

  Rooted<JSFunction*> fun(cx, frame->script()->getFunction(pc));
  Rooted<JSObject*> env(cx, frame->environmentChain());

  TryAttachStub<LambdaIRGenerator>("Lambda", cx, frame, stub, JSOp(*pc), fun,
                                   frame);

  JSObject* clone = Lambda(cx, fun, env);
  if (!clone) {
    return false;
  }

  res.setObject(*clone);
  return true;
}

bool FallbackICCodeCompiler::emit_Lambda() {
  EmitRestoreTailCallReg(masm);

  masm.push(ICStubReg);
  masm.pushBaselineFramePtr(FramePointer, R0.scratchReg());

  using Fn =
      bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, MutableHandleValue);
  return tailCallVM<Fn, DoLambdaFallback>(masm);
}

//
// CloseIter_Fallback
//

bool DoCloseIterFallback(JSContext* cx, BaselineFrame* frame,
                         ICFallbackStub* stub, HandleObject iter) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);
  FallbackICSpew(cx, stub, "CloseIter");

  jsbytecode* pc = StubOffsetToPc(stub, frame->script());
  CompletionKind kind = CompletionKind(GET_UINT8(pc));

  TryAttachStub<CloseIterIRGenerator>("CloseIter", cx, frame, stub, iter, kind);

  return CloseIterOperation(cx, iter, kind);
}

bool FallbackICCodeCompiler::emit_CloseIter() {
  EmitRestoreTailCallReg(masm);

  masm.push(R0.scratchReg());
  masm.push(ICStubReg);
  pushStubPayload(masm, R0.scratchReg());

  using Fn =
      bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, HandleObject);
  return tailCallVM<Fn, DoCloseIterFallback>(masm);
}

//
// OptimizeGetIterator_Fallback
//

bool DoOptimizeGetIteratorFallback(JSContext* cx, BaselineFrame* frame,
                                   ICFallbackStub* stub, HandleValue value,
                                   MutableHandleValue res) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);
  FallbackICSpew(cx, stub, "OptimizeGetIterator");

  TryAttachStub<OptimizeGetIteratorIRGenerator>("OptimizeGetIterator", cx,
                                                frame, stub, value);

  bool result = OptimizeGetIterator(value, cx);
  res.setBoolean(result);
  return true;
}

bool FallbackICCodeCompiler::emit_OptimizeGetIterator() {
  EmitRestoreTailCallReg(masm);

  masm.pushValue(R0);
  masm.push(ICStubReg);
  pushStubPayload(masm, R0.scratchReg());

  using Fn = bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, HandleValue,
                      MutableHandleValue);
  return tailCallVM<Fn, DoOptimizeGetIteratorFallback>(masm);
}

//
// GetImport_Fallback
//

bool DoGetImportFallback(JSContext* cx, BaselineFrame* frame,
                         ICFallbackStub* stub, MutableHandleValue res) {
  stub->incrementEnteredCount();
  MaybeNotifyWarp(frame->outerScript(), stub);
  FallbackICSpew(cx, stub, "GetImport");

  RootedObject envChain(cx, frame->environmentChain());
  RootedScript script(cx, frame->script());
  jsbytecode* pc = StubOffsetToPc(stub, script);

  TryAttachStub<GetImportIRGenerator>("GetImport", cx, frame, stub);

  return GetImportOperation(cx, envChain, script, pc, res);
}

bool FallbackICCodeCompiler::emit_GetImport() {
  EmitRestoreTailCallReg(masm);

  masm.push(ICStubReg);
  pushStubPayload(masm, R0.scratchReg());

  using Fn =
      bool (*)(JSContext*, BaselineFrame*, ICFallbackStub*, MutableHandleValue);
  return tailCallVM<Fn, DoGetImportFallback>(masm);
}

bool JitRuntime::generateBaselineICFallbackCode(JSContext* cx) {
  TempAllocator temp(&cx->tempLifoAlloc());
  StackMacroAssembler masm(cx, temp);
  PerfSpewerRangeRecorder rangeRecorder(masm);
  AutoCreatedBy acb(masm, "JitRuntime::generateBaselineICFallbackCode");

  BaselineICFallbackCode& fallbackCode = baselineICFallbackCode_.ref();
  FallbackICCodeCompiler compiler(cx, fallbackCode, masm);

  JitSpew(JitSpew_Codegen, "# Emitting Baseline IC fallback code");

#define EMIT_CODE(kind)                                            \
  {                                                                \
    AutoCreatedBy acb(masm, "kind=" #kind);                        \
    uint32_t offset = startTrampolineCode(masm);                   \
    InitMacroAssemblerForICStub(masm);                             \
    if (!compiler.emit_##kind()) {                                 \
      return false;                                                \
    }                                                              \
    fallbackCode.initOffset(BaselineICFallbackKind::kind, offset); \
    rangeRecorder.recordOffset("BaselineICFallback: " #kind);      \
  }
  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;
  }

  rangeRecorder.collectRangesForJitCode(code);

#ifdef MOZ_VTUNE
  vtune::MarkStub(code, "BaselineICFallback");
#endif

  fallbackCode.initCode(code);
  return true;
}

}  // namespace jit
}  // namespace js
