Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  CacheIRCompiler.cpp

  Sprache: C
 

/* -*- 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/CacheIRCompiler.h"

#include "mozilla/ArrayUtils.h"
#include "mozilla/FunctionTypeTraits.h"
#include "mozilla/MaybeOneOf.h"
#include "mozilla/ScopeExit.h"

#include <type_traits>
#include <utility>

#include "jslibmath.h"
#include "jsmath.h"

#include "builtin/DataViewObject.h"
#include "builtin/Object.h"
#include "gc/GCEnum.h"
#include "jit/BaselineCacheIRCompiler.h"
#include "jit/CacheIRGenerator.h"
#include "jit/IonCacheIRCompiler.h"
#include "jit/JitFrames.h"
#include "jit/JitRuntime.h"
#include "jit/JitZone.h"
#include "jit/SharedICHelpers.h"
#include "jit/SharedICRegisters.h"
#include "jit/VMFunctions.h"
#include "js/friend/DOMProxy.h"     // JS::ExpandoAndGeneration
#include "js/friend/XrayJitInfo.h"  // js::jit::GetXrayJitInfo
#include "js/ScalarType.h"          // js::Scalar::Type
#include "js/SweepingAPI.h"
#include "proxy/DOMProxy.h"
#include "proxy/Proxy.h"
#include "proxy/ScriptedProxyHandler.h"
#include "vm/ArgumentsObject.h"
#include "vm/ArrayBufferObject.h"
#include "vm/ArrayBufferViewObject.h"
#include "vm/BigIntType.h"
#include "vm/FunctionFlags.h"  // js::FunctionFlags
#include "vm/GeneratorObject.h"
#include "vm/GetterSetter.h"
#include "vm/Interpreter.h"
#include "vm/TypeofEqOperand.h"  // TypeofEqOperand
#include "vm/Uint8Clamped.h"

#include "builtin/Boolean-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "jit/SharedICHelpers-inl.h"
#include "jit/VMFunctionList-inl.h"

using namespace js;
using namespace js::jit;

using mozilla::Maybe;

using JS::ExpandoAndGeneration;

ValueOperand CacheRegisterAllocator::useValueRegister(MacroAssembler& masm,
                                                      ValOperandId op) {
  OperandLocation& loc = operandLocations_[op.id()];

  switch (loc.kind()) {
    case OperandLocation::ValueReg:
      currentOpRegs_.add(loc.valueReg());
      return loc.valueReg();

    case OperandLocation::ValueStack: {
      ValueOperand reg = allocateValueRegister(masm);
      popValue(masm, &loc, reg);
      return reg;
    }

    case OperandLocation::BaselineFrame: {
      ValueOperand reg = allocateValueRegister(masm);
      Address addr = addressOf(masm, loc.baselineFrameSlot());
      masm.loadValue(addr, reg);
      loc.setValueReg(reg);
      return reg;
    }

    case OperandLocation::Constant: {
      ValueOperand reg = allocateValueRegister(masm);
      masm.moveValue(loc.constant(), reg);
      loc.setValueReg(reg);
      return reg;
    }

    case OperandLocation::PayloadReg: {
      // Temporarily add the payload register to currentOpRegs_ so
      // allocateValueRegister will stay away from it.
      currentOpRegs_.add(loc.payloadReg());
      ValueOperand reg = allocateValueRegister(masm);
      masm.tagValue(loc.payloadType(), loc.payloadReg(), reg);
      currentOpRegs_.take(loc.payloadReg());
      availableRegs_.add(loc.payloadReg());
      loc.setValueReg(reg);
      return reg;
    }

    case OperandLocation::PayloadStack: {
      ValueOperand reg = allocateValueRegister(masm);
      popPayload(masm, &loc, reg.scratchReg());
      masm.tagValue(loc.payloadType(), reg.scratchReg(), reg);
      loc.setValueReg(reg);
      return reg;
    }

    case OperandLocation::DoubleReg: {
      ValueOperand reg = allocateValueRegister(masm);
      {
        ScratchDoubleScope fpscratch(masm);
        masm.boxDouble(loc.doubleReg(), reg, fpscratch);
      }
      loc.setValueReg(reg);
      return reg;
    }

    case OperandLocation::Uninitialized:
      break;
  }

  MOZ_CRASH();
}

// Load a value operand directly into a float register. Caller must have
// guarded isNumber on the provided val.
void CacheRegisterAllocator::ensureDoubleRegister(MacroAssembler& masm,
                                                  NumberOperandId op,
                                                  FloatRegister dest) const {
  // If AutoScratchFloatRegister is active, we have to add sizeof(double) to
  // any stack slot offsets below.
  int32_t stackOffset = hasAutoScratchFloatRegisterSpill() ? sizeof(double) : 0;

  const OperandLocation& loc = operandLocations_[op.id()];

  Label failure, done;
  switch (loc.kind()) {
    case OperandLocation::ValueReg: {
      masm.ensureDouble(loc.valueReg(), dest, &failure);
      break;
    }

    case OperandLocation::ValueStack: {
      Address addr = valueAddress(masm, &loc);
      addr.offset += stackOffset;
      masm.ensureDouble(addr, dest, &failure);
      break;
    }

    case OperandLocation::BaselineFrame: {
      Address addr = addressOf(masm, loc.baselineFrameSlot());
      addr.offset += stackOffset;
      masm.ensureDouble(addr, dest, &failure);
      break;
    }

    case OperandLocation::DoubleReg: {
      masm.moveDouble(loc.doubleReg(), dest);
      return;
    }

    case OperandLocation::Constant: {
      MOZ_ASSERT(loc.constant().isNumber(),
                 "Caller must ensure the operand is a number value");
      masm.loadConstantDouble(loc.constant().toNumber(), dest);
      return;
    }

    case OperandLocation::PayloadReg: {
      // Doubles can't be stored in payload registers, so this must be an int32.
      MOZ_ASSERT(loc.payloadType() == JSVAL_TYPE_INT32,
                 "Caller must ensure the operand is a number value");
      masm.convertInt32ToDouble(loc.payloadReg(), dest);
      return;
    }

    case OperandLocation::PayloadStack: {
      // Doubles can't be stored in payload registers, so this must be an int32.
      MOZ_ASSERT(loc.payloadType() == JSVAL_TYPE_INT32,
                 "Caller must ensure the operand is a number value");
      MOZ_ASSERT(loc.payloadStack() <= stackPushed_);
      Address addr = payloadAddress(masm, &loc);
      addr.offset += stackOffset;
      masm.convertInt32ToDouble(addr, dest);
      return;
    }

    case OperandLocation::Uninitialized:
      MOZ_CRASH("Unhandled operand type in ensureDoubleRegister");
      return;
  }
  masm.jump(&done);
  masm.bind(&failure);
  masm.assumeUnreachable(
      "Missing guard allowed non-number to hit ensureDoubleRegister");
  masm.bind(&done);
}

void CacheRegisterAllocator::copyToScratchRegister(MacroAssembler& masm,
                                                   TypedOperandId typedId,
                                                   Register dest) const {
  // If AutoScratchFloatRegister is active, we have to add sizeof(double) to
  // any stack slot offsets below.
  int32_t stackOffset = hasAutoScratchFloatRegisterSpill() ? sizeof(double) : 0;

  const OperandLocation& loc = operandLocations_[typedId.id()];

  switch (loc.kind()) {
    case OperandLocation::ValueReg: {
      masm.unboxNonDouble(loc.valueReg(), dest, typedId.type());
      break;
    }
    case OperandLocation::ValueStack: {
      Address addr = valueAddress(masm, &loc);
      addr.offset += stackOffset;
      masm.unboxNonDouble(addr, dest, typedId.type());
      break;
    }
    case OperandLocation::BaselineFrame: {
      Address addr = addressOf(masm, loc.baselineFrameSlot());
      addr.offset += stackOffset;
      masm.unboxNonDouble(addr, dest, typedId.type());
      break;
    }
    case OperandLocation::PayloadReg: {
      MOZ_ASSERT(loc.payloadType() == typedId.type());
      masm.mov(loc.payloadReg(), dest);
      return;
    }
    case OperandLocation::PayloadStack: {
      MOZ_ASSERT(loc.payloadType() == typedId.type());
      MOZ_ASSERT(loc.payloadStack() <= stackPushed_);
      Address addr = payloadAddress(masm, &loc);
      addr.offset += stackOffset;
      masm.loadPtr(addr, dest);
      return;
    }
    case OperandLocation::DoubleReg:
    case OperandLocation::Constant:
    case OperandLocation::Uninitialized:
      MOZ_CRASH("Unhandled operand location");
  }
}

void CacheRegisterAllocator::copyToScratchValueRegister(
    MacroAssembler& masm, ValOperandId valId, ValueOperand dest) const {
  MOZ_ASSERT(!addedFailurePath_);
  MOZ_ASSERT(!hasAutoScratchFloatRegisterSpill());

  const OperandLocation& loc = operandLocations_[valId.id()];
  switch (loc.kind()) {
    case OperandLocation::ValueReg:
      masm.moveValue(loc.valueReg(), dest);
      break;
    case OperandLocation::ValueStack: {
      Address addr = valueAddress(masm, &loc);
      masm.loadValue(addr, dest);
      break;
    }
    case OperandLocation::BaselineFrame: {
      Address addr = addressOf(masm, loc.baselineFrameSlot());
      masm.loadValue(addr, dest);
      break;
    }
    case OperandLocation::Constant:
      masm.moveValue(loc.constant(), dest);
      break;
    case OperandLocation::PayloadReg:
      masm.tagValue(loc.payloadType(), loc.payloadReg(), dest);
      break;
    case OperandLocation::PayloadStack: {
      Address addr = payloadAddress(masm, &loc);
      masm.loadPtr(addr, dest.scratchReg());
      masm.tagValue(loc.payloadType(), dest.scratchReg(), dest);
      break;
    }
    case OperandLocation::DoubleReg: {
      ScratchDoubleScope fpscratch(masm);
      masm.boxDouble(loc.doubleReg(), dest, fpscratch);
      break;
    }
    case OperandLocation::Uninitialized:
      MOZ_CRASH();
  }
}

Register CacheRegisterAllocator::useRegister(MacroAssembler& masm,
                                             TypedOperandId typedId) {
  MOZ_ASSERT(!addedFailurePath_);
  MOZ_ASSERT(!hasAutoScratchFloatRegisterSpill());

  OperandLocation& loc = operandLocations_[typedId.id()];
  switch (loc.kind()) {
    case OperandLocation::PayloadReg:
      currentOpRegs_.add(loc.payloadReg());
      return loc.payloadReg();

    case OperandLocation::ValueReg: {
      // It's possible the value is still boxed: as an optimization, we unbox
      // the first time we use a value as object.
      ValueOperand val = loc.valueReg();
      availableRegs_.add(val);
      Register reg = val.scratchReg();
      availableRegs_.take(reg);
      masm.unboxNonDouble(val, reg, typedId.type());
      loc.setPayloadReg(reg, typedId.type());
      currentOpRegs_.add(reg);
      return reg;
    }

    case OperandLocation::PayloadStack: {
      Register reg = allocateRegister(masm);
      popPayload(masm, &loc, reg);
      return reg;
    }

    case OperandLocation::ValueStack: {
      // The value is on the stack, but boxed. If it's on top of the stack we
      // unbox it and then remove it from the stack, else we just unbox.
      Register reg = allocateRegister(masm);
      if (loc.valueStack() == stackPushed_) {
        masm.unboxNonDouble(Address(masm.getStackPointer(), 0), reg,
                            typedId.type());
        masm.addToStackPtr(Imm32(sizeof(js::Value)));
        MOZ_ASSERT(stackPushed_ >= sizeof(js::Value));
        stackPushed_ -= sizeof(js::Value);
      } else {
        MOZ_ASSERT(loc.valueStack() < stackPushed_);
        masm.unboxNonDouble(
            Address(masm.getStackPointer(), stackPushed_ - loc.valueStack()),
            reg, typedId.type());
      }
      loc.setPayloadReg(reg, typedId.type());
      return reg;
    }

    case OperandLocation::BaselineFrame: {
      Register reg = allocateRegister(masm);
      Address addr = addressOf(masm, loc.baselineFrameSlot());
      masm.unboxNonDouble(addr, reg, typedId.type());
      loc.setPayloadReg(reg, typedId.type());
      return reg;
    };

    case OperandLocation::Constant: {
      Value v = loc.constant();
      Register reg = allocateRegister(masm);
      if (v.isString()) {
        masm.movePtr(ImmGCPtr(v.toString()), reg);
      } else if (v.isSymbol()) {
        masm.movePtr(ImmGCPtr(v.toSymbol()), reg);
      } else if (v.isBigInt()) {
        masm.movePtr(ImmGCPtr(v.toBigInt()), reg);
      } else if (v.isBoolean()) {
        masm.movePtr(ImmWord(v.toBoolean() ? 1 : 0), reg);
      } else {
        MOZ_CRASH("Unexpected Value");
      }
      loc.setPayloadReg(reg, v.extractNonDoubleType());
      return reg;
    }

    case OperandLocation::DoubleReg:
    case OperandLocation::Uninitialized:
      break;
  }

  MOZ_CRASH();
}

ConstantOrRegister CacheRegisterAllocator::useConstantOrRegister(
    MacroAssembler& masm, ValOperandId val) {
  MOZ_ASSERT(!addedFailurePath_);
  MOZ_ASSERT(!hasAutoScratchFloatRegisterSpill());

  OperandLocation& loc = operandLocations_[val.id()];
  switch (loc.kind()) {
    case OperandLocation::Constant:
      return loc.constant();

    case OperandLocation::PayloadReg:
    case OperandLocation::PayloadStack: {
      JSValueType payloadType = loc.payloadType();
      Register reg = useRegister(masm, TypedOperandId(val, payloadType));
      return TypedOrValueRegister(MIRTypeFromValueType(payloadType),
                                  AnyRegister(reg));
    }

    case OperandLocation::ValueReg:
    case OperandLocation::ValueStack:
    case OperandLocation::BaselineFrame:
      return TypedOrValueRegister(useValueRegister(masm, val));

    case OperandLocation::DoubleReg:
      return TypedOrValueRegister(MIRType::Double,
                                  AnyRegister(loc.doubleReg()));

    case OperandLocation::Uninitialized:
      break;
  }

  MOZ_CRASH();
}

Register CacheRegisterAllocator::defineRegister(MacroAssembler& masm,
                                                TypedOperandId typedId) {
  MOZ_ASSERT(!addedFailurePath_);
  MOZ_ASSERT(!hasAutoScratchFloatRegisterSpill());

  OperandLocation& loc = operandLocations_[typedId.id()];
  MOZ_ASSERT(loc.kind() == OperandLocation::Uninitialized);

  Register reg = allocateRegister(masm);
  loc.setPayloadReg(reg, typedId.type());
  return reg;
}

ValueOperand CacheRegisterAllocator::defineValueRegister(MacroAssembler& masm,
                                                         ValOperandId val) {
  MOZ_ASSERT(!addedFailurePath_);
  MOZ_ASSERT(!hasAutoScratchFloatRegisterSpill());

  OperandLocation& loc = operandLocations_[val.id()];
  MOZ_ASSERT(loc.kind() == OperandLocation::Uninitialized);

  ValueOperand reg = allocateValueRegister(masm);
  loc.setValueReg(reg);
  return reg;
}

void CacheRegisterAllocator::freeDeadOperandLocations(MacroAssembler& masm) {
  // See if any operands are dead so we can reuse their registers. Note that
  // we skip the input operands, as those are also used by failure paths, and
  // we currently don't track those uses.
  for (size_t i = writer_.numInputOperands(); i < operandLocations_.length();
       i++) {
    if (!writer_.operandIsDead(i, currentInstruction_)) {
      continue;
    }

    OperandLocation& loc = operandLocations_[i];
    switch (loc.kind()) {
      case OperandLocation::PayloadReg:
        availableRegs_.add(loc.payloadReg());
        break;
      case OperandLocation::ValueReg:
        availableRegs_.add(loc.valueReg());
        break;
      case OperandLocation::PayloadStack:
        masm.propagateOOM(freePayloadSlots_.append(loc.payloadStack()));
        break;
      case OperandLocation::ValueStack:
        masm.propagateOOM(freeValueSlots_.append(loc.valueStack()));
        break;
      case OperandLocation::Uninitialized:
      case OperandLocation::BaselineFrame:
      case OperandLocation::Constant:
      case OperandLocation::DoubleReg:
        break;
    }
    loc.setUninitialized();
  }
}

void CacheRegisterAllocator::discardStack(MacroAssembler& masm) {
  // This should only be called when we are no longer using the operands,
  // as we're discarding everything from the native stack. Set all operand
  // locations to Uninitialized to catch bugs.
  for (size_t i = 0; i < operandLocations_.length(); i++) {
    operandLocations_[i].setUninitialized();
  }

  if (stackPushed_ > 0) {
    masm.addToStackPtr(Imm32(stackPushed_));
    stackPushed_ = 0;
  }
  freePayloadSlots_.clear();
  freeValueSlots_.clear();
}

Register CacheRegisterAllocator::allocateRegister(MacroAssembler& masm) {
  MOZ_ASSERT(!addedFailurePath_);
  MOZ_ASSERT(!hasAutoScratchFloatRegisterSpill());

  if (availableRegs_.empty()) {
    freeDeadOperandLocations(masm);
  }

  if (availableRegs_.empty()) {
    // Still no registers available, try to spill unused operands to
    // the stack.
    for (size_t i = 0; i < operandLocations_.length(); i++) {
      OperandLocation& loc = operandLocations_[i];
      if (loc.kind() == OperandLocation::PayloadReg) {
        Register reg = loc.payloadReg();
        if (currentOpRegs_.has(reg)) {
          continue;
        }

        spillOperandToStack(masm, &loc);
        availableRegs_.add(reg);
        break;  // We got a register, so break out of the loop.
      }
      if (loc.kind() == OperandLocation::ValueReg) {
        ValueOperand reg = loc.valueReg();
        if (currentOpRegs_.aliases(reg)) {
          continue;
        }

        spillOperandToStack(masm, &loc);
        availableRegs_.add(reg);
        break;  // Break out of the loop.
      }
    }
  }

  if (availableRegs_.empty() && !availableRegsAfterSpill_.empty()) {
    Register reg = availableRegsAfterSpill_.takeAny();
    masm.push(reg);
    stackPushed_ += sizeof(uintptr_t);

    masm.propagateOOM(spilledRegs_.append(SpilledRegister(reg, stackPushed_)));

    availableRegs_.add(reg);
  }

  // At this point, there must be a free register.
  MOZ_RELEASE_ASSERT(!availableRegs_.empty());

  Register reg = availableRegs_.takeAny();
  currentOpRegs_.add(reg);
  return reg;
}

void CacheRegisterAllocator::allocateFixedRegister(MacroAssembler& masm,
                                                   Register reg) {
  MOZ_ASSERT(!addedFailurePath_);
  MOZ_ASSERT(!hasAutoScratchFloatRegisterSpill());

  // Fixed registers should be allocated first, to ensure they're
  // still available.
  MOZ_ASSERT(!currentOpRegs_.has(reg), "Register is in use");

  freeDeadOperandLocations(masm);

  if (availableRegs_.has(reg)) {
    availableRegs_.take(reg);
    currentOpRegs_.add(reg);
    return;
  }

  // Register may be available only after spilling contents.
  if (availableRegsAfterSpill_.has(reg)) {
    availableRegsAfterSpill_.take(reg);
    masm.push(reg);
    stackPushed_ += sizeof(uintptr_t);

    masm.propagateOOM(spilledRegs_.append(SpilledRegister(reg, stackPushed_)));
    currentOpRegs_.add(reg);
    return;
  }

  // The register must be used by some operand. Spill it to the stack.
  for (size_t i = 0; i < operandLocations_.length(); i++) {
    OperandLocation& loc = operandLocations_[i];
    if (loc.kind() == OperandLocation::PayloadReg) {
      if (loc.payloadReg() != reg) {
        continue;
      }

      spillOperandToStackOrRegister(masm, &loc);
      currentOpRegs_.add(reg);
      return;
    }
    if (loc.kind() == OperandLocation::ValueReg) {
      if (!loc.valueReg().aliases(reg)) {
        continue;
      }

      ValueOperand valueReg = loc.valueReg();
      spillOperandToStackOrRegister(masm, &loc);

      availableRegs_.add(valueReg);
      availableRegs_.take(reg);
      currentOpRegs_.add(reg);
      return;
    }
  }

  MOZ_CRASH("Invalid register");
}

void CacheRegisterAllocator::allocateFixedValueRegister(MacroAssembler& masm,
                                                        ValueOperand reg) {
#ifdef JS_NUNBOX32
  allocateFixedRegister(masm, reg.payloadReg());
  allocateFixedRegister(masm, reg.typeReg());
#else
  allocateFixedRegister(masm, reg.valueReg());
#endif
}

#ifdef JS_NUNBOX32
// Possible miscompilation in clang-12 (bug 1689641)
MOZ_NEVER_INLINE
#endif
ValueOperand CacheRegisterAllocator::allocateValueRegister(
    MacroAssembler& masm) {
#ifdef JS_NUNBOX32
  Register reg1 = allocateRegister(masm);
  Register reg2 = allocateRegister(masm);
  return ValueOperand(reg1, reg2);
#else
  Register reg = allocateRegister(masm);
  return ValueOperand(reg);
#endif
}

bool CacheRegisterAllocator::init() {
  if (!origInputLocations_.resize(writer_.numInputOperands())) {
    return false;
  }
  if (!operandLocations_.resize(writer_.numOperandIds())) {
    return false;
  }
  return true;
}

void CacheRegisterAllocator::initAvailableRegsAfterSpill() {
  // Registers not in availableRegs_ and not used by input operands are
  // available after being spilled.
  availableRegsAfterSpill_.set() = GeneralRegisterSet::Intersect(
      GeneralRegisterSet::Not(availableRegs_.set()),
      GeneralRegisterSet::Not(inputRegisterSet()));
}

void CacheRegisterAllocator::fixupAliasedInputs(MacroAssembler& masm) {
  // If IC inputs alias each other, make sure they are stored in different
  // locations so we don't have to deal with this complexity in the rest of
  // the allocator.
  //
  // Note that this can happen in IonMonkey with something like |o.foo = o|
  // or |o[i] = i|.

  size_t numInputs = writer_.numInputOperands();
  MOZ_ASSERT(origInputLocations_.length() == numInputs);

  for (size_t i = 1; i < numInputs; i++) {
    OperandLocation& loc1 = operandLocations_[i];
    if (!loc1.isInRegister()) {
      continue;
    }

    for (size_t j = 0; j < i; j++) {
      OperandLocation& loc2 = operandLocations_[j];
      if (!loc1.aliasesReg(loc2)) {
        continue;
      }

      // loc1 and loc2 alias so we spill one of them. If one is a
      // ValueReg and the other is a PayloadReg, we have to spill the
      // PayloadReg: spilling the ValueReg instead would leave its type
      // register unallocated on 32-bit platforms.
      if (loc1.kind() == OperandLocation::ValueReg) {
        spillOperandToStack(masm, &loc2);
      } else {
        MOZ_ASSERT(loc1.kind() == OperandLocation::PayloadReg);
        spillOperandToStack(masm, &loc1);
        break;  // Spilled loc1, so nothing else will alias it.
      }
    }
  }

#ifdef DEBUG
  assertValidState();
#endif
}

GeneralRegisterSet CacheRegisterAllocator::inputRegisterSet() const {
  MOZ_ASSERT(origInputLocations_.length() == writer_.numInputOperands());

  AllocatableGeneralRegisterSet result;
  for (size_t i = 0; i < writer_.numInputOperands(); i++) {
    const OperandLocation& loc = operandLocations_[i];
    MOZ_ASSERT(loc == origInputLocations_[i]);

    switch (loc.kind()) {
      case OperandLocation::PayloadReg:
        result.addUnchecked(loc.payloadReg());
        continue;
      case OperandLocation::ValueReg:
        result.addUnchecked(loc.valueReg());
        continue;
      case OperandLocation::PayloadStack:
      case OperandLocation::ValueStack:
      case OperandLocation::BaselineFrame:
      case OperandLocation::Constant:
      case OperandLocation::DoubleReg:
        continue;
      case OperandLocation::Uninitialized:
        break;
    }
    MOZ_CRASH("Invalid kind");
  }

  return result.set();
}

JSValueType CacheRegisterAllocator::knownType(ValOperandId val) const {
  const OperandLocation& loc = operandLocations_[val.id()];

  switch (loc.kind()) {
    case OperandLocation::ValueReg:
    case OperandLocation::ValueStack:
    case OperandLocation::BaselineFrame:
      return JSVAL_TYPE_UNKNOWN;

    case OperandLocation::PayloadStack:
    case OperandLocation::PayloadReg:
      return loc.payloadType();

    case OperandLocation::Constant:
      return loc.constant().isDouble() ? JSVAL_TYPE_DOUBLE
                                       : loc.constant().extractNonDoubleType();

    case OperandLocation::DoubleReg:
      return JSVAL_TYPE_DOUBLE;

    case OperandLocation::Uninitialized:
      break;
  }

  MOZ_CRASH("Invalid kind");
}

void CacheRegisterAllocator::initInputLocation(
    size_t i, const TypedOrValueRegister& reg) {
  if (reg.hasValue()) {
    initInputLocation(i, reg.valueReg());
  } else if (reg.typedReg().isFloat()) {
    MOZ_ASSERT(reg.type() == MIRType::Double);
    initInputLocation(i, reg.typedReg().fpu());
  } else {
    initInputLocation(i, reg.typedReg().gpr(),
                      ValueTypeFromMIRType(reg.type()));
  }
}

void CacheRegisterAllocator::initInputLocation(
    size_t i, const ConstantOrRegister& value) {
  if (value.constant()) {
    initInputLocation(i, value.value());
  } else {
    initInputLocation(i, value.reg());
  }
}

void CacheRegisterAllocator::spillOperandToStack(MacroAssembler& masm,
                                                 OperandLocation* loc) {
  MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end());

  if (loc->kind() == OperandLocation::ValueReg) {
    if (!freeValueSlots_.empty()) {
      uint32_t stackPos = freeValueSlots_.popCopy();
      MOZ_ASSERT(stackPos <= stackPushed_);
      masm.storeValue(loc->valueReg(),
                      Address(masm.getStackPointer(), stackPushed_ - stackPos));
      loc->setValueStack(stackPos);
      return;
    }
    stackPushed_ += sizeof(js::Value);
    masm.pushValue(loc->valueReg());
    loc->setValueStack(stackPushed_);
    return;
  }

  MOZ_ASSERT(loc->kind() == OperandLocation::PayloadReg);

  if (!freePayloadSlots_.empty()) {
    uint32_t stackPos = freePayloadSlots_.popCopy();
    MOZ_ASSERT(stackPos <= stackPushed_);
    masm.storePtr(loc->payloadReg(),
                  Address(masm.getStackPointer(), stackPushed_ - stackPos));
    loc->setPayloadStack(stackPos, loc->payloadType());
    return;
  }
  stackPushed_ += sizeof(uintptr_t);
  masm.push(loc->payloadReg());
  loc->setPayloadStack(stackPushed_, loc->payloadType());
}

void CacheRegisterAllocator::spillOperandToStackOrRegister(
    MacroAssembler& masm, OperandLocation* loc) {
  MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end());

  // If enough registers are available, use them.
  if (loc->kind() == OperandLocation::ValueReg) {
    static const size_t BoxPieces = sizeof(Value) / sizeof(uintptr_t);
    if (availableRegs_.set().size() >= BoxPieces) {
      ValueOperand reg = availableRegs_.takeAnyValue();
      masm.moveValue(loc->valueReg(), reg);
      loc->setValueReg(reg);
      return;
    }
  } else {
    MOZ_ASSERT(loc->kind() == OperandLocation::PayloadReg);
    if (!availableRegs_.empty()) {
      Register reg = availableRegs_.takeAny();
      masm.movePtr(loc->payloadReg(), reg);
      loc->setPayloadReg(reg, loc->payloadType());
      return;
    }
  }

  // Not enough registers available, spill to the stack.
  spillOperandToStack(masm, loc);
}

void CacheRegisterAllocator::popPayload(MacroAssembler& masm,
                                        OperandLocation* loc, Register dest) {
  MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end());
  MOZ_ASSERT(stackPushed_ >= sizeof(uintptr_t));

  // The payload is on the stack. If it's on top of the stack we can just
  // pop it, else we emit a load.
  if (loc->payloadStack() == stackPushed_) {
    masm.pop(dest);
    stackPushed_ -= sizeof(uintptr_t);
  } else {
    MOZ_ASSERT(loc->payloadStack() < stackPushed_);
    masm.loadPtr(payloadAddress(masm, loc), dest);
    masm.propagateOOM(freePayloadSlots_.append(loc->payloadStack()));
  }

  loc->setPayloadReg(dest, loc->payloadType());
}

Address CacheRegisterAllocator::valueAddress(MacroAssembler& masm,
                                             const OperandLocation* loc) const {
  MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end());
  return Address(masm.getStackPointer(), stackPushed_ - loc->valueStack());
}

Address CacheRegisterAllocator::payloadAddress(
    MacroAssembler& masm, const OperandLocation* loc) const {
  MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end());
  return Address(masm.getStackPointer(), stackPushed_ - loc->payloadStack());
}

void CacheRegisterAllocator::popValue(MacroAssembler& masm,
                                      OperandLocation* loc, ValueOperand dest) {
  MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end());
  MOZ_ASSERT(stackPushed_ >= sizeof(js::Value));

  // The Value is on the stack. If it's on top of the stack we can just
  // pop it, else we emit a load.
  if (loc->valueStack() == stackPushed_) {
    masm.popValue(dest);
    stackPushed_ -= sizeof(js::Value);
  } else {
    MOZ_ASSERT(loc->valueStack() < stackPushed_);
    masm.loadValue(
        Address(masm.getStackPointer(), stackPushed_ - loc->valueStack()),
        dest);
    masm.propagateOOM(freeValueSlots_.append(loc->valueStack()));
  }

  loc->setValueReg(dest);
}

#ifdef DEBUG
void CacheRegisterAllocator::assertValidState() const {
  // Assert different operands don't have aliasing storage. We depend on this
  // when spilling registers, for instance.

  if (!JitOptions.fullDebugChecks) {
    return;
  }

  for (size_t i = 0; i < operandLocations_.length(); i++) {
    const auto& loc1 = operandLocations_[i];
    if (loc1.isUninitialized()) {
      continue;
    }

    for (size_t j = 0; j < i; j++) {
      const auto& loc2 = operandLocations_[j];
      if (loc2.isUninitialized()) {
        continue;
      }
      MOZ_ASSERT(!loc1.aliasesReg(loc2));
    }
  }
}
#endif

bool OperandLocation::aliasesReg(const OperandLocation& other) const {
  MOZ_ASSERT(&other != this);

  switch (other.kind_) {
    case PayloadReg:
      return aliasesReg(other.payloadReg());
    case ValueReg:
      return aliasesReg(other.valueReg());
    case PayloadStack:
    case ValueStack:
    case BaselineFrame:
    case Constant:
    case DoubleReg:
      return false;
    case Uninitialized:
      break;
  }

  MOZ_CRASH("Invalid kind");
}

void CacheRegisterAllocator::restoreInputState(MacroAssembler& masm,
                                               bool shouldDiscardStack) {
  size_t numInputOperands = origInputLocations_.length();
  MOZ_ASSERT(writer_.numInputOperands() == numInputOperands);

  for (size_t j = 0; j < numInputOperands; j++) {
    const OperandLocation& dest = origInputLocations_[j];
    OperandLocation& cur = operandLocations_[j];
    if (dest == cur) {
      continue;
    }

    auto autoAssign = mozilla::MakeScopeExit([&] { cur = dest; });

    // We have a cycle if a destination register will be used later
    // as source register. If that happens, just push the current value
    // on the stack and later get it from there.
    for (size_t k = j + 1; k < numInputOperands; k++) {
      OperandLocation& laterSource = operandLocations_[k];
      if (dest.aliasesReg(laterSource)) {
        spillOperandToStack(masm, &laterSource);
      }
    }

    if (dest.kind() == OperandLocation::ValueReg) {
      // We have to restore a Value register.
      switch (cur.kind()) {
        case OperandLocation::ValueReg:
          masm.moveValue(cur.valueReg(), dest.valueReg());
          continue;
        case OperandLocation::PayloadReg:
          masm.tagValue(cur.payloadType(), cur.payloadReg(), dest.valueReg());
          continue;
        case OperandLocation::PayloadStack: {
          Register scratch = dest.valueReg().scratchReg();
          popPayload(masm, &cur, scratch);
          masm.tagValue(cur.payloadType(), scratch, dest.valueReg());
          continue;
        }
        case OperandLocation::ValueStack:
          popValue(masm, &cur, dest.valueReg());
          continue;
        case OperandLocation::DoubleReg:
          masm.boxDouble(cur.doubleReg(), dest.valueReg(), cur.doubleReg());
          continue;
        case OperandLocation::Constant:
        case OperandLocation::BaselineFrame:
        case OperandLocation::Uninitialized:
          break;
      }
    } else if (dest.kind() == OperandLocation::PayloadReg) {
      // We have to restore a payload register.
      switch (cur.kind()) {
        case OperandLocation::ValueReg:
          MOZ_ASSERT(dest.payloadType() != JSVAL_TYPE_DOUBLE);
          masm.unboxNonDouble(cur.valueReg(), dest.payloadReg(),
                              dest.payloadType());
          continue;
        case OperandLocation::PayloadReg:
          MOZ_ASSERT(cur.payloadType() == dest.payloadType());
          masm.mov(cur.payloadReg(), dest.payloadReg());
          continue;
        case OperandLocation::PayloadStack: {
          MOZ_ASSERT(cur.payloadType() == dest.payloadType());
          popPayload(masm, &cur, dest.payloadReg());
          continue;
        }
        case OperandLocation::ValueStack:
          MOZ_ASSERT(stackPushed_ >= sizeof(js::Value));
          MOZ_ASSERT(cur.valueStack() <= stackPushed_);
          MOZ_ASSERT(dest.payloadType() != JSVAL_TYPE_DOUBLE);
          masm.unboxNonDouble(
              Address(masm.getStackPointer(), stackPushed_ - cur.valueStack()),
              dest.payloadReg(), dest.payloadType());
          continue;
        case OperandLocation::Constant:
        case OperandLocation::BaselineFrame:
        case OperandLocation::DoubleReg:
        case OperandLocation::Uninitialized:
          break;
      }
    } else if (dest.kind() == OperandLocation::Constant ||
               dest.kind() == OperandLocation::BaselineFrame ||
               dest.kind() == OperandLocation::DoubleReg) {
      // Nothing to do.
      continue;
    }

    MOZ_CRASH("Invalid kind");
  }

  for (const SpilledRegister& spill : spilledRegs_) {
    MOZ_ASSERT(stackPushed_ >= sizeof(uintptr_t));

    if (spill.stackPushed == stackPushed_) {
      masm.pop(spill.reg);
      stackPushed_ -= sizeof(uintptr_t);
    } else {
      MOZ_ASSERT(spill.stackPushed < stackPushed_);
      masm.loadPtr(
          Address(masm.getStackPointer(), stackPushed_ - spill.stackPushed),
          spill.reg);
    }
  }

  if (shouldDiscardStack) {
    discardStack(masm);
  }
}

size_t CacheIRStubInfo::stubDataSize() const {
  size_t field = 0;
  size_t size = 0;
  while (true) {
    StubField::Type type = fieldType(field++);
    if (type == StubField::Type::Limit) {
      return size;
    }
    size += StubField::sizeInBytes(type);
  }
}

template <typename T>
static GCPtr<T>* AsGCPtr(void* ptr) {
  return static_cast<GCPtr<T>*>(ptr);
}

void CacheIRStubInfo::replaceStubRawWord(uint8_t* stubData, uint32_t offset,
                                         uintptr_t oldWord,
                                         uintptr_t newWord) const {
  MOZ_ASSERT(uintptr_t(stubData + offset) % sizeof(uintptr_t) == 0);
  uintptr_t* addr = reinterpret_cast<uintptr_t*>(stubData + offset);
  MOZ_ASSERT(*addr == oldWord);
  *addr = newWord;
}

void CacheIRStubInfo::replaceStubRawValueBits(uint8_t* stubData,
                                              uint32_t offset, uint64_t oldBits,
                                              uint64_t newBits) const {
  MOZ_ASSERT(uint64_t(stubData + offset) % sizeof(uint64_t) == 0);
  uint64_t* addr = reinterpret_cast<uint64_t*>(stubData + offset);
  MOZ_ASSERT(*addr == oldBits);
  *addr = newBits;
}

template <class Stub, StubField::Type type>
typename MapStubFieldToType<type>::WrappedType& CacheIRStubInfo::getStubField(
    Stub* stub, uint32_t offset) const {
  uint8_t* stubData = (uint8_t*)stub + stubDataOffset_;
  MOZ_ASSERT(uintptr_t(stubData + offset) % sizeof(uintptr_t) == 0);

  using WrappedType = typename MapStubFieldToType<type>::WrappedType;
  return *reinterpret_cast<WrappedType*>(stubData + offset);
}

#define INSTANTIATE_GET_STUB_FIELD(Type)                                   \
  template typename MapStubFieldToType<Type>::WrappedType&                 \
  CacheIRStubInfo::getStubField<ICCacheIRStub, Type>(ICCacheIRStub * stub, \
                                                     uint32_t offset) const;
INSTANTIATE_GET_STUB_FIELD(StubField::Type::Shape)
INSTANTIATE_GET_STUB_FIELD(StubField::Type::WeakShape)
INSTANTIATE_GET_STUB_FIELD(StubField::Type::WeakGetterSetter)
INSTANTIATE_GET_STUB_FIELD(StubField::Type::JSObject)
INSTANTIATE_GET_STUB_FIELD(StubField::Type::WeakObject)
INSTANTIATE_GET_STUB_FIELD(StubField::Type::Symbol)
INSTANTIATE_GET_STUB_FIELD(StubField::Type::String)
INSTANTIATE_GET_STUB_FIELD(StubField::Type::WeakBaseScript)
INSTANTIATE_GET_STUB_FIELD(StubField::Type::Value)
INSTANTIATE_GET_STUB_FIELD(StubField::Type::Id)
#undef INSTANTIATE_GET_STUB_FIELD

template <class Stub, class T>
T* CacheIRStubInfo::getPtrStubField(Stub* stub, uint32_t offset) const {
  uint8_t* stubData = (uint8_t*)stub + stubDataOffset_;
  MOZ_ASSERT(uintptr_t(stubData + offset) % sizeof(uintptr_t) == 0);

  return *reinterpret_cast<T**>(stubData + offset);
}

template gc::AllocSite* CacheIRStubInfo::getPtrStubField(ICCacheIRStub* stub,
                                                         uint32_t offset) const;

template <StubField::Type type, typename V>
static void InitWrappedPtr(void* ptr, V val) {
  using RawType = typename MapStubFieldToType<type>::RawType;
  using WrappedType = typename MapStubFieldToType<type>::WrappedType;
  auto* wrapped = static_cast<WrappedType*>(ptr);
  new (wrapped) WrappedType(mozilla::BitwiseCast<RawType>(val));
}

static void InitWordStubField(StubField::Type type, void* dest,
                              uintptr_t value) {
  MOZ_ASSERT(StubField::sizeIsWord(type));
  MOZ_ASSERT((uintptr_t(dest) % sizeof(uintptr_t)) == 0,
             "Unaligned stub field");

  switch (type) {
    case StubField::Type::RawInt32:
    case StubField::Type::RawPointer:
    case StubField::Type::AllocSite:
      *static_cast<uintptr_t*>(dest) = value;
      break;
    case StubField::Type::Shape:
      InitWrappedPtr<StubField::Type::Shape>(dest, value);
      break;
    case StubField::Type::WeakShape:
      // No read barrier required to copy weak pointer.
      InitWrappedPtr<StubField::Type::WeakShape>(dest, value);
      break;
    case StubField::Type::WeakGetterSetter:
      // No read barrier required to copy weak pointer.
      InitWrappedPtr<StubField::Type::WeakGetterSetter>(dest, value);
      break;
    case StubField::Type::JSObject:
      InitWrappedPtr<StubField::Type::JSObject>(dest, value);
      break;
    case StubField::Type::WeakObject:
      // No read barrier required to copy weak pointer.
      InitWrappedPtr<StubField::Type::WeakObject>(dest, value);
      break;
    case StubField::Type::Symbol:
      InitWrappedPtr<StubField::Type::Symbol>(dest, value);
      break;
    case StubField::Type::String:
      InitWrappedPtr<StubField::Type::String>(dest, value);
      break;
    case StubField::Type::WeakBaseScript:
      // No read barrier required to copy weak pointer.
      InitWrappedPtr<StubField::Type::WeakBaseScript>(dest, value);
      break;
    case StubField::Type::JitCode:
      InitWrappedPtr<StubField::Type::JitCode>(dest, value);
      break;
    case StubField::Type::Id:
      AsGCPtr<jsid>(dest)->init(jsid::fromRawBits(value));
      break;
    case StubField::Type::RawInt64:
    case StubField::Type::Double:
    case StubField::Type::Value:
    case StubField::Type::Limit:
      MOZ_CRASH("Invalid type");
  }
}

static void InitInt64StubField(StubField::Type type, void* dest,
                               uint64_t value) {
  MOZ_ASSERT(StubField::sizeIsInt64(type));
  MOZ_ASSERT((uintptr_t(dest) % sizeof(uint64_t)) == 0, "Unaligned stub field");

  switch (type) {
    case StubField::Type::RawInt64:
    case StubField::Type::Double:
      *static_cast<uint64_t*>(dest) = value;
      break;
    case StubField::Type::Value:
      AsGCPtr<Value>(dest)->init(Value::fromRawBits(value));
      break;
    case StubField::Type::RawInt32:
    case StubField::Type::RawPointer:
    case StubField::Type::AllocSite:
    case StubField::Type::Shape:
    case StubField::Type::WeakShape:
    case StubField::Type::WeakGetterSetter:
    case StubField::Type::JSObject:
    case StubField::Type::WeakObject:
    case StubField::Type::Symbol:
    case StubField::Type::String:
    case StubField::Type::WeakBaseScript:
    case StubField::Type::JitCode:
    case StubField::Type::Id:
    case StubField::Type::Limit:
      MOZ_CRASH("Invalid type");
  }
}

void CacheIRWriter::copyStubData(uint8_t* dest) const {
  MOZ_ASSERT(!failed());

  for (const StubField& field : stubFields_) {
    if (field.sizeIsWord()) {
      InitWordStubField(field.type(), dest, field.asWord());
      dest += sizeof(uintptr_t);
    } else {
      InitInt64StubField(field.type(), dest, field.asInt64());
      dest += sizeof(uint64_t);
    }
  }
}

ICCacheIRStub* ICCacheIRStub::clone(JSRuntime* rt, ICStubSpace& newSpace) {
  const CacheIRStubInfo* info = stubInfo();
  MOZ_ASSERT(info->makesGCCalls());

  size_t bytesNeeded = info->stubDataOffset() + info->stubDataSize();

  AutoEnterOOMUnsafeRegion oomUnsafe;
  void* newStubMem = newSpace.alloc(bytesNeeded);
  if (!newStubMem) {
    oomUnsafe.crash("ICCacheIRStub::clone");
  }

  ICCacheIRStub* newStub = new (newStubMem) ICCacheIRStub(*this);

  const uint8_t* src = this->stubDataStart();
  uint8_t* dest = newStub->stubDataStart();

  // Because this can be called during sweeping when discarding JIT code, we
  // have to lock the store buffer
  gc::AutoLockStoreBuffer lock(rt);

  uint32_t field = 0;
  while (true) {
    StubField::Type type = info->fieldType(field);
    if (type == StubField::Type::Limit) {
      break;  // Done.
    }

    if (StubField::sizeIsWord(type)) {
      const uintptr_t* srcField = reinterpret_cast<const uintptr_t*>(src);
      InitWordStubField(type, dest, *srcField);
      src += sizeof(uintptr_t);
      dest += sizeof(uintptr_t);
    } else {
      const uint64_t* srcField = reinterpret_cast<const uint64_t*>(src);
      InitInt64StubField(type, dest, *srcField);
      src += sizeof(uint64_t);
      dest += sizeof(uint64_t);
    }

    field++;
  }

  return newStub;
}

template <typename T>
static inline bool ShouldTraceWeakEdgeInStub(JSTracer* trc) {
  if constexpr (std::is_same_v<T, IonICStub>) {
    // 'Weak' edges are traced strongly in IonICs.
    return true;
  } else {
    static_assert(std::is_same_v<T, ICCacheIRStub>);
    return trc->traceWeakEdges();
  }
}

template <typename T>
void jit::TraceCacheIRStub(JSTracer* trc, T* stub,
                           const CacheIRStubInfo* stubInfo) {
  using Type = StubField::Type;

  uint32_t field = 0;
  size_t offset = 0;
  while (true) {
    Type fieldType = stubInfo->fieldType(field);
    switch (fieldType) {
      case Type::RawInt32:
      case Type::RawPointer:
      case Type::RawInt64:
      case Type::Double:
        break;
      case Type::Shape: {
        // For CCW IC stubs, we can store same-zone but cross-compartment
        // shapes. Use TraceSameZoneCrossCompartmentEdge to not assert in the
        // GC. Note: CacheIRWriter::writeShapeField asserts we never store
        // cross-zone shapes.
        GCPtr<Shape*>& shapeField =
            stubInfo->getStubField<T, Type::Shape>(stub, offset);
        TraceSameZoneCrossCompartmentEdge(trc, &shapeField, "cacheir-shape");
        break;
      }
      case Type::WeakShape:
        if (ShouldTraceWeakEdgeInStub<T>(trc)) {
          WeakHeapPtr<Shape*>& shapeField =
              stubInfo->getStubField<T, Type::WeakShape>(stub, offset);
          if (shapeField) {
            TraceSameZoneCrossCompartmentEdge(trc, &shapeField,
                                              "cacheir-weak-shape");
          }
        }
        break;
      case Type::WeakGetterSetter:
        if (ShouldTraceWeakEdgeInStub<T>(trc)) {
          TraceNullableEdge(
              trc,
              &stubInfo->getStubField<T, Type::WeakGetterSetter>(stub, offset),
              "cacheir-weak-getter-setter");
        }
        break;
      case Type::JSObject: {
        TraceEdge(trc, &stubInfo->getStubField<T, Type::JSObject>(stub, offset),
                  "cacheir-object");
        break;
      }
      case Type::WeakObject:
        if (ShouldTraceWeakEdgeInStub<T>(trc)) {
          TraceNullableEdge(
              trc, &stubInfo->getStubField<T, Type::WeakObject>(stub, offset),
              "cacheir-weak-object");
        }
        break;
      case Type::Symbol:
        TraceEdge(trc, &stubInfo->getStubField<T, Type::Symbol>(stub, offset),
                  "cacheir-symbol");
        break;
      case Type::String:
        TraceEdge(trc, &stubInfo->getStubField<T, Type::String>(stub, offset),
                  "cacheir-string");
        break;
      case Type::WeakBaseScript:
        if (ShouldTraceWeakEdgeInStub<T>(trc)) {
          TraceNullableEdge(
              trc,
              &stubInfo->getStubField<T, Type::WeakBaseScript>(stub, offset),
              "cacheir-weak-script");
        }
        break;
      case Type::JitCode:
        TraceEdge(trc, &stubInfo->getStubField<T, Type::JitCode>(stub, offset),
                  "cacheir-jitcode");
        break;
      case Type::Id:
        TraceEdge(trc, &stubInfo->getStubField<T, Type::Id>(stub, offset),
                  "cacheir-id");
        break;
      case Type::Value:
        TraceEdge(trc, &stubInfo->getStubField<T, Type::Value>(stub, offset),
                  "cacheir-value");
        break;
      case Type::AllocSite: {
        gc::AllocSite* site =
            stubInfo->getPtrStubField<T, gc::AllocSite>(stub, offset);
        site->trace(trc);
        break;
      }
      case Type::Limit:
        return;  // Done.
    }
    field++;
    offset += StubField::sizeInBytes(fieldType);
  }
}

template void jit::TraceCacheIRStub(JSTracer* trc, ICCacheIRStub* stub,
                                    const CacheIRStubInfo* stubInfo);

template void jit::TraceCacheIRStub(JSTracer* trc, IonICStub* stub,
                                    const CacheIRStubInfo* stubInfo);

template <typename T>
bool jit::TraceWeakCacheIRStub(JSTracer* trc, T* stub,
                               const CacheIRStubInfo* stubInfo) {
  using Type = StubField::Type;

  // Trace all fields before returning because this stub can be traced again
  // later through TraceBaselineStubFrame.
  bool isDead = false;

  uint32_t field = 0;
  size_t offset = 0;
  while (true) {
    Type fieldType = stubInfo->fieldType(field);
    switch (fieldType) {
      case Type::WeakShape: {
        WeakHeapPtr<Shape*>& shapeField =
            stubInfo->getStubField<T, Type::WeakShape>(stub, offset);
        auto r = TraceWeakEdge(trc, &shapeField, "cacheir-weak-shape");
        if (r.isDead()) {
          isDead = true;
        }
        break;
      }
      case Type::WeakObject: {
        WeakHeapPtr<JSObject*>& objectField =
            stubInfo->getStubField<T, Type::WeakObject>(stub, offset);
        auto r = TraceWeakEdge(trc, &objectField, "cacheir-weak-object");
        if (r.isDead()) {
          isDead = true;
        }
        break;
      }
      case Type::WeakBaseScript: {
        WeakHeapPtr<BaseScript*>& scriptField =
            stubInfo->getStubField<T, Type::WeakBaseScript>(stub, offset);
        auto r = TraceWeakEdge(trc, &scriptField, "cacheir-weak-script");
        if (r.isDead()) {
          isDead = true;
        }
        break;
      }
      case Type::WeakGetterSetter: {
        WeakHeapPtr<GetterSetter*>& getterSetterField =
            stubInfo->getStubField<T, Type::WeakGetterSetter>(stub, offset);
        auto r = TraceWeakEdge(trc, &getterSetterField,
                               "cacheir-weak-getter-setter");
        if (r.isDead()) {
          isDead = true;
        }
        break;
      }
      case Type::Limit:
        // Done.
        return !isDead;
      case Type::RawInt32:
      case Type::RawPointer:
      case Type::Shape:
      case Type::JSObject:
      case Type::Symbol:
      case Type::String:
      case Type::JitCode:
      case Type::Id:
      case Type::AllocSite:
      case Type::RawInt64:
      case Type::Value:
      case Type::Double:
        break;  // Skip non-weak fields.
    }
    field++;
    offset += StubField::sizeInBytes(fieldType);
  }
}

template bool jit::TraceWeakCacheIRStub(JSTracer* trc, ICCacheIRStub* stub,
                                        const CacheIRStubInfo* stubInfo);

template bool jit::TraceWeakCacheIRStub(JSTracer* trc, IonICStub* stub,
                                        const CacheIRStubInfo* stubInfo);

bool CacheIRWriter::stubDataEquals(const uint8_t* stubData) const {
  MOZ_ASSERT(!failed());

  const uintptr_t* stubDataWords = reinterpret_cast<const uintptr_t*>(stubData);

  for (const StubField& field : stubFields_) {
    if (field.sizeIsWord()) {
      if (field.asWord() != *stubDataWords) {
        return false;
      }
      stubDataWords++;
      continue;
    }

    if (field.asInt64() != *reinterpret_cast<const uint64_t*>(stubDataWords)) {
      return false;
    }
    stubDataWords += sizeof(uint64_t) / sizeof(uintptr_t);
  }

  return true;
}

bool CacheIRWriter::stubDataEqualsIgnoring(const uint8_t* stubData,
                                           uint32_t ignoreOffset) const {
  MOZ_ASSERT(!failed());

  uint32_t offset = 0;
  for (const StubField& field : stubFields_) {
    if (offset != ignoreOffset) {
      if (field.sizeIsWord()) {
        uintptr_t raw = *reinterpret_cast<const uintptr_t*>(stubData + offset);
        if (field.asWord() != raw) {
          return false;
        }
      } else {
        uint64_t raw = *reinterpret_cast<const uint64_t*>(stubData + offset);
        if (field.asInt64() != raw) {
          return false;
        }
      }
    }
    offset += StubField::sizeInBytes(field.type());
  }

  return true;
}

HashNumber CacheIRStubKey::hash(const CacheIRStubKey::Lookup& l) {
  HashNumber hash = mozilla::HashBytes(l.code, l.length);
  hash = mozilla::AddToHash(hash, uint32_t(l.kind));
  hash = mozilla::AddToHash(hash, uint32_t(l.engine));
  return hash;
}

bool CacheIRStubKey::match(const CacheIRStubKey& entry,
                           const CacheIRStubKey::Lookup& l) {
  if (entry.stubInfo->kind() != l.kind) {
    return false;
  }

  if (entry.stubInfo->engine() != l.engine) {
    return false;
  }

  if (entry.stubInfo->codeLength() != l.length) {
    return false;
  }

  if (!mozilla::ArrayEqual(entry.stubInfo->code(), l.code, l.length)) {
    return false;
  }

  return true;
}

CacheIRReader::CacheIRReader(const CacheIRStubInfo* stubInfo)
    : CacheIRReader(stubInfo->code(),
                    stubInfo->code() + stubInfo->codeLength()) {}

CacheIRStubInfo* CacheIRStubInfo::New(CacheKind kind, ICStubEngine engine,
                                      bool makesGCCalls,
                                      uint32_t stubDataOffset,
                                      const CacheIRWriter& writer) {
  size_t numStubFields = writer.numStubFields();
  size_t bytesNeeded =
      sizeof(CacheIRStubInfo) + writer.codeLength() +
      (numStubFields + 1);  // +1 for the GCType::Limit terminator.
  uint8_t* p = js_pod_malloc<uint8_t>(bytesNeeded);
  if (!p) {
    return nullptr;
  }

  // Copy the CacheIR code.
  uint8_t* codeStart = p + sizeof(CacheIRStubInfo);
  mozilla::PodCopy(codeStart, writer.codeStart(), writer.codeLength());

  static_assert(sizeof(StubField::Type) == sizeof(uint8_t),
                "StubField::Type must fit in uint8_t");

  // Copy the stub field types.
  uint8_t* fieldTypes = codeStart + writer.codeLength();
  for (size_t i = 0; i < numStubFields; i++) {
    fieldTypes[i] = uint8_t(writer.stubFieldType(i));
  }
  fieldTypes[numStubFields] = uint8_t(StubField::Type::Limit);

  return new (p) CacheIRStubInfo(kind, engine, makesGCCalls, stubDataOffset,
                                 writer.codeLength());
}

bool OperandLocation::operator==(const OperandLocation& other) const {
  if (kind_ != other.kind_) {
    return false;
  }

  switch (kind()) {
    case Uninitialized:
      return true;
    case PayloadReg:
      return payloadReg() == other.payloadReg() &&
             payloadType() == other.payloadType();
    case ValueReg:
      return valueReg() == other.valueReg();
    case PayloadStack:
      return payloadStack() == other.payloadStack() &&
             payloadType() == other.payloadType();
    case ValueStack:
      return valueStack() == other.valueStack();
    case BaselineFrame:
      return baselineFrameSlot() == other.baselineFrameSlot();
    case Constant:
      return constant() == other.constant();
    case DoubleReg:
      return doubleReg() == other.doubleReg();
  }

  MOZ_CRASH("Invalid OperandLocation kind");
}

AutoOutputRegister::AutoOutputRegister(CacheIRCompiler& compiler)
    : output_(compiler.outputUnchecked_.ref()), alloc_(compiler.allocator) {
  if (output_.hasValue()) {
    alloc_.allocateFixedValueRegister(compiler.masm, output_.valueReg());
  } else if (!output_.typedReg().isFloat()) {
    alloc_.allocateFixedRegister(compiler.masm, output_.typedReg().gpr());
  }
}

AutoOutputRegister::~AutoOutputRegister() {
  if (output_.hasValue()) {
    alloc_.releaseValueRegister(output_.valueReg());
  } else if (!output_.typedReg().isFloat()) {
    alloc_.releaseRegister(output_.typedReg().gpr());
  }
}

bool FailurePath::canShareFailurePath(const FailurePath& other) const {
  if (stackPushed_ != other.stackPushed_) {
    return false;
  }

  if (spilledRegs_.length() != other.spilledRegs_.length()) {
    return false;
  }

  for (size_t i = 0; i < spilledRegs_.length(); i++) {
    if (spilledRegs_[i] != other.spilledRegs_[i]) {
      return false;
    }
  }

  MOZ_ASSERT(inputs_.length() == other.inputs_.length());

  for (size_t i = 0; i < inputs_.length(); i++) {
    if (inputs_[i] != other.inputs_[i]) {
      return false;
    }
  }
  return true;
}

bool CacheIRCompiler::addFailurePath(FailurePath** failure) {
#ifdef DEBUG
  allocator.setAddedFailurePath();
#endif
  MOZ_ASSERT(!allocator.hasAutoScratchFloatRegisterSpill());

  FailurePath newFailure;
  for (size_t i = 0; i < writer_.numInputOperands(); i++) {
    if (!newFailure.appendInput(allocator.operandLocation(i))) {
      return false;
    }
  }
  if (!newFailure.setSpilledRegs(allocator.spilledRegs())) {
    return false;
  }
  newFailure.setStackPushed(allocator.stackPushed());

  // Reuse the previous failure path if the current one is the same, to
  // avoid emitting duplicate code.
  if (failurePaths.length() > 0 &&
      failurePaths.back().canShareFailurePath(newFailure)) {
    *failure = &failurePaths.back();
    return true;
  }

  if (!failurePaths.append(std::move(newFailure))) {
    return false;
  }

  *failure = &failurePaths.back();
  return true;
}

bool CacheIRCompiler::emitFailurePath(size_t index) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  FailurePath& failure = failurePaths[index];

  allocator.setStackPushed(failure.stackPushed());

  for (size_t i = 0; i < writer_.numInputOperands(); i++) {
    allocator.setOperandLocation(i, failure.input(i));
  }

  if (!allocator.setSpilledRegs(failure.spilledRegs())) {
    return false;
  }

  masm.bind(failure.label());
  allocator.restoreInputState(masm);
  return true;
}

bool CacheIRCompiler::emitGuardIsNumber(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  JSValueType knownType = allocator.knownType(inputId);

  // Doubles and ints are numbers!
  if (knownType == JSVAL_TYPE_DOUBLE || knownType == JSVAL_TYPE_INT32) {
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);
  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchTestNumber(Assembler::NotEqual, input, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardToObject(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  if (allocator.knownType(inputId) == JSVAL_TYPE_OBJECT) {
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);
  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }
  masm.branchTestObject(Assembler::NotEqual, input, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardIsNullOrUndefined(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  JSValueType knownType = allocator.knownType(inputId);
  if (knownType == JSVAL_TYPE_UNDEFINED || knownType == JSVAL_TYPE_NULL) {
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);
  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label success;
  masm.branchTestNull(Assembler::Equal, input, &success);
  masm.branchTestUndefined(Assembler::NotEqual, input, failure->label());

  masm.bind(&success);
  return true;
}

bool CacheIRCompiler::emitGuardIsNull(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  JSValueType knownType = allocator.knownType(inputId);
  if (knownType == JSVAL_TYPE_NULL) {
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);
  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchTestNull(Assembler::NotEqual, input, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardIsUndefined(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  JSValueType knownType = allocator.knownType(inputId);
  if (knownType == JSVAL_TYPE_UNDEFINED) {
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);
  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchTestUndefined(Assembler::NotEqual, input, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardIsNotUninitializedLexical(ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  ValueOperand val = allocator.useValueRegister(masm, valId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchTestMagicValue(Assembler::Equal, val, JS_UNINITIALIZED_LEXICAL,
                            failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardBooleanToInt32(ValOperandId inputId,
                                              Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register output = allocator.defineRegister(masm, resultId);

  if (allocator.knownType(inputId) == JSVAL_TYPE_BOOLEAN) {
    Register input =
        allocator.useRegister(masm, BooleanOperandId(inputId.id()));
    masm.move32(input, output);
    return true;
  }
  ValueOperand input = allocator.useValueRegister(masm, inputId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.fallibleUnboxBoolean(input, output, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardToString(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  if (allocator.knownType(inputId) == JSVAL_TYPE_STRING) {
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);
  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }
  masm.branchTestString(Assembler::NotEqual, input, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardToSymbol(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  if (allocator.knownType(inputId) == JSVAL_TYPE_SYMBOL) {
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);
  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }
  masm.branchTestSymbol(Assembler::NotEqual, input, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardToBigInt(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  if (allocator.knownType(inputId) == JSVAL_TYPE_BIGINT) {
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);
  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }
  masm.branchTestBigInt(Assembler::NotEqual, input, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardToBoolean(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  if (allocator.knownType(inputId) == JSVAL_TYPE_BOOLEAN) {
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);
  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }
  masm.branchTestBoolean(Assembler::NotEqual, input, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardToInt32(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  if (allocator.knownType(inputId) == JSVAL_TYPE_INT32) {
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchTestInt32(Assembler::NotEqual, input, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardToNonGCThing(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  ValueOperand input = allocator.useValueRegister(masm, inputId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchTestGCThing(Assembler::Equal, input, failure->label());
  return true;
}

// Infallible |emitDouble| emitters can use this implementation to avoid
// generating extra clean-up instructions to restore the scratch float register.
// To select this function simply omit the |Label* fail| parameter for the
// emitter lambda function.
template <typename EmitDouble>
static std::enable_if_t<mozilla::FunctionTypeTraits<EmitDouble>::arity == 1,
                        void>
EmitGuardDouble(CacheIRCompiler* compiler, MacroAssembler& masm,
                ValueOperand input, FailurePath* failure,
                EmitDouble emitDouble) {
  AutoScratchFloatRegister floatReg(compiler);

  masm.unboxDouble(input, floatReg);
  emitDouble(floatReg.get());
}

template <typename EmitDouble>
static std::enable_if_t<mozilla::FunctionTypeTraits<EmitDouble>::arity == 2,
                        void>
EmitGuardDouble(CacheIRCompiler* compiler, MacroAssembler& masm,
                ValueOperand input, FailurePath* failure,
                EmitDouble emitDouble) {
  AutoScratchFloatRegister floatReg(compiler, failure);

  masm.unboxDouble(input, floatReg);
  emitDouble(floatReg.get(), floatReg.failure());
}

template <typename EmitInt32, typename EmitDouble>
static void EmitGuardInt32OrDouble(CacheIRCompiler* compiler,
                                   MacroAssembler& masm, ValueOperand input,
                                   Register output, FailurePath* failure,
                                   EmitInt32 emitInt32, EmitDouble emitDouble) {
  Label done;

  {
    ScratchTagScope tag(masm, input);
    masm.splitTagForTest(input, tag);

    Label notInt32;
    masm.branchTestInt32(Assembler::NotEqual, tag, ¬Int32);
    {
      ScratchTagScopeRelease _(&tag);

      masm.unboxInt32(input, output);
      emitInt32();

      masm.jump(&done);
    }
    masm.bind(¬Int32);

    masm.branchTestDouble(Assembler::NotEqual, tag, failure->label());
    {
      ScratchTagScopeRelease _(&tag);

      EmitGuardDouble(compiler, masm, input, failure, emitDouble);
    }
  }

  masm.bind(&done);
}

bool CacheIRCompiler::emitGuardToInt32Index(ValOperandId inputId,
                                            Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register output = allocator.defineRegister(masm, resultId);

  if (allocator.knownType(inputId) == JSVAL_TYPE_INT32) {
    Register input = allocator.useRegister(masm, Int32OperandId(inputId.id()));
    masm.move32(input, output);
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  EmitGuardInt32OrDouble(
      this, masm, input, output, failure,
      []() {
        // No-op if the value is already an int32.
      },
      [&](FloatRegister floatReg, Label* fail) {
        // ToPropertyKey(-0.0) is "0", so we can truncate -0.0 to 0 here.
        masm.convertDoubleToInt32(floatReg, output, fail, false);
      });

  return true;
}

bool CacheIRCompiler::emitInt32ToIntPtr(Int32OperandId inputId,
                                        IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register input = allocator.useRegister(masm, inputId);
  Register output = allocator.defineRegister(masm, resultId);

  masm.move32SignExtendToPtr(input, output);
  return true;
}

bool CacheIRCompiler::emitGuardNumberToIntPtrIndex(NumberOperandId inputId,
                                                   bool supportOOB,
                                                   IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register output = allocator.defineRegister(masm, resultId);

  FailurePath* failure = nullptr;
  if (!supportOOB) {
    if (!addFailurePath(&failure)) {
      return false;
    }
  }

  AutoScratchFloatRegister floatReg(this, failure);
  allocator.ensureDoubleRegister(masm, inputId, floatReg);

  // ToPropertyKey(-0.0) is "0", so we can truncate -0.0 to 0 here.
  if (supportOOB) {
    Label done, fail;
    masm.convertDoubleToPtr(floatReg, output, &fail, false);
    masm.jump(&done);

    // Substitute the invalid index with an arbitrary out-of-bounds index.
    masm.bind(&fail);
    masm.movePtr(ImmWord(-1), output);

    masm.bind(&done);
  } else {
    masm.convertDoubleToPtr(floatReg, output, floatReg.failure(), false);
  }

  return true;
}

static void TruncateDoubleModUint32(MacroAssembler& masm,
                                    FloatRegister floatReg, Register result,
                                    const LiveRegisterSet& liveVolatileRegs) {
  Label truncateABICall;
  masm.branchTruncateDoubleMaybeModUint32(floatReg, result, &truncateABICall);

  if (truncateABICall.used()) {
    Label done;
    masm.jump(&done);

    masm.bind(&truncateABICall);
    LiveRegisterSet save = liveVolatileRegs;
    save.takeUnchecked(floatReg);
    // Bug 1451976
    save.takeUnchecked(floatReg.asSingle());
    masm.PushRegsInMask(save);

    using Fn = int32_t (*)(double);
    masm.setupUnalignedABICall(result);
    masm.passABIArg(floatReg, ABIType::Float64);
    masm.callWithABI<Fn, JS::ToInt32>(ABIType::General,
                                      CheckUnsafeCallWithABI::DontCheckOther);
    masm.storeCallInt32Result(result);

    LiveRegisterSet ignore;
    ignore.add(result);
    masm.PopRegsInMaskIgnore(save, ignore);

    masm.bind(&done);
  }
}

bool CacheIRCompiler::emitGuardToInt32ModUint32(ValOperandId inputId,
                                                Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register output = allocator.defineRegister(masm, resultId);

  if (allocator.knownType(inputId) == JSVAL_TYPE_INT32) {
    ConstantOrRegister input = allocator.useConstantOrRegister(masm, inputId);
    if (input.constant()) {
      masm.move32(Imm32(input.value().toInt32()), output);
    } else {
      MOZ_ASSERT(input.reg().type() == MIRType::Int32);
      masm.move32(input.reg().typedReg().gpr(), output);
    }
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  EmitGuardInt32OrDouble(
      this, masm, input, output, failure,
      []() {
        // No-op if the value is already an int32.
      },
      [&](FloatRegister floatReg) {
        TruncateDoubleModUint32(masm, floatReg, output, liveVolatileRegs());
      });

  return true;
}

bool CacheIRCompiler::emitGuardToUint8Clamped(ValOperandId inputId,
                                              Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register output = allocator.defineRegister(masm, resultId);

  if (allocator.knownType(inputId) == JSVAL_TYPE_INT32) {
    ConstantOrRegister input = allocator.useConstantOrRegister(masm, inputId);
    if (input.constant()) {
      masm.move32(Imm32(ClampDoubleToUint8(input.value().toInt32())), output);
    } else {
      MOZ_ASSERT(input.reg().type() == MIRType::Int32);
      masm.move32(input.reg().typedReg().gpr(), output);
      masm.clampIntToUint8(output);
    }
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  EmitGuardInt32OrDouble(
      this, masm, input, output, failure,
      [&]() {
        // |output| holds the unboxed int32 value.
        masm.clampIntToUint8(output);
      },
      [&](FloatRegister floatReg) {
        masm.clampDoubleToUint8(floatReg, output);
      });

  return true;
}

bool CacheIRCompiler::emitGuardNonDoubleType(ValOperandId inputId,
                                             ValueType type) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  if (allocator.knownType(inputId) == JSValueType(type)) {
    return true;
  }

  ValueOperand input = allocator.useValueRegister(masm, inputId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  switch (type) {
    case ValueType::String:
      masm.branchTestString(Assembler::NotEqual, input, failure->label());
      break;
    case ValueType::Symbol:
      masm.branchTestSymbol(Assembler::NotEqual, input, failure->label());
      break;
    case ValueType::BigInt:
      masm.branchTestBigInt(Assembler::NotEqual, input, failure->label());
      break;
    case ValueType::Int32:
      masm.branchTestInt32(Assembler::NotEqual, input, failure->label());
      break;
    case ValueType::Boolean:
      masm.branchTestBoolean(Assembler::NotEqual, input, failure->label());
      break;
    case ValueType::Undefined:
      masm.branchTestUndefined(Assembler::NotEqual, input, failure->label());
      break;
    case ValueType::Null:
      masm.branchTestNull(Assembler::NotEqual, input, failure->label());
      break;
    case ValueType::Double:
    case ValueType::Magic:
    case ValueType::PrivateGCThing:
    case ValueType::Object:
#ifdef ENABLE_RECORD_TUPLE
    case ValueType::ExtendedPrimitive:
#endif
      MOZ_CRASH("unexpected type");
  }

  return true;
}

static const JSClass* ClassFor(JSContext* cx, GuardClassKind kind) {
  switch (kind) {
    case GuardClassKind::Array:
    case GuardClassKind::PlainObject:
    case GuardClassKind::FixedLengthArrayBuffer:
    case GuardClassKind::ResizableArrayBuffer:
    case GuardClassKind::FixedLengthSharedArrayBuffer:
    case GuardClassKind::GrowableSharedArrayBuffer:
    case GuardClassKind::FixedLengthDataView:
    case GuardClassKind::ResizableDataView:
    case GuardClassKind::MappedArguments:
    case GuardClassKind::UnmappedArguments:
    case GuardClassKind::Set:
    case GuardClassKind::Map:
    case GuardClassKind::BoundFunction:
    case GuardClassKind::Date:
      return ClassFor(kind);
    case GuardClassKind::WindowProxy:
      return cx->runtime()->maybeWindowProxyClass();
    case GuardClassKind::JSFunction:
      MOZ_CRASH("must be handled by caller");
  }
  MOZ_CRASH("unexpected kind");
}

bool CacheIRCompiler::emitGuardClass(ObjOperandId objId, GuardClassKind kind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  if (kind == GuardClassKind::JSFunction) {
    if (objectGuardNeedsSpectreMitigations(objId)) {
      masm.branchTestObjIsFunction(Assembler::NotEqual, obj, scratch, obj,
                                   failure->label());
    } else {
      masm.branchTestObjIsFunctionNoSpectreMitigations(
          Assembler::NotEqual, obj, scratch, failure->label());
    }
    return true;
  }

  const JSClass* clasp = ClassFor(cx_, kind);
  MOZ_ASSERT(clasp);

  if (objectGuardNeedsSpectreMitigations(objId)) {
    masm.branchTestObjClass(Assembler::NotEqual, obj, clasp, scratch, obj,
                            failure->label());
  } else {
    masm.branchTestObjClassNoSpectreMitigations(Assembler::NotEqual, obj, clasp,
                                                scratch, failure->label());
  }

  return true;
}

bool CacheIRCompiler::emitGuardEitherClass(ObjOperandId objId,
                                           GuardClassKind kind1,
                                           GuardClassKind kind2) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // We don't yet need this case, so it's unsupported for now.
  MOZ_ASSERT(kind1 != GuardClassKind::JSFunction &&
             kind2 != GuardClassKind::JSFunction);

  const JSClass* clasp1 = ClassFor(cx_, kind1);
  MOZ_ASSERT(clasp1);

  const JSClass* clasp2 = ClassFor(cx_, kind2);
  MOZ_ASSERT(clasp2);

  if (objectGuardNeedsSpectreMitigations(objId)) {
    masm.branchTestObjClass(Assembler::NotEqual, obj, {clasp1, clasp2}, scratch,
                            obj, failure->label());
  } else {
    masm.branchTestObjClassNoSpectreMitigations(
        Assembler::NotEqual, obj, {clasp1, clasp2}, scratch, failure->label());
  }

  return true;
}

bool CacheIRCompiler::emitGuardNullProto(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadObjProto(obj, scratch);
  masm.branchTestPtr(Assembler::NonZero, scratch, scratch, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardIsExtensible(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchIfObjectNotExtensible(obj, scratch, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardDynamicSlotIsSpecificObject(
    ObjOperandId objId, ObjOperandId expectedId, uint32_t slotOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  Register expectedObject = allocator.useRegister(masm, expectedId);

  // Allocate registers before the failure path to make sure they're registered
  // by addFailurePath.
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Guard on the expected object.
  StubFieldOffset slot(slotOffset, StubField::Type::RawInt32);
  masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch1);
  emitLoadStubField(slot, scratch2);
  BaseObjectSlotIndex expectedSlot(scratch1, scratch2);
  masm.fallibleUnboxObject(expectedSlot, scratch1, failure->label());
  masm.branchPtr(Assembler::NotEqual, expectedObject, scratch1,
                 failure->label());

  return true;
}

bool CacheIRCompiler::emitGuardDynamicSlotIsNotObject(ObjOperandId objId,
                                                      uint32_t slotOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Guard that the slot isn't an object.
  StubFieldOffset slot(slotOffset, StubField::Type::RawInt32);
  masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch1);
  emitLoadStubField(slot, scratch2);
  BaseObjectSlotIndex expectedSlot(scratch1, scratch2);
  masm.branchTestObject(Assembler::Equal, expectedSlot, failure->label());

  return true;
}

bool CacheIRCompiler::emitGuardFixedSlotValue(ObjOperandId objId,
                                              uint32_t offsetOffset,
                                              uint32_t valOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);

  AutoScratchRegister scratch(allocator, masm);
  AutoScratchValueRegister scratchVal(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  StubFieldOffset offset(offsetOffset, StubField::Type::RawInt32);
  emitLoadStubField(offset, scratch);

  StubFieldOffset val(valOffset, StubField::Type::Value);
  emitLoadValueStubField(val, scratchVal);

  BaseIndex slotVal(obj, scratch, TimesOne);
  masm.branchTestValue(Assembler::NotEqual, slotVal, scratchVal,
                       failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardDynamicSlotValue(ObjOperandId objId,
                                                uint32_t offsetOffset,
                                                uint32_t valOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchValueRegister scratchVal(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch1);

  StubFieldOffset offset(offsetOffset, StubField::Type::RawInt32);
  emitLoadStubField(offset, scratch2);

  StubFieldOffset val(valOffset, StubField::Type::Value);
  emitLoadValueStubField(val, scratchVal);

  BaseIndex slotVal(scratch1, scratch2, TimesOne);
  masm.branchTestValue(Assembler::NotEqual, slotVal, scratchVal,
                       failure->label());
  return true;
}

bool CacheIRCompiler::emitLoadScriptedProxyHandler(ObjOperandId resultId,
                                                   ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  Register output = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), output);
  Address handlerAddr(output, js::detail::ProxyReservedSlots::offsetOfSlot(
                                  ScriptedProxyHandler::HANDLER_EXTRA));
  masm.fallibleUnboxObject(handlerAddr, output, failure->label());

  return true;
}

bool CacheIRCompiler::emitIdToStringOrSymbol(ValOperandId resultId,
                                             ValOperandId idId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  ValueOperand id = allocator.useValueRegister(masm, idId);
  ValueOperand output = allocator.defineValueRegister(masm, resultId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.moveValue(id, output);

  Label done, intDone, callVM;
  {
    ScratchTagScope tag(masm, output);
    masm.splitTagForTest(output, tag);
    masm.branchTestString(Assembler::Equal, tag, &done);
    masm.branchTestSymbol(Assembler::Equal, tag, &done);
    masm.branchTestInt32(Assembler::NotEqual, tag, failure->label());
  }

  Register intReg = output.scratchReg();
  masm.unboxInt32(output, intReg);

  // Fast path for small integers.
  masm.lookupStaticIntString(intReg, intReg, scratch, cx_->staticStrings(),
                             &callVM);
  masm.jump(&intDone);

  masm.bind(&callVM);
  LiveRegisterSet volatileRegs = liveVolatileRegs();
  masm.PushRegsInMask(volatileRegs);

  using Fn = JSLinearString* (*)(JSContext* cx, int32_t i);
  masm.setupUnalignedABICall(scratch);
  masm.loadJSContext(scratch);
  masm.passABIArg(scratch);
  masm.passABIArg(intReg);
  masm.callWithABI<Fn, js::Int32ToStringPure>();

  masm.storeCallPointerResult(intReg);

  LiveRegisterSet ignore;
  ignore.add(intReg);
  masm.PopRegsInMaskIgnore(volatileRegs, ignore);

  masm.branchPtr(Assembler::Equal, intReg, ImmPtr(nullptr), failure->label());

  masm.bind(&intDone);
  masm.tagValue(JSVAL_TYPE_STRING, intReg, output);
  masm.bind(&done);

  return true;
}

bool CacheIRCompiler::emitLoadFixedSlot(ValOperandId resultId,
                                        ObjOperandId objId,
                                        uint32_t offsetOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  ValueOperand output = allocator.defineValueRegister(masm, resultId);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  StubFieldOffset slotIndex(offsetOffset, StubField::Type::RawInt32);
  emitLoadStubField(slotIndex, scratch);

  masm.loadValue(BaseIndex(obj, scratch, TimesOne), output);
  return true;
}

bool CacheIRCompiler::emitLoadDynamicSlot(ValOperandId resultId,
                                          ObjOperandId objId,
                                          uint32_t slotOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  ValueOperand output = allocator.defineValueRegister(masm, resultId);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch1(allocator, masm);
  Register scratch2 = output.scratchReg();

  StubFieldOffset slotIndex(slotOffset, StubField::Type::RawInt32);
  emitLoadStubField(slotIndex, scratch2);

  masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch1);
  masm.loadValue(BaseObjectSlotIndex(scratch1, scratch2), output);
  return true;
}

bool CacheIRCompiler::emitGuardIsNativeObject(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchIfNonNativeObj(obj, scratch, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardIsProxy(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchTestObjectIsProxy(false, obj, scratch, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardIsNotProxy(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchTestObjectIsProxy(true, obj, scratch, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardIsNotArrayBufferMaybeShared(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadObjClassUnsafe(obj, scratch);
  masm.branchPtr(Assembler::Equal, scratch,
                 ImmPtr(&FixedLengthArrayBufferObject::class_),
                 failure->label());
  masm.branchPtr(Assembler::Equal, scratch,
                 ImmPtr(&FixedLengthSharedArrayBufferObject::class_),
                 failure->label());
  masm.branchPtr(Assembler::Equal, scratch,
                 ImmPtr(&ResizableArrayBufferObject::class_), failure->label());
  masm.branchPtr(Assembler::Equal, scratch,
                 ImmPtr(&GrowableSharedArrayBufferObject::class_),
                 failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardIsTypedArray(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadObjClassUnsafe(obj, scratch);
  masm.branchIfClassIsNotTypedArray(scratch, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardIsFixedLengthTypedArray(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadObjClassUnsafe(obj, scratch);
  masm.branchIfClassIsNotFixedLengthTypedArray(scratch, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardIsResizableTypedArray(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadObjClassUnsafe(obj, scratch);
  masm.branchIfClassIsNotResizableTypedArray(scratch, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardIsNotDOMProxy(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchTestProxyHandlerFamily(Assembler::Equal, obj, scratch,
                                    GetDOMProxyHandlerFamily(),
                                    failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardNoDenseElements(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Load obj->elements.
  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);

  // Make sure there are no dense elements.
  Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
  masm.branch32(Assembler::NotEqual, initLength, Imm32(0), failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardSpecificInt32(Int32OperandId numId,
                                             int32_t expected) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register num = allocator.useRegister(masm, numId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branch32(Assembler::NotEqual, num, Imm32(expected), failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardStringToInt32(StringOperandId strId,
                                             Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register str = allocator.useRegister(masm, strId);
  Register output = allocator.defineRegister(masm, resultId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.guardStringToInt32(str, output, scratch, liveVolatileRegs(),
                          failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardStringToNumber(StringOperandId strId,
                                              NumberOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register str = allocator.useRegister(masm, strId);
  ValueOperand output = allocator.defineValueRegister(masm, resultId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label vmCall, done;
  // Use indexed value as fast path if possible.
  masm.loadStringIndexValue(str, scratch, &vmCall);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output);
  masm.jump(&done);
  {
    masm.bind(&vmCall);

    // Reserve stack for holding the result value of the call.
    masm.reserveStack(sizeof(double));
    masm.moveStackPtrTo(output.payloadOrValueReg());

    // We cannot use callVM, as callVM expects to be able to clobber all
    // operands, however, since this op is not the last in the generated IC, we
    // want to be able to reference other live values.
    LiveRegisterSet volatileRegs = liveVolatileRegs();
    masm.PushRegsInMask(volatileRegs);

    using Fn = bool (*)(JSContext* cx, JSString* str, double* result);
    masm.setupUnalignedABICall(scratch);
    masm.loadJSContext(scratch);
    masm.passABIArg(scratch);
    masm.passABIArg(str);
    masm.passABIArg(output.payloadOrValueReg());
    masm.callWithABI<Fn, js::StringToNumberPure>();
    masm.storeCallPointerResult(scratch);

    LiveRegisterSet ignore;
    ignore.add(scratch);
    masm.PopRegsInMaskIgnore(volatileRegs, ignore);

    Label ok;
    masm.branchIfTrueBool(scratch, &ok);
    {
      // OOM path, recovered by StringToNumberPure.
      //
      // Use addToStackPtr instead of freeStack as freeStack tracks stack height
      // flow-insensitively, and using it twice would confuse the stack height
      // tracking.
      masm.addToStackPtr(Imm32(sizeof(double)));
      masm.jump(failure->label());
    }
    masm.bind(&ok);

    {
      ScratchDoubleScope fpscratch(masm);
      masm.loadDouble(Address(output.payloadOrValueReg(), 0), fpscratch);
      masm.boxDouble(fpscratch, output, fpscratch);
    }
    masm.freeStack(sizeof(double));
  }
  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitNumberParseIntResult(StringOperandId strId,
                                               Int32OperandId radixId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);
  Register radix = allocator.useRegister(masm, radixId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, callvm.output());

#ifdef DEBUG
  Label ok;
  masm.branch32(Assembler::Equal, radix, Imm32(0), &ok);
  masm.branch32(Assembler::Equal, radix, Imm32(10), &ok);
  masm.assumeUnreachable("radix must be 0 or 10 for indexed value fast path");
  masm.bind(&ok);
#endif

  // Discard the stack to ensure it's balanced when we skip the vm-call.
  allocator.discardStack(masm);

  // Use indexed value as fast path if possible.
  Label vmCall, done;
  masm.loadStringIndexValue(str, scratch, &vmCall);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, callvm.outputValueReg());
  masm.jump(&done);
  {
    masm.bind(&vmCall);

    callvm.prepare();
    masm.Push(radix);
    masm.Push(str);

    using Fn = bool (*)(JSContext*, HandleString, int32_t, MutableHandleValue);
    callvm.call<Fn, js::NumberParseInt>();
  }
  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitDoubleParseIntResult(NumberOperandId numId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch2(*this, FloatReg1);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  allocator.ensureDoubleRegister(masm, numId, floatScratch1);

  masm.branchDouble(Assembler::DoubleUnordered, floatScratch1, floatScratch1,
                    failure->label());
  masm.branchTruncateDoubleToInt32(floatScratch1, scratch, failure->label());

  Label ok;
  masm.branch32(Assembler::NotEqual, scratch, Imm32(0), &ok);
  {
    // Accept both +0 and -0 and return 0.
    masm.loadConstantDouble(0.0, floatScratch2);
    masm.branchDouble(Assembler::DoubleEqual, floatScratch1, floatScratch2,
                      &ok);

    // Fail if a non-zero input is in the exclusive range (-1, 1.0e-6).
    masm.loadConstantDouble(DOUBLE_DECIMAL_IN_SHORTEST_LOW, floatScratch2);
    masm.branchDouble(Assembler::DoubleLessThan, floatScratch1, floatScratch2,
                      failure->label());
  }
  masm.bind(&ok);

  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitStringToAtom(StringOperandId stringId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register str = allocator.useRegister(masm, stringId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label done, vmCall;
  masm.branchTest32(Assembler::NonZero, Address(str, JSString::offsetOfFlags()),
                    Imm32(JSString::ATOM_BIT), &done);

  masm.tryFastAtomize(str, scratch, str, &vmCall);
  masm.jump(&done);

  masm.bind(&vmCall);
  LiveRegisterSet save = liveVolatileRegs();
  masm.PushRegsInMask(save);

  using Fn = JSAtom* (*)(JSContext* cx, JSString* str);
  masm.setupUnalignedABICall(scratch);
  masm.loadJSContext(scratch);
  masm.passABIArg(scratch);
  masm.passABIArg(str);
  masm.callWithABI<Fn, jit::AtomizeStringNoGC>();
  masm.storeCallPointerResult(scratch);

  LiveRegisterSet ignore;
  ignore.add(scratch);
  masm.PopRegsInMaskIgnore(save, ignore);

  masm.branchPtr(Assembler::Equal, scratch, Imm32(0), failure->label());
  masm.movePtr(scratch.get(), str);

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitBooleanToNumber(BooleanOperandId booleanId,
                                          NumberOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register boolean = allocator.useRegister(masm, booleanId);
  ValueOperand output = allocator.defineValueRegister(masm, resultId);
  masm.tagValue(JSVAL_TYPE_INT32, boolean, output);
  return true;
}

bool CacheIRCompiler::emitGuardStringToIndex(StringOperandId strId,
                                             Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register str = allocator.useRegister(masm, strId);
  Register output = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label vmCall, done;
  masm.loadStringIndexValue(str, output, &vmCall);
  masm.jump(&done);

  {
    masm.bind(&vmCall);
    LiveRegisterSet save = liveVolatileRegs();
    masm.PushRegsInMask(save);

    using Fn = int32_t (*)(JSString* str);
    masm.setupUnalignedABICall(output);
    masm.passABIArg(str);
    masm.callWithABI<Fn, GetIndexFromString>();
    masm.storeCallInt32Result(output);

    LiveRegisterSet ignore;
    ignore.add(output);
    masm.PopRegsInMaskIgnore(save, ignore);

    // GetIndexFromString returns a negative value on failure.
    masm.branchTest32(Assembler::Signed, output, output, failure->label());
  }

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitLoadProto(ObjOperandId objId, ObjOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  Register reg = allocator.defineRegister(masm, resultId);
  masm.loadObjProto(obj, reg);

#ifdef DEBUG
  // We shouldn't encounter a null or lazy proto.
  MOZ_ASSERT(uintptr_t(TaggedProto::LazyProto) == 1);

  Label done;
  masm.branchPtr(Assembler::Above, reg, ImmWord(1), &done);
  masm.assumeUnreachable("Unexpected null or lazy proto in CacheIR LoadProto");
  masm.bind(&done);
#endif
  return true;
}

bool CacheIRCompiler::emitLoadEnclosingEnvironment(ObjOperandId objId,
                                                   ObjOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  Register reg = allocator.defineRegister(masm, resultId);
  masm.unboxObject(
      Address(obj, EnvironmentObject::offsetOfEnclosingEnvironment()), reg);
  return true;
}

bool CacheIRCompiler::emitLoadWrapperTarget(ObjOperandId objId,
                                            ObjOperandId resultId,
                                            bool fallible) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  Register reg = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (fallible && !addFailurePath(&failure)) {
    return false;
  }

  masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), reg);

  Address targetAddr(reg,
                     js::detail::ProxyReservedSlots::offsetOfPrivateSlot());
  if (fallible) {
    masm.fallibleUnboxObject(targetAddr, reg, failure->label());
  } else {
    masm.unboxObject(targetAddr, reg);
  }

  return true;
}

bool CacheIRCompiler::emitLoadValueTag(ValOperandId valId,
                                       ValueTagOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  ValueOperand val = allocator.useValueRegister(masm, valId);
  Register res = allocator.defineRegister(masm, resultId);

  Register tag = masm.extractTag(val, res);
  if (tag != res) {
    masm.mov(tag, res);
  }
  return true;
}

bool CacheIRCompiler::emitLoadDOMExpandoValue(ObjOperandId objId,
                                              ValOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  ValueOperand val = allocator.defineValueRegister(masm, resultId);

  masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()),
               val.scratchReg());
  masm.loadValue(Address(val.scratchReg(),
                         js::detail::ProxyReservedSlots::offsetOfPrivateSlot()),
                 val);
  return true;
}

bool CacheIRCompiler::emitLoadDOMExpandoValueIgnoreGeneration(
    ObjOperandId objId, ValOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  ValueOperand output = allocator.defineValueRegister(masm, resultId);

  // Determine the expando's Address.
  Register scratch = output.scratchReg();
  masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), scratch);
  Address expandoAddr(scratch,
                      js::detail::ProxyReservedSlots::offsetOfPrivateSlot());

#ifdef DEBUG
  // Private values are stored as doubles, so assert we have a double.
  Label ok;
  masm.branchTestDouble(Assembler::Equal, expandoAddr, &ok);
  masm.assumeUnreachable("DOM expando is not a PrivateValue!");
  masm.bind(&ok);
#endif

  // Load the ExpandoAndGeneration* from the PrivateValue.
  masm.loadPrivate(expandoAddr, scratch);

  // Load expandoAndGeneration->expando into the output Value register.
  masm.loadValue(Address(scratch, ExpandoAndGeneration::offsetOfExpando()),
                 output);
  return true;
}

bool CacheIRCompiler::emitLoadUndefinedResult() {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  masm.moveValue(UndefinedValue(), output.valueReg());
  return true;
}

static void EmitStoreBoolean(MacroAssembler& masm, bool b,
                             const AutoOutputRegister& output) {
  if (output.hasValue()) {
    Value val = BooleanValue(b);
    masm.moveValue(val, output.valueReg());
  } else {
    MOZ_ASSERT(output.type() == JSVAL_TYPE_BOOLEAN);
    masm.movePtr(ImmWord(b), output.typedReg().gpr());
  }
}

bool CacheIRCompiler::emitLoadBooleanResult(bool val) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  EmitStoreBoolean(masm, val, output);
  return true;
}

bool CacheIRCompiler::emitLoadOperandResult(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  ValueOperand input = allocator.useValueRegister(masm, inputId);
  masm.moveValue(input, output.valueReg());
  return true;
}

static void EmitStoreResult(MacroAssembler& masm, Register reg,
                            JSValueType type,
                            const AutoOutputRegister& output) {
  if (output.hasValue()) {
    masm.tagValue(type, reg, output.valueReg());
    return;
  }
  if (type == JSVAL_TYPE_INT32 && output.typedReg().isFloat()) {
    masm.convertInt32ToDouble(reg, output.typedReg().fpu());
    return;
  }
  if (type == output.type()) {
    masm.mov(reg, output.typedReg().gpr());
    return;
  }
  masm.assumeUnreachable("Should have monitored result");
}

bool CacheIRCompiler::emitLoadInt32ArrayLengthResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
  masm.load32(Address(scratch, ObjectElements::offsetOfLength()), scratch);

  // Guard length fits in an int32.
  masm.branchTest32(Assembler::Signed, scratch, scratch, failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitLoadInt32ArrayLength(ObjOperandId objId,
                                               Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  Register res = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), res);
  masm.load32(Address(res, ObjectElements::offsetOfLength()), res);

  // Guard length fits in an int32.
  masm.branchTest32(Assembler::Signed, res, res, failure->label());
  return true;
}

bool CacheIRCompiler::emitDoubleAddResult(NumberOperandId lhsId,
                                          NumberOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg1);

  allocator.ensureDoubleRegister(masm, lhsId, floatScratch0);
  allocator.ensureDoubleRegister(masm, rhsId, floatScratch1);

  masm.addDouble(floatScratch1, floatScratch0);
  masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);

  return true;
}
bool CacheIRCompiler::emitDoubleSubResult(NumberOperandId lhsId,
                                          NumberOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg1);

  allocator.ensureDoubleRegister(masm, lhsId, floatScratch0);
  allocator.ensureDoubleRegister(masm, rhsId, floatScratch1);

  masm.subDouble(floatScratch1, floatScratch0);
  masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);

  return true;
}
bool CacheIRCompiler::emitDoubleMulResult(NumberOperandId lhsId,
                                          NumberOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg1);

  allocator.ensureDoubleRegister(masm, lhsId, floatScratch0);
  allocator.ensureDoubleRegister(masm, rhsId, floatScratch1);

  masm.mulDouble(floatScratch1, floatScratch0);
  masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);

  return true;
}
bool CacheIRCompiler::emitDoubleDivResult(NumberOperandId lhsId,
                                          NumberOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg1);

  allocator.ensureDoubleRegister(masm, lhsId, floatScratch0);
  allocator.ensureDoubleRegister(masm, rhsId, floatScratch1);

  masm.divDouble(floatScratch1, floatScratch0);
  masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);

  return true;
}
bool CacheIRCompiler::emitDoubleModResult(NumberOperandId lhsId,
                                          NumberOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg1);

  allocator.ensureDoubleRegister(masm, lhsId, floatScratch0);
  allocator.ensureDoubleRegister(masm, rhsId, floatScratch1);

  LiveRegisterSet save = liveVolatileRegs();
  masm.PushRegsInMask(save);

  using Fn = double (*)(double a, double b);
  masm.setupUnalignedABICall(scratch);
  masm.passABIArg(floatScratch0, ABIType::Float64);
  masm.passABIArg(floatScratch1, ABIType::Float64);
  masm.callWithABI<Fn, js::NumberMod>(ABIType::Float64);
  masm.storeCallFloatResult(floatScratch0);

  LiveRegisterSet ignore;
  ignore.add(floatScratch0);
  masm.PopRegsInMaskIgnore(save, ignore);

  masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);

  return true;
}
bool CacheIRCompiler::emitDoublePowResult(NumberOperandId lhsId,
                                          NumberOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg1);

  allocator.ensureDoubleRegister(masm, lhsId, floatScratch0);
  allocator.ensureDoubleRegister(masm, rhsId, floatScratch1);

  LiveRegisterSet save = liveVolatileRegs();
  masm.PushRegsInMask(save);

  using Fn = double (*)(double x, double y);
  masm.setupUnalignedABICall(scratch);
  masm.passABIArg(floatScratch0, ABIType::Float64);
  masm.passABIArg(floatScratch1, ABIType::Float64);
  masm.callWithABI<Fn, js::ecmaPow>(ABIType::Float64);
  masm.storeCallFloatResult(floatScratch0);

  LiveRegisterSet ignore;
  ignore.add(floatScratch0);
  masm.PopRegsInMaskIgnore(save, ignore);

  masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);

  return true;
}

bool CacheIRCompiler::emitInt32AddResult(Int32OperandId lhsId,
                                         Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.mov(rhs, scratch);
  masm.branchAdd32(Assembler::Overflow, lhs, scratch, failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());

  return true;
}
bool CacheIRCompiler::emitInt32SubResult(Int32OperandId lhsId,
                                         Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.mov(lhs, scratch);
  masm.branchSub32(Assembler::Overflow, rhs, scratch, failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitInt32MulResult(Int32OperandId lhsId,
                                         Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  AutoScratchRegister scratch(allocator, masm);
  AutoScratchRegisterMaybeOutput scratch2(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label maybeNegZero, done;
  masm.mov(lhs, scratch);
  masm.branchMul32(Assembler::Overflow, rhs, scratch, failure->label());
  masm.branchTest32(Assembler::Zero, scratch, scratch, &maybeNegZero);
  masm.jump(&done);

  masm.bind(&maybeNegZero);
  masm.mov(lhs, scratch2);
  // Result is -0 if exactly one of lhs or rhs is negative.
  masm.or32(rhs, scratch2);
  masm.branchTest32(Assembler::Signed, scratch2, scratch2, failure->label());

  masm.bind(&done);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitInt32DivResult(Int32OperandId lhsId,
                                         Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  AutoScratchRegister rem(allocator, masm);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Prevent division by 0.
  masm.branchTest32(Assembler::Zero, rhs, rhs, failure->label());

  // Prevent -2147483648 / -1.
  Label notOverflow;
  masm.branch32(Assembler::NotEqual, lhs, Imm32(INT32_MIN), ¬Overflow);
  masm.branch32(Assembler::Equal, rhs, Imm32(-1), failure->label());
  masm.bind(¬Overflow);

  // Prevent negative 0.
  Label notZero;
  masm.branchTest32(Assembler::NonZero, lhs, lhs, ¬Zero);
  masm.branchTest32(Assembler::Signed, rhs, rhs, failure->label());
  masm.bind(¬Zero);

  masm.mov(lhs, scratch);
  masm.flexibleDivMod32(rhs, scratch, rem, false, liveVolatileRegs());

  // A remainder implies a double result.
  masm.branchTest32(Assembler::NonZero, rem, rem, failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitInt32ModResult(Int32OperandId lhsId,
                                         Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // x % 0 results in NaN
  masm.branchTest32(Assembler::Zero, rhs, rhs, failure->label());

  // Prevent -2147483648 % -1.
  //
  // Traps on x86 and has undefined behavior on ARM32 (when __aeabi_idivmod is
  // called).
  Label notOverflow;
  masm.branch32(Assembler::NotEqual, lhs, Imm32(INT32_MIN), ¬Overflow);
  masm.branch32(Assembler::Equal, rhs, Imm32(-1), failure->label());
  masm.bind(¬Overflow);

  masm.mov(lhs, scratch);
  masm.flexibleRemainder32(rhs, scratch, false, liveVolatileRegs());

  // Modulo takes the sign of the dividend; we can't return negative zero here.
  Label notZero;
  masm.branchTest32(Assembler::NonZero, scratch, scratch, ¬Zero);
  masm.branchTest32(Assembler::Signed, lhs, lhs, failure->label());
  masm.bind(¬Zero);

  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitInt32PowResult(Int32OperandId lhsId,
                                         Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register base = allocator.useRegister(masm, lhsId);
  Register power = allocator.useRegister(masm, rhsId);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegisterMaybeOutputType scratch2(allocator, masm, output);
  AutoScratchRegister scratch3(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.pow32(base, power, scratch1, scratch2, scratch3, failure->label());

  masm.tagValue(JSVAL_TYPE_INT32, scratch1, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitInt32BitOrResult(Int32OperandId lhsId,
                                           Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);

  masm.mov(rhs, scratch);
  masm.or32(lhs, scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());

  return true;
}
bool CacheIRCompiler::emitInt32BitXorResult(Int32OperandId lhsId,
                                            Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);

  masm.mov(rhs, scratch);
  masm.xor32(lhs, scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());

  return true;
}
bool CacheIRCompiler::emitInt32BitAndResult(Int32OperandId lhsId,
                                            Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);

  masm.mov(rhs, scratch);
  masm.and32(lhs, scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());

  return true;
}
bool CacheIRCompiler::emitInt32LeftShiftResult(Int32OperandId lhsId,
                                               Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  masm.mov(lhs, scratch);
  masm.flexibleLshift32(rhs, scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitInt32RightShiftResult(Int32OperandId lhsId,
                                                Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  masm.mov(lhs, scratch);
  masm.flexibleRshift32Arithmetic(rhs, scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitInt32URightShiftResult(Int32OperandId lhsId,
                                                 Int32OperandId rhsId,
                                                 bool forceDouble) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.mov(lhs, scratch);
  masm.flexibleRshift32(rhs, scratch);
  if (forceDouble) {
    ScratchDoubleScope fpscratch(masm);
    masm.convertUInt32ToDouble(scratch, fpscratch);
    masm.boxDouble(fpscratch, output.valueReg(), fpscratch);
  } else {
    masm.branchTest32(Assembler::Signed, scratch, scratch, failure->label());
    masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  }
  return true;
}

bool CacheIRCompiler::emitInt32NegationResult(Int32OperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register val = allocator.useRegister(masm, inputId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Guard against 0 and MIN_INT by checking if low 31-bits are all zero.
  // Both of these result in a double.
  masm.branchTest32(Assembler::Zero, val, Imm32(0x7fffffff), failure->label());
  masm.mov(val, scratch);
  masm.neg32(scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitInt32IncResult(Int32OperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register input = allocator.useRegister(masm, inputId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.mov(input, scratch);
  masm.branchAdd32(Assembler::Overflow, Imm32(1), scratch, failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitInt32DecResult(Int32OperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register input = allocator.useRegister(masm, inputId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.mov(input, scratch);
  masm.branchSub32(Assembler::Overflow, Imm32(1), scratch, failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitInt32NotResult(Int32OperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register val = allocator.useRegister(masm, inputId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  masm.mov(val, scratch);
  masm.not32(scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitDoubleNegationResult(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  AutoScratchFloatRegister floatReg(this);

  allocator.ensureDoubleRegister(masm, inputId, floatReg);

  masm.negateDouble(floatReg);
  masm.boxDouble(floatReg, output.valueReg(), floatReg);

  return true;
}

bool CacheIRCompiler::emitDoubleIncDecResult(bool isInc,
                                             NumberOperandId inputId) {
  AutoOutputRegister output(*this);

  AutoScratchFloatRegister floatReg(this);

  allocator.ensureDoubleRegister(masm, inputId, floatReg);

  {
    ScratchDoubleScope fpscratch(masm);
    masm.loadConstantDouble(1.0, fpscratch);
    if (isInc) {
      masm.addDouble(fpscratch, floatReg);
    } else {
      masm.subDouble(fpscratch, floatReg);
    }
  }
  masm.boxDouble(floatReg, output.valueReg(), floatReg);

  return true;
}

bool CacheIRCompiler::emitDoubleIncResult(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  return emitDoubleIncDecResult(true, inputId);
}

bool CacheIRCompiler::emitDoubleDecResult(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  return emitDoubleIncDecResult(false, inputId);
}

template <typename Fn, Fn fn>
bool CacheIRCompiler::emitBigIntBinaryOperationShared(BigIntOperandId lhsId,
                                                      BigIntOperandId rhsId) {
  AutoCallVM callvm(masm, this, allocator);
  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);

  callvm.prepare();

  masm.Push(rhs);
  masm.Push(lhs);

  callvm.call<Fn, fn>();
  return true;
}

bool CacheIRCompiler::emitBigIntAddResult(BigIntOperandId lhsId,
                                          BigIntOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt, HandleBigInt);
  return emitBigIntBinaryOperationShared<Fn, BigInt::add>(lhsId, rhsId);
}

bool CacheIRCompiler::emitBigIntSubResult(BigIntOperandId lhsId,
                                          BigIntOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt, HandleBigInt);
  return emitBigIntBinaryOperationShared<Fn, BigInt::sub>(lhsId, rhsId);
}

bool CacheIRCompiler::emitBigIntMulResult(BigIntOperandId lhsId,
                                          BigIntOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt, HandleBigInt);
  return emitBigIntBinaryOperationShared<Fn, BigInt::mul>(lhsId, rhsId);
}

bool CacheIRCompiler::emitBigIntDivResult(BigIntOperandId lhsId,
                                          BigIntOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt, HandleBigInt);
  return emitBigIntBinaryOperationShared<Fn, BigInt::div>(lhsId, rhsId);
}

bool CacheIRCompiler::emitBigIntModResult(BigIntOperandId lhsId,
                                          BigIntOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt, HandleBigInt);
  return emitBigIntBinaryOperationShared<Fn, BigInt::mod>(lhsId, rhsId);
}

bool CacheIRCompiler::emitBigIntPowResult(BigIntOperandId lhsId,
                                          BigIntOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt, HandleBigInt);
  return emitBigIntBinaryOperationShared<Fn, BigInt::pow>(lhsId, rhsId);
}

bool CacheIRCompiler::emitBigIntBitAndResult(BigIntOperandId lhsId,
                                             BigIntOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt, HandleBigInt);
  return emitBigIntBinaryOperationShared<Fn, BigInt::bitAnd>(lhsId, rhsId);
}

bool CacheIRCompiler::emitBigIntBitOrResult(BigIntOperandId lhsId,
                                            BigIntOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt, HandleBigInt);
  return emitBigIntBinaryOperationShared<Fn, BigInt::bitOr>(lhsId, rhsId);
}

bool CacheIRCompiler::emitBigIntBitXorResult(BigIntOperandId lhsId,
                                             BigIntOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt, HandleBigInt);
  return emitBigIntBinaryOperationShared<Fn, BigInt::bitXor>(lhsId, rhsId);
}

bool CacheIRCompiler::emitBigIntLeftShiftResult(BigIntOperandId lhsId,
                                                BigIntOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt, HandleBigInt);
  return emitBigIntBinaryOperationShared<Fn, BigInt::lsh>(lhsId, rhsId);
}

bool CacheIRCompiler::emitBigIntRightShiftResult(BigIntOperandId lhsId,
                                                 BigIntOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt, HandleBigInt);
  return emitBigIntBinaryOperationShared<Fn, BigInt::rsh>(lhsId, rhsId);
}

template <typename Fn, Fn fn>
bool CacheIRCompiler::emitBigIntUnaryOperationShared(BigIntOperandId inputId) {
  AutoCallVM callvm(masm, this, allocator);
  Register val = allocator.useRegister(masm, inputId);

  callvm.prepare();

  masm.Push(val);

  callvm.call<Fn, fn>();
  return true;
}

bool CacheIRCompiler::emitBigIntNotResult(BigIntOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt);
  return emitBigIntUnaryOperationShared<Fn, BigInt::bitNot>(inputId);
}

bool CacheIRCompiler::emitBigIntNegationResult(BigIntOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt);
  return emitBigIntUnaryOperationShared<Fn, BigInt::neg>(inputId);
}

bool CacheIRCompiler::emitBigIntIncResult(BigIntOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt);
  return emitBigIntUnaryOperationShared<Fn, BigInt::inc>(inputId);
}

bool CacheIRCompiler::emitBigIntDecResult(BigIntOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  using Fn = BigInt* (*)(JSContext*, HandleBigInt);
  return emitBigIntUnaryOperationShared<Fn, BigInt::dec>(inputId);
}

bool CacheIRCompiler::emitBigIntToIntPtr(BigIntOperandId inputId,
                                         IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register input = allocator.useRegister(masm, inputId);
  Register output = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadBigIntPtr(input, output, failure->label());
  return true;
}

static gc::Heap InitialBigIntHeap(JSContext* cx) {
  JS::Zone* zone = cx->zone();
  return zone->allocNurseryBigInts() ? gc::Heap::Default : gc::Heap::Tenured;
}

static void EmitAllocateBigInt(MacroAssembler& masm, Register result,
                               Register temp, const LiveRegisterSet& liveSet,
                               gc::Heap initialHeap, Label* fail) {
  Label fallback, done;
  masm.newGCBigInt(result, temp, initialHeap, &fallback);
  masm.jump(&done);
  {
    masm.bind(&fallback);

    // Request a minor collection at a later time if nursery allocation failed.
    bool requestMinorGC = initialHeap == gc::Heap::Default;

    masm.PushRegsInMask(liveSet);
    using Fn = void* (*)(JSContext* cx, bool requestMinorGC);
    masm.setupUnalignedABICall(temp);
    masm.loadJSContext(temp);
    masm.passABIArg(temp);
    masm.move32(Imm32(requestMinorGC), result);
    masm.passABIArg(result);
    masm.callWithABI<Fn, jit::AllocateBigIntNoGC>();
    masm.storeCallPointerResult(result);

    masm.PopRegsInMask(liveSet);
    masm.branchPtr(Assembler::Equal, result, ImmWord(0), fail);
  }
  masm.bind(&done);
}

bool CacheIRCompiler::emitIntPtrToBigIntResult(IntPtrOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register input = allocator.useRegister(masm, inputId);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  LiveRegisterSet save = liveVolatileRegs();
  save.takeUnchecked(scratch1);
  save.takeUnchecked(scratch2);
  save.takeUnchecked(output);

  // Allocate a new BigInt. The code after this must be infallible.
  gc::Heap initialHeap = InitialBigIntHeap(cx_);
  EmitAllocateBigInt(masm, scratch1, scratch2, save, initialHeap,
                     failure->label());

  masm.movePtr(input, scratch2);
  masm.initializeBigIntPtr(scratch1, scratch2);

  masm.tagValue(JSVAL_TYPE_BIGINT, scratch1, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitBigIntPtrAdd(IntPtrOperandId lhsId,
                                       IntPtrOperandId rhsId,
                                       IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  Register output = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.movePtr(rhs, output);
  masm.branchAddPtr(Assembler::Overflow, lhs, output, failure->label());
  return true;
}

bool CacheIRCompiler::emitBigIntPtrSub(IntPtrOperandId lhsId,
                                       IntPtrOperandId rhsId,
                                       IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  Register output = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.movePtr(lhs, output);
  masm.branchSubPtr(Assembler::Overflow, rhs, output, failure->label());
  return true;
}

bool CacheIRCompiler::emitBigIntPtrMul(IntPtrOperandId lhsId,
                                       IntPtrOperandId rhsId,
                                       IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  Register output = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.movePtr(rhs, output);
  masm.branchMulPtr(Assembler::Overflow, lhs, output, failure->label());
  return true;
}

bool CacheIRCompiler::emitBigIntPtrDiv(IntPtrOperandId lhsId,
                                       IntPtrOperandId rhsId,
                                       IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  Register output = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  static constexpr auto DigitMin = std::numeric_limits<
      mozilla::SignedStdintTypeForSize<sizeof(BigInt::Digit)>::Type>::min();

  // Prevent division by 0.
  masm.branchTestPtr(Assembler::Zero, rhs, rhs, failure->label());

  // Prevent INTPTR_MIN / -1.
  Label notOverflow;
  masm.branchPtr(Assembler::NotEqual, lhs, ImmWord(DigitMin), ¬Overflow);
  masm.branchPtr(Assembler::Equal, rhs, Imm32(-1), failure->label());
  masm.bind(¬Overflow);

  LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(),
                               liveVolatileFloatRegs());
  masm.movePtr(lhs, output);
  masm.flexibleQuotientPtr(rhs, output, false, volatileRegs);
  return true;
}

bool CacheIRCompiler::emitBigIntPtrMod(IntPtrOperandId lhsId,
                                       IntPtrOperandId rhsId,
                                       IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  Register output = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  static constexpr auto DigitMin = std::numeric_limits<
      mozilla::SignedStdintTypeForSize<sizeof(BigInt::Digit)>::Type>::min();

  // Prevent division by 0.
  masm.branchTestPtr(Assembler::Zero, rhs, rhs, failure->label());

  masm.movePtr(lhs, output);

  // Prevent INTPTR_MIN / -1.
  Label notOverflow;
  masm.branchPtr(Assembler::NotEqual, lhs, ImmWord(DigitMin), ¬Overflow);
  masm.branchPtr(Assembler::NotEqual, rhs, Imm32(-1), ¬Overflow);
  masm.movePtr(ImmWord(0), output);
  masm.bind(¬Overflow);

  LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(),
                               liveVolatileFloatRegs());
  masm.flexibleRemainderPtr(rhs, output, false, volatileRegs);
  return true;
}

bool CacheIRCompiler::emitBigIntPtrPow(IntPtrOperandId lhsId,
                                       IntPtrOperandId rhsId,
                                       IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  Register output = allocator.defineRegister(masm, resultId);
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.powPtr(lhs, rhs, output, scratch1, scratch2, failure->label());
  return true;
}

bool CacheIRCompiler::emitBigIntPtrBitOr(IntPtrOperandId lhsId,
                                         IntPtrOperandId rhsId,
                                         IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  Register output = allocator.defineRegister(masm, resultId);

  masm.movePtr(rhs, output);
  masm.orPtr(lhs, output);
  return true;
}

bool CacheIRCompiler::emitBigIntPtrBitXor(IntPtrOperandId lhsId,
                                          IntPtrOperandId rhsId,
                                          IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  Register output = allocator.defineRegister(masm, resultId);

  masm.movePtr(rhs, output);
  masm.xorPtr(lhs, output);
  return true;
}

bool CacheIRCompiler::emitBigIntPtrBitAnd(IntPtrOperandId lhsId,
                                          IntPtrOperandId rhsId,
                                          IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  Register output = allocator.defineRegister(masm, resultId);

  masm.movePtr(rhs, output);
  masm.andPtr(lhs, output);
  return true;
}

bool CacheIRCompiler::emitBigIntPtrLeftShift(IntPtrOperandId lhsId,
                                             IntPtrOperandId rhsId,
                                             IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  Register output = allocator.defineRegister(masm, resultId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label done;

  masm.movePtr(lhs, output);

  // 0n << x == 0n
  masm.branchPtr(Assembler::Equal, lhs, Imm32(0), &done);

  // x << DigitBits with x != 0n always exceeds pointer-sized storage.
  masm.branchPtr(Assembler::GreaterThanOrEqual, rhs, Imm32(BigInt::DigitBits),
                 failure->label());

  // x << -DigitBits == x >> DigitBits, which is either 0n or -1n.
  Label shift;
  masm.branchPtr(Assembler::GreaterThan, rhs,
                 Imm32(-int32_t(BigInt::DigitBits)), &shift);
  {
    masm.rshiftPtrArithmetic(Imm32(BigInt::DigitBits - 1), output);
    masm.jump(&done);
  }
  masm.bind(&shift);

  // |x << -y| is computed as |x >> y|.
  Label leftShift;
  masm.branchPtr(Assembler::GreaterThanOrEqual, rhs, Imm32(0), &leftShift);
  {
    masm.movePtr(rhs, scratch);
    masm.negPtr(scratch);
    masm.flexibleRshiftPtrArithmetic(scratch, output);
    masm.jump(&done);
  }
  masm.bind(&leftShift);

  masm.flexibleLshiftPtr(rhs, output);

  // Check for overflow: ((lhs << rhs) >> rhs) == lhs.
  masm.movePtr(output, scratch);
  masm.flexibleRshiftPtrArithmetic(rhs, scratch);
  masm.branchPtr(Assembler::NotEqual, scratch, lhs, failure->label());

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitBigIntPtrRightShift(IntPtrOperandId lhsId,
                                              IntPtrOperandId rhsId,
                                              IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);
  Register output = allocator.defineRegister(masm, resultId);
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label done;

  masm.movePtr(lhs, output);

  // 0n >> x == 0n
  masm.branchPtr(Assembler::Equal, lhs, Imm32(0), &done);

  // x >> -DigitBits == x << DigitBits, which exceeds pointer-sized storage.
  masm.branchPtr(Assembler::LessThanOrEqual, rhs,
                 Imm32(-int32_t(BigInt::DigitBits)), failure->label());

  // x >> DigitBits is either 0n or -1n.
  Label shift;
  masm.branchPtr(Assembler::LessThan, rhs, Imm32(BigInt::DigitBits), &shift);
  {
    masm.rshiftPtrArithmetic(Imm32(BigInt::DigitBits - 1), output);
    masm.jump(&done);
  }
  masm.bind(&shift);

  // |x >> -y| is computed as |x << y|.
  Label rightShift;
  masm.branchPtr(Assembler::GreaterThanOrEqual, rhs, Imm32(0), &rightShift);
  {
    masm.movePtr(rhs, scratch1);
    masm.negPtr(scratch1);
    masm.flexibleLshiftPtr(scratch1, output);

    // Check for overflow: ((lhs << rhs) >> rhs) == lhs.
    masm.movePtr(output, scratch2);
    masm.flexibleRshiftPtrArithmetic(scratch1, scratch2);
    masm.branchPtr(Assembler::NotEqual, scratch2, lhs, failure->label());

    masm.jump(&done);
  }
  masm.bind(&rightShift);

  masm.flexibleRshiftPtrArithmetic(rhs, output);

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitBigIntPtrNegation(IntPtrOperandId inputId,
                                            IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register input = allocator.useRegister(masm, inputId);
  Register output = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.movePtr(input, output);
  masm.branchNegPtr(Assembler::Overflow, output, failure->label());
  return true;
}

bool CacheIRCompiler::emitBigIntPtrInc(IntPtrOperandId inputId,
                                       IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register input = allocator.useRegister(masm, inputId);
  Register output = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.movePtr(input, output);
  masm.branchAddPtr(Assembler::Overflow, Imm32(1), output, failure->label());
  return true;
}

bool CacheIRCompiler::emitBigIntPtrDec(IntPtrOperandId inputId,
                                       IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register input = allocator.useRegister(masm, inputId);
  Register output = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.movePtr(input, output);
  masm.branchSubPtr(Assembler::Overflow, Imm32(1), output, failure->label());
  return true;
}

bool CacheIRCompiler::emitBigIntPtrNot(IntPtrOperandId inputId,
                                       IntPtrOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register input = allocator.useRegister(masm, inputId);
  Register output = allocator.defineRegister(masm, resultId);

  masm.movePtr(input, output);
  masm.notPtr(output);
  return true;
}

bool CacheIRCompiler::emitTruncateDoubleToUInt32(NumberOperandId inputId,
                                                 Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register res = allocator.defineRegister(masm, resultId);

  AutoScratchFloatRegister floatReg(this);

  allocator.ensureDoubleRegister(masm, inputId, floatReg);

  TruncateDoubleModUint32(masm, floatReg, res, liveVolatileRegs());
  return true;
}

bool CacheIRCompiler::emitDoubleToUint8Clamped(NumberOperandId inputId,
                                               Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register res = allocator.defineRegister(masm, resultId);

  AutoScratchFloatRegister floatReg(this);

  allocator.ensureDoubleRegister(masm, inputId, floatReg);

  masm.clampDoubleToUint8(floatReg, res);
  return true;
}

bool CacheIRCompiler::emitLoadArgumentsObjectLengthResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadArgumentsObjectLength(obj, scratch, failure->label());

  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitLoadArgumentsObjectLength(ObjOperandId objId,
                                                    Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  Register res = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadArgumentsObjectLength(obj, res, failure->label());
  return true;
}

bool CacheIRCompiler::emitLoadArrayBufferByteLengthInt32Result(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadArrayBufferByteLengthIntPtr(obj, scratch);
  masm.guardNonNegativeIntPtrToInt32(scratch, failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitLoadArrayBufferByteLengthDoubleResult(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  ScratchDoubleScope fpscratch(masm);
  masm.loadArrayBufferByteLengthIntPtr(obj, scratch);
  masm.convertIntPtrToDouble(scratch, fpscratch);
  masm.boxDouble(fpscratch, output.valueReg(), fpscratch);
  return true;
}

bool CacheIRCompiler::emitLoadArrayBufferViewLengthInt32Result(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadArrayBufferViewLengthIntPtr(obj, scratch);
  masm.guardNonNegativeIntPtrToInt32(scratch, failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitLoadArrayBufferViewLengthDoubleResult(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  ScratchDoubleScope fpscratch(masm);
  masm.loadArrayBufferViewLengthIntPtr(obj, scratch);
  masm.convertIntPtrToDouble(scratch, fpscratch);
  masm.boxDouble(fpscratch, output.valueReg(), fpscratch);
  return true;
}

bool CacheIRCompiler::emitLoadBoundFunctionNumArgs(ObjOperandId objId,
                                                   Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  Register output = allocator.defineRegister(masm, resultId);

  masm.unboxInt32(Address(obj, BoundFunctionObject::offsetOfFlagsSlot()),
                  output);
  masm.rshift32(Imm32(BoundFunctionObject::NumBoundArgsShift), output);
  return true;
}

bool CacheIRCompiler::emitLoadBoundFunctionTarget(ObjOperandId objId,
                                                  ObjOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  Register output = allocator.defineRegister(masm, resultId);

  masm.unboxObject(Address(obj, BoundFunctionObject::offsetOfTargetSlot()),
                   output);
  return true;
}

bool CacheIRCompiler::emitLoadBoundFunctionArgument(ObjOperandId objId,
                                                    uint32_t index,
                                                    ValOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  ValueOperand output = allocator.defineValueRegister(masm, resultId);
  AutoScratchRegister scratch(allocator, masm);

  constexpr size_t inlineArgsOffset =
      BoundFunctionObject::offsetOfFirstInlineBoundArg();

  masm.unboxObject(Address(obj, inlineArgsOffset), scratch);
  masm.loadPtr(Address(scratch, NativeObject::offsetOfElements()), scratch);
  masm.loadValue(Address(scratch, index * sizeof(Value)), output);
  return true;
}

bool CacheIRCompiler::emitGuardBoundFunctionIsConstructor(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Address flagsSlot(obj, BoundFunctionObject::offsetOfFlagsSlot());
  masm.branchTest32(Assembler::Zero, flagsSlot,
                    Imm32(BoundFunctionObject::IsConstructorFlag),
                    failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardObjectIdentity(ObjOperandId obj1Id,
                                              ObjOperandId obj2Id) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj1 = allocator.useRegister(masm, obj1Id);
  Register obj2 = allocator.useRegister(masm, obj2Id);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchPtr(Assembler::NotEqual, obj1, obj2, failure->label());
  return true;
}

bool CacheIRCompiler::emitLoadFunctionLengthResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Get the JSFunction flags and arg count.
  masm.load32(Address(obj, JSFunction::offsetOfFlagsAndArgCount()), scratch);

  // Functions with a SelfHostedLazyScript must be compiled with the slow-path
  // before the function length is known. If the length was previously resolved,
  // the length property may be shadowed.
  masm.branchTest32(
      Assembler::NonZero, scratch,
      Imm32(FunctionFlags::SELFHOSTLAZY | FunctionFlags::RESOLVED_LENGTH),
      failure->label());

  masm.loadFunctionLength(obj, scratch, scratch, failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitLoadFunctionNameResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadFunctionName(obj, scratch, ImmGCPtr(cx_->names().empty_),
                        failure->label());

  masm.tagValue(JSVAL_TYPE_STRING, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitLinearizeForCharAccess(StringOperandId strId,
                                                 Int32OperandId indexId,
                                                 StringOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register str = allocator.useRegister(masm, strId);
  Register index = allocator.useRegister(masm, indexId);
  Register result = allocator.defineRegister(masm, resultId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label done;
  masm.movePtr(str, result);

  // We can omit the bounds check, because we only compare the index against the
  // string length. In the worst case we unnecessarily linearize the string
  // when the index is out-of-bounds.

  masm.branchIfCanLoadStringChar(str, index, scratch, &done);
  {
    LiveRegisterSet volatileRegs = liveVolatileRegs();
    masm.PushRegsInMask(volatileRegs);

    using Fn = JSLinearString* (*)(JSString*);
    masm.setupUnalignedABICall(scratch);
    masm.passABIArg(str);
    masm.callWithABI<Fn, js::jit::LinearizeForCharAccessPure>();
    masm.storeCallPointerResult(result);

    LiveRegisterSet ignore;
    ignore.add(result);
    masm.PopRegsInMaskIgnore(volatileRegs, ignore);

    masm.branchTestPtr(Assembler::Zero, result, result, failure->label());
  }

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitLinearizeForCodePointAccess(
    StringOperandId strId, Int32OperandId indexId, StringOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register str = allocator.useRegister(masm, strId);
  Register index = allocator.useRegister(masm, indexId);
  Register result = allocator.defineRegister(masm, resultId);
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label done;
  masm.movePtr(str, result);

  // We can omit the bounds check, because we only compare the index against the
  // string length. In the worst case we unnecessarily linearize the string
  // when the index is out-of-bounds.

  masm.branchIfCanLoadStringCodePoint(str, index, scratch1, scratch2, &done);
  {
    LiveRegisterSet volatileRegs = liveVolatileRegs();
    masm.PushRegsInMask(volatileRegs);

    using Fn = JSLinearString* (*)(JSString*);
    masm.setupUnalignedABICall(scratch1);
    masm.passABIArg(str);
    masm.callWithABI<Fn, js::jit::LinearizeForCharAccessPure>();
    masm.storeCallPointerResult(result);

    LiveRegisterSet ignore;
    ignore.add(result);
    masm.PopRegsInMaskIgnore(volatileRegs, ignore);

    masm.branchTestPtr(Assembler::Zero, result, result, failure->label());
  }

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitToRelativeStringIndex(Int32OperandId indexId,
                                                StringOperandId strId,
                                                Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register index = allocator.useRegister(masm, indexId);
  Register str = allocator.useRegister(masm, strId);
  Register result = allocator.defineRegister(masm, resultId);

  // If |index| is non-negative, it's an index relative to the start of the
  // string. Otherwise it's an index relative to the end of the string.
  masm.move32(Imm32(0), result);
  masm.cmp32Load32(Assembler::LessThan, index, Imm32(0),
                   Address(str, JSString::offsetOfLength()), result);
  masm.add32(index, result);
  return true;
}

bool CacheIRCompiler::emitLoadStringLengthResult(StringOperandId strId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register str = allocator.useRegister(masm, strId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  masm.loadStringLength(str, scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitLoadStringCharCodeResult(StringOperandId strId,
                                                   Int32OperandId indexId,
                                                   bool handleOOB) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register str = allocator.useRegister(masm, strId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegisterMaybeOutputType scratch2(allocator, masm, output);
  AutoScratchRegister scratch3(allocator, masm);

  // Bounds check, load string char.
  Label done;
  if (!handleOOB) {
    FailurePath* failure;
    if (!addFailurePath(&failure)) {
      return false;
    }

    masm.spectreBoundsCheck32(index, Address(str, JSString::offsetOfLength()),
                              scratch1, failure->label());
    masm.loadStringChar(str, index, scratch1, scratch2, scratch3,
                        failure->label());
  } else {
    // Return NaN for out-of-bounds access.
    masm.moveValue(JS::NaNValue(), output.valueReg());

    // The bounds check mustn't use a scratch register which aliases the output.
    MOZ_ASSERT(!output.valueReg().aliases(scratch3));

    // This CacheIR op is always preceded by |LinearizeForCharAccess|, so we're
    // guaranteed to see no nested ropes.
    Label loadFailed;
    masm.spectreBoundsCheck32(index, Address(str, JSString::offsetOfLength()),
                              scratch3, &done);
    masm.loadStringChar(str, index, scratch1, scratch2, scratch3, &loadFailed);

    Label loadedChar;
    masm.jump(&loadedChar);
    masm.bind(&loadFailed);
    masm.assumeUnreachable("loadStringChar can't fail for linear strings");
    masm.bind(&loadedChar);
  }

  masm.tagValue(JSVAL_TYPE_INT32, scratch1, output.valueReg());
  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitLoadStringCodePointResult(StringOperandId strId,
                                                    Int32OperandId indexId,
                                                    bool handleOOB) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register str = allocator.useRegister(masm, strId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegisterMaybeOutputType scratch2(allocator, masm, output);
  AutoScratchRegister scratch3(allocator, masm);

  // Bounds check, load string char.
  Label done;
  if (!handleOOB) {
    FailurePath* failure;
    if (!addFailurePath(&failure)) {
      return false;
    }

    masm.spectreBoundsCheck32(index, Address(str, JSString::offsetOfLength()),
                              scratch1, failure->label());
    masm.loadStringCodePoint(str, index, scratch1, scratch2, scratch3,
                             failure->label());
  } else {
    // Return undefined for out-of-bounds access.
    masm.moveValue(JS::UndefinedValue(), output.valueReg());

    // The bounds check mustn't use a scratch register which aliases the output.
    MOZ_ASSERT(!output.valueReg().aliases(scratch3));

    // This CacheIR op is always preceded by |LinearizeForCodePointAccess|, so
    // we're guaranteed to see no nested ropes or split surrogates.
    Label loadFailed;
    masm.spectreBoundsCheck32(index, Address(str, JSString::offsetOfLength()),
                              scratch3, &done);
    masm.loadStringCodePoint(str, index, scratch1, scratch2, scratch3,
                             &loadFailed);

    Label loadedChar;
    masm.jump(&loadedChar);
    masm.bind(&loadFailed);
    masm.assumeUnreachable("loadStringCodePoint can't fail for linear strings");
    masm.bind(&loadedChar);
  }

  masm.tagValue(JSVAL_TYPE_INT32, scratch1, output.valueReg());
  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitNewMapObjectResult(uint32_t templateObjectOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  callvm.prepare();
  masm.Push(ImmPtr(nullptr));  // proto

  using Fn = MapObject* (*)(JSContext*, HandleObject);
  callvm.call<Fn, MapObject::create>();
  return true;
}

bool CacheIRCompiler::emitNewSetObjectResult(uint32_t templateObjectOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  callvm.prepare();
  masm.Push(ImmPtr(nullptr));  // proto

  using Fn = SetObject* (*)(JSContext*, HandleObject);
  callvm.call<Fn, SetObject::create>();
  return true;
}

bool CacheIRCompiler::emitNewMapObjectFromIterableResult(
    uint32_t templateObjectOffset, ValOperandId iterableId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);
  ValueOperand iterable = allocator.useValueRegister(masm, iterableId);

  callvm.prepare();
  masm.Push(ImmPtr(nullptr));  // allocatedFromJit
  masm.Push(iterable);
  masm.Push(ImmPtr(nullptr));  // proto

  using Fn = MapObject* (*)(JSContext*, Handle<JSObject*>, Handle<Value>,
                            Handle<MapObject*>);
  callvm.call<Fn, MapObject::createFromIterable>();
  return true;
}

bool CacheIRCompiler::emitNewSetObjectFromIterableResult(
    uint32_t templateObjectOffset, ValOperandId iterableId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);
  ValueOperand iterable = allocator.useValueRegister(masm, iterableId);

  callvm.prepare();
  masm.Push(ImmPtr(nullptr));  // allocatedFromJit
  masm.Push(iterable);
  masm.Push(ImmPtr(nullptr));  // proto

  using Fn = SetObject* (*)(JSContext*, Handle<JSObject*>, Handle<Value>,
                            Handle<SetObject*>);
  callvm.call<Fn, SetObject::createFromIterable>();
  return true;
}

bool CacheIRCompiler::emitNewStringObjectResult(uint32_t templateObjectOffset,
                                                StringOperandId strId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);

  callvm.prepare();
  masm.Push(str);

  using Fn = JSObject* (*)(JSContext*, HandleString);
  callvm.call<Fn, NewStringObject>();
  return true;
}

bool CacheIRCompiler::emitStringIncludesResult(StringOperandId strId,
                                               StringOperandId searchStrId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);
  Register searchStr = allocator.useRegister(masm, searchStrId);

  callvm.prepare();
  masm.Push(searchStr);
  masm.Push(str);

  using Fn = bool (*)(JSContext*, HandleString, HandleString, bool*);
  callvm.call<Fn, js::StringIncludes>();
  return true;
}

bool CacheIRCompiler::emitStringIndexOfResult(StringOperandId strId,
                                              StringOperandId searchStrId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);
  Register searchStr = allocator.useRegister(masm, searchStrId);

  callvm.prepare();
  masm.Push(searchStr);
  masm.Push(str);

  using Fn = bool (*)(JSContext*, HandleString, HandleString, int32_t*);
  callvm.call<Fn, js::StringIndexOf>();
  return true;
}

bool CacheIRCompiler::emitStringLastIndexOfResult(StringOperandId strId,
                                                  StringOperandId searchStrId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);
  Register searchStr = allocator.useRegister(masm, searchStrId);

  callvm.prepare();
  masm.Push(searchStr);
  masm.Push(str);

  using Fn = bool (*)(JSContext*, HandleString, HandleString, int32_t*);
  callvm.call<Fn, js::StringLastIndexOf>();
  return true;
}

bool CacheIRCompiler::emitStringStartsWithResult(StringOperandId strId,
                                                 StringOperandId searchStrId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);
  Register searchStr = allocator.useRegister(masm, searchStrId);

  callvm.prepare();
  masm.Push(searchStr);
  masm.Push(str);

  using Fn = bool (*)(JSContext*, HandleString, HandleString, bool*);
  callvm.call<Fn, js::StringStartsWith>();
  return true;
}

bool CacheIRCompiler::emitStringEndsWithResult(StringOperandId strId,
                                               StringOperandId searchStrId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);
  Register searchStr = allocator.useRegister(masm, searchStrId);

  callvm.prepare();
  masm.Push(searchStr);
  masm.Push(str);

  using Fn = bool (*)(JSContext*, HandleString, HandleString, bool*);
  callvm.call<Fn, js::StringEndsWith>();
  return true;
}

bool CacheIRCompiler::emitStringToLowerCaseResult(StringOperandId strId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);

  callvm.prepare();
  masm.Push(str);

  using Fn = JSLinearString* (*)(JSContext*, JSString*);
  callvm.call<Fn, js::StringToLowerCase>();
  return true;
}

bool CacheIRCompiler::emitStringToUpperCaseResult(StringOperandId strId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);

  callvm.prepare();
  masm.Push(str);

  using Fn = JSLinearString* (*)(JSContext*, JSString*);
  callvm.call<Fn, js::StringToUpperCase>();
  return true;
}

bool CacheIRCompiler::emitStringTrimResult(StringOperandId strId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);

  callvm.prepare();
  masm.Push(str);

  using Fn = JSString* (*)(JSContext*, HandleString);
  callvm.call<Fn, js::StringTrim>();
  return true;
}

bool CacheIRCompiler::emitStringTrimStartResult(StringOperandId strId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);

  callvm.prepare();
  masm.Push(str);

  using Fn = JSString* (*)(JSContext*, HandleString);
  callvm.call<Fn, js::StringTrimStart>();
  return true;
}

bool CacheIRCompiler::emitStringTrimEndResult(StringOperandId strId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);

  callvm.prepare();
  masm.Push(str);

  using Fn = JSString* (*)(JSContext*, HandleString);
  callvm.call<Fn, js::StringTrimEnd>();
  return true;
}

bool CacheIRCompiler::emitLoadArgumentsObjectArgResult(ObjOperandId objId,
                                                       Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadArgumentsObjectElement(obj, index, output.valueReg(), scratch,
                                  failure->label());
  return true;
}

bool CacheIRCompiler::emitLoadArgumentsObjectArgHoleResult(
    ObjOperandId objId, Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadArgumentsObjectElementHole(obj, index, output.valueReg(), scratch,
                                      failure->label());
  return true;
}

bool CacheIRCompiler::emitLoadArgumentsObjectArgExistsResult(
    ObjOperandId objId, Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegisterMaybeOutput scratch2(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadArgumentsObjectElementExists(obj, index, scratch2, scratch1,
                                        failure->label());
  EmitStoreResult(masm, scratch2, JSVAL_TYPE_BOOLEAN, output);
  return true;
}

bool CacheIRCompiler::emitLoadDenseElementResult(ObjOperandId objId,
                                                 Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegisterMaybeOutput scratch2(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Load obj->elements.
  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch1);

  // Bounds check.
  Address initLength(scratch1, ObjectElements::offsetOfInitializedLength());
  masm.spectreBoundsCheck32(index, initLength, scratch2, failure->label());

  // Hole check.
  BaseObjectElementIndex element(scratch1, index);
  masm.branchTestMagic(Assembler::Equal, element, failure->label());
  masm.loadTypedOrValue(element, output);
  return true;
}

bool CacheIRCompiler::emitGuardInt32IsNonNegative(Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register index = allocator.useRegister(masm, indexId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branch32(Assembler::LessThan, index, Imm32(0), failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardIndexIsNotDenseElement(ObjOperandId objId,
                                                      Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegister scratch(allocator, masm);
  AutoSpectreBoundsScratchRegister spectreScratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Load obj->elements.
  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);

  // Ensure index >= initLength or the element is a hole.
  Label notDense;
  Address capacity(scratch, ObjectElements::offsetOfInitializedLength());
  masm.spectreBoundsCheck32(index, capacity, spectreScratch, ¬Dense);

  BaseValueIndex element(scratch, index);
  masm.branchTestMagic(Assembler::Equal, element, ¬Dense);

  masm.jump(failure->label());

  masm.bind(¬Dense);
  return true;
}

bool CacheIRCompiler::emitGuardIndexIsValidUpdateOrAdd(ObjOperandId objId,
                                                       Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegister scratch(allocator, masm);
  AutoSpectreBoundsScratchRegister spectreScratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Load obj->elements.
  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);

  Label success;

  // If length is writable, branch to &success.  All indices are writable.
  Address flags(scratch, ObjectElements::offsetOfFlags());
  masm.branchTest32(Assembler::Zero, flags,
                    Imm32(ObjectElements::Flags::NONWRITABLE_ARRAY_LENGTH),
                    &success);

  // Otherwise, ensure index is in bounds.
  Address length(scratch, ObjectElements::offsetOfLength());
  masm.spectreBoundsCheck32(index, length, spectreScratch,
                            /* failure = */ failure->label());
  masm.bind(&success);
  return true;
}

bool CacheIRCompiler::emitGuardTagNotEqual(ValueTagOperandId lhsId,
                                           ValueTagOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label done;
  masm.branch32(Assembler::Equal, lhs, rhs, failure->label());

  // If both lhs and rhs are numbers, can't use tag comparison to do inequality
  // comparison
  masm.branchTestNumber(Assembler::NotEqual, lhs, &done);
  masm.branchTestNumber(Assembler::NotEqual, rhs, &done);
  masm.jump(failure->label());

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitGuardXrayExpandoShapeAndDefaultProto(
    ObjOperandId objId, uint32_t shapeWrapperOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  StubFieldOffset shapeWrapper(shapeWrapperOffset, StubField::Type::JSObject);

  AutoScratchRegister scratch(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), scratch);
  Address holderAddress(scratch,
                        sizeof(Value) * GetXrayJitInfo()->xrayHolderSlot);
  Address expandoAddress(scratch, NativeObject::getFixedSlotOffset(
                                      GetXrayJitInfo()->holderExpandoSlot));

  masm.fallibleUnboxObject(holderAddress, scratch, failure->label());
  masm.fallibleUnboxObject(expandoAddress, scratch, failure->label());

  // Unwrap the expando before checking its shape.
  masm.loadPtr(Address(scratch, ProxyObject::offsetOfReservedSlots()), scratch);
  masm.unboxObject(
      Address(scratch, js::detail::ProxyReservedSlots::offsetOfPrivateSlot()),
      scratch);

  emitLoadStubField(shapeWrapper, scratch2);
  LoadShapeWrapperContents(masm, scratch2, scratch2, failure->label());
  masm.branchTestObjShape(Assembler::NotEqual, scratch, scratch2, scratch3,
                          scratch, failure->label());

  // The reserved slots on the expando should all be in fixed slots.
  Address protoAddress(scratch, NativeObject::getFixedSlotOffset(
                                    GetXrayJitInfo()->expandoProtoSlot));
  masm.branchTestUndefined(Assembler::NotEqual, protoAddress, failure->label());

  return true;
}

bool CacheIRCompiler::emitGuardXrayNoExpando(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadPtr(Address(obj, ProxyObject::offsetOfReservedSlots()), scratch);
  Address holderAddress(scratch,
                        sizeof(Value) * GetXrayJitInfo()->xrayHolderSlot);
  Address expandoAddress(scratch, NativeObject::getFixedSlotOffset(
                                      GetXrayJitInfo()->holderExpandoSlot));

  Label done;
  masm.fallibleUnboxObject(holderAddress, scratch, &done);
  masm.branchTestObject(Assembler::Equal, expandoAddress, failure->label());
  masm.bind(&done);

  return true;
}

bool CacheIRCompiler::emitGuardNoAllocationMetadataBuilder(
    uint32_t builderAddrOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  StubFieldOffset builderField(builderAddrOffset, StubField::Type::RawPointer);
  emitLoadStubField(builderField, scratch);
  masm.branchPtr(Assembler::NotEqual, Address(scratch, 0), ImmWord(0),
                 failure->label());

  return true;
}

bool CacheIRCompiler::emitGuardFunctionHasJitEntry(ObjOperandId funId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register fun = allocator.useRegister(masm, funId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchIfFunctionHasNoJitEntry(fun, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardFunctionHasNoJitEntry(ObjOperandId funId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, funId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchIfFunctionHasJitEntry(obj, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardFunctionIsNonBuiltinCtor(ObjOperandId funId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register fun = allocator.useRegister(masm, funId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchIfNotFunctionIsNonBuiltinCtor(fun, scratch, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardFunctionIsConstructor(ObjOperandId funId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register funcReg = allocator.useRegister(masm, funId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Ensure obj is a constructor
  masm.branchTestFunctionFlags(funcReg, FunctionFlags::CONSTRUCTOR,
                               Assembler::Zero, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardNotClassConstructor(ObjOperandId funId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register fun = allocator.useRegister(masm, funId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchFunctionKind(Assembler::Equal, FunctionFlags::ClassConstructor,
                          fun, scratch, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardArrayIsPacked(ObjOperandId arrayId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register array = allocator.useRegister(masm, arrayId);
  AutoScratchRegister scratch(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchArrayIsNotPacked(array, scratch, scratch2, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardArgumentsObjectFlags(ObjOperandId objId,
                                                    uint8_t flags) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchTestArgumentsObjectFlags(obj, scratch, flags, Assembler::NonZero,
                                      failure->label());
  return true;
}

bool CacheIRCompiler::emitLoadDenseElementHoleResult(ObjOperandId objId,
                                                     Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegisterMaybeOutput scratch2(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Make sure the index is nonnegative.
  masm.branch32(Assembler::LessThan, index, Imm32(0), failure->label());

  // Load obj->elements.
  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch1);

  // Guard on the initialized length.
  Label hole;
  Address initLength(scratch1, ObjectElements::offsetOfInitializedLength());
  masm.spectreBoundsCheck32(index, initLength, scratch2, &hole);

  // Load the value.
  Label done;
  masm.loadValue(BaseObjectElementIndex(scratch1, index), output.valueReg());
  masm.branchTestMagic(Assembler::NotEqual, output.valueReg(), &done);

  // Load undefined for the hole.
  masm.bind(&hole);
  masm.moveValue(UndefinedValue(), output.valueReg());

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitLoadTypedArrayElementExistsResult(
    ObjOperandId objId, IntPtrOperandId indexId, ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Maybe<AutoScratchRegister> scratch2;
  if (viewKind == ArrayBufferViewKind::Resizable) {
    scratch2.emplace(allocator, masm);
  }

  Label outOfBounds, done;

  // Bounds check.
  if (viewKind == ArrayBufferViewKind::FixedLength) {
    masm.loadArrayBufferViewLengthIntPtr(obj, scratch);
  } else {
    // Bounds check doesn't require synchronization. See IsValidIntegerIndex
    // abstract operation which reads the underlying buffer byte length using
    // "unordered" memory order.
    auto sync = Synchronization::None();

    masm.loadResizableTypedArrayLengthIntPtr(sync, obj, scratch, *scratch2);
  }
  masm.branchPtr(Assembler::BelowOrEqual, scratch, index, &outOfBounds);
  EmitStoreBoolean(masm, true, output);
  masm.jump(&done);

  masm.bind(&outOfBounds);
  EmitStoreBoolean(masm, false, output);

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitLoadDenseElementExistsResult(ObjOperandId objId,
                                                       Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Load obj->elements.
  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);

  // Bounds check. Unsigned compare sends negative indices to next IC.
  Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
  masm.branch32(Assembler::BelowOrEqual, initLength, index, failure->label());

  // Hole check.
  BaseObjectElementIndex element(scratch, index);
  masm.branchTestMagic(Assembler::Equal, element, failure->label());

  EmitStoreBoolean(masm, true, output);
  return true;
}

bool CacheIRCompiler::emitLoadDenseElementHoleExistsResult(
    ObjOperandId objId, Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Make sure the index is nonnegative.
  masm.branch32(Assembler::LessThan, index, Imm32(0), failure->label());

  // Load obj->elements.
  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);

  // Guard on the initialized length.
  Label hole;
  Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
  masm.branch32(Assembler::BelowOrEqual, initLength, index, &hole);

  // Load value and replace with true.
  Label done;
  BaseObjectElementIndex element(scratch, index);
  masm.branchTestMagic(Assembler::Equal, element, &hole);
  EmitStoreBoolean(masm, true, output);
  masm.jump(&done);

  // Load false for the hole.
  masm.bind(&hole);
  EmitStoreBoolean(masm, false, output);

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitPackedArrayPopResult(ObjOperandId arrayId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register array = allocator.useRegister(masm, arrayId);
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.packedArrayPop(array, output.valueReg(), scratch1, scratch2,
                      failure->label());
  return true;
}

bool CacheIRCompiler::emitPackedArrayShiftResult(ObjOperandId arrayId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register array = allocator.useRegister(masm, arrayId);
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.packedArrayShift(array, output.valueReg(), scratch1, scratch2,
                        liveVolatileRegs(), failure->label());
  return true;
}

bool CacheIRCompiler::emitIsObjectResult(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  ValueOperand val = allocator.useValueRegister(masm, inputId);

  masm.testObjectSet(Assembler::Equal, val, scratch);

  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitIsPackedArrayResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister scratch(allocator, masm);

  Register outputScratch = output.valueReg().scratchReg();
  masm.setIsPackedArray(obj, outputScratch, scratch);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, outputScratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitIsCallableResult(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegisterMaybeOutput scratch2(allocator, masm, output);

  ValueOperand val = allocator.useValueRegister(masm, inputId);

  Label isObject, done;
  masm.branchTestObject(Assembler::Equal, val, &isObject);
  // Primitives are never callable.
  masm.move32(Imm32(0), scratch2);
  masm.jump(&done);

  masm.bind(&isObject);
  masm.unboxObject(val, scratch1);

  Label isProxy;
  masm.isCallable(scratch1, scratch2, &isProxy);
  masm.jump(&done);

  masm.bind(&isProxy);
  {
    LiveRegisterSet volatileRegs = liveVolatileRegs();
    masm.PushRegsInMask(volatileRegs);

    using Fn = bool (*)(JSObject* obj);
    masm.setupUnalignedABICall(scratch2);
    masm.passABIArg(scratch1);
    masm.callWithABI<Fn, ObjectIsCallable>();
    masm.storeCallBoolResult(scratch2);

    LiveRegisterSet ignore;
    ignore.add(scratch2);
    masm.PopRegsInMaskIgnore(volatileRegs, ignore);
  }

  masm.bind(&done);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch2, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitIsConstructorResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  Register obj = allocator.useRegister(masm, objId);

  Label isProxy, done;
  masm.isConstructor(obj, scratch, &isProxy);
  masm.jump(&done);

  masm.bind(&isProxy);
  {
    LiveRegisterSet volatileRegs = liveVolatileRegs();
    masm.PushRegsInMask(volatileRegs);

    using Fn = bool (*)(JSObject* obj);
    masm.setupUnalignedABICall(scratch);
    masm.passABIArg(obj);
    masm.callWithABI<Fn, ObjectIsConstructor>();
    masm.storeCallBoolResult(scratch);

    LiveRegisterSet ignore;
    ignore.add(scratch);
    masm.PopRegsInMaskIgnore(volatileRegs, ignore);
  }

  masm.bind(&done);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitIsCrossRealmArrayConstructorResult(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Register obj = allocator.useRegister(masm, objId);

  masm.setIsCrossRealmArrayConstructor(obj, scratch);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitArrayBufferViewByteOffsetInt32Result(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Register obj = allocator.useRegister(masm, objId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadArrayBufferViewByteOffsetIntPtr(obj, scratch);
  masm.guardNonNegativeIntPtrToInt32(scratch, failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitArrayBufferViewByteOffsetDoubleResult(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  ScratchDoubleScope fpscratch(masm);
  masm.loadArrayBufferViewByteOffsetIntPtr(obj, scratch);
  masm.convertIntPtrToDouble(scratch, fpscratch);
  masm.boxDouble(fpscratch, output.valueReg(), fpscratch);
  return true;
}

bool CacheIRCompiler::
    emitResizableTypedArrayByteOffsetMaybeOutOfBoundsInt32Result(
        ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadResizableTypedArrayByteOffsetMaybeOutOfBoundsIntPtr(obj, scratch1,
                                                               scratch2);
  masm.guardNonNegativeIntPtrToInt32(scratch1, failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, scratch1, output.valueReg());
  return true;
}

bool CacheIRCompiler::
    emitResizableTypedArrayByteOffsetMaybeOutOfBoundsDoubleResult(
        ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);

  ScratchDoubleScope fpscratch(masm);
  masm.loadResizableTypedArrayByteOffsetMaybeOutOfBoundsIntPtr(obj, scratch1,
                                                               scratch2);
  masm.convertIntPtrToDouble(scratch1, fpscratch);
  masm.boxDouble(fpscratch, output.valueReg(), fpscratch);
  return true;
}

bool CacheIRCompiler::emitTypedArrayByteLengthInt32Result(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadArrayBufferViewLengthIntPtr(obj, scratch1);
  masm.guardNonNegativeIntPtrToInt32(scratch1, failure->label());
  masm.typedArrayElementSize(obj, scratch2);

  masm.branchMul32(Assembler::Overflow, scratch2.get(), scratch1,
                   failure->label());

  masm.tagValue(JSVAL_TYPE_INT32, scratch1, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitTypedArrayByteLengthDoubleResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);

  masm.loadArrayBufferViewLengthIntPtr(obj, scratch1);
  masm.typedArrayElementSize(obj, scratch2);
  masm.mulPtr(scratch2, scratch1);

  ScratchDoubleScope fpscratch(masm);
  masm.convertIntPtrToDouble(scratch1, fpscratch);
  masm.boxDouble(fpscratch, output.valueReg(), fpscratch);
  return true;
}

bool CacheIRCompiler::emitResizableTypedArrayByteLengthInt32Result(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Explicit |byteLength| accesses are seq-consistent atomic loads.
  auto sync = Synchronization::Load();

  masm.loadResizableTypedArrayLengthIntPtr(sync, obj, scratch1, scratch2);
  masm.guardNonNegativeIntPtrToInt32(scratch1, failure->label());
  masm.typedArrayElementSize(obj, scratch2);

  masm.branchMul32(Assembler::Overflow, scratch2.get(), scratch1,
                   failure->label());

  masm.tagValue(JSVAL_TYPE_INT32, scratch1, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitResizableTypedArrayByteLengthDoubleResult(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);

  // Explicit |byteLength| accesses are seq-consistent atomic loads.
  auto sync = Synchronization::Load();

  masm.loadResizableTypedArrayLengthIntPtr(sync, obj, scratch1, scratch2);
  masm.typedArrayElementSize(obj, scratch2);
  masm.mulPtr(scratch2, scratch1);

  ScratchDoubleScope fpscratch(masm);
  masm.convertIntPtrToDouble(scratch1, fpscratch);
  masm.boxDouble(fpscratch, output.valueReg(), fpscratch);
  return true;
}

bool CacheIRCompiler::emitResizableTypedArrayLengthInt32Result(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Explicit |length| accesses are seq-consistent atomic loads.
  auto sync = Synchronization::Load();

  masm.loadResizableTypedArrayLengthIntPtr(sync, obj, scratch1, scratch2);
  masm.guardNonNegativeIntPtrToInt32(scratch1, failure->label());

  masm.tagValue(JSVAL_TYPE_INT32, scratch1, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitResizableTypedArrayLengthDoubleResult(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);

  // Explicit |length| accesses are seq-consistent atomic loads.
  auto sync = Synchronization::Load();

  masm.loadResizableTypedArrayLengthIntPtr(sync, obj, scratch1, scratch2);

  ScratchDoubleScope fpscratch(masm);
  masm.convertIntPtrToDouble(scratch1, fpscratch);
  masm.boxDouble(fpscratch, output.valueReg(), fpscratch);
  return true;
}

bool CacheIRCompiler::emitTypedArrayElementSizeResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Register obj = allocator.useRegister(masm, objId);

  masm.typedArrayElementSize(obj, scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitResizableDataViewByteLengthInt32Result(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Explicit |byteLength| accesses are seq-consistent atomic loads.
  auto sync = Synchronization::Load();

  masm.loadResizableDataViewByteLengthIntPtr(sync, obj, scratch1, scratch2);
  masm.guardNonNegativeIntPtrToInt32(scratch1, failure->label());

  masm.tagValue(JSVAL_TYPE_INT32, scratch1, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitResizableDataViewByteLengthDoubleResult(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);

  // Explicit |byteLength| accesses are seq-consistent atomic loads.
  auto sync = Synchronization::Load();

  masm.loadResizableDataViewByteLengthIntPtr(sync, obj, scratch1, scratch2);

  ScratchDoubleScope fpscratch(masm);
  masm.convertIntPtrToDouble(scratch1, fpscratch);
  masm.boxDouble(fpscratch, output.valueReg(), fpscratch);
  return true;
}

bool CacheIRCompiler::emitGrowableSharedArrayBufferByteLengthInt32Result(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Register obj = allocator.useRegister(masm, objId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Explicit |byteLength| accesses are seq-consistent atomic loads.
  auto sync = Synchronization::Load();

  masm.loadGrowableSharedArrayBufferByteLengthIntPtr(sync, obj, scratch);
  masm.guardNonNegativeIntPtrToInt32(scratch, failure->label());

  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitGrowableSharedArrayBufferByteLengthDoubleResult(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Register obj = allocator.useRegister(masm, objId);

  // Explicit |byteLength| accesses are seq-consistent atomic loads.
  auto sync = Synchronization::Load();

  masm.loadGrowableSharedArrayBufferByteLengthIntPtr(sync, obj, scratch);

  ScratchDoubleScope fpscratch(masm);
  masm.convertIntPtrToDouble(scratch, fpscratch);
  masm.boxDouble(fpscratch, output.valueReg(), fpscratch);
  return true;
}

bool CacheIRCompiler::emitGuardHasAttachedArrayBuffer(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoScratchRegister scratch(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchIfHasDetachedArrayBuffer(obj, scratch, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardResizableArrayBufferViewInBounds(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoScratchRegister scratch(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchIfResizableArrayBufferViewOutOfBounds(obj, scratch,
                                                   failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardResizableArrayBufferViewInBoundsOrDetached(
    ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoScratchRegister scratch(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label done;
  masm.branchIfResizableArrayBufferViewInBounds(obj, scratch, &done);
  masm.branchIfHasAttachedArrayBuffer(obj, scratch, failure->label());
  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitIsTypedArrayConstructorResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Register obj = allocator.useRegister(masm, objId);

  masm.setIsDefinitelyTypedArrayConstructor(obj, scratch);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitGetNextMapSetEntryForIteratorResult(
    ObjOperandId iterId, ObjOperandId resultArrId, bool isMap) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Register iter = allocator.useRegister(masm, iterId);
  Register resultArr = allocator.useRegister(masm, resultArrId);

  LiveRegisterSet save = liveVolatileRegs();
  save.takeUnchecked(output.valueReg());
  save.takeUnchecked(scratch);
  masm.PushRegsInMask(save);

  masm.setupUnalignedABICall(scratch);
  masm.passABIArg(iter);
  masm.passABIArg(resultArr);
  if (isMap) {
    using Fn = bool (*)(MapIteratorObject* iter, ArrayObject* resultPairObj);
    masm.callWithABI<Fn, MapIteratorObject::next>();
  } else {
    using Fn = bool (*)(SetIteratorObject* iter, ArrayObject* resultObj);
    masm.callWithABI<Fn, SetIteratorObject::next>();
  }
  masm.storeCallBoolResult(scratch);

  masm.PopRegsInMask(save);

  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg());
  return true;
}

void CacheIRCompiler::emitActivateIterator(Register objBeingIterated,
                                           Register iterObject,
                                           Register nativeIter,
                                           Register scratch, Register scratch2,
                                           uint32_t enumeratorsAddrOffset) {
  // 'objectBeingIterated_' must be nullptr, so we don't need a pre-barrier.
  Address iterObjAddr(nativeIter,
                      NativeIterator::offsetOfObjectBeingIterated());
#ifdef DEBUG
  Label ok;
  masm.branchPtr(Assembler::Equal, iterObjAddr, ImmPtr(nullptr), &ok);
  masm.assumeUnreachable("iterator with non-null object");
  masm.bind(&ok);
#endif

  // Mark iterator as active.
  Address iterFlagsAddr(nativeIter, NativeIterator::offsetOfFlagsAndCount());
  masm.storePtr(objBeingIterated, iterObjAddr);
  masm.or32(Imm32(NativeIterator::Flags::Active), iterFlagsAddr);

  // Post-write barrier for stores to 'objectBeingIterated_'.
  emitPostBarrierSlot(
      iterObject,
      TypedOrValueRegister(MIRType::Object, AnyRegister(objBeingIterated)),
      scratch);

  // Chain onto the active iterator stack.
  StubFieldOffset enumeratorsAddr(enumeratorsAddrOffset,
                                  StubField::Type::RawPointer);
  emitLoadStubField(enumeratorsAddr, scratch);
  masm.registerIterator(scratch, nativeIter, scratch2);
}

bool CacheIRCompiler::emitObjectToIteratorResult(
    ObjOperandId objId, uint32_t enumeratorsAddrOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);
  Register obj = allocator.useRegister(masm, objId);

  AutoScratchRegister iterObj(allocator, masm);
  AutoScratchRegister scratch(allocator, masm);
  AutoScratchRegisterMaybeOutput scratch2(allocator, masm, callvm.output());
  AutoScratchRegisterMaybeOutputType scratch3(allocator, masm, callvm.output());

  Label callVM, done;
  masm.maybeLoadIteratorFromShape(obj, iterObj, scratch, scratch2, scratch3,
                                  &callVM);

  masm.loadPrivate(
      Address(iterObj, PropertyIteratorObject::offsetOfIteratorSlot()),
      scratch);

  emitActivateIterator(obj, iterObj, scratch, scratch2, scratch3,
                       enumeratorsAddrOffset);
  masm.jump(&done);

  masm.bind(&callVM);
  callvm.prepare();
  masm.Push(obj);
  using Fn = PropertyIteratorObject* (*)(JSContext*, HandleObject);
  callvm.call<Fn, GetIterator>();
  masm.storeCallPointerResult(iterObj);

  masm.bind(&done);
  EmitStoreResult(masm, iterObj, JSVAL_TYPE_OBJECT, callvm.output());
  return true;
}

bool CacheIRCompiler::emitValueToIteratorResult(ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  ValueOperand val = allocator.useValueRegister(masm, valId);

  callvm.prepare();

  masm.Push(val);

  using Fn = PropertyIteratorObject* (*)(JSContext*, HandleValue);
  callvm.call<Fn, ValueToIterator>();
  return true;
}

bool CacheIRCompiler::emitNewArrayIteratorResult(
    uint32_t templateObjectOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  callvm.prepare();

  using Fn = ArrayIteratorObject* (*)(JSContext*);
  callvm.call<Fn, NewArrayIterator>();
  return true;
}

bool CacheIRCompiler::emitNewStringIteratorResult(
    uint32_t templateObjectOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  callvm.prepare();

  using Fn = StringIteratorObject* (*)(JSContext*);
  callvm.call<Fn, NewStringIterator>();
  return true;
}

bool CacheIRCompiler::emitNewRegExpStringIteratorResult(
    uint32_t templateObjectOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  callvm.prepare();

  using Fn = RegExpStringIteratorObject* (*)(JSContext*);
  callvm.call<Fn, NewRegExpStringIterator>();
  return true;
}

bool CacheIRCompiler::emitObjectCreateResult(uint32_t templateObjectOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);
  AutoScratchRegister scratch(allocator, masm);

  StubFieldOffset objectField(templateObjectOffset, StubField::Type::JSObject);
  emitLoadStubField(objectField, scratch);

  callvm.prepare();
  masm.Push(scratch);

  using Fn = PlainObject* (*)(JSContext*, Handle<PlainObject*>);
  callvm.call<Fn, ObjectCreateWithTemplate>();
  return true;
}

bool CacheIRCompiler::emitObjectKeysResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);
  Register obj = allocator.useRegister(masm, objId);

  // Our goal is only to record calls to Object.keys, to elide it when
  // partially used, not to provide an alternative implementation.
  {
    callvm.prepare();
    masm.Push(obj);

    using Fn = JSObject* (*)(JSContext*, HandleObject);
    callvm.call<Fn, jit::ObjectKeys>();
  }

  return true;
}

bool CacheIRCompiler::emitNewArrayFromLengthResult(
    uint32_t templateObjectOffset, Int32OperandId lengthId,
    uint32_t siteOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);
  AutoScratchRegister scratch(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  Register length = allocator.useRegister(masm, lengthId);

  StubFieldOffset objectField(templateObjectOffset, StubField::Type::JSObject);
  emitLoadStubField(objectField, scratch);

  StubFieldOffset siteField(siteOffset, StubField::Type::AllocSite);
  emitLoadStubField(siteField, scratch2);

  callvm.prepare();
  masm.Push(scratch2);
  masm.Push(length);
  masm.Push(scratch);

  using Fn = ArrayObject* (*)(JSContext*, Handle<ArrayObject*>, int32_t,
                              gc::AllocSite*);
  callvm.call<Fn, ArrayConstructorOneArg>();
  return true;
}

bool CacheIRCompiler::emitNewTypedArrayFromLengthResult(
    uint32_t templateObjectOffset, Int32OperandId lengthId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);
  AutoScratchRegister scratch(allocator, masm);
  Register length = allocator.useRegister(masm, lengthId);

  StubFieldOffset objectField(templateObjectOffset, StubField::Type::JSObject);
  emitLoadStubField(objectField, scratch);

  callvm.prepare();
  masm.Push(length);
  masm.Push(scratch);

  using Fn = TypedArrayObject* (*)(JSContext*, HandleObject, int32_t length);
  callvm.call<Fn, NewTypedArrayWithTemplateAndLength>();
  return true;
}

bool CacheIRCompiler::emitNewTypedArrayFromArrayBufferResult(
    uint32_t templateObjectOffset, ObjOperandId bufferId,
    ValOperandId byteOffsetId, ValOperandId lengthId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

#ifdef JS_CODEGEN_X86
  MOZ_CRASH("Instruction not supported on 32-bit x86, not enough registers");
#endif

  AutoCallVM callvm(masm, this, allocator);
  AutoScratchRegister scratch(allocator, masm);
  Register buffer = allocator.useRegister(masm, bufferId);
  ValueOperand byteOffset = allocator.useValueRegister(masm, byteOffsetId);
  ValueOperand length = allocator.useValueRegister(masm, lengthId);

  StubFieldOffset objectField(templateObjectOffset, StubField::Type::JSObject);
  emitLoadStubField(objectField, scratch);

  callvm.prepare();
  masm.Push(length);
  masm.Push(byteOffset);
  masm.Push(buffer);
  masm.Push(scratch);

  using Fn = TypedArrayObject* (*)(JSContext*, HandleObject, HandleObject,
                                   HandleValue, HandleValue);
  callvm.call<Fn, NewTypedArrayWithTemplateAndBuffer>();
  return true;
}

bool CacheIRCompiler::emitNewTypedArrayFromArrayResult(
    uint32_t templateObjectOffset, ObjOperandId arrayId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);
  AutoScratchRegister scratch(allocator, masm);
  Register array = allocator.useRegister(masm, arrayId);

  StubFieldOffset objectField(templateObjectOffset, StubField::Type::JSObject);
  emitLoadStubField(objectField, scratch);

  callvm.prepare();
  masm.Push(array);
  masm.Push(scratch);

  using Fn = TypedArrayObject* (*)(JSContext*, HandleObject, HandleObject);
  callvm.call<Fn, NewTypedArrayWithTemplateAndArray>();
  return true;
}

bool CacheIRCompiler::emitAddSlotAndCallAddPropHook(ObjOperandId objId,
                                                    ValOperandId rhsId,
                                                    uint32_t newShapeOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  AutoScratchRegister scratch(allocator, masm);
  Register obj = allocator.useRegister(masm, objId);
  ValueOperand rhs = allocator.useValueRegister(masm, rhsId);

  StubFieldOffset shapeField(newShapeOffset, StubField::Type::Shape);
  emitLoadStubField(shapeField, scratch);

  callvm.prepare();

  masm.Push(scratch);
  masm.Push(rhs);
  masm.Push(obj);

  using Fn =
      bool (*)(JSContext*, Handle<NativeObject*>, HandleValue, Handle<Shape*>);
  callvm.callNoResult<Fn, AddSlotAndCallAddPropHook>();
  return true;
}

bool CacheIRCompiler::emitMathAbsInt32Result(Int32OperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  Register input = allocator.useRegister(masm, inputId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.mov(input, scratch);
  // Don't negate already positive values.
  Label positive;
  masm.branchTest32(Assembler::NotSigned, scratch, scratch, &positive);
  // neg32 might overflow for INT_MIN.
  masm.branchNeg32(Assembler::Overflow, scratch, failure->label());
  masm.bind(&positive);

  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitMathAbsNumberResult(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoAvailableFloatRegister scratch(*this, FloatReg0);

  allocator.ensureDoubleRegister(masm, inputId, scratch);

  masm.absDouble(scratch, scratch);
  masm.boxDouble(scratch, output.valueReg(), scratch);
  return true;
}

bool CacheIRCompiler::emitMathClz32Result(Int32OperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Register input = allocator.useRegister(masm, inputId);

  masm.clz32(input, scratch, /* knownNotZero = */ false);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitMathSignInt32Result(Int32OperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Register input = allocator.useRegister(masm, inputId);

  masm.signInt32(input, scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitMathSignNumberResult(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch2(*this, FloatReg1);

  allocator.ensureDoubleRegister(masm, inputId, floatScratch1);

  masm.signDouble(floatScratch1, floatScratch2);
  masm.boxDouble(floatScratch2, output.valueReg(), floatScratch2);
  return true;
}

bool CacheIRCompiler::emitMathSignNumberToInt32Result(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch2(*this, FloatReg1);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  allocator.ensureDoubleRegister(masm, inputId, floatScratch1);

  masm.signDoubleToInt32(floatScratch1, scratch, floatScratch2,
                         failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitMathImulResult(Int32OperandId lhsId,
                                         Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);

  masm.mov(lhs, scratch);
  masm.mul32(rhs, scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitMathSqrtNumberResult(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoAvailableFloatRegister scratch(*this, FloatReg0);

  allocator.ensureDoubleRegister(masm, inputId, scratch);

  masm.sqrtDouble(scratch, scratch);
  masm.boxDouble(scratch, output.valueReg(), scratch);
  return true;
}

bool CacheIRCompiler::emitMathFloorNumberResult(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoAvailableFloatRegister scratch(*this, FloatReg0);

  allocator.ensureDoubleRegister(masm, inputId, scratch);

  if (Assembler::HasRoundInstruction(RoundingMode::Down)) {
    masm.nearbyIntDouble(RoundingMode::Down, scratch, scratch);
    masm.boxDouble(scratch, output.valueReg(), scratch);
    return true;
  }

  return emitMathFunctionNumberResultShared(UnaryMathFunction::Floor, scratch,
                                            output.valueReg());
}

bool CacheIRCompiler::emitMathCeilNumberResult(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoAvailableFloatRegister scratch(*this, FloatReg0);

  allocator.ensureDoubleRegister(masm, inputId, scratch);

  if (Assembler::HasRoundInstruction(RoundingMode::Up)) {
    masm.nearbyIntDouble(RoundingMode::Up, scratch, scratch);
    masm.boxDouble(scratch, output.valueReg(), scratch);
    return true;
  }

  return emitMathFunctionNumberResultShared(UnaryMathFunction::Ceil, scratch,
                                            output.valueReg());
}

bool CacheIRCompiler::emitMathTruncNumberResult(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoAvailableFloatRegister scratch(*this, FloatReg0);

  allocator.ensureDoubleRegister(masm, inputId, scratch);

  if (Assembler::HasRoundInstruction(RoundingMode::TowardsZero)) {
    masm.nearbyIntDouble(RoundingMode::TowardsZero, scratch, scratch);
    masm.boxDouble(scratch, output.valueReg(), scratch);
    return true;
  }

  return emitMathFunctionNumberResultShared(UnaryMathFunction::Trunc, scratch,
                                            output.valueReg());
}

bool CacheIRCompiler::emitMathFRoundNumberResult(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoAvailableFloatRegister scratch(*this, FloatReg0);
  FloatRegister scratchFloat32 = scratch.get().asSingle();

  allocator.ensureDoubleRegister(masm, inputId, scratch);

  masm.convertDoubleToFloat32(scratch, scratchFloat32);
  masm.convertFloat32ToDouble(scratchFloat32, scratch);

  masm.boxDouble(scratch, output.valueReg(), scratch);
  return true;
}

bool CacheIRCompiler::emitMathF16RoundNumberResult(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  AutoAvailableFloatRegister floatScratch(*this, FloatReg0);

  allocator.ensureDoubleRegister(masm, inputId, floatScratch);

  if (MacroAssembler::SupportsFloat64To16()) {
    masm.convertDoubleToFloat16(floatScratch, floatScratch);
    masm.convertFloat16ToDouble(floatScratch, floatScratch);
  } else {
    LiveRegisterSet save = liveVolatileRegs();
    save.takeUnchecked(floatScratch);
    masm.PushRegsInMask(save);

    using Fn = double (*)(double);
    masm.setupUnalignedABICall(scratch);
    masm.passABIArg(floatScratch, ABIType::Float64);
    masm.callWithABI<Fn, js::RoundFloat16>(ABIType::Float64);
    masm.storeCallFloatResult(floatScratch);

    masm.PopRegsInMask(save);
  }

  masm.boxDouble(floatScratch, output.valueReg(), floatScratch);
  return true;
}

bool CacheIRCompiler::emitMathHypot2NumberResult(NumberOperandId first,
                                                 NumberOperandId second) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg1);

  allocator.ensureDoubleRegister(masm, first, floatScratch0);
  allocator.ensureDoubleRegister(masm, second, floatScratch1);

  LiveRegisterSet save = liveVolatileRegs();
  masm.PushRegsInMask(save);

  using Fn = double (*)(double x, double y);
  masm.setupUnalignedABICall(scratch);
  masm.passABIArg(floatScratch0, ABIType::Float64);
  masm.passABIArg(floatScratch1, ABIType::Float64);

  masm.callWithABI<Fn, ecmaHypot>(ABIType::Float64);
  masm.storeCallFloatResult(floatScratch0);

  LiveRegisterSet ignore;
  ignore.add(floatScratch0);
  masm.PopRegsInMaskIgnore(save, ignore);

  masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);
  return true;
}

bool CacheIRCompiler::emitMathHypot3NumberResult(NumberOperandId first,
                                                 NumberOperandId second,
                                                 NumberOperandId third) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg1);
  AutoAvailableFloatRegister floatScratch2(*this, FloatReg2);

  allocator.ensureDoubleRegister(masm, first, floatScratch0);
  allocator.ensureDoubleRegister(masm, second, floatScratch1);
  allocator.ensureDoubleRegister(masm, third, floatScratch2);

  LiveRegisterSet save = liveVolatileRegs();
  masm.PushRegsInMask(save);

  using Fn = double (*)(double x, double y, double z);
  masm.setupUnalignedABICall(scratch);
  masm.passABIArg(floatScratch0, ABIType::Float64);
  masm.passABIArg(floatScratch1, ABIType::Float64);
  masm.passABIArg(floatScratch2, ABIType::Float64);

  masm.callWithABI<Fn, hypot3>(ABIType::Float64);
  masm.storeCallFloatResult(floatScratch0);

  LiveRegisterSet ignore;
  ignore.add(floatScratch0);
  masm.PopRegsInMaskIgnore(save, ignore);

  masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);
  return true;
}

bool CacheIRCompiler::emitMathHypot4NumberResult(NumberOperandId first,
                                                 NumberOperandId second,
                                                 NumberOperandId third,
                                                 NumberOperandId fourth) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg1);
  AutoAvailableFloatRegister floatScratch2(*this, FloatReg2);
  AutoAvailableFloatRegister floatScratch3(*this, FloatReg3);

  allocator.ensureDoubleRegister(masm, first, floatScratch0);
  allocator.ensureDoubleRegister(masm, second, floatScratch1);
  allocator.ensureDoubleRegister(masm, third, floatScratch2);
  allocator.ensureDoubleRegister(masm, fourth, floatScratch3);

  LiveRegisterSet save = liveVolatileRegs();
  masm.PushRegsInMask(save);

  using Fn = double (*)(double x, double y, double z, double w);
  masm.setupUnalignedABICall(scratch);
  masm.passABIArg(floatScratch0, ABIType::Float64);
  masm.passABIArg(floatScratch1, ABIType::Float64);
  masm.passABIArg(floatScratch2, ABIType::Float64);
  masm.passABIArg(floatScratch3, ABIType::Float64);

  masm.callWithABI<Fn, hypot4>(ABIType::Float64);
  masm.storeCallFloatResult(floatScratch0);

  LiveRegisterSet ignore;
  ignore.add(floatScratch0);
  masm.PopRegsInMaskIgnore(save, ignore);

  masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);
  return true;
}

bool CacheIRCompiler::emitMathAtan2NumberResult(NumberOperandId yId,
                                                NumberOperandId xId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg1);

  allocator.ensureDoubleRegister(masm, yId, floatScratch0);
  allocator.ensureDoubleRegister(masm, xId, floatScratch1);

  LiveRegisterSet save = liveVolatileRegs();
  masm.PushRegsInMask(save);

  using Fn = double (*)(double x, double y);
  masm.setupUnalignedABICall(scratch);
  masm.passABIArg(floatScratch0, ABIType::Float64);
  masm.passABIArg(floatScratch1, ABIType::Float64);
  masm.callWithABI<Fn, js::ecmaAtan2>(ABIType::Float64);
  masm.storeCallFloatResult(floatScratch0);

  LiveRegisterSet ignore;
  ignore.add(floatScratch0);
  masm.PopRegsInMaskIgnore(save, ignore);

  masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);

  return true;
}

bool CacheIRCompiler::emitMathFloorToInt32Result(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  AutoAvailableFloatRegister scratchFloat(*this, FloatReg0);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  allocator.ensureDoubleRegister(masm, inputId, scratchFloat);

  masm.floorDoubleToInt32(scratchFloat, scratch, failure->label());

  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitMathCeilToInt32Result(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  AutoAvailableFloatRegister scratchFloat(*this, FloatReg0);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  allocator.ensureDoubleRegister(masm, inputId, scratchFloat);

  masm.ceilDoubleToInt32(scratchFloat, scratch, failure->label());

  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitMathTruncToInt32Result(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  AutoAvailableFloatRegister scratchFloat(*this, FloatReg0);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  allocator.ensureDoubleRegister(masm, inputId, scratchFloat);

  masm.truncDoubleToInt32(scratchFloat, scratch, failure->label());

  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitMathRoundToInt32Result(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  AutoAvailableFloatRegister scratchFloat0(*this, FloatReg0);
  AutoAvailableFloatRegister scratchFloat1(*this, FloatReg1);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  allocator.ensureDoubleRegister(masm, inputId, scratchFloat0);

  masm.roundDoubleToInt32(scratchFloat0, scratch, scratchFloat1,
                          failure->label());

  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitInt32MinMax(bool isMax, Int32OperandId firstId,
                                      Int32OperandId secondId,
                                      Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register first = allocator.useRegister(masm, firstId);
  Register second = allocator.useRegister(masm, secondId);
  Register result = allocator.defineRegister(masm, resultId);

  Assembler::Condition cond =
      isMax ? Assembler::GreaterThan : Assembler::LessThan;
  masm.move32(first, result);
  masm.cmp32Move32(cond, second, first, second, result);
  return true;
}

bool CacheIRCompiler::emitNumberMinMax(bool isMax, NumberOperandId firstId,
                                       NumberOperandId secondId,
                                       NumberOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  ValueOperand output = allocator.defineValueRegister(masm, resultId);

  AutoAvailableFloatRegister scratch1(*this, FloatReg0);
  AutoAvailableFloatRegister scratch2(*this, FloatReg1);

  allocator.ensureDoubleRegister(masm, firstId, scratch1);
  allocator.ensureDoubleRegister(masm, secondId, scratch2);

  if (isMax) {
    masm.maxDouble(scratch2, scratch1, /* handleNaN = */ true);
  } else {
    masm.minDouble(scratch2, scratch1, /* handleNaN = */ true);
  }

  masm.boxDouble(scratch1, output, scratch1);
  return true;
}

bool CacheIRCompiler::emitInt32MinMaxArrayResult(ObjOperandId arrayId,
                                                 bool isMax) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register array = allocator.useRegister(masm, arrayId);

  AutoScratchRegister scratch(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegisterMaybeOutputType scratch3(allocator, masm, output);
  AutoScratchRegisterMaybeOutput result(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.minMaxArrayInt32(array, result, scratch, scratch2, scratch3, isMax,
                        failure->label());
  masm.tagValue(JSVAL_TYPE_INT32, result, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitNumberMinMaxArrayResult(ObjOperandId arrayId,
                                                  bool isMax) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register array = allocator.useRegister(masm, arrayId);

  AutoAvailableFloatRegister result(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch(*this, FloatReg1);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.minMaxArrayNumber(array, result, floatScratch, scratch1, scratch2, isMax,
                         failure->label());
  masm.boxDouble(result, output.valueReg(), result);
  return true;
}

bool CacheIRCompiler::emitMathFunctionNumberResultShared(
    UnaryMathFunction fun, FloatRegister inputScratch, ValueOperand output) {
  UnaryMathFunctionType funPtr = GetUnaryMathFunctionPtr(fun);

  LiveRegisterSet save = liveVolatileRegs();
  save.takeUnchecked(inputScratch);
  masm.PushRegsInMask(save);

  masm.setupUnalignedABICall(output.scratchReg());
  masm.passABIArg(inputScratch, ABIType::Float64);
  masm.callWithABI(DynamicFunction<UnaryMathFunctionType>(funPtr),
                   ABIType::Float64);
  masm.storeCallFloatResult(inputScratch);

  masm.PopRegsInMask(save);

  masm.boxDouble(inputScratch, output, inputScratch);
  return true;
}

bool CacheIRCompiler::emitMathFunctionNumberResult(NumberOperandId inputId,
                                                   UnaryMathFunction fun) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoAvailableFloatRegister scratch(*this, FloatReg0);

  allocator.ensureDoubleRegister(masm, inputId, scratch);

  return emitMathFunctionNumberResultShared(fun, scratch, output.valueReg());
}

static void EmitStoreDenseElement(MacroAssembler& masm,
                                  const ConstantOrRegister& value,
                                  BaseObjectElementIndex target) {
  if (value.constant()) {
    Value v = value.value();
    masm.storeValue(v, target);
    return;
  }

  TypedOrValueRegister reg = value.reg();
  masm.storeTypedOrValue(reg, target);
}

bool CacheIRCompiler::emitStoreDenseElement(ObjOperandId objId,
                                            Int32OperandId indexId,
                                            ValOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  ConstantOrRegister val = allocator.useConstantOrRegister(masm, rhsId);

  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Load obj->elements in scratch.
  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);

  // Bounds check. Unfortunately we don't have more registers available on
  // x86, so use InvalidReg and emit slightly slower code on x86.
  Register spectreTemp = InvalidReg;
  Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
  masm.spectreBoundsCheck32(index, initLength, spectreTemp, failure->label());

  // Hole check.
  BaseObjectElementIndex element(scratch, index);
  masm.branchTestMagic(Assembler::Equal, element, failure->label());

  // Perform the store.
  EmitPreBarrier(masm, element, MIRType::Value);
  EmitStoreDenseElement(masm, val, element);

  emitPostBarrierElement(obj, val, scratch, index);
  return true;
}

static void EmitAssertExtensibleElements(MacroAssembler& masm,
                                         Register elementsReg) {
#ifdef DEBUG
  // Preceding shape guards ensure the object elements are extensible.
  Address elementsFlags(elementsReg, ObjectElements::offsetOfFlags());
  Label ok;
  masm.branchTest32(Assembler::Zero, elementsFlags,
                    Imm32(ObjectElements::Flags::NOT_EXTENSIBLE), &ok);
  masm.assumeUnreachable("Unexpected non-extensible elements");
  masm.bind(&ok);
#endif
}

static void EmitAssertWritableArrayLengthElements(MacroAssembler& masm,
                                                  Register elementsReg) {
#ifdef DEBUG
  // Preceding shape guards ensure the array length is writable.
  Address elementsFlags(elementsReg, ObjectElements::offsetOfFlags());
  Label ok;
  masm.branchTest32(Assembler::Zero, elementsFlags,
                    Imm32(ObjectElements::Flags::NONWRITABLE_ARRAY_LENGTH),
                    &ok);
  masm.assumeUnreachable("Unexpected non-writable array length elements");
  masm.bind(&ok);
#endif
}

bool CacheIRCompiler::emitStoreDenseElementHole(ObjOperandId objId,
                                                Int32OperandId indexId,
                                                ValOperandId rhsId,
                                                bool handleAdd) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  ConstantOrRegister val = allocator.useConstantOrRegister(masm, rhsId);

  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Load obj->elements in scratch.
  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);

  EmitAssertExtensibleElements(masm, scratch);
  if (handleAdd) {
    EmitAssertWritableArrayLengthElements(masm, scratch);
  }

  BaseObjectElementIndex element(scratch, index);
  Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
  Address elementsFlags(scratch, ObjectElements::offsetOfFlags());

  // We don't have enough registers on x86 so use InvalidReg. This will emit
  // slightly less efficient code on x86.
  Register spectreTemp = InvalidReg;

  Label storeSkipPreBarrier;
  if (handleAdd) {
    // Bounds check.
    Label inBounds, outOfBounds;
    masm.spectreBoundsCheck32(index, initLength, spectreTemp, &outOfBounds);
    masm.jump(&inBounds);

    // If we're out-of-bounds, only handle the index == initLength case.
    masm.bind(&outOfBounds);
    masm.branch32(Assembler::NotEqual, initLength, index, failure->label());

    // If index < capacity, we can add a dense element inline. If not we
    // need to allocate more elements.
    Label allocElement, addNewElement;
    Address capacity(scratch, ObjectElements::offsetOfCapacity());
    masm.spectreBoundsCheck32(index, capacity, spectreTemp, &allocElement);
    masm.jump(&addNewElement);

    masm.bind(&allocElement);

    LiveRegisterSet save = liveVolatileRegs();
    save.takeUnchecked(scratch);
    masm.PushRegsInMask(save);

    using Fn = bool (*)(JSContext* cx, NativeObject* obj);
    masm.setupUnalignedABICall(scratch);
    masm.loadJSContext(scratch);
    masm.passABIArg(scratch);
    masm.passABIArg(obj);
    masm.callWithABI<Fn, NativeObject::addDenseElementPure>();
    masm.storeCallPointerResult(scratch);

    masm.PopRegsInMask(save);
    masm.branchIfFalseBool(scratch, failure->label());

    // Load the reallocated elements pointer.
    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);

    masm.bind(&addNewElement);

    // Increment initLength.
    masm.add32(Imm32(1), initLength);

    // If length is now <= index, increment length too.
    Label skipIncrementLength;
    Address length(scratch, ObjectElements::offsetOfLength());
    masm.branch32(Assembler::Above, length, index, &skipIncrementLength);
    masm.add32(Imm32(1), length);
    masm.bind(&skipIncrementLength);

    // Skip EmitPreBarrier as the memory is uninitialized.
    masm.jump(&storeSkipPreBarrier);

    masm.bind(&inBounds);
  } else {
    // Fail if index >= initLength.
    masm.spectreBoundsCheck32(index, initLength, spectreTemp, failure->label());
  }

  EmitPreBarrier(masm, element, MIRType::Value);

  masm.bind(&storeSkipPreBarrier);
  EmitStoreDenseElement(masm, val, element);

  emitPostBarrierElement(obj, val, scratch, index);
  return true;
}

bool CacheIRCompiler::emitArrayPush(ObjOperandId objId, ValOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  ValueOperand val = allocator.useValueRegister(masm, rhsId);

  AutoScratchRegisterMaybeOutput scratchLength(allocator, masm, output);
  AutoScratchRegisterMaybeOutputType scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Load obj->elements in scratch.
  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);

  EmitAssertExtensibleElements(masm, scratch);
  EmitAssertWritableArrayLengthElements(masm, scratch);

  Address elementsInitLength(scratch,
                             ObjectElements::offsetOfInitializedLength());
  Address elementsLength(scratch, ObjectElements::offsetOfLength());
  Address capacity(scratch, ObjectElements::offsetOfCapacity());

  // Fail if length != initLength.
  masm.load32(elementsInitLength, scratchLength);
  masm.branch32(Assembler::NotEqual, elementsLength, scratchLength,
                failure->label());

  // If scratchLength < capacity, we can add a dense element inline. If not we
  // need to allocate more elements.
  Label allocElement, addNewElement;
  masm.spectreBoundsCheck32(scratchLength, capacity, InvalidReg, &allocElement);
  masm.jump(&addNewElement);

  masm.bind(&allocElement);

  LiveRegisterSet save = liveVolatileRegs();
  save.takeUnchecked(scratch);
  masm.PushRegsInMask(save);

  using Fn = bool (*)(JSContext* cx, NativeObject* obj);
  masm.setupUnalignedABICall(scratch);
  masm.loadJSContext(scratch);
  masm.passABIArg(scratch);
  masm.passABIArg(obj);
  masm.callWithABI<Fn, NativeObject::addDenseElementPure>();
  masm.storeCallPointerResult(scratch);

  masm.PopRegsInMask(save);
  masm.branchIfFalseBool(scratch, failure->label());

  // Load the reallocated elements pointer.
  masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);

  masm.bind(&addNewElement);

  // Increment initLength and length.
  masm.add32(Imm32(1), elementsInitLength);
  masm.add32(Imm32(1), elementsLength);

  // Store the value.
  BaseObjectElementIndex element(scratch, scratchLength);
  masm.storeValue(val, element);
  emitPostBarrierElement(obj, val, scratch, scratchLength);

  // Return value is new length.
  masm.add32(Imm32(1), scratchLength);
  masm.tagValue(JSVAL_TYPE_INT32, scratchLength, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitStoreTypedArrayElement(ObjOperandId objId,
                                                 Scalar::Type elementType,
                                                 IntPtrOperandId indexId,
                                                 uint32_t rhsId, bool handleOOB,
                                                 ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);

  Maybe<Register> valInt32;
  Maybe<Register> valBigInt;
  switch (elementType) {
    case Scalar::Int8:
    case Scalar::Uint8:
    case Scalar::Int16:
    case Scalar::Uint16:
    case Scalar::Int32:
    case Scalar::Uint32:
    case Scalar::Uint8Clamped:
      valInt32.emplace(allocator.useRegister(masm, Int32OperandId(rhsId)));
      break;

    case Scalar::Float16:
    case Scalar::Float32:
    case Scalar::Float64:
      allocator.ensureDoubleRegister(masm, NumberOperandId(rhsId),
                                     floatScratch0);
      break;

    case Scalar::BigInt64:
    case Scalar::BigUint64:
      valBigInt.emplace(allocator.useRegister(masm, BigIntOperandId(rhsId)));
      break;

    case Scalar::MaxTypedArrayViewType:
    case Scalar::Int64:
    case Scalar::Simd128:
      MOZ_CRASH("Unsupported TypedArray type");
  }

  AutoScratchRegister scratch1(allocator, masm);
  Maybe<AutoScratchRegister> scratch2;
  Maybe<AutoSpectreBoundsScratchRegister> spectreScratch;
  if (Scalar::isBigIntType(elementType) || elementType == Scalar::Float16 ||
      viewKind == ArrayBufferViewKind::Resizable) {
    scratch2.emplace(allocator, masm);
  } else {
    spectreScratch.emplace(allocator, masm);
  }

  FailurePath* failure = nullptr;
  if (!handleOOB) {
    if (!addFailurePath(&failure)) {
      return false;
    }
  }

  // Bounds check.
  Label done;
  emitTypedArrayBoundsCheck(viewKind, obj, index, scratch1, scratch2,
                            spectreScratch,
                            handleOOB ? &done : failure->label());

  // Load the elements vector.
  masm.loadPtr(Address(obj, ArrayBufferViewObject::dataOffset()), scratch1);

  BaseIndex dest(scratch1, index, ScaleFromScalarType(elementType));

  if (Scalar::isBigIntType(elementType)) {
#ifdef JS_PUNBOX64
    Register64 temp(scratch2->get());
#else
    // We don't have more registers available on x86, so spill |obj|.
    masm.push(obj);
    Register64 temp(scratch2->get(), obj);
#endif

    masm.loadBigInt64(*valBigInt, temp);
    masm.storeToTypedBigIntArray(temp, dest);

#ifndef JS_PUNBOX64
    masm.pop(obj);
#endif
  } else if (Scalar::isFloatingType(elementType)) {
    Register temp = scratch2 ? scratch2->get() : InvalidReg;
    masm.storeToTypedFloatArray(elementType, floatScratch0, dest, temp,
                                liveVolatileRegs());
  } else {
    masm.storeToTypedIntArray(elementType, *valInt32, dest);
  }

  masm.bind(&done);
  return true;
}

void CacheIRCompiler::emitTypedArrayBoundsCheck(ArrayBufferViewKind viewKind,
                                                Register obj, Register index,
                                                Register scratch,
                                                Register maybeScratch,
                                                Register spectreScratch,
                                                Label* fail) {
  // |index| must not alias any scratch register.
  MOZ_ASSERT(index != scratch);
  MOZ_ASSERT(index != maybeScratch);
  MOZ_ASSERT(index != spectreScratch);

  // Use |maybeScratch| when no explicit |spectreScratch| is present.
  if (spectreScratch == InvalidReg) {
    spectreScratch = maybeScratch;
  }

  if (viewKind == ArrayBufferViewKind::FixedLength) {
    masm.loadArrayBufferViewLengthIntPtr(obj, scratch);
    masm.spectreBoundsCheckPtr(index, scratch, spectreScratch, fail);
  } else {
    if (maybeScratch == InvalidReg) {
      // Spill |index| to use it as an additional scratch register.
      masm.push(index);

      maybeScratch = index;
    }

    // Bounds check doesn't require synchronization. See IsValidIntegerIndex
    // abstract operation which reads the underlying buffer byte length using
    // "unordered" memory order.
    auto sync = Synchronization::None();

    masm.loadResizableTypedArrayLengthIntPtr(sync, obj, scratch, maybeScratch);

    if (maybeScratch == index) {
      // Restore |index|.
      masm.pop(index);
    }

    masm.spectreBoundsCheckPtr(index, scratch, spectreScratch, fail);
  }
}

void CacheIRCompiler::emitTypedArrayBoundsCheck(
    ArrayBufferViewKind viewKind, Register obj, Register index,
    Register scratch, mozilla::Maybe<Register> maybeScratch,
    mozilla::Maybe<Register> spectreScratch, Label* fail) {
  emitTypedArrayBoundsCheck(viewKind, obj, index, scratch,
                            maybeScratch.valueOr(InvalidReg),
                            spectreScratch.valueOr(InvalidReg), fail);
}

bool CacheIRCompiler::emitLoadTypedArrayElementResult(
    ObjOperandId objId, IntPtrOperandId indexId, Scalar::Type elementType,
    bool handleOOB, bool forceDoubleForUint32, ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);

  AutoScratchRegister scratch1(allocator, masm);
#ifdef JS_PUNBOX64
  AutoScratchRegister scratch2(allocator, masm);
#else
  // There are too few registers available on x86, so we may need to reuse the
  // output's scratch register.
  AutoScratchRegisterMaybeOutput scratch2(allocator, masm, output);
#endif

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Bounds check.
  Label outOfBounds;
  emitTypedArrayBoundsCheck(viewKind, obj, index, scratch1, scratch2, scratch2,
                            handleOOB ? &outOfBounds : failure->label());

  // Allocate BigInt if needed. The code after this should be infallible.
  Maybe<Register> bigInt;
  if (Scalar::isBigIntType(elementType)) {
    bigInt.emplace(output.valueReg().scratchReg());

    LiveRegisterSet save = liveVolatileRegs();
    save.takeUnchecked(scratch1);
    save.takeUnchecked(scratch2);
    save.takeUnchecked(output);

    gc::Heap initialHeap = InitialBigIntHeap(cx_);
    EmitAllocateBigInt(masm, *bigInt, scratch1, save, initialHeap,
                       failure->label());
  }

  // Load the elements vector.
  masm.loadPtr(Address(obj, ArrayBufferViewObject::dataOffset()), scratch1);

  // Load the value.
  BaseIndex source(scratch1, index, ScaleFromScalarType(elementType));

  if (Scalar::isBigIntType(elementType)) {
#ifdef JS_PUNBOX64
    Register64 temp(scratch2);
#else
    // We don't have more registers available on x86, so spill |obj| and
    // additionally use the output's type register.
    MOZ_ASSERT(output.valueReg().scratchReg() != output.valueReg().typeReg());
    masm.push(obj);
    Register64 temp(output.valueReg().typeReg(), obj);
#endif

    masm.loadFromTypedBigIntArray(elementType, source, output.valueReg(),
                                  *bigInt, temp);

#ifndef JS_PUNBOX64
    masm.pop(obj);
#endif
  } else {
    MacroAssembler::Uint32Mode uint32Mode =
        forceDoubleForUint32 ? MacroAssembler::Uint32Mode::ForceDouble
                             : MacroAssembler::Uint32Mode::FailOnDouble;
    masm.loadFromTypedArray(elementType, source, output.valueReg(), uint32Mode,
                            scratch1, failure->label(), liveVolatileRegs());
  }

  if (handleOOB) {
    Label done;
    masm.jump(&done);

    masm.bind(&outOfBounds);
    masm.moveValue(UndefinedValue(), output.valueReg());

    masm.bind(&done);
  }

  return true;
}

void CacheIRCompiler::emitDataViewBoundsCheck(ArrayBufferViewKind viewKind,
                                              size_t byteSize, Register obj,
                                              Register offset, Register scratch,
                                              Register maybeScratch,
                                              Label* fail) {
  // |offset| must not alias any scratch register.
  MOZ_ASSERT(offset != scratch);
  MOZ_ASSERT(offset != maybeScratch);

  if (viewKind == ArrayBufferViewKind::FixedLength) {
    masm.loadArrayBufferViewLengthIntPtr(obj, scratch);
  } else {
    if (maybeScratch == InvalidReg) {
      // Spill |offset| to use it as an additional scratch register.
      masm.push(offset);

      maybeScratch = offset;
    }

    // Bounds check doesn't require synchronization. See GetViewValue and
    // SetViewValue abstract operations which read the underlying buffer byte
    // length using "unordered" memory order.
    auto sync = Synchronization::None();

    masm.loadResizableDataViewByteLengthIntPtr(sync, obj, scratch,
                                               maybeScratch);

    if (maybeScratch == offset) {
      // Restore |offset|.
      masm.pop(offset);
    }
  }

  // Ensure both offset < length and offset + (byteSize - 1) < length.
  if (byteSize == 1) {
    masm.spectreBoundsCheckPtr(offset, scratch, InvalidReg, fail);
  } else {
    // temp := length - (byteSize - 1)
    // if temp < 0: fail
    // if offset >= temp: fail
    masm.branchSubPtr(Assembler::Signed, Imm32(byteSize - 1), scratch, fail);
    masm.spectreBoundsCheckPtr(offset, scratch, InvalidReg, fail);
  }
}

bool CacheIRCompiler::emitLoadDataViewValueResult(
    ObjOperandId objId, IntPtrOperandId offsetId,
    BooleanOperandId littleEndianId, Scalar::Type elementType,
    bool forceDoubleForUint32, ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  Register offset = allocator.useRegister(masm, offsetId);
  Register littleEndian = allocator.useRegister(masm, littleEndianId);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);

  Register64 outputReg64 = output.valueReg().toRegister64();
  Register outputScratch = outputReg64.scratchReg();

  Register scratch2;
#ifndef JS_CODEGEN_X86
  Maybe<AutoScratchRegister> maybeScratch2;
  if (viewKind == ArrayBufferViewKind::Resizable ||
      (elementType == Scalar::Float16 &&
       !MacroAssembler::SupportsFloat32To16())) {
    maybeScratch2.emplace(allocator, masm);
    scratch2 = *maybeScratch2;
  }
#else
  // Not enough registers on x86, so use the other part of outputReg64.
  scratch2 = outputReg64.secondScratchReg();
#endif

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  const size_t byteSize = Scalar::byteSize(elementType);

  emitDataViewBoundsCheck(viewKind, byteSize, obj, offset, outputScratch,
                          scratch2, failure->label());

  masm.loadPtr(Address(obj, DataViewObject::dataOffset()), outputScratch);

  // Load the value.
  BaseIndex source(outputScratch, offset, TimesOne);
  switch (elementType) {
    case Scalar::Int8:
      masm.load8SignExtend(source, outputScratch);
      break;
    case Scalar::Uint8:
      masm.load8ZeroExtend(source, outputScratch);
      break;
    case Scalar::Int16:
      masm.load16UnalignedSignExtend(source, outputScratch);
      break;
    case Scalar::Uint16:
    case Scalar::Float16:
      masm.load16UnalignedZeroExtend(source, outputScratch);
      break;
    case Scalar::Int32:
    case Scalar::Uint32:
    case Scalar::Float32:
      masm.load32Unaligned(source, outputScratch);
      break;
    case Scalar::Float64:
    case Scalar::BigInt64:
    case Scalar::BigUint64:
      masm.load64Unaligned(source, outputReg64);
      break;
    case Scalar::Uint8Clamped:
    default:
      MOZ_CRASH("Invalid typed array type");
  }

  // Swap the bytes in the loaded value.
  if (byteSize > 1) {
    Label skip;
    masm.branch32(MOZ_LITTLE_ENDIAN() ? Assembler::NotEqual : Assembler::Equal,
                  littleEndian, Imm32(0), &skip);

    switch (elementType) {
      case Scalar::Int16:
        masm.byteSwap16SignExtend(outputScratch);
        break;
      case Scalar::Uint16:
      case Scalar::Float16:
        masm.byteSwap16ZeroExtend(outputScratch);
        break;
      case Scalar::Int32:
      case Scalar::Uint32:
      case Scalar::Float32:
        masm.byteSwap32(outputScratch);
        break;
      case Scalar::Float64:
      case Scalar::BigInt64:
      case Scalar::BigUint64:
        masm.byteSwap64(outputReg64);
        break;
      case Scalar::Int8:
      case Scalar::Uint8:
      case Scalar::Uint8Clamped:
      default:
        MOZ_CRASH("Invalid type");
    }

    masm.bind(&skip);
  }

  // Move the value into the output register.
  switch (elementType) {
    case Scalar::Int8:
    case Scalar::Uint8:
    case Scalar::Int16:
    case Scalar::Uint16:
    case Scalar::Int32:
      masm.tagValue(JSVAL_TYPE_INT32, outputScratch, output.valueReg());
      break;
    case Scalar::Uint32: {
      MacroAssembler::Uint32Mode uint32Mode =
          forceDoubleForUint32 ? MacroAssembler::Uint32Mode::ForceDouble
                               : MacroAssembler::Uint32Mode::FailOnDouble;
      masm.boxUint32(outputScratch, output.valueReg(), uint32Mode,
                     failure->label());
      break;
    }
    case Scalar::Float16: {
      FloatRegister scratchFloat32 = floatScratch0.get().asSingle();
      masm.moveGPRToFloat16(outputScratch, scratchFloat32, scratch2,
                            liveVolatileRegs());
      masm.canonicalizeFloat(scratchFloat32);
      masm.convertFloat32ToDouble(scratchFloat32, floatScratch0);
      masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);
      break;
    }
    case Scalar::Float32: {
      FloatRegister scratchFloat32 = floatScratch0.get().asSingle();
      masm.moveGPRToFloat32(outputScratch, scratchFloat32);
      masm.canonicalizeFloat(scratchFloat32);
      masm.convertFloat32ToDouble(scratchFloat32, floatScratch0);
      masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);
      break;
    }
    case Scalar::Float64:
      masm.moveGPR64ToDouble(outputReg64, floatScratch0);
      masm.canonicalizeDouble(floatScratch0);
      masm.boxDouble(floatScratch0, output.valueReg(), floatScratch0);
      break;
    case Scalar::BigInt64:
    case Scalar::BigUint64: {
      // We need two extra registers. Reuse the obj/littleEndian registers.
      Register bigInt = obj;
      Register bigIntScratch = littleEndian;
      masm.push(bigInt);
      masm.push(bigIntScratch);
      Label fail, done;
      LiveRegisterSet save = liveVolatileRegs();
      save.takeUnchecked(bigInt);
      save.takeUnchecked(bigIntScratch);
      gc::Heap initialHeap = InitialBigIntHeap(cx_);
      EmitAllocateBigInt(masm, bigInt, bigIntScratch, save, initialHeap, &fail);
      masm.jump(&done);

      masm.bind(&fail);
      masm.pop(bigIntScratch);
      masm.pop(bigInt);
      masm.jump(failure->label());

      masm.bind(&done);
      masm.initializeBigInt64(elementType, bigInt, outputReg64);
      masm.tagValue(JSVAL_TYPE_BIGINT, bigInt, output.valueReg());
      masm.pop(bigIntScratch);
      masm.pop(bigInt);
      break;
    }
    case Scalar::Uint8Clamped:
    default:
      MOZ_CRASH("Invalid typed array type");
  }

  return true;
}

bool CacheIRCompiler::emitStoreDataViewValueResult(
    ObjOperandId objId, IntPtrOperandId offsetId, uint32_t valueId,
    BooleanOperandId littleEndianId, Scalar::Type elementType,
    ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
#ifdef JS_CODEGEN_X86
  // Use a scratch register to avoid running out of the registers.
  Register obj = output.valueReg().typeReg();
  allocator.copyToScratchRegister(masm, objId, obj);
#else
  Register obj = allocator.useRegister(masm, objId);
#endif
  Register offset = allocator.useRegister(masm, offsetId);
  Register littleEndian = allocator.useRegister(masm, littleEndianId);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  Maybe<Register> valInt32;
  Maybe<Register> valBigInt;
  switch (elementType) {
    case Scalar::Int8:
    case Scalar::Uint8:
    case Scalar::Int16:
    case Scalar::Uint16:
    case Scalar::Int32:
    case Scalar::Uint32:
    case Scalar::Uint8Clamped:
      valInt32.emplace(allocator.useRegister(masm, Int32OperandId(valueId)));
      break;

    case Scalar::Float16:
    case Scalar::Float32:
    case Scalar::Float64:
      allocator.ensureDoubleRegister(masm, NumberOperandId(valueId),
                                     floatScratch0);
      break;

    case Scalar::BigInt64:
    case Scalar::BigUint64:
      valBigInt.emplace(allocator.useRegister(masm, BigIntOperandId(valueId)));
      break;

    case Scalar::MaxTypedArrayViewType:
    case Scalar::Int64:
    case Scalar::Simd128:
      MOZ_CRASH("Unsupported type");
  }

  Register scratch1 = output.valueReg().scratchReg();
  MOZ_ASSERT(scratch1 != obj, "scratchReg must not be typeReg");

  // On platforms with enough registers, |scratch2| is an extra scratch register
  // (pair) used for byte-swapping the value.
#ifndef JS_CODEGEN_X86
  mozilla::MaybeOneOf<AutoScratchRegister, AutoScratchRegister64> scratch2;
  switch (elementType) {
    case Scalar::Int8:
    case Scalar::Uint8:
      break;
    case Scalar::Int16:
    case Scalar::Uint16:
    case Scalar::Int32:
    case Scalar::Uint32:
    case Scalar::Float16:
    case Scalar::Float32:
      scratch2.construct<AutoScratchRegister>(allocator, masm);
      break;
    case Scalar::Float64:
    case Scalar::BigInt64:
    case Scalar::BigUint64:
      scratch2.construct<AutoScratchRegister64>(allocator, masm);
      break;
    case Scalar::Uint8Clamped:
    default:
      MOZ_CRASH("Invalid type");
  }
#endif

  Register boundsCheckScratch;
#ifndef JS_CODEGEN_X86
  Maybe<AutoScratchRegister> maybeBoundsCheckScratch;
  if (viewKind == ArrayBufferViewKind::Resizable) {
    if (scratch2.constructed<AutoScratchRegister>()) {
      boundsCheckScratch = scratch2.ref<AutoScratchRegister>().get();
    } else if (scratch2.constructed<AutoScratchRegister64>()) {
      boundsCheckScratch =
          scratch2.ref<AutoScratchRegister64>().get().scratchReg();
    } else {
      maybeBoundsCheckScratch.emplace(allocator, masm);
      boundsCheckScratch = *maybeBoundsCheckScratch;
    }
  }
#else
  // Not enough registers on x86.
#endif

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  const size_t byteSize = Scalar::byteSize(elementType);

  emitDataViewBoundsCheck(viewKind, byteSize, obj, offset, scratch1,
                          boundsCheckScratch, failure->label());

  masm.loadPtr(Address(obj, DataViewObject::dataOffset()), scratch1);
  BaseIndex dest(scratch1, offset, TimesOne);

  if (byteSize == 1) {
    // Byte swapping has no effect, so just do the byte store.
    masm.store8(*valInt32, dest);
    masm.moveValue(UndefinedValue(), output.valueReg());
    return true;
  }

  // On 32-bit x86, |obj| is already a scratch register so use that. If we need
  // a Register64 we also use the littleEndian register and use the stack
  // location for the check below.
  bool pushedLittleEndian = false;
#ifdef JS_CODEGEN_X86
  if (byteSize == 8) {
    masm.push(littleEndian);
    pushedLittleEndian = true;
  }
  auto valScratch32 = [&]() -> Register { return obj; };
  auto valScratch64 = [&]() -> Register64 {
    return Register64(obj, littleEndian);
  };
#else
  auto valScratch32 = [&]() -> Register {
    return scratch2.ref<AutoScratchRegister>();
  };
  auto valScratch64 = [&]() -> Register64 {
    return scratch2.ref<AutoScratchRegister64>();
  };
#endif

  // Load the value into a gpr register.
  switch (elementType) {
    case Scalar::Int16:
    case Scalar::Uint16:
    case Scalar::Int32:
    case Scalar::Uint32:
      masm.move32(*valInt32, valScratch32());
      break;
    case Scalar::Float16: {
      FloatRegister scratchFloat32 = floatScratch0.get().asSingle();
      masm.convertDoubleToFloat16(floatScratch0, scratchFloat32, valScratch32(),
                                  liveVolatileRegs());
      masm.canonicalizeFloatIfDeterministic(scratchFloat32);
      masm.moveFloat16ToGPR(scratchFloat32, valScratch32(), liveVolatileRegs());
      break;
    }
    case Scalar::Float32: {
      FloatRegister scratchFloat32 = floatScratch0.get().asSingle();
      masm.convertDoubleToFloat32(floatScratch0, scratchFloat32);
      masm.canonicalizeFloatIfDeterministic(scratchFloat32);
      masm.moveFloat32ToGPR(scratchFloat32, valScratch32());
      break;
    }
    case Scalar::Float64: {
      masm.canonicalizeDoubleIfDeterministic(floatScratch0);
      masm.moveDoubleToGPR64(floatScratch0, valScratch64());
      break;
    }
    case Scalar::BigInt64:
    case Scalar::BigUint64:
      masm.loadBigInt64(*valBigInt, valScratch64());
      break;
    case Scalar::Int8:
    case Scalar::Uint8:
    case Scalar::Uint8Clamped:
    default:
      MOZ_CRASH("Invalid type");
  }

  // Swap the bytes in the loaded value.
  Label skip;
  if (pushedLittleEndian) {
    masm.branch32(MOZ_LITTLE_ENDIAN() ? Assembler::NotEqual : Assembler::Equal,
                  Address(masm.getStackPointer(), 0), Imm32(0), &skip);
  } else {
    masm.branch32(MOZ_LITTLE_ENDIAN() ? Assembler::NotEqual : Assembler::Equal,
                  littleEndian, Imm32(0), &skip);
  }
  switch (elementType) {
    case Scalar::Int16:
      masm.byteSwap16SignExtend(valScratch32());
      break;
    case Scalar::Uint16:
    case Scalar::Float16:
      masm.byteSwap16ZeroExtend(valScratch32());
      break;
    case Scalar::Int32:
    case Scalar::Uint32:
    case Scalar::Float32:
      masm.byteSwap32(valScratch32());
      break;
    case Scalar::Float64:
    case Scalar::BigInt64:
    case Scalar::BigUint64:
      masm.byteSwap64(valScratch64());
      break;
    case Scalar::Int8:
    case Scalar::Uint8:
    case Scalar::Uint8Clamped:
    default:
      MOZ_CRASH("Invalid type");
  }
  masm.bind(&skip);

  // Store the value.
  switch (elementType) {
    case Scalar::Int16:
    case Scalar::Uint16:
    case Scalar::Float16:
      masm.store16Unaligned(valScratch32(), dest);
      break;
    case Scalar::Int32:
    case Scalar::Uint32:
    case Scalar::Float32:
      masm.store32Unaligned(valScratch32(), dest);
      break;
    case Scalar::Float64:
    case Scalar::BigInt64:
    case Scalar::BigUint64:
      masm.store64Unaligned(valScratch64(), dest);
      break;
    case Scalar::Int8:
    case Scalar::Uint8:
    case Scalar::Uint8Clamped:
    default:
      MOZ_CRASH("Invalid typed array type");
  }

#ifdef JS_CODEGEN_X86
  // Restore registers.
  if (pushedLittleEndian) {
    masm.pop(littleEndian);
  }
#endif

  masm.moveValue(UndefinedValue(), output.valueReg());
  return true;
}

bool CacheIRCompiler::emitStoreFixedSlotUndefinedResult(ObjOperandId objId,
                                                        uint32_t offsetOffset,
                                                        ValOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Register obj = allocator.useRegister(masm, objId);
  ValueOperand val = allocator.useValueRegister(masm, rhsId);

  StubFieldOffset offset(offsetOffset, StubField::Type::RawInt32);
  emitLoadStubField(offset, scratch);

  BaseIndex slot(obj, scratch, TimesOne);
  EmitPreBarrier(masm, slot, MIRType::Value);
  masm.storeValue(val, slot);
  emitPostBarrierSlot(obj, val, scratch);

  masm.moveValue(UndefinedValue(), output.valueReg());
  return true;
}

bool CacheIRCompiler::emitLoadObjectResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);

  EmitStoreResult(masm, obj, JSVAL_TYPE_OBJECT, output);

  return true;
}

bool CacheIRCompiler::emitLoadStringResult(StringOperandId strId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register str = allocator.useRegister(masm, strId);

  masm.tagValue(JSVAL_TYPE_STRING, str, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitLoadSymbolResult(SymbolOperandId symId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register sym = allocator.useRegister(masm, symId);

  masm.tagValue(JSVAL_TYPE_SYMBOL, sym, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitLoadInt32Result(Int32OperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register val = allocator.useRegister(masm, valId);

  masm.tagValue(JSVAL_TYPE_INT32, val, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitLoadBigIntResult(BigIntOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register val = allocator.useRegister(masm, valId);

  masm.tagValue(JSVAL_TYPE_BIGINT, val, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitLoadDoubleResult(NumberOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  ValueOperand val = allocator.useValueRegister(masm, valId);

#ifdef DEBUG
  Label ok;
  masm.branchTestDouble(Assembler::Equal, val, &ok);
  masm.branchTestInt32(Assembler::Equal, val, &ok);
  masm.assumeUnreachable("input must be double or int32");
  masm.bind(&ok);
#endif

  masm.moveValue(val, output.valueReg());
  masm.convertInt32ValueToDouble(output.valueReg());

  return true;
}

bool CacheIRCompiler::emitLoadTypeOfObjectResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  Label slowCheck, isObject, isCallable, isUndefined, done;
  masm.typeOfObject(obj, scratch, &slowCheck, &isObject, &isCallable,
                    &isUndefined);

  masm.bind(&isCallable);
  masm.moveValue(StringValue(cx_->names().function), output.valueReg());
  masm.jump(&done);

  masm.bind(&isUndefined);
  masm.moveValue(StringValue(cx_->names().undefined), output.valueReg());
  masm.jump(&done);

  masm.bind(&isObject);
  masm.moveValue(StringValue(cx_->names().object), output.valueReg());
  masm.jump(&done);

  {
    masm.bind(&slowCheck);
    LiveRegisterSet save = liveVolatileRegs();
    masm.PushRegsInMask(save);

    using Fn = JSString* (*)(JSObject* obj, JSRuntime* rt);
    masm.setupUnalignedABICall(scratch);
    masm.passABIArg(obj);
    masm.movePtr(ImmPtr(cx_->runtime()), scratch);
    masm.passABIArg(scratch);
    masm.callWithABI<Fn, TypeOfNameObject>();
    masm.storeCallPointerResult(scratch);

    LiveRegisterSet ignore;
    ignore.add(scratch);
    masm.PopRegsInMaskIgnore(save, ignore);

    masm.tagValue(JSVAL_TYPE_STRING, scratch, output.valueReg());
  }

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitLoadTypeOfEqObjectResult(ObjOperandId objId,
                                                   TypeofEqOperand operand) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  JSType type = operand.type();
  JSOp compareOp = operand.compareOp();
  bool result;

  Label slowCheck, isObject, isCallable, isUndefined, done;
  masm.typeOfObject(obj, scratch, &slowCheck, &isObject, &isCallable,
                    &isUndefined);

  masm.bind(&isCallable);
  result = type == JSTYPE_FUNCTION;
  if (compareOp == JSOp::Ne) {
    result = !result;
  }
  masm.moveValue(BooleanValue(result), output.valueReg());
  masm.jump(&done);

  masm.bind(&isUndefined);
  result = type == JSTYPE_UNDEFINED;
  if (compareOp == JSOp::Ne) {
    result = !result;
  }
  masm.moveValue(BooleanValue(result), output.valueReg());
  masm.jump(&done);

  masm.bind(&isObject);
  result = type == JSTYPE_OBJECT;
  if (compareOp == JSOp::Ne) {
    result = !result;
  }
  masm.moveValue(BooleanValue(result), output.valueReg());
  masm.jump(&done);

  {
    masm.bind(&slowCheck);
    LiveRegisterSet save = liveVolatileRegs();
    save.takeUnchecked(output.valueReg());
    save.takeUnchecked(scratch);
    masm.PushRegsInMask(save);

    using Fn = bool (*)(JSObject* obj, TypeofEqOperand operand);
    masm.setupUnalignedABICall(scratch);
    masm.passABIArg(obj);
    masm.move32(Imm32(TypeofEqOperand(type, compareOp).rawValue()), scratch);
    masm.passABIArg(scratch);
    masm.callWithABI<Fn, TypeOfEqObject>();
    masm.storeCallBoolResult(scratch);

    masm.PopRegsInMask(save);

    masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg());
  }

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitLoadInt32TruthyResult(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  ValueOperand val = allocator.useValueRegister(masm, inputId);

  Label ifFalse, done;
  masm.branchTestInt32Truthy(false, val, &ifFalse);
  masm.moveValue(BooleanValue(true), output.valueReg());
  masm.jump(&done);

  masm.bind(&ifFalse);
  masm.moveValue(BooleanValue(false), output.valueReg());

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitLoadStringTruthyResult(StringOperandId strId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register str = allocator.useRegister(masm, strId);

  Label ifFalse, done;
  masm.branch32(Assembler::Equal, Address(str, JSString::offsetOfLength()),
                Imm32(0), &ifFalse);
  masm.moveValue(BooleanValue(true), output.valueReg());
  masm.jump(&done);

  masm.bind(&ifFalse);
  masm.moveValue(BooleanValue(false), output.valueReg());

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitLoadDoubleTruthyResult(NumberOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  AutoScratchFloatRegister floatReg(this);

  allocator.ensureDoubleRegister(masm, inputId, floatReg);

  Label ifFalse, done;

  masm.branchTestDoubleTruthy(false, floatReg, &ifFalse);
  masm.moveValue(BooleanValue(true), output.valueReg());
  masm.jump(&done);

  masm.bind(&ifFalse);
  masm.moveValue(BooleanValue(false), output.valueReg());

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitLoadObjectTruthyResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  Label emulatesUndefined, slowPath, done;
  masm.branchIfObjectEmulatesUndefined(obj, scratch, &slowPath,
                                       &emulatesUndefined);
  masm.moveValue(BooleanValue(true), output.valueReg());
  masm.jump(&done);

  masm.bind(&emulatesUndefined);
  masm.moveValue(BooleanValue(false), output.valueReg());
  masm.jump(&done);

  masm.bind(&slowPath);
  {
    LiveRegisterSet volatileRegs = liveVolatileRegs();
    volatileRegs.takeUnchecked(scratch);
    volatileRegs.takeUnchecked(output);
    masm.PushRegsInMask(volatileRegs);

    using Fn = bool (*)(JSObject* obj);
    masm.setupUnalignedABICall(scratch);
    masm.passABIArg(obj);
    masm.callWithABI<Fn, js::EmulatesUndefined>();
    masm.storeCallBoolResult(scratch);
    masm.xor32(Imm32(1), scratch);

    masm.PopRegsInMask(volatileRegs);

    masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg());
  }

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitLoadBigIntTruthyResult(BigIntOperandId bigIntId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register bigInt = allocator.useRegister(masm, bigIntId);

  Label ifFalse, done;
  masm.branch32(Assembler::Equal,
                Address(bigInt, BigInt::offsetOfDigitLength()), Imm32(0),
                &ifFalse);
  masm.moveValue(BooleanValue(true), output.valueReg());
  masm.jump(&done);

  masm.bind(&ifFalse);
  masm.moveValue(BooleanValue(false), output.valueReg());

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitLoadValueTruthyResult(ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  ValueOperand value = allocator.useValueRegister(masm, inputId);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchFloatRegister floatReg(this);

  Label ifFalse, ifTrue, done;

  {
    ScratchTagScope tag(masm, value);
    masm.splitTagForTest(value, tag);

    masm.branchTestUndefined(Assembler::Equal, tag, &ifFalse);
    masm.branchTestNull(Assembler::Equal, tag, &ifFalse);

    Label notBoolean;
    masm.branchTestBoolean(Assembler::NotEqual, tag, ¬Boolean);
    {
      ScratchTagScopeRelease _(&tag);
      masm.branchTestBooleanTruthy(false, value, &ifFalse);
      masm.jump(&ifTrue);
    }
    masm.bind(¬Boolean);

    Label notInt32;
    masm.branchTestInt32(Assembler::NotEqual, tag, ¬Int32);
    {
      ScratchTagScopeRelease _(&tag);
      masm.branchTestInt32Truthy(false, value, &ifFalse);
      masm.jump(&ifTrue);
    }
    masm.bind(¬Int32);

    Label notObject;
    masm.branchTestObject(Assembler::NotEqual, tag, ¬Object);
    {
      ScratchTagScopeRelease _(&tag);

      Register obj = masm.extractObject(value, scratch1);

      Label slowPath;
      masm.branchIfObjectEmulatesUndefined(obj, scratch2, &slowPath, &ifFalse);
      masm.jump(&ifTrue);

      masm.bind(&slowPath);
      {
        LiveRegisterSet volatileRegs = liveVolatileRegs();
        volatileRegs.takeUnchecked(scratch1);
        volatileRegs.takeUnchecked(scratch2);
        volatileRegs.takeUnchecked(output);
        masm.PushRegsInMask(volatileRegs);

        using Fn = bool (*)(JSObject* obj);
        masm.setupUnalignedABICall(scratch2);
        masm.passABIArg(obj);
        masm.callWithABI<Fn, js::EmulatesUndefined>();
        masm.storeCallPointerResult(scratch2);

        masm.PopRegsInMask(volatileRegs);

        masm.branchIfTrueBool(scratch2, &ifFalse);
        masm.jump(&ifTrue);
      }
    }
    masm.bind(¬Object);

    Label notString;
    masm.branchTestString(Assembler::NotEqual, tag, ¬String);
    {
      ScratchTagScopeRelease _(&tag);
      masm.branchTestStringTruthy(false, value, &ifFalse);
      masm.jump(&ifTrue);
    }
    masm.bind(¬String);

    Label notBigInt;
    masm.branchTestBigInt(Assembler::NotEqual, tag, ¬BigInt);
    {
      ScratchTagScopeRelease _(&tag);
      masm.branchTestBigIntTruthy(false, value, &ifFalse);
      masm.jump(&ifTrue);
    }
    masm.bind(¬BigInt);

    masm.branchTestSymbol(Assembler::Equal, tag, &ifTrue);

#ifdef DEBUG
    Label isDouble;
    masm.branchTestDouble(Assembler::Equal, tag, &isDouble);
    masm.assumeUnreachable("Unexpected value type");
    masm.bind(&isDouble);
#endif

    {
      ScratchTagScopeRelease _(&tag);
      masm.unboxDouble(value, floatReg);
      masm.branchTestDoubleTruthy(false, floatReg, &ifFalse);
    }

    // Fall through to true case.
  }

  masm.bind(&ifTrue);
  masm.moveValue(BooleanValue(true), output.valueReg());
  masm.jump(&done);

  masm.bind(&ifFalse);
  masm.moveValue(BooleanValue(false), output.valueReg());

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitComparePointerResultShared(JSOp op,
                                                     TypedOperandId lhsId,
                                                     TypedOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  Register left = allocator.useRegister(masm, lhsId);
  Register right = allocator.useRegister(masm, rhsId);

  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  Label ifTrue, done;
  masm.branchPtr(JSOpToCondition(op, /* signed = */ true), left, right,
                 &ifTrue);

  EmitStoreBoolean(masm, false, output);
  masm.jump(&done);

  masm.bind(&ifTrue);
  EmitStoreBoolean(masm, true, output);
  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitCompareObjectResult(JSOp op, ObjOperandId lhsId,
                                              ObjOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  return emitComparePointerResultShared(op, lhsId, rhsId);
}

bool CacheIRCompiler::emitCompareSymbolResult(JSOp op, SymbolOperandId lhsId,
                                              SymbolOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  return emitComparePointerResultShared(op, lhsId, rhsId);
}

bool CacheIRCompiler::emitCompareInt32Result(JSOp op, Int32OperandId lhsId,
                                             Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register left = allocator.useRegister(masm, lhsId);
  Register right = allocator.useRegister(masm, rhsId);

  Label ifTrue, done;
  masm.branch32(JSOpToCondition(op, /* signed = */ true), left, right, &ifTrue);

  EmitStoreBoolean(masm, false, output);
  masm.jump(&done);

  masm.bind(&ifTrue);
  EmitStoreBoolean(masm, true, output);
  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitCompareDoubleResult(JSOp op, NumberOperandId lhsId,
                                              NumberOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg1);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  allocator.ensureDoubleRegister(masm, lhsId, floatScratch0);
  allocator.ensureDoubleRegister(masm, rhsId, floatScratch1);

  Label done, ifTrue;
  masm.branchDouble(JSOpToDoubleCondition(op), floatScratch0, floatScratch1,
                    &ifTrue);
  EmitStoreBoolean(masm, false, output);
  masm.jump(&done);

  masm.bind(&ifTrue);
  EmitStoreBoolean(masm, true, output);
  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitCompareBigIntResult(JSOp op, BigIntOperandId lhsId,
                                              BigIntOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);

  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  LiveRegisterSet save = liveVolatileRegs();
  masm.PushRegsInMask(save);

  masm.setupUnalignedABICall(scratch);

  // Push the operands in reverse order for JSOp::Le and JSOp::Gt:
  // - |left <= right| is implemented as |right >= left|.
  // - |left > right| is implemented as |right < left|.
  if (op == JSOp::Le || op == JSOp::Gt) {
    masm.passABIArg(rhs);
    masm.passABIArg(lhs);
  } else {
    masm.passABIArg(lhs);
    masm.passABIArg(rhs);
  }

  using Fn = bool (*)(BigInt*, BigInt*);
  Fn fn;
  if (op == JSOp::Eq || op == JSOp::StrictEq) {
    fn = jit::BigIntEqual<EqualityKind::Equal>;
  } else if (op == JSOp::Ne || op == JSOp::StrictNe) {
    fn = jit::BigIntEqual<EqualityKind::NotEqual>;
  } else if (op == JSOp::Lt || op == JSOp::Gt) {
    fn = jit::BigIntCompare<ComparisonKind::LessThan>;
  } else {
    MOZ_ASSERT(op == JSOp::Le || op == JSOp::Ge);
    fn = jit::BigIntCompare<ComparisonKind::GreaterThanOrEqual>;
  }

  masm.callWithABI(DynamicFunction<Fn>(fn));
  masm.storeCallBoolResult(scratch);

  LiveRegisterSet ignore;
  ignore.add(scratch);
  masm.PopRegsInMaskIgnore(save, ignore);

  EmitStoreResult(masm, scratch, JSVAL_TYPE_BOOLEAN, output);
  return true;
}

bool CacheIRCompiler::emitCompareBigIntInt32Result(JSOp op,
                                                   BigIntOperandId lhsId,
                                                   Int32OperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register bigInt = allocator.useRegister(masm, lhsId);
  Register int32 = allocator.useRegister(masm, rhsId);

  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);

  Label ifTrue, ifFalse;
  masm.compareBigIntAndInt32(op, bigInt, int32, scratch1, scratch2, &ifTrue,
                             &ifFalse);

  Label done;
  masm.bind(&ifFalse);
  EmitStoreBoolean(masm, false, output);
  masm.jump(&done);

  masm.bind(&ifTrue);
  EmitStoreBoolean(masm, true, output);

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitCompareBigIntNumberResult(JSOp op,
                                                    BigIntOperandId lhsId,
                                                    NumberOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);

  Register lhs = allocator.useRegister(masm, lhsId);
  allocator.ensureDoubleRegister(masm, rhsId, floatScratch0);

  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  LiveRegisterSet save = liveVolatileRegs();
  masm.PushRegsInMask(save);

  masm.setupUnalignedABICall(scratch);

  // Push the operands in reverse order for JSOp::Le and JSOp::Gt:
  // - |left <= right| is implemented as |right >= left|.
  // - |left > right| is implemented as |right < left|.
  if (op == JSOp::Le || op == JSOp::Gt) {
    masm.passABIArg(floatScratch0, ABIType::Float64);
    masm.passABIArg(lhs);
  } else {
    masm.passABIArg(lhs);
    masm.passABIArg(floatScratch0, ABIType::Float64);
  }

  using FnBigIntNumber = bool (*)(BigInt*, double);
  using FnNumberBigInt = bool (*)(double, BigInt*);
  switch (op) {
    case JSOp::Eq: {
      masm.callWithABI<FnBigIntNumber,
                       jit::BigIntNumberEqual<EqualityKind::Equal>>();
      break;
    }
    case JSOp::Ne: {
      masm.callWithABI<FnBigIntNumber,
                       jit::BigIntNumberEqual<EqualityKind::NotEqual>>();
      break;
    }
    case JSOp::Lt: {
      masm.callWithABI<FnBigIntNumber,
                       jit::BigIntNumberCompare<ComparisonKind::LessThan>>();
      break;
    }
    case JSOp::Gt: {
      masm.callWithABI<FnNumberBigInt,
                       jit::NumberBigIntCompare<ComparisonKind::LessThan>>();
      break;
    }
    case JSOp::Le: {
      masm.callWithABI<
          FnNumberBigInt,
          jit::NumberBigIntCompare<ComparisonKind::GreaterThanOrEqual>>();
      break;
    }
    case JSOp::Ge: {
      masm.callWithABI<
          FnBigIntNumber,
          jit::BigIntNumberCompare<ComparisonKind::GreaterThanOrEqual>>();
      break;
    }
    default:
      MOZ_CRASH("unhandled op");
  }

  masm.storeCallBoolResult(scratch);

  LiveRegisterSet ignore;
  ignore.add(scratch);
  masm.PopRegsInMaskIgnore(save, ignore);

  EmitStoreResult(masm, scratch, JSVAL_TYPE_BOOLEAN, output);
  return true;
}

bool CacheIRCompiler::emitCompareBigIntStringResult(JSOp op,
                                                    BigIntOperandId lhsId,
                                                    StringOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoCallVM callvm(masm, this, allocator);

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);

  callvm.prepare();

  // Push the operands in reverse order for JSOp::Le and JSOp::Gt:
  // - |left <= right| is implemented as |right >= left|.
  // - |left > right| is implemented as |right < left|.
  if (op == JSOp::Le || op == JSOp::Gt) {
    masm.Push(lhs);
    masm.Push(rhs);
  } else {
    masm.Push(rhs);
    masm.Push(lhs);
  }

  using FnBigIntString =
      bool (*)(JSContext*, HandleBigInt, HandleString, bool*);
  using FnStringBigInt =
      bool (*)(JSContext*, HandleString, HandleBigInt, bool*);

  switch (op) {
    case JSOp::Eq: {
      constexpr auto Equal = EqualityKind::Equal;
      callvm.call<FnBigIntString, BigIntStringEqual<Equal>>();
      break;
    }
    case JSOp::Ne: {
      constexpr auto NotEqual = EqualityKind::NotEqual;
      callvm.call<FnBigIntString, BigIntStringEqual<NotEqual>>();
      break;
    }
    case JSOp::Lt: {
      constexpr auto LessThan = ComparisonKind::LessThan;
      callvm.call<FnBigIntString, BigIntStringCompare<LessThan>>();
      break;
    }
    case JSOp::Gt: {
      constexpr auto LessThan = ComparisonKind::LessThan;
      callvm.call<FnStringBigInt, StringBigIntCompare<LessThan>>();
      break;
    }
    case JSOp::Le: {
      constexpr auto GreaterThanOrEqual = ComparisonKind::GreaterThanOrEqual;
      callvm.call<FnStringBigInt, StringBigIntCompare<GreaterThanOrEqual>>();
      break;
    }
    case JSOp::Ge: {
      constexpr auto GreaterThanOrEqual = ComparisonKind::GreaterThanOrEqual;
      callvm.call<FnBigIntString, BigIntStringCompare<GreaterThanOrEqual>>();
      break;
    }
    default:
      MOZ_CRASH("unhandled op");
  }
  return true;
}

bool CacheIRCompiler::emitCompareNullUndefinedResult(JSOp op, bool isUndefined,
                                                     ValOperandId inputId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  ValueOperand input = allocator.useValueRegister(masm, inputId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);

  if (IsStrictEqualityOp(op)) {
    if (isUndefined) {
      masm.testUndefinedSet(JSOpToCondition(op, false), input, scratch);
    } else {
      masm.testNullSet(JSOpToCondition(op, false), input, scratch);
    }
    EmitStoreResult(masm, scratch, JSVAL_TYPE_BOOLEAN, output);
    return true;
  }

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  MOZ_ASSERT(IsLooseEqualityOp(op));

  Label nullOrLikeUndefined, notNullOrLikeUndefined, done;
  {
    ScratchTagScope tag(masm, input);
    masm.splitTagForTest(input, tag);

    if (isUndefined) {
      masm.branchTestUndefined(Assembler::Equal, tag, &nullOrLikeUndefined);
      masm.branchTestNull(Assembler::Equal, tag, &nullOrLikeUndefined);
    } else {
      masm.branchTestNull(Assembler::Equal, tag, &nullOrLikeUndefined);
      masm.branchTestUndefined(Assembler::Equal, tag, &nullOrLikeUndefined);
    }
    masm.branchTestObject(Assembler::NotEqual, tag, ¬NullOrLikeUndefined);

    {
      ScratchTagScopeRelease _(&tag);

      masm.unboxObject(input, scratch);
      masm.branchIfObjectEmulatesUndefined(scratch, scratch2, failure->label(),
                                           &nullOrLikeUndefined);
      masm.jump(¬NullOrLikeUndefined);
    }
  }

  masm.bind(&nullOrLikeUndefined);
  EmitStoreBoolean(masm, op == JSOp::Eq, output);
  masm.jump(&done);

  masm.bind(¬NullOrLikeUndefined);
  EmitStoreBoolean(masm, op == JSOp::Ne, output);

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitCompareDoubleSameValueResult(NumberOperandId lhsId,
                                                       NumberOperandId rhsId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);
  AutoAvailableFloatRegister floatScratch1(*this, FloatReg1);
  AutoAvailableFloatRegister floatScratch2(*this, FloatReg2);

  allocator.ensureDoubleRegister(masm, lhsId, floatScratch0);
  allocator.ensureDoubleRegister(masm, rhsId, floatScratch1);

  masm.sameValueDouble(floatScratch0, floatScratch1, floatScratch2, scratch);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitIndirectTruncateInt32Result(Int32OperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  Register val = allocator.useRegister(masm, valId);

  if (output.hasValue()) {
    masm.tagValue(JSVAL_TYPE_INT32, val, output.valueReg());
  } else {
    masm.mov(val, output.typedReg().gpr());
  }
  return true;
}

bool CacheIRCompiler::emitCallPrintString(const char* str) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  masm.printf(str);
  return true;
}

bool CacheIRCompiler::emitBreakpoint() {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  masm.breakpoint();
  return true;
}

void CacheIRCompiler::emitPostBarrierShared(Register obj,
                                            const ConstantOrRegister& val,
                                            Register scratch,
                                            Register maybeIndex) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  if (val.constant()) {
    MOZ_ASSERT_IF(val.value().isGCThing(),
                  !IsInsideNursery(val.value().toGCThing()));
    return;
  }

  TypedOrValueRegister reg = val.reg();
  if (reg.hasTyped() && !NeedsPostBarrier(reg.type())) {
    return;
  }

  Label skipBarrier;
  if (reg.hasValue()) {
    masm.branchValueIsNurseryCell(Assembler::NotEqual, reg.valueReg(), scratch,
                                  &skipBarrier);
  } else {
    masm.branchPtrInNurseryChunk(Assembler::NotEqual, reg.typedReg().gpr(),
                                 scratch, &skipBarrier);
  }
  masm.branchPtrInNurseryChunk(Assembler::Equal, obj, scratch, &skipBarrier);

  // Check one element cache to avoid VM call.
  auto* lastCellAddr = cx_->runtime()->gc.addressOfLastBufferedWholeCell();
  masm.branchPtr(Assembler::Equal, AbsoluteAddress(lastCellAddr), obj,
                 &skipBarrier);

  // Call one of these, depending on maybeIndex:
  //
  //   void PostWriteBarrier(JSRuntime* rt, JSObject* obj);
  //   void PostWriteElementBarrier(JSRuntime* rt, JSObject* obj,
  //                                int32_t index);
  LiveRegisterSet save = liveVolatileRegs();
  masm.PushRegsInMask(save);
  masm.setupUnalignedABICall(scratch);
  masm.movePtr(ImmPtr(cx_->runtime()), scratch);
  masm.passABIArg(scratch);
  masm.passABIArg(obj);
  if (maybeIndex != InvalidReg) {
    masm.passABIArg(maybeIndex);
    using Fn = void (*)(JSRuntime* rt, JSObject* obj, int32_t index);
    masm.callWithABI<Fn, PostWriteElementBarrier>();
  } else {
    using Fn = void (*)(JSRuntime* rt, js::gc::Cell* cell);
    masm.callWithABI<Fn, PostWriteBarrier>();
  }
  masm.PopRegsInMask(save);

  masm.bind(&skipBarrier);
}

bool CacheIRCompiler::emitWrapResult() {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label done;
  // We only have to wrap objects, because we are in the same zone.
  masm.branchTestObject(Assembler::NotEqual, output.valueReg(), &done);

  Register obj = output.valueReg().scratchReg();
  masm.unboxObject(output.valueReg(), obj);

  LiveRegisterSet save = liveVolatileRegs();
  masm.PushRegsInMask(save);

  using Fn = JSObject* (*)(JSContext* cx, JSObject* obj);
  masm.setupUnalignedABICall(scratch);
  masm.loadJSContext(scratch);
  masm.passABIArg(scratch);
  masm.passABIArg(obj);
  masm.callWithABI<Fn, WrapObjectPure>();
  masm.storeCallPointerResult(obj);

  LiveRegisterSet ignore;
  ignore.add(obj);
  masm.PopRegsInMaskIgnore(save, ignore);

  // We could not get a wrapper for this object.
  masm.branchTestPtr(Assembler::Zero, obj, obj, failure->label());

  // We clobbered the output register, so we have to retag.
  masm.tagValue(JSVAL_TYPE_OBJECT, obj, output.valueReg());

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitMegamorphicLoadSlotByValueResult(ObjOperandId objId,
                                                           ValOperandId idId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  Register obj = allocator.useRegister(masm, objId);
  ValueOperand idVal = allocator.useValueRegister(masm, idId);

#ifdef JS_CODEGEN_X86
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegisterMaybeOutputType scratch2(allocator, masm, output);
#else
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
#endif

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

#ifdef JS_CODEGEN_X86
  masm.xorPtr(scratch2, scratch2);
#else
  Label cacheHit;
  masm.emitMegamorphicCacheLookupByValue(
      idVal, obj, scratch1, scratch3, scratch2, output.valueReg(), &cacheHit);
#endif

  masm.branchIfNonNativeObj(obj, scratch1, failure->label());

  // idVal will be in vp[0], result will be stored in vp[1].
  masm.reserveStack(sizeof(Value));
  masm.Push(idVal);
  masm.moveStackPtrTo(idVal.scratchReg());

  LiveRegisterSet volatileRegs = liveVolatileRegs();
  volatileRegs.takeUnchecked(scratch1);
  volatileRegs.takeUnchecked(idVal);
  masm.PushRegsInMask(volatileRegs);

  using Fn = bool (*)(JSContext* cx, JSObject* obj,
                      MegamorphicCache::Entry* cacheEntry, Value* vp);
  masm.setupUnalignedABICall(scratch1);
  masm.loadJSContext(scratch1);
  masm.passABIArg(scratch1);
  masm.passABIArg(obj);
  masm.passABIArg(scratch2);
  masm.passABIArg(idVal.scratchReg());
  masm.callWithABI<Fn, GetNativeDataPropertyByValuePure>();

  masm.storeCallPointerResult(scratch1);
  masm.PopRegsInMask(volatileRegs);

  masm.Pop(idVal);

  Label ok;
  uint32_t framePushed = masm.framePushed();
  masm.branchIfTrueBool(scratch1, &ok);
  masm.adjustStack(sizeof(Value));
  masm.jump(failure->label());

  masm.bind(&ok);
  masm.setFramePushed(framePushed);
  masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output);
  masm.adjustStack(sizeof(Value));

#ifndef JS_CODEGEN_X86
  masm.bind(&cacheHit);
#endif
  return true;
}

bool CacheIRCompiler::emitMegamorphicLoadSlotByValuePermissiveResult(
    ObjOperandId objId, ValOperandId idId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  const AutoOutputRegister& output = callvm.output();

  Register obj = allocator.useRegister(masm, objId);
  ValueOperand idVal = allocator.useValueRegister(masm, idId);

#ifdef JS_CODEGEN_X86
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegisterMaybeOutputType scratch2(allocator, masm, output);
#else
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
#endif

#ifdef JS_CODEGEN_X86
  masm.xorPtr(scratch2, scratch2);
#else
  Label cacheHit;
  masm.emitMegamorphicCacheLookupByValue(
      idVal, obj, scratch1, scratch3, scratch2, output.valueReg(), &cacheHit);
#endif

  callvm.prepare();

  masm.Push(scratch2);
  masm.Push(idVal);
  masm.Push(obj);

  using Fn = bool (*)(JSContext*, HandleObject, HandleValue,
                      MegamorphicCacheEntry*, MutableHandleValue);
  callvm.call<Fn, GetElemMaybeCached>();

#ifndef JS_CODEGEN_X86
  masm.bind(&cacheHit);
#endif
  return true;
}

bool CacheIRCompiler::emitMegamorphicHasPropResult(ObjOperandId objId,
                                                   ValOperandId idId,
                                                   bool hasOwn) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  Register obj = allocator.useRegister(masm, objId);
  ValueOperand idVal = allocator.useValueRegister(masm, idId);

#ifdef JS_CODEGEN_X86
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegisterMaybeOutputType scratch2(allocator, masm, output);
#else
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
#endif

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

#ifndef JS_CODEGEN_X86
  Label cacheHit, done;
  masm.emitMegamorphicCacheLookupExists(idVal, obj, scratch1, scratch3,
                                        scratch2, output.maybeReg(), &cacheHit,
                                        hasOwn);
#else
  masm.xorPtr(scratch2, scratch2);
#endif

  masm.branchIfNonNativeObj(obj, scratch1, failure->label());

  // idVal will be in vp[0], result will be stored in vp[1].
  masm.reserveStack(sizeof(Value));
  masm.Push(idVal);
  masm.moveStackPtrTo(idVal.scratchReg());

  LiveRegisterSet volatileRegs = liveVolatileRegs();
  volatileRegs.takeUnchecked(scratch1);
  volatileRegs.takeUnchecked(idVal);
  masm.PushRegsInMask(volatileRegs);

  using Fn = bool (*)(JSContext* cx, JSObject* obj,
                      MegamorphicCache::Entry* cacheEntry, Value* vp);
  masm.setupUnalignedABICall(scratch1);
  masm.loadJSContext(scratch1);
  masm.passABIArg(scratch1);
  masm.passABIArg(obj);
  masm.passABIArg(scratch2);
  masm.passABIArg(idVal.scratchReg());
  if (hasOwn) {
    masm.callWithABI<Fn, HasNativeDataPropertyPure<true>>();
  } else {
    masm.callWithABI<Fn, HasNativeDataPropertyPure<false>>();
  }
  masm.storeCallPointerResult(scratch1);
  masm.PopRegsInMask(volatileRegs);

  masm.Pop(idVal);

  Label ok;
  uint32_t framePushed = masm.framePushed();
  masm.branchIfTrueBool(scratch1, &ok);
  masm.adjustStack(sizeof(Value));
  masm.jump(failure->label());

  masm.bind(&ok);
  masm.setFramePushed(framePushed);
  masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output);
  masm.adjustStack(sizeof(Value));

#ifndef JS_CODEGEN_X86
  masm.jump(&done);
  masm.bind(&cacheHit);
  if (output.hasValue()) {
    masm.tagValue(JSVAL_TYPE_BOOLEAN, output.valueReg().scratchReg(),
                  output.valueReg());
  }
  masm.bind(&done);
#endif
  return true;
}

bool CacheIRCompiler::emitSmallObjectVariableKeyHasOwnResult(
    StringOperandId idId, uint32_t propNamesOffset, uint32_t shapeOffset) {
  StubFieldOffset propNames(propNamesOffset, StubField::Type::JSObject);
  AutoOutputRegister output(*this);
  Register id = allocator.useRegister(masm, idId);
  AutoScratchRegisterMaybeOutput propNamesReg(allocator, masm, output);
  AutoScratchRegister endScratch(allocator, masm);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  emitLoadStubField(propNames, propNamesReg);

  Label trueResult, falseResult, loop, done;

  masm.loadPtr(Address(propNamesReg, NativeObject::offsetOfElements()),
               propNamesReg);
  // Compute end pointer.
  Address lengthAddr(propNamesReg, ObjectElements::offsetOfInitializedLength());
  masm.load32(lengthAddr, endScratch);
  masm.branch32(Assembler::Equal, endScratch, Imm32(0), &falseResult);
  BaseObjectElementIndex endPtrAddr(propNamesReg, endScratch);
  masm.computeEffectiveAddress(endPtrAddr, endScratch);

  masm.bind(&loop);

  Address atomAddr(propNamesReg.get(), 0);

  masm.unboxString(atomAddr, scratch);
  masm.branchPtr(Assembler::Equal, scratch, id, &trueResult);

  masm.addPtr(Imm32(sizeof(Value)), propNamesReg);
  masm.branchPtr(Assembler::Below, propNamesReg, endScratch, &loop);

  masm.bind(&falseResult);
  if (output.hasValue()) {
    masm.moveValue(BooleanValue(false), output.valueReg());
  } else {
    masm.move32(Imm32(0), output.typedReg().gpr());
  }
  masm.jump(&done);
  masm.bind(&trueResult);
  if (output.hasValue()) {
    masm.moveValue(BooleanValue(true), output.valueReg());
  } else {
    masm.move32(Imm32(1), output.typedReg().gpr());
  }
  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitCallObjectHasSparseElementResult(
    ObjOperandId objId, Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);

  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.reserveStack(sizeof(Value));
  masm.moveStackPtrTo(scratch2.get());

  LiveRegisterSet volatileRegs = liveVolatileRegs();
  volatileRegs.takeUnchecked(scratch1);
  volatileRegs.takeUnchecked(index);
  masm.PushRegsInMask(volatileRegs);

  using Fn =
      bool (*)(JSContext* cx, NativeObject* obj, int32_t index, Value* vp);
  masm.setupUnalignedABICall(scratch1);
  masm.loadJSContext(scratch1);
  masm.passABIArg(scratch1);
  masm.passABIArg(obj);
  masm.passABIArg(index);
  masm.passABIArg(scratch2);
  masm.callWithABI<Fn, HasNativeElementPure>();
  masm.storeCallPointerResult(scratch1);
  masm.PopRegsInMask(volatileRegs);

  Label ok;
  uint32_t framePushed = masm.framePushed();
  masm.branchIfTrueBool(scratch1, &ok);
  masm.adjustStack(sizeof(Value));
  masm.jump(failure->label());

  masm.bind(&ok);
  masm.setFramePushed(framePushed);
  masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output);
  masm.adjustStack(sizeof(Value));
  return true;
}

/*
 * Move a constant value into register dest.
 */

void CacheIRCompiler::emitLoadStubFieldConstant(StubFieldOffset val,
                                                Register dest) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  MOZ_ASSERT(mode_ == Mode::Ion);
  switch (val.getStubFieldType()) {
    case StubField::Type::Shape:
      masm.movePtr(ImmGCPtr(shapeStubField(val.getOffset())), dest);
      break;
    case StubField::Type::WeakGetterSetter:
      masm.movePtr(ImmGCPtr(weakGetterSetterStubField(val.getOffset())), dest);
      break;
    case StubField::Type::String:
      masm.movePtr(ImmGCPtr(stringStubField(val.getOffset())), dest);
      break;
    case StubField::Type::JSObject:
      masm.movePtr(ImmGCPtr(objectStubField(val.getOffset())), dest);
      break;
    case StubField::Type::RawPointer:
      masm.movePtr(ImmPtr(pointerStubField(val.getOffset())), dest);
      break;
    case StubField::Type::RawInt32:
      masm.move32(Imm32(int32StubField(val.getOffset())), dest);
      break;
    case StubField::Type::Id:
      masm.movePropertyKey(idStubField(val.getOffset()), dest);
      break;
    default:
      MOZ_CRASH("Unhandled stub field constant type");
  }
}

/*
 * After this is done executing, dest contains the value; either through a
 * constant load or through the load from the stub data.
 *
 * The current policy is that Baseline will use loads from the stub data (to
 * allow IC sharing), where as Ion doesn't share ICs, and so we can safely use
 * constants in the IC.
 */

void CacheIRCompiler::emitLoadStubField(StubFieldOffset val, Register dest) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  if (stubFieldPolicy_ == StubFieldPolicy::Constant) {
    emitLoadStubFieldConstant(val, dest);
  } else {
    Address load(ICStubReg, stubDataOffset_ + val.getOffset());

    switch (val.getStubFieldType()) {
      case StubField::Type::RawPointer:
      case StubField::Type::Shape:
      case StubField::Type::WeakGetterSetter:
      case StubField::Type::JSObject:
      case StubField::Type::Symbol:
      case StubField::Type::String:
      case StubField::Type::Id:
      case StubField::Type::AllocSite:
        masm.loadPtr(load, dest);
        break;
      case StubField::Type::RawInt32:
        masm.load32(load, dest);
        break;
      default:
        MOZ_CRASH("Unhandled stub field constant type");
    }
  }
}

void CacheIRCompiler::emitLoadValueStubField(StubFieldOffset val,
                                             ValueOperand dest) {
  MOZ_ASSERT(val.getStubFieldType() == StubField::Type::Value);

  if (stubFieldPolicy_ == StubFieldPolicy::Constant) {
    MOZ_ASSERT(mode_ == Mode::Ion);
    masm.moveValue(valueStubField(val.getOffset()), dest);
  } else {
    Address addr(ICStubReg, stubDataOffset_ + val.getOffset());
    masm.loadValue(addr, dest);
  }
}

void CacheIRCompiler::emitLoadDoubleValueStubField(StubFieldOffset val,
                                                   ValueOperand dest,
                                                   FloatRegister scratch) {
  MOZ_ASSERT(val.getStubFieldType() == StubField::Type::Double);

  if (stubFieldPolicy_ == StubFieldPolicy::Constant) {
    MOZ_ASSERT(mode_ == Mode::Ion);
    double d = doubleStubField(val.getOffset());
    masm.moveValue(DoubleValue(d), dest);
  } else {
    Address addr(ICStubReg, stubDataOffset_ + val.getOffset());
    masm.loadDouble(addr, scratch);
    masm.boxDouble(scratch, dest, scratch);
  }
}

bool CacheIRCompiler::emitLoadInstanceOfObjectResult(ValOperandId lhsId,
                                                     ObjOperandId protoId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  ValueOperand lhs = allocator.useValueRegister(masm, lhsId);
  Register proto = allocator.useRegister(masm, protoId);

  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  Label returnFalse, returnTrue, done;
  masm.fallibleUnboxObject(lhs, scratch, &returnFalse);

  // LHS is an object. Load its proto.
  masm.loadObjProto(scratch, scratch);
  {
    // Walk the proto chain until we either reach the target object,
    // nullptr or LazyProto.
    Label loop;
    masm.bind(&loop);

    masm.branchPtr(Assembler::Equal, scratch, proto, &returnTrue);
    masm.branchTestPtr(Assembler::Zero, scratch, scratch, &returnFalse);

    MOZ_ASSERT(uintptr_t(TaggedProto::LazyProto) == 1);
    masm.branchPtr(Assembler::Equal, scratch, ImmWord(1), failure->label());

    masm.loadObjProto(scratch, scratch);
    masm.jump(&loop);
  }

  masm.bind(&returnFalse);
  EmitStoreBoolean(masm, false, output);
  masm.jump(&done);

  masm.bind(&returnTrue);
  EmitStoreBoolean(masm, true, output);
  // fallthrough
  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitMegamorphicLoadSlotResult(ObjOperandId objId,
                                                    uint32_t idOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);

  Register obj = allocator.useRegister(masm, objId);
  StubFieldOffset id(idOffset, StubField::Type::Id);

  AutoScratchRegisterMaybeOutput idReg(allocator, masm, output);
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegisterMaybeOutputType scratch3(allocator, masm, output);

#ifdef JS_CODEGEN_X86
  masm.xorPtr(scratch3, scratch3);
#else
  Label cacheHit;
  emitLoadStubField(id, idReg);
  masm.emitMegamorphicCacheLookupByValue(idReg.get(), obj, scratch1, scratch2,
                                         scratch3, output.valueReg(),
                                         &cacheHit);
#endif

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.branchIfNonNativeObj(obj, scratch1, failure->label());

  masm.Push(UndefinedValue());
  masm.moveStackPtrTo(idReg.get());

  LiveRegisterSet volatileRegs = liveVolatileRegs();
  volatileRegs.takeUnchecked(scratch1);
  volatileRegs.takeUnchecked(scratch2);
  volatileRegs.takeUnchecked(scratch3);
  volatileRegs.takeUnchecked(idReg);
  masm.PushRegsInMask(volatileRegs);

  using Fn = bool (*)(JSContext* cx, JSObject* obj, PropertyKey id,
                      MegamorphicCache::Entry* cacheEntry, Value* vp);
  masm.setupUnalignedABICall(scratch1);
  masm.loadJSContext(scratch1);
  masm.passABIArg(scratch1);
  masm.passABIArg(obj);
  emitLoadStubField(id, scratch2);
  masm.passABIArg(scratch2);
  masm.passABIArg(scratch3);
  masm.passABIArg(idReg);

#ifdef JS_CODEGEN_X86
  masm.callWithABI<Fn, GetNativeDataPropertyPureWithCacheLookup>();
#else
  masm.callWithABI<Fn, GetNativeDataPropertyPure>();
#endif

  masm.storeCallPointerResult(scratch2);
  masm.PopRegsInMask(volatileRegs);

  masm.loadTypedOrValue(Address(masm.getStackPointer(), 0), output);
  masm.adjustStack(sizeof(Value));

  masm.branchIfFalseBool(scratch2, failure->label());
#ifndef JS_CODEGEN_X86
  masm.bind(&cacheHit);
#endif

  return true;
}

bool CacheIRCompiler::emitMegamorphicLoadSlotPermissiveResult(
    ObjOperandId objId, uint32_t idOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoCallVM callvm(masm, this, allocator);

  const AutoOutputRegister& output = callvm.output();

  Register obj = allocator.useRegister(masm, objId);
  StubFieldOffset id(idOffset, StubField::Type::Id);

  AutoScratchRegisterMaybeOutput idReg(allocator, masm, output);
  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegisterMaybeOutputType scratch3(allocator, masm, output);

#ifdef JS_CODEGEN_X86
  masm.xorPtr(scratch3, scratch3);
#else
  Label cacheHit;
  emitLoadStubField(id, idReg);
  masm.emitMegamorphicCacheLookupByValue(idReg.get(), obj, scratch1, scratch2,
                                         scratch3, output.valueReg(),
                                         &cacheHit);
#endif

  callvm.prepare();

  emitLoadStubField(id, scratch2);
  masm.Push(scratch3);
  masm.Push(scratch2);
  masm.Push(obj);

  using Fn = bool (*)(JSContext*, HandleObject, HandleId,
                      MegamorphicCacheEntry*, MutableHandleValue);
  callvm.call<Fn, GetPropMaybeCached>();

#ifndef JS_CODEGEN_X86
  masm.bind(&cacheHit);
#endif

  return true;
}

bool CacheIRCompiler::emitMegamorphicStoreSlot(ObjOperandId objId,
                                               uint32_t idOffset,
                                               ValOperandId rhsId,
                                               bool strict) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register obj = allocator.useRegister(masm, objId);
  ConstantOrRegister val = allocator.useConstantOrRegister(masm, rhsId);
  StubFieldOffset id(idOffset, StubField::Type::Id);
  AutoScratchRegister scratch(allocator, masm);

  callvm.prepare();

  masm.Push(Imm32(strict));
  masm.Push(val);
  emitLoadStubField(id, scratch);
  masm.Push(scratch);
  masm.Push(obj);

  using Fn = bool (*)(JSContext*, HandleObject, HandleId, HandleValue, bool);
  callvm.callNoResult<Fn, SetPropertyMegamorphic<false>>();
  return true;
}

bool CacheIRCompiler::emitGuardHasGetterSetter(ObjOperandId objId,
                                               uint32_t idOffset,
                                               uint32_t getterSetterOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);

  StubFieldOffset id(idOffset, StubField::Type::Id);
  StubFieldOffset getterSetter(getterSetterOffset,
                               StubField::Type::WeakGetterSetter);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  LiveRegisterSet volatileRegs = liveVolatileRegs();
  volatileRegs.takeUnchecked(scratch1);
  volatileRegs.takeUnchecked(scratch2);
  masm.PushRegsInMask(volatileRegs);

  using Fn = bool (*)(JSContext* cx, JSObject* obj, jsid id,
                      GetterSetter* getterSetter);
  masm.setupUnalignedABICall(scratch1);
  masm.loadJSContext(scratch1);
  masm.passABIArg(scratch1);
  masm.passABIArg(obj);
  emitLoadStubField(id, scratch2);
  masm.passABIArg(scratch2);
  emitLoadStubField(getterSetter, scratch3);
  masm.passABIArg(scratch3);
  masm.callWithABI<Fn, ObjectHasGetterSetterPure>();
  masm.storeCallPointerResult(scratch1);
  masm.PopRegsInMask(volatileRegs);

  masm.branchIfFalseBool(scratch1, failure->label());
  return true;
}

bool CacheIRCompiler::emitGuardWasmArg(ValOperandId argId,
                                       wasm::ValType::Kind kind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  // All values can be boxed as AnyRef.
  if (kind == wasm::ValType::Ref) {
    return true;
  }
  MOZ_ASSERT(kind != wasm::ValType::V128);

  ValueOperand arg = allocator.useValueRegister(masm, argId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Check that the argument can be converted to the Wasm type in Warp code
  // without bailing out.
  Label done;
  switch (kind) {
    case wasm::ValType::I32:
    case wasm::ValType::F32:
    case wasm::ValType::F64: {
      // Argument must be number, bool, or undefined.
      masm.branchTestNumber(Assembler::Equal, arg, &done);
      masm.branchTestBoolean(Assembler::Equal, arg, &done);
      masm.branchTestUndefined(Assembler::NotEqual, arg, failure->label());
      break;
    }
    case wasm::ValType::I64: {
      // Argument must be bigint, bool, or string.
      masm.branchTestBigInt(Assembler::Equal, arg, &done);
      masm.branchTestBoolean(Assembler::Equal, arg, &done);
      masm.branchTestString(Assembler::NotEqual, arg, failure->label());
      break;
    }
    default:
      MOZ_CRASH("Unexpected kind");
  }
  masm.bind(&done);

  return true;
}

bool CacheIRCompiler::emitGuardMultipleShapes(ObjOperandId objId,
                                              uint32_t shapesOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegister shapes(allocator, masm);
  AutoScratchRegister scratch(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);

  bool needSpectreMitigations = objectGuardNeedsSpectreMitigations(objId);

  Register spectreScratch = InvalidReg;
  Maybe<AutoScratchRegister> maybeSpectreScratch;
  if (needSpectreMitigations) {
    maybeSpectreScratch.emplace(allocator, masm);
    spectreScratch = *maybeSpectreScratch;
  }

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // The stub field contains a ListObject. Load its elements.
  StubFieldOffset shapeArray(shapesOffset, StubField::Type::JSObject);
  emitLoadStubField(shapeArray, shapes);
  masm.loadPtr(Address(shapes, NativeObject::offsetOfElements()), shapes);

  masm.branchTestObjShapeList(Assembler::NotEqual, obj, shapes, scratch,
                              scratch2, spectreScratch, failure->label());
  return true;
}

bool CacheIRCompiler::emitLoadObject(ObjOperandId resultId,
                                     uint32_t objOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register reg = allocator.defineRegister(masm, resultId);
  StubFieldOffset obj(objOffset, StubField::Type::JSObject);
  emitLoadStubField(obj, reg);
  return true;
}

bool CacheIRCompiler::emitLoadProtoObject(ObjOperandId resultId,
                                          uint32_t objOffset,
                                          ObjOperandId receiverObjId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register reg = allocator.defineRegister(masm, resultId);
  StubFieldOffset obj(objOffset, StubField::Type::JSObject);
  emitLoadStubField(obj, reg);
  return true;
}

bool CacheIRCompiler::emitLoadInt32Constant(uint32_t valOffset,
                                            Int32OperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register reg = allocator.defineRegister(masm, resultId);
  StubFieldOffset val(valOffset, StubField::Type::RawInt32);
  emitLoadStubField(val, reg);
  return true;
}

bool CacheIRCompiler::emitLoadBooleanConstant(bool val,
                                              BooleanOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register reg = allocator.defineRegister(masm, resultId);
  masm.move32(Imm32(val), reg);
  return true;
}

bool CacheIRCompiler::emitLoadDoubleConstant(uint32_t valOffset,
                                             NumberOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  ValueOperand output = allocator.defineValueRegister(masm, resultId);
  StubFieldOffset val(valOffset, StubField::Type::Double);

  AutoScratchFloatRegister floatReg(this);

  emitLoadDoubleValueStubField(val, output, floatReg);
  return true;
}

bool CacheIRCompiler::emitLoadUndefined(ValOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  ValueOperand reg = allocator.defineValueRegister(masm, resultId);
  masm.moveValue(UndefinedValue(), reg);
  return true;
}

bool CacheIRCompiler::emitLoadConstantString(uint32_t strOffset,
                                             StringOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register reg = allocator.defineRegister(masm, resultId);
  StubFieldOffset str(strOffset, StubField::Type::String);
  emitLoadStubField(str, reg);
  return true;
}

bool CacheIRCompiler::emitCallInt32ToString(Int32OperandId inputId,
                                            StringOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register input = allocator.useRegister(masm, inputId);
  Register result = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  LiveRegisterSet volatileRegs = liveVolatileRegs();
  volatileRegs.takeUnchecked(result);
  masm.PushRegsInMask(volatileRegs);

  using Fn = JSLinearString* (*)(JSContext* cx, int32_t i);
  masm.setupUnalignedABICall(result);
  masm.loadJSContext(result);
  masm.passABIArg(result);
  masm.passABIArg(input);
  masm.callWithABI<Fn, js::Int32ToStringPure>();

  masm.storeCallPointerResult(result);
  masm.PopRegsInMask(volatileRegs);

  masm.branchPtr(Assembler::Equal, result, ImmPtr(nullptr), failure->label());
  return true;
}

bool CacheIRCompiler::emitCallNumberToString(NumberOperandId inputId,
                                             StringOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoAvailableFloatRegister floatScratch0(*this, FloatReg0);

  allocator.ensureDoubleRegister(masm, inputId, floatScratch0);
  Register result = allocator.defineRegister(masm, resultId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  LiveRegisterSet volatileRegs = liveVolatileRegs();
  volatileRegs.takeUnchecked(result);
  masm.PushRegsInMask(volatileRegs);

  using Fn = JSString* (*)(JSContext* cx, double d);
  masm.setupUnalignedABICall(result);
  masm.loadJSContext(result);
  masm.passABIArg(result);
  masm.passABIArg(floatScratch0, ABIType::Float64);
  masm.callWithABI<Fn, js::NumberToStringPure>();

  masm.storeCallPointerResult(result);
  masm.PopRegsInMask(volatileRegs);

  masm.branchPtr(Assembler::Equal, result, ImmPtr(nullptr), failure->label());
  return true;
}

bool CacheIRCompiler::emitInt32ToStringWithBaseResult(Int32OperandId inputId,
                                                      Int32OperandId baseId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);
  Register input = allocator.useRegister(masm, inputId);
  Register base = allocator.useRegister(masm, baseId);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // AutoCallVM's AutoSaveLiveRegisters aren't accounted for in FailurePath, so
  // we can't use both at the same time. This isn't an issue here, because Ion
  // doesn't support CallICs. If that ever changes, this code must be updated.
  MOZ_ASSERT(isBaseline(), "Can't use FailurePath with AutoCallVM in Ion ICs");

  masm.branch32(Assembler::LessThan, base, Imm32(2), failure->label());
  masm.branch32(Assembler::GreaterThan, base, Imm32(36), failure->label());

  // Use lower-case characters by default.
  constexpr bool lowerCase = true;

  callvm.prepare();

  masm.Push(Imm32(lowerCase));
  masm.Push(base);
  masm.Push(input);

  using Fn = JSLinearString* (*)(JSContext*, int32_t, int32_t, bool);
  callvm.call<Fn, js::Int32ToStringWithBase<CanGC>>();
  return true;
}

bool CacheIRCompiler::emitBooleanToString(BooleanOperandId inputId,
                                          StringOperandId resultId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  Register boolean = allocator.useRegister(masm, inputId);
  Register result = allocator.defineRegister(masm, resultId);
  const JSAtomState& names = cx_->names();
  Label true_, done;

  masm.branchTest32(Assembler::NonZero, boolean, boolean, &true_);

  // False case
  masm.movePtr(ImmGCPtr(names.false_), result);
  masm.jump(&done);

  // True case
  masm.bind(&true_);
  masm.movePtr(ImmGCPtr(names.true_), result);
  masm.bind(&done);

  return true;
}

bool CacheIRCompiler::emitObjectToStringResult(ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  LiveRegisterSet volatileRegs = liveVolatileRegs();
  volatileRegs.takeUnchecked(output.valueReg());
  volatileRegs.takeUnchecked(scratch);
  masm.PushRegsInMask(volatileRegs);

  using Fn = JSString* (*)(JSContext*, JSObject*);
  masm.setupUnalignedABICall(scratch);
  masm.loadJSContext(scratch);
  masm.passABIArg(scratch);
  masm.passABIArg(obj);
  masm.callWithABI<Fn, js::ObjectClassToString>();
  masm.storeCallPointerResult(scratch);

  masm.PopRegsInMask(volatileRegs);

  masm.branchPtr(Assembler::Equal, scratch, ImmPtr(nullptr), failure->label());
  masm.tagValue(JSVAL_TYPE_STRING, scratch, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitConcatStringsResult(StringOperandId lhsId,
                                              StringOperandId rhsId,
                                              uint32_t stubOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);
  const AutoOutputRegister& output = callvm.output();

  Register lhs = allocator.useRegister(masm, lhsId);
  Register rhs = allocator.useRegister(masm, rhsId);

  // Discard the stack to ensure it's balanced when we skip the vm-call.
  allocator.discardStack(masm);

  Label done;
  {
    // The StringConcat stub uses CallTemp registers 0 to 5.
    LiveGeneralRegisterSet liveRegs;
    liveRegs.add(CallTempReg0);
    liveRegs.add(CallTempReg1);
    liveRegs.add(CallTempReg2);
    liveRegs.add(CallTempReg3);
    liveRegs.add(CallTempReg4);
    liveRegs.add(CallTempReg5);
#ifdef JS_USE_LINK_REGISTER
    liveRegs.add(ICTailCallReg);
#endif
    liveRegs.takeUnchecked(output.valueReg());
    masm.PushRegsInMask(liveRegs);

    // The stub expects lhs in CallTempReg0 and rhs in CallTempReg1.
    masm.moveRegPair(lhs, rhs, CallTempReg0, CallTempReg1);

    uint32_t framePushed = masm.framePushed();

    // Call cx->zone()->jitZone()->stringConcatStub. See also the comment and
    // code in CallRegExpStub.
    Label vmCall;
    Register temp = CallTempReg2;
    masm.loadJSContext(temp);
    masm.loadPtr(Address(temp, JSContext::offsetOfZone()), temp);
    masm.loadPtr(Address(temp, Zone::offsetOfJitZone()), temp);
    masm.loadPtr(Address(temp, JitZone::offsetOfStringConcatStub()), temp);
    masm.branchTestPtr(Assembler::Zero, temp, temp, &vmCall);
    masm.call(Address(temp, JitCode::offsetOfCode()));

    // The result is returned in CallTempReg5 (nullptr on failure).
    masm.branchTestPtr(Assembler::Zero, CallTempReg5, CallTempReg5, &vmCall);
    masm.tagValue(JSVAL_TYPE_STRING, CallTempReg5, output.valueReg());
    masm.PopRegsInMask(liveRegs);
    masm.jump(&done);

    masm.bind(&vmCall);
    masm.setFramePushed(framePushed);
    masm.PopRegsInMask(liveRegs);
  }

  {
    callvm.prepare();

    masm.Push(static_cast<js::jit::Imm32>(int32_t(js::gc::Heap::Default)));
    masm.Push(rhs);
    masm.Push(lhs);

    using Fn =
        JSString* (*)(JSContext*, HandleString, HandleString, js::gc::Heap);
    callvm.call<Fn, ConcatStrings<CanGC>>();
  }

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitCallIsSuspendedGeneratorResult(ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);
  ValueOperand input = allocator.useValueRegister(masm, valId);

  // Test if it's an object.
  Label returnFalse, done;
  masm.fallibleUnboxObject(input, scratch, &returnFalse);

  // Test if it's a GeneratorObject.
  masm.branchTestObjClass(Assembler::NotEqual, scratch,
                          &GeneratorObject::class_, scratch2, scratch,
                          &returnFalse);

  // If the resumeIndex slot holds an int32 value < RESUME_INDEX_RUNNING,
  // the generator is suspended.
  Address addr(scratch, AbstractGeneratorObject::offsetOfResumeIndexSlot());
  masm.fallibleUnboxInt32(addr, scratch, &returnFalse);
  masm.branch32(Assembler::AboveOrEqual, scratch,
                Imm32(AbstractGeneratorObject::RESUME_INDEX_RUNNING),
                &returnFalse);

  masm.moveValue(BooleanValue(true), output.valueReg());
  masm.jump(&done);

  masm.bind(&returnFalse);
  masm.moveValue(BooleanValue(false), output.valueReg());

  masm.bind(&done);
  return true;
}

// This op generates no code. It is consumed by the transpiler.
bool CacheIRCompiler::emitMetaScriptedThisShape(uint32_t) { return true; }

bool CacheIRCompiler::emitCallNativeGetElementResult(ObjOperandId objId,
                                                     Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoCallVM callvm(masm, this, allocator);

  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);

  callvm.prepare();

  masm.Push(index);
  masm.Push(TypedOrValueRegister(MIRType::Object, AnyRegister(obj)));
  masm.Push(obj);

  using Fn = bool (*)(JSContext*, Handle<NativeObject*>, HandleValue, int32_t,
                      MutableHandleValue);
  callvm.call<Fn, NativeGetElement>();

  return true;
}

bool CacheIRCompiler::emitCallNativeGetElementSuperResult(
    ObjOperandId objId, Int32OperandId indexId, ValOperandId receiverId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoCallVM callvm(masm, this, allocator);

  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  ValueOperand receiver = allocator.useValueRegister(masm, receiverId);

  callvm.prepare();

  masm.Push(index);
  masm.Push(receiver);
  masm.Push(obj);

  using Fn = bool (*)(JSContext*, Handle<NativeObject*>, HandleValue, int32_t,
                      MutableHandleValue);
  callvm.call<Fn, NativeGetElement>();

  return true;
}

bool CacheIRCompiler::emitProxyHasPropResult(ObjOperandId objId,
                                             ValOperandId idId, bool hasOwn) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoCallVM callvm(masm, this, allocator);

  Register obj = allocator.useRegister(masm, objId);
  ValueOperand idVal = allocator.useValueRegister(masm, idId);

  callvm.prepare();

  masm.Push(idVal);
  masm.Push(obj);

  using Fn = bool (*)(JSContext*, HandleObject, HandleValue, bool*);
  if (hasOwn) {
    callvm.call<Fn, ProxyHasOwn>();
  } else {
    callvm.call<Fn, ProxyHas>();
  }
  return true;
}

bool CacheIRCompiler::emitProxyGetByValueResult(ObjOperandId objId,
                                                ValOperandId idId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoCallVM callvm(masm, this, allocator);

  Register obj = allocator.useRegister(masm, objId);
  ValueOperand idVal = allocator.useValueRegister(masm, idId);

  callvm.prepare();
  masm.Push(idVal);
  masm.Push(obj);

  using Fn =
      bool (*)(JSContext*, HandleObject, HandleValue, MutableHandleValue);
  callvm.call<Fn, ProxyGetPropertyByValue>();
  return true;
}

bool CacheIRCompiler::emitCallGetSparseElementResult(ObjOperandId objId,
                                                     Int32OperandId indexId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register obj = allocator.useRegister(masm, objId);
  Register id = allocator.useRegister(masm, indexId);

  callvm.prepare();
  masm.Push(id);
  masm.Push(obj);

  using Fn = bool (*)(JSContext* cx, Handle<NativeObject*> obj, int32_t int_id,
                      MutableHandleValue result);
  callvm.call<Fn, GetSparseElementHelper>();
  return true;
}

bool CacheIRCompiler::emitRegExpSearcherLastLimitResult() {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);

  masm.loadAndClearRegExpSearcherLastLimit(scratch1, scratch2);

  masm.tagValue(JSVAL_TYPE_INT32, scratch1, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitRegExpFlagResult(ObjOperandId regexpId,
                                           int32_t flagsMask) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register regexp = allocator.useRegister(masm, regexpId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  Address flagsAddr(
      regexp, NativeObject::getFixedSlotOffset(RegExpObject::flagsSlot()));
  masm.unboxInt32(flagsAddr, scratch);

  Label ifFalse, done;
  masm.branchTest32(Assembler::Zero, scratch, Imm32(flagsMask), &ifFalse);
  masm.moveValue(BooleanValue(true), output.valueReg());
  masm.jump(&done);

  masm.bind(&ifFalse);
  masm.moveValue(BooleanValue(false), output.valueReg());

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitCallSubstringKernelResult(StringOperandId strId,
                                                    Int32OperandId beginId,
                                                    Int32OperandId lengthId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);
  Register begin = allocator.useRegister(masm, beginId);
  Register length = allocator.useRegister(masm, lengthId);

  callvm.prepare();
  masm.Push(length);
  masm.Push(begin);
  masm.Push(str);

  using Fn = JSString* (*)(JSContext* cx, HandleString str, int32_t begin,
                           int32_t len);
  callvm.call<Fn, SubstringKernel>();
  return true;
}

bool CacheIRCompiler::emitStringReplaceStringResult(
    StringOperandId strId, StringOperandId patternId,
    StringOperandId replacementId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);
  Register pattern = allocator.useRegister(masm, patternId);
  Register replacement = allocator.useRegister(masm, replacementId);

  callvm.prepare();
  masm.Push(replacement);
  masm.Push(pattern);
  masm.Push(str);

  using Fn =
      JSString* (*)(JSContext*, HandleString, HandleString, HandleString);
  callvm.call<Fn, jit::StringReplace>();
  return true;
}

bool CacheIRCompiler::emitStringSplitStringResult(StringOperandId strId,
                                                  StringOperandId separatorId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);
  Register separator = allocator.useRegister(masm, separatorId);

  callvm.prepare();
  masm.Push(Imm32(INT32_MAX));
  masm.Push(separator);
  masm.Push(str);

  using Fn = ArrayObject* (*)(JSContext*, HandleString, HandleString, uint32_t);
  callvm.call<Fn, js::StringSplitString>();
  return true;
}

bool CacheIRCompiler::emitRegExpPrototypeOptimizableResult(
    ObjOperandId protoId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register proto = allocator.useRegister(masm, protoId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  Label slow, done;
  masm.branchIfNotRegExpPrototypeOptimizable(
      proto, scratch, /* maybeGlobal = */ nullptr, &slow);
  masm.moveValue(BooleanValue(true), output.valueReg());
  masm.jump(&done);

  {
    masm.bind(&slow);

    LiveRegisterSet volatileRegs = liveVolatileRegs();
    volatileRegs.takeUnchecked(scratch);
    masm.PushRegsInMask(volatileRegs);

    using Fn = bool (*)(JSContext* cx, JSObject* proto);
    masm.setupUnalignedABICall(scratch);
    masm.loadJSContext(scratch);
    masm.passABIArg(scratch);
    masm.passABIArg(proto);
    masm.callWithABI<Fn, RegExpPrototypeOptimizableRaw>();
    masm.storeCallBoolResult(scratch);

    masm.PopRegsInMask(volatileRegs);
    masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg());
  }

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitRegExpInstanceOptimizableResult(
    ObjOperandId regexpId, ObjOperandId protoId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register regexp = allocator.useRegister(masm, regexpId);
  Register proto = allocator.useRegister(masm, protoId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  Label slow, done;
  masm.branchIfNotRegExpInstanceOptimizable(regexp, scratch,
                                            /* maybeGlobal = */ nullptr, &slow);
  masm.moveValue(BooleanValue(true), output.valueReg());
  masm.jump(&done);

  {
    masm.bind(&slow);

    LiveRegisterSet volatileRegs = liveVolatileRegs();
    volatileRegs.takeUnchecked(scratch);
    masm.PushRegsInMask(volatileRegs);

    using Fn = bool (*)(JSContext* cx, JSObject* obj, JSObject* proto);
    masm.setupUnalignedABICall(scratch);
    masm.loadJSContext(scratch);
    masm.passABIArg(scratch);
    masm.passABIArg(regexp);
    masm.passABIArg(proto);
    masm.callWithABI<Fn, RegExpInstanceOptimizableRaw>();
    masm.storeCallBoolResult(scratch);

    masm.PopRegsInMask(volatileRegs);
    masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg());
  }

  masm.bind(&done);
  return true;
}

bool CacheIRCompiler::emitGetFirstDollarIndexResult(StringOperandId strId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register str = allocator.useRegister(masm, strId);

  callvm.prepare();
  masm.Push(str);

  using Fn = bool (*)(JSContext*, JSString*, int32_t*);
  callvm.call<Fn, GetFirstDollarIndexRaw>();
  return true;
}

bool CacheIRCompiler::emitAtomicsCompareExchangeResult(
    ObjOperandId objId, IntPtrOperandId indexId, uint32_t expectedId,
    uint32_t replacementId, Scalar::Type elementType,
    ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Maybe<AutoOutputRegister> output;
  Maybe<AutoCallVM> callvm;
  if (!Scalar::isBigIntType(elementType)) {
    output.emplace(*this);
  } else {
    callvm.emplace(masm, this, allocator);
  }
#ifdef JS_CODEGEN_X86
  // Use a scratch register to avoid running out of registers.
  Register obj = output ? output->valueReg().typeReg()
                        : callvm->outputValueReg().typeReg();
  allocator.copyToScratchRegister(masm, objId, obj);
#else
  Register obj = allocator.useRegister(masm, objId);
#endif
  Register index = allocator.useRegister(masm, indexId);
  Register expected;
  Register replacement;
  if (!Scalar::isBigIntType(elementType)) {
    expected = allocator.useRegister(masm, Int32OperandId(expectedId));
    replacement = allocator.useRegister(masm, Int32OperandId(replacementId));
  } else {
    expected = allocator.useRegister(masm, BigIntOperandId(expectedId));
    replacement = allocator.useRegister(masm, BigIntOperandId(replacementId));
  }

  Register scratch = output ? output->valueReg().scratchReg()
                            : callvm->outputValueReg().scratchReg();
  MOZ_ASSERT(scratch != obj, "scratchReg must not be typeReg");

  Maybe<AutoScratchRegister> scratch2;
  if (viewKind == ArrayBufferViewKind::Resizable) {
#ifdef JS_CODEGEN_X86
    // Not enough spare registers on x86.
#else
    scratch2.emplace(allocator, masm);
#endif
  }

  // Not enough registers on X86.
  constexpr auto spectreTemp = mozilla::Nothing{};

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // AutoCallVM's AutoSaveLiveRegisters aren't accounted for in FailurePath, so
  // we can't use both at the same time. This isn't an issue here, because Ion
  // doesn't support CallICs. If that ever changes, this code must be updated.
  MOZ_ASSERT(isBaseline(), "Can't use FailurePath with AutoCallVM in Ion ICs");

  // Bounds check.
  emitTypedArrayBoundsCheck(viewKind, obj, index, scratch, scratch2,
                            spectreTemp, failure->label());

  // Atomic operations are highly platform-dependent, for example x86/x64 has
  // specific requirements on which registers are used; MIPS needs multiple
  // additional temporaries. Therefore we're using either an ABI or VM call here
  // instead of handling each platform separately.

  if (Scalar::isBigIntType(elementType)) {
    callvm->prepare();

    masm.Push(replacement);
    masm.Push(expected);
    masm.Push(index);
    masm.Push(obj);

    using Fn = BigInt* (*)(JSContext*, TypedArrayObject*, size_t, const BigInt*,
                           const BigInt*);
    callvm->call<Fn, jit::AtomicsCompareExchange64>();
    return true;
  }

  {
    LiveRegisterSet volatileRegs = liveVolatileRegs();
    volatileRegs.takeUnchecked(output->valueReg());
    volatileRegs.takeUnchecked(scratch);
    masm.PushRegsInMask(volatileRegs);

    masm.setupUnalignedABICall(scratch);
    masm.passABIArg(obj);
    masm.passABIArg(index);
    masm.passABIArg(expected);
    masm.passABIArg(replacement);
    masm.callWithABI(DynamicFunction<AtomicsCompareExchangeFn>(
        AtomicsCompareExchange(elementType)));
    masm.storeCallInt32Result(scratch);

    masm.PopRegsInMask(volatileRegs);
  }

  if (elementType != Scalar::Uint32) {
    masm.tagValue(JSVAL_TYPE_INT32, scratch, output->valueReg());
  } else {
    ScratchDoubleScope fpscratch(masm);
    masm.convertUInt32ToDouble(scratch, fpscratch);
    masm.boxDouble(fpscratch, output->valueReg(), fpscratch);
  }

  return true;
}

bool CacheIRCompiler::emitAtomicsReadModifyWriteResult(
    ObjOperandId objId, IntPtrOperandId indexId, uint32_t valueId,
    Scalar::Type elementType, ArrayBufferViewKind viewKind,
    AtomicsReadWriteModifyFn fn) {
  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  Register value = allocator.useRegister(masm, Int32OperandId(valueId));
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Maybe<AutoScratchRegisterMaybeOutputType> scratch2;
  if (viewKind == ArrayBufferViewKind::Resizable) {
    scratch2.emplace(allocator, masm, output);
  }

  // Not enough registers on X86.
  constexpr auto spectreTemp = mozilla::Nothing{};

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Bounds check.
  emitTypedArrayBoundsCheck(viewKind, obj, index, scratch, scratch2,
                            spectreTemp, failure->label());

  // See comment in emitAtomicsCompareExchange for why we use an ABI call.
  {
    LiveRegisterSet volatileRegs = liveVolatileRegs();
    volatileRegs.takeUnchecked(output.valueReg());
    volatileRegs.takeUnchecked(scratch);
    masm.PushRegsInMask(volatileRegs);

    masm.setupUnalignedABICall(scratch);
    masm.passABIArg(obj);
    masm.passABIArg(index);
    masm.passABIArg(value);
    masm.callWithABI(DynamicFunction<AtomicsReadWriteModifyFn>(fn));
    masm.storeCallInt32Result(scratch);

    masm.PopRegsInMask(volatileRegs);
  }

  if (elementType != Scalar::Uint32) {
    masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  } else {
    ScratchDoubleScope fpscratch(masm);
    masm.convertUInt32ToDouble(scratch, fpscratch);
    masm.boxDouble(fpscratch, output.valueReg(), fpscratch);
  }

  return true;
}

template <CacheIRCompiler::AtomicsReadWriteModify64Fn fn>
bool CacheIRCompiler::emitAtomicsReadModifyWriteResult64(
    ObjOperandId objId, IntPtrOperandId indexId, uint32_t valueId,
    ArrayBufferViewKind viewKind) {
  AutoCallVM callvm(masm, this, allocator);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  Register value = allocator.useRegister(masm, BigIntOperandId(valueId));
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, callvm.output());
  Maybe<AutoScratchRegisterMaybeOutputType> scratch2;
  if (viewKind == ArrayBufferViewKind::Resizable) {
    scratch2.emplace(allocator, masm, callvm.output());
  }

  // Not enough registers on X86.
  constexpr auto spectreTemp = mozilla::Nothing{};

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // AutoCallVM's AutoSaveLiveRegisters aren't accounted for in FailurePath, so
  // we can't use both at the same time. This isn't an issue here, because Ion
  // doesn't support CallICs. If that ever changes, this code must be updated.
  MOZ_ASSERT(isBaseline(), "Can't use FailurePath with AutoCallVM in Ion ICs");

  // Bounds check.
  emitTypedArrayBoundsCheck(viewKind, obj, index, scratch, scratch2,
                            spectreTemp, failure->label());

  // See comment in emitAtomicsCompareExchange for why we use a VM call.

  callvm.prepare();

  masm.Push(value);
  masm.Push(index);
  masm.Push(obj);

  callvm.call<AtomicsReadWriteModify64Fn, fn>();
  return true;
}

bool CacheIRCompiler::emitAtomicsExchangeResult(ObjOperandId objId,
                                                IntPtrOperandId indexId,
                                                uint32_t valueId,
                                                Scalar::Type elementType,
                                                ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  if (Scalar::isBigIntType(elementType)) {
    return emitAtomicsReadModifyWriteResult64<jit::AtomicsExchange64>(
        objId, indexId, valueId, viewKind);
  }
  return emitAtomicsReadModifyWriteResult(objId, indexId, valueId, elementType,
                                          viewKind,
                                          AtomicsExchange(elementType));
}

bool CacheIRCompiler::emitAtomicsAddResult(
    ObjOperandId objId, IntPtrOperandId indexId, uint32_t valueId,
    Scalar::Type elementType, bool forEffect, ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  if (Scalar::isBigIntType(elementType)) {
    return emitAtomicsReadModifyWriteResult64<jit::AtomicsAdd64>(
        objId, indexId, valueId, viewKind);
  }
  return emitAtomicsReadModifyWriteResult(objId, indexId, valueId, elementType,
                                          viewKind, AtomicsAdd(elementType));
}

bool CacheIRCompiler::emitAtomicsSubResult(
    ObjOperandId objId, IntPtrOperandId indexId, uint32_t valueId,
    Scalar::Type elementType, bool forEffect, ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  if (Scalar::isBigIntType(elementType)) {
    return emitAtomicsReadModifyWriteResult64<jit::AtomicsSub64>(
        objId, indexId, valueId, viewKind);
  }
  return emitAtomicsReadModifyWriteResult(objId, indexId, valueId, elementType,
                                          viewKind, AtomicsSub(elementType));
}

bool CacheIRCompiler::emitAtomicsAndResult(
    ObjOperandId objId, IntPtrOperandId indexId, uint32_t valueId,
    Scalar::Type elementType, bool forEffect, ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  if (Scalar::isBigIntType(elementType)) {
    return emitAtomicsReadModifyWriteResult64<jit::AtomicsAnd64>(
        objId, indexId, valueId, viewKind);
  }
  return emitAtomicsReadModifyWriteResult(objId, indexId, valueId, elementType,
                                          viewKind, AtomicsAnd(elementType));
}

bool CacheIRCompiler::emitAtomicsOrResult(
    ObjOperandId objId, IntPtrOperandId indexId, uint32_t valueId,
    Scalar::Type elementType, bool forEffect, ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  if (Scalar::isBigIntType(elementType)) {
    return emitAtomicsReadModifyWriteResult64<jit::AtomicsOr64>(
        objId, indexId, valueId, viewKind);
  }
  return emitAtomicsReadModifyWriteResult(objId, indexId, valueId, elementType,
                                          viewKind, AtomicsOr(elementType));
}

bool CacheIRCompiler::emitAtomicsXorResult(
    ObjOperandId objId, IntPtrOperandId indexId, uint32_t valueId,
    Scalar::Type elementType, bool forEffect, ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  if (Scalar::isBigIntType(elementType)) {
    return emitAtomicsReadModifyWriteResult64<jit::AtomicsXor64>(
        objId, indexId, valueId, viewKind);
  }
  return emitAtomicsReadModifyWriteResult(objId, indexId, valueId, elementType,
                                          viewKind, AtomicsXor(elementType));
}

bool CacheIRCompiler::emitAtomicsLoadResult(ObjOperandId objId,
                                            IntPtrOperandId indexId,
                                            Scalar::Type elementType,
                                            ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Maybe<AutoOutputRegister> output;
  Maybe<AutoCallVM> callvm;
  if (!Scalar::isBigIntType(elementType)) {
    output.emplace(*this);
  } else {
    callvm.emplace(masm, this, allocator);
  }
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm,
                                         output ? *output : callvm->output());
  Maybe<AutoSpectreBoundsScratchRegister> spectreTemp;
  Maybe<AutoScratchRegister> scratch2;
  if (viewKind == ArrayBufferViewKind::FixedLength) {
    spectreTemp.emplace(allocator, masm);
  } else {
    scratch2.emplace(allocator, masm);
  }
  AutoAvailableFloatRegister floatReg(*this, FloatReg0);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // AutoCallVM's AutoSaveLiveRegisters aren't accounted for in FailurePath, so
  // we can't use both at the same time. This isn't an issue here, because Ion
  // doesn't support CallICs. If that ever changes, this code must be updated.
  MOZ_ASSERT(isBaseline(), "Can't use FailurePath with AutoCallVM in Ion ICs");

  // Bounds check.
  emitTypedArrayBoundsCheck(viewKind, obj, index, scratch, scratch2,
                            spectreTemp, failure->label());

  // Atomic operations are highly platform-dependent, for example x86/arm32 has
  // specific requirements on which registers are used. Therefore we're using a
  // VM call here instead of handling each platform separately.
  if (Scalar::isBigIntType(elementType)) {
    callvm->prepare();

    masm.Push(index);
    masm.Push(obj);

    using Fn = BigInt* (*)(JSContext*, TypedArrayObject*, size_t);
    callvm->call<Fn, jit::AtomicsLoad64>();
    return true;
  }

  // Load the elements vector.
  masm.loadPtr(Address(obj, ArrayBufferViewObject::dataOffset()), scratch);

  // Load the value.
  BaseIndex source(scratch, index, ScaleFromScalarType(elementType));

  // NOTE: the generated code must match the assembly code in gen_load in
  // GenerateAtomicOperations.py
  auto sync = Synchronization::Load();

  masm.memoryBarrierBefore(sync);

  Label* failUint32 = nullptr;
  MacroAssembler::Uint32Mode mode = MacroAssembler::Uint32Mode::ForceDouble;
  masm.loadFromTypedArray(elementType, source, output->valueReg(), mode,
                          InvalidReg, failUint32, LiveRegisterSet{});
  masm.memoryBarrierAfter(sync);

  return true;
}

bool CacheIRCompiler::emitAtomicsStoreResult(ObjOperandId objId,
                                             IntPtrOperandId indexId,
                                             uint32_t valueId,
                                             Scalar::Type elementType,
                                             ArrayBufferViewKind viewKind) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register obj = allocator.useRegister(masm, objId);
  Register index = allocator.useRegister(masm, indexId);
  Maybe<Register> valueInt32;
  Maybe<Register> valueBigInt;
  if (!Scalar::isBigIntType(elementType)) {
    valueInt32.emplace(allocator.useRegister(masm, Int32OperandId(valueId)));
  } else {
    valueBigInt.emplace(allocator.useRegister(masm, BigIntOperandId(valueId)));
  }
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);
  Maybe<AutoScratchRegisterMaybeOutputType> scratch2;
  if (viewKind == ArrayBufferViewKind::Resizable) {
    scratch2.emplace(allocator, masm, output);
  }

  // Not enough registers on X86.
  constexpr auto spectreTemp = mozilla::Nothing{};

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  // Bounds check.
  emitTypedArrayBoundsCheck(viewKind, obj, index, scratch, scratch2,
                            spectreTemp, failure->label());

  if (!Scalar::isBigIntType(elementType)) {
    // Load the elements vector.
    masm.loadPtr(Address(obj, ArrayBufferViewObject::dataOffset()), scratch);

    // Store the value.
    BaseIndex dest(scratch, index, ScaleFromScalarType(elementType));

    // NOTE: the generated code must match the assembly code in gen_store in
    // GenerateAtomicOperations.py
    auto sync = Synchronization::Store();

    masm.memoryBarrierBefore(sync);
    masm.storeToTypedIntArray(elementType, *valueInt32, dest);
    masm.memoryBarrierAfter(sync);

    masm.tagValue(JSVAL_TYPE_INT32, *valueInt32, output.valueReg());
  } else {
    // See comment in emitAtomicsCompareExchange for why we use an ABI call.

    LiveRegisterSet volatileRegs = liveVolatileRegs();
    volatileRegs.takeUnchecked(output.valueReg());
    volatileRegs.takeUnchecked(scratch);
    masm.PushRegsInMask(volatileRegs);

    using Fn = void (*)(TypedArrayObject*, size_t, const BigInt*);
    masm.setupUnalignedABICall(scratch);
    masm.passABIArg(obj);
    masm.passABIArg(index);
    masm.passABIArg(*valueBigInt);
    masm.callWithABI<Fn, jit::AtomicsStore64>();

    masm.PopRegsInMask(volatileRegs);

    masm.tagValue(JSVAL_TYPE_BIGINT, *valueBigInt, output.valueReg());
  }

  return true;
}

bool CacheIRCompiler::emitAtomicsIsLockFreeResult(Int32OperandId valueId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register value = allocator.useRegister(masm, valueId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  masm.atomicIsLockFreeJS(value, scratch);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch, output.valueReg());

  return true;
}

bool CacheIRCompiler::emitAtomicsPauseResult() {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);

  masm.atomicPause();
  masm.moveValue(UndefinedValue(), output.valueReg());

  return true;
}

bool CacheIRCompiler::emitBigIntAsIntNResult(Int32OperandId bitsId,
                                             BigIntOperandId bigIntId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register bits = allocator.useRegister(masm, bitsId);
  Register bigInt = allocator.useRegister(masm, bigIntId);

  callvm.prepare();
  masm.Push(bits);
  masm.Push(bigInt);

  using Fn = BigInt* (*)(JSContext*, HandleBigInt, int32_t);
  callvm.call<Fn, jit::BigIntAsIntN>();
  return true;
}

bool CacheIRCompiler::emitBigIntAsUintNResult(Int32OperandId bitsId,
                                              BigIntOperandId bigIntId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register bits = allocator.useRegister(masm, bitsId);
  Register bigInt = allocator.useRegister(masm, bigIntId);

  callvm.prepare();
  masm.Push(bits);
  masm.Push(bigInt);

  using Fn = BigInt* (*)(JSContext*, HandleBigInt, int32_t);
  callvm.call<Fn, jit::BigIntAsUintN>();
  return true;
}

bool CacheIRCompiler::emitSetHasResult(ObjOperandId setId, ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register set = allocator.useRegister(masm, setId);
  ValueOperand val = allocator.useValueRegister(masm, valId);

  callvm.prepare();
  masm.Push(val);
  masm.Push(set);

  using Fn = bool (*)(JSContext*, Handle<SetObject*>, HandleValue, bool*);
  callvm.call<Fn, jit::SetObjectHas>();
  return true;
}

bool CacheIRCompiler::emitSetHasNonGCThingResult(ObjOperandId setId,
                                                 ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register set = allocator.useRegister(masm, setId);
  ValueOperand val = allocator.useValueRegister(masm, valId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
  AutoScratchRegister scratch4(allocator, masm);
  AutoAvailableFloatRegister scratchFloat(*this, FloatReg0);

  masm.toHashableNonGCThing(val, output.valueReg(), scratchFloat);
  masm.prepareHashNonGCThing(output.valueReg(), scratch1, scratch2);

  masm.setObjectHasNonBigInt(set, output.valueReg(), scratch1, scratch2,
                             scratch3, scratch4);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch2, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitSetHasSymbolResult(ObjOperandId setId,
                                             SymbolOperandId symId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register set = allocator.useRegister(masm, setId);
  Register sym = allocator.useRegister(masm, symId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
  AutoScratchRegister scratch4(allocator, masm);

  masm.prepareHashSymbol(sym, scratch1);

  masm.tagValue(JSVAL_TYPE_SYMBOL, sym, output.valueReg());
  masm.setObjectHasNonBigInt(set, output.valueReg(), scratch1, scratch2,
                             scratch3, scratch4);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch2, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitSetHasBigIntResult(ObjOperandId setId,
                                             BigIntOperandId bigIntId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register set = allocator.useRegister(masm, setId);
  Register bigInt = allocator.useRegister(masm, bigIntId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
  AutoScratchRegister scratch4(allocator, masm);
  AutoScratchRegister scratch5(allocator, masm);
#ifndef JS_CODEGEN_ARM
  AutoScratchRegister scratch6(allocator, masm);
#else
  // We don't have more registers available on ARM32.
  Register scratch6 = set;

  masm.push(set);
#endif

  masm.prepareHashBigInt(bigInt, scratch1, scratch2, scratch3, scratch4);

  masm.tagValue(JSVAL_TYPE_BIGINT, bigInt, output.valueReg());
  masm.setObjectHasBigInt(set, output.valueReg(), scratch1, scratch2, scratch3,
                          scratch4, scratch5, scratch6);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch2, output.valueReg());

#ifdef JS_CODEGEN_ARM
  masm.pop(set);
#endif
  return true;
}

bool CacheIRCompiler::emitSetHasObjectResult(ObjOperandId setId,
                                             ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register set = allocator.useRegister(masm, setId);
  Register obj = allocator.useRegister(masm, objId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
  AutoScratchRegister scratch4(allocator, masm);
  AutoScratchRegister scratch5(allocator, masm);

  masm.tagValue(JSVAL_TYPE_OBJECT, obj, output.valueReg());
  masm.prepareHashObject(set, output.valueReg(), scratch1, scratch2, scratch3,
                         scratch4, scratch5);

  masm.setObjectHasNonBigInt(set, output.valueReg(), scratch1, scratch2,
                             scratch3, scratch4);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch2, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitSetDeleteResult(ObjOperandId setId,
                                          ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register set = allocator.useRegister(masm, setId);
  ValueOperand val = allocator.useValueRegister(masm, valId);

  callvm.prepare();
  masm.Push(val);
  masm.Push(set);

  using Fn = bool (*)(JSContext*, Handle<SetObject*>, HandleValue, bool*);
  callvm.call<Fn, jit::SetObjectDelete>();
  return true;
}

bool CacheIRCompiler::emitSetAddResult(ObjOperandId setId, ValOperandId keyId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register set = allocator.useRegister(masm, setId);
  ValueOperand key = allocator.useValueRegister(masm, keyId);

  callvm.prepare();
  masm.Push(key);
  masm.Push(set);

  using Fn =
      bool (*)(JSContext*, Handle<SetObject*>, HandleValue, MutableHandleValue);
  callvm.call<Fn, jit::SetObjectAddFromIC>();
  return true;
}

bool CacheIRCompiler::emitSetSizeResult(ObjOperandId setId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register set = allocator.useRegister(masm, setId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  masm.loadSetObjectSize(set, scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitMapHasResult(ObjOperandId mapId, ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register map = allocator.useRegister(masm, mapId);
  ValueOperand val = allocator.useValueRegister(masm, valId);

  callvm.prepare();
  masm.Push(val);
  masm.Push(map);

  using Fn = bool (*)(JSContext*, Handle<MapObject*>, HandleValue, bool*);
  callvm.call<Fn, jit::MapObjectHas>();
  return true;
}

bool CacheIRCompiler::emitMapHasNonGCThingResult(ObjOperandId mapId,
                                                 ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register map = allocator.useRegister(masm, mapId);
  ValueOperand val = allocator.useValueRegister(masm, valId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
  AutoScratchRegister scratch4(allocator, masm);
  AutoAvailableFloatRegister scratchFloat(*this, FloatReg0);

  masm.toHashableNonGCThing(val, output.valueReg(), scratchFloat);
  masm.prepareHashNonGCThing(output.valueReg(), scratch1, scratch2);

  masm.mapObjectHasNonBigInt(map, output.valueReg(), scratch1, scratch2,
                             scratch3, scratch4);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch2, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitMapHasSymbolResult(ObjOperandId mapId,
                                             SymbolOperandId symId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register map = allocator.useRegister(masm, mapId);
  Register sym = allocator.useRegister(masm, symId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
  AutoScratchRegister scratch4(allocator, masm);

  masm.prepareHashSymbol(sym, scratch1);

  masm.tagValue(JSVAL_TYPE_SYMBOL, sym, output.valueReg());
  masm.mapObjectHasNonBigInt(map, output.valueReg(), scratch1, scratch2,
                             scratch3, scratch4);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch2, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitMapHasBigIntResult(ObjOperandId mapId,
                                             BigIntOperandId bigIntId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register map = allocator.useRegister(masm, mapId);
  Register bigInt = allocator.useRegister(masm, bigIntId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
  AutoScratchRegister scratch4(allocator, masm);
  AutoScratchRegister scratch5(allocator, masm);
#ifndef JS_CODEGEN_ARM
  AutoScratchRegister scratch6(allocator, masm);
#else
  // We don't have more registers available on ARM32.
  Register scratch6 = map;

  masm.push(map);
#endif

  masm.prepareHashBigInt(bigInt, scratch1, scratch2, scratch3, scratch4);

  masm.tagValue(JSVAL_TYPE_BIGINT, bigInt, output.valueReg());
  masm.mapObjectHasBigInt(map, output.valueReg(), scratch1, scratch2, scratch3,
                          scratch4, scratch5, scratch6);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch2, output.valueReg());

#ifdef JS_CODEGEN_ARM
  masm.pop(map);
#endif
  return true;
}

bool CacheIRCompiler::emitMapHasObjectResult(ObjOperandId mapId,
                                             ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register map = allocator.useRegister(masm, mapId);
  Register obj = allocator.useRegister(masm, objId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
  AutoScratchRegister scratch4(allocator, masm);
  AutoScratchRegister scratch5(allocator, masm);

  masm.tagValue(JSVAL_TYPE_OBJECT, obj, output.valueReg());
  masm.prepareHashObject(map, output.valueReg(), scratch1, scratch2, scratch3,
                         scratch4, scratch5);

  masm.mapObjectHasNonBigInt(map, output.valueReg(), scratch1, scratch2,
                             scratch3, scratch4);
  masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch2, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitMapGetResult(ObjOperandId mapId, ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register map = allocator.useRegister(masm, mapId);
  ValueOperand val = allocator.useValueRegister(masm, valId);

  callvm.prepare();
  masm.Push(val);
  masm.Push(map);

  using Fn =
      bool (*)(JSContext*, Handle<MapObject*>, HandleValue, MutableHandleValue);
  callvm.call<Fn, jit::MapObjectGet>();
  return true;
}

bool CacheIRCompiler::emitMapDeleteResult(ObjOperandId mapId,
                                          ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register map = allocator.useRegister(masm, mapId);
  ValueOperand val = allocator.useValueRegister(masm, valId);

  callvm.prepare();
  masm.Push(val);
  masm.Push(map);

  using Fn = bool (*)(JSContext*, Handle<MapObject*>, HandleValue, bool*);
  callvm.call<Fn, jit::MapObjectDelete>();
  return true;
}

bool CacheIRCompiler::emitMapSetResult(ObjOperandId mapId, ValOperandId keyId,
                                       ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register map = allocator.useRegister(masm, mapId);
  ValueOperand key = allocator.useValueRegister(masm, keyId);
  ValueOperand val = allocator.useValueRegister(masm, valId);

  callvm.prepare();
  masm.Push(val);
  masm.Push(key);
  masm.Push(map);

  using Fn = bool (*)(JSContext*, Handle<MapObject*>, HandleValue, HandleValue,
                      MutableHandleValue);
  callvm.call<Fn, jit::MapObjectSetFromIC>();
  return true;
}

bool CacheIRCompiler::emitMapGetNonGCThingResult(ObjOperandId mapId,
                                                 ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register map = allocator.useRegister(masm, mapId);
  ValueOperand val = allocator.useValueRegister(masm, valId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
  AutoScratchRegister scratch4(allocator, masm);
  AutoAvailableFloatRegister scratchFloat(*this, FloatReg0);

  masm.toHashableNonGCThing(val, output.valueReg(), scratchFloat);
  masm.prepareHashNonGCThing(output.valueReg(), scratch1, scratch2);

  masm.mapObjectGetNonBigInt(map, output.valueReg(), scratch1,
                             output.valueReg(), scratch2, scratch3, scratch4);
  return true;
}

bool CacheIRCompiler::emitMapGetSymbolResult(ObjOperandId mapId,
                                             SymbolOperandId symId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register map = allocator.useRegister(masm, mapId);
  Register sym = allocator.useRegister(masm, symId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
  AutoScratchRegister scratch4(allocator, masm);

  masm.prepareHashSymbol(sym, scratch1);

  masm.tagValue(JSVAL_TYPE_SYMBOL, sym, output.valueReg());
  masm.mapObjectGetNonBigInt(map, output.valueReg(), scratch1,
                             output.valueReg(), scratch2, scratch3, scratch4);
  return true;
}

bool CacheIRCompiler::emitMapGetBigIntResult(ObjOperandId mapId,
                                             BigIntOperandId bigIntId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register map = allocator.useRegister(masm, mapId);
  Register bigInt = allocator.useRegister(masm, bigIntId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
  AutoScratchRegister scratch4(allocator, masm);
  AutoScratchRegister scratch5(allocator, masm);
#ifndef JS_CODEGEN_ARM
  AutoScratchRegister scratch6(allocator, masm);
#else
  // We don't have more registers available on ARM32.
  Register scratch6 = map;

  masm.push(map);
#endif

  masm.prepareHashBigInt(bigInt, scratch1, scratch2, scratch3, scratch4);

  masm.tagValue(JSVAL_TYPE_BIGINT, bigInt, output.valueReg());
  masm.mapObjectGetBigInt(map, output.valueReg(), scratch1, output.valueReg(),
                          scratch2, scratch3, scratch4, scratch5, scratch6);

#ifdef JS_CODEGEN_ARM
  masm.pop(map);
#endif
  return true;
}

bool CacheIRCompiler::emitMapGetObjectResult(ObjOperandId mapId,
                                             ObjOperandId objId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register map = allocator.useRegister(masm, mapId);
  Register obj = allocator.useRegister(masm, objId);

  AutoScratchRegister scratch1(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);
  AutoScratchRegister scratch3(allocator, masm);
  AutoScratchRegister scratch4(allocator, masm);
  AutoScratchRegister scratch5(allocator, masm);

  masm.tagValue(JSVAL_TYPE_OBJECT, obj, output.valueReg());
  masm.prepareHashObject(map, output.valueReg(), scratch1, scratch2, scratch3,
                         scratch4, scratch5);

  masm.mapObjectGetNonBigInt(map, output.valueReg(), scratch1,
                             output.valueReg(), scratch2, scratch3, scratch4);
  return true;
}

bool CacheIRCompiler::emitMapSizeResult(ObjOperandId mapId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  Register map = allocator.useRegister(masm, mapId);
  AutoScratchRegisterMaybeOutput scratch(allocator, masm, output);

  masm.loadMapObjectSize(map, scratch);
  masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg());
  return true;
}

bool CacheIRCompiler::emitDateFillLocalTimeSlots(ObjOperandId dateId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register date = allocator.useRegister(masm, dateId);
  AutoScratchRegister scratch(allocator, masm);

  masm.dateFillLocalTimeSlots(date, scratch, liveVolatileRegs());
  return true;
}

bool CacheIRCompiler::emitDateHoursFromSecondsIntoYearResult(
    ValOperandId secondsIntoYearId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  ValueOperand secondsIntoYear =
      allocator.useValueRegister(masm, secondsIntoYearId);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);

  masm.dateHoursFromSecondsIntoYear(secondsIntoYear, output.valueReg(),
                                    scratch1, scratch2);
  return true;
}

bool CacheIRCompiler::emitDateMinutesFromSecondsIntoYearResult(
    ValOperandId secondsIntoYearId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  ValueOperand secondsIntoYear =
      allocator.useValueRegister(masm, secondsIntoYearId);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);

  masm.dateMinutesFromSecondsIntoYear(secondsIntoYear, output.valueReg(),
                                      scratch1, scratch2);
  return true;
}

bool CacheIRCompiler::emitDateSecondsFromSecondsIntoYearResult(
    ValOperandId secondsIntoYearId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);
  ValueOperand secondsIntoYear =
      allocator.useValueRegister(masm, secondsIntoYearId);
  AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
  AutoScratchRegister scratch2(allocator, masm);

  masm.dateSecondsFromSecondsIntoYear(secondsIntoYear, output.valueReg(),
                                      scratch1, scratch2);
  return true;
}

bool CacheIRCompiler::emitArrayFromArgumentsObjectResult(ObjOperandId objId,
                                                         uint32_t shapeOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);

  Register obj = allocator.useRegister(masm, objId);

  callvm.prepare();
  masm.Push(obj);

  using Fn = ArrayObject* (*)(JSContext*, Handle<ArgumentsObject*>);
  callvm.call<Fn, js::ArrayFromArgumentsObject>();
  return true;
}

bool CacheIRCompiler::emitGuardGlobalGeneration(uint32_t expectedOffset,
                                                uint32_t generationAddrOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoScratchRegister scratch(allocator, masm);
  AutoScratchRegister scratch2(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  StubFieldOffset expected(expectedOffset, StubField::Type::RawInt32);
  emitLoadStubField(expected, scratch);

  StubFieldOffset generationAddr(generationAddrOffset,
                                 StubField::Type::RawPointer);
  emitLoadStubField(generationAddr, scratch2);

  masm.branch32(Assembler::NotEqual, Address(scratch2, 0), scratch,
                failure->label());

  return true;
}

bool CacheIRCompiler::emitGuardFuse(RealmFuses::FuseIndex fuseIndex) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
  AutoScratchRegister scratch(allocator, masm);

  FailurePath* failure;
  if (!addFailurePath(&failure)) {
    return false;
  }

  masm.loadRealmFuse(fuseIndex, scratch);
  masm.branchPtr(Assembler::NotEqual, scratch, ImmPtr(nullptr),
                 failure->label());
  return true;
}

bool CacheIRCompiler::emitBailout() {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  // Generates no code.

  return true;
}

bool CacheIRCompiler::emitAssertFloat32Result(ValOperandId valId,
                                              bool mustFloat32) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);

  // NOP when not in IonMonkey
  masm.moveValue(UndefinedValue(), output.valueReg());

  return true;
}

bool CacheIRCompiler::emitAssertRecoveredOnBailoutResult(ValOperandId valId,
                                                         bool mustBeRecovered) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoOutputRegister output(*this);

  // NOP when not in IonMonkey
  masm.moveValue(UndefinedValue(), output.valueReg());

  return true;
}

bool CacheIRCompiler::emitAssertPropertyLookup(ObjOperandId objId,
                                               uint32_t idOffset,
                                               uint32_t slotOffset) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  Register obj = allocator.useRegister(masm, objId);

  AutoScratchRegister id(allocator, masm);
  AutoScratchRegister slot(allocator, masm);

  LiveRegisterSet save = liveVolatileRegs();
  masm.PushRegsInMask(save);

  masm.setupUnalignedABICall(id);

  StubFieldOffset idField(idOffset, StubField::Type::Id);
  emitLoadStubField(idField, id);

  StubFieldOffset slotField(slotOffset, StubField::Type::RawInt32);
  emitLoadStubField(slotField, slot);

  masm.passABIArg(obj);
  masm.passABIArg(id);
  masm.passABIArg(slot);
  using Fn = void (*)(NativeObject*, PropertyKey, uint32_t);
  masm.callWithABI<Fn, js::jit::AssertPropertyLookup>();
  masm.PopRegsInMask(save);

  return true;
}

#ifdef FUZZING_JS_FUZZILLI
bool CacheIRCompiler::emitFuzzilliHashResult(ValOperandId valId) {
  JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

  AutoCallVM callvm(masm, this, allocator);
  const AutoOutputRegister& output = callvm.output();

  ValueOperand input = allocator.useValueRegister(masm, valId);
  AutoScratchRegister scratch(allocator, masm);
  AutoScratchRegisterMaybeOutput scratch2(allocator, masm, output);
  AutoScratchRegisterMaybeOutputType scratchJSContext(allocator, masm, output);
  AutoScratchFloatRegister floatReg(this);

  Label hashDouble, updateHash, done;

  Label isInt32, isDouble, isNull, isUndefined, isBoolean, isBigInt, isObject;
  {
    ScratchTagScope tag(masm, input);
    masm.splitTagForTest(input, tag);

    masm.branchTestInt32(Assembler::Equal, tag, &isInt32);
    masm.branchTestDouble(Assembler::Equal, tag, &isDouble);
    masm.branchTestNull(Assembler::Equal, tag, &isNull);
    masm.branchTestUndefined(Assembler::Equal, tag, &isUndefined);
    masm.branchTestBoolean(Assembler::Equal, tag, &isBoolean);
    masm.branchTestBigInt(Assembler::Equal, tag, &isBigInt);
    masm.branchTestObject(Assembler::Equal, tag, &isObject);

    // Symbol or String.
    masm.move32(Imm32(0), scratch);
    masm.jump(&updateHash);
  }

  masm.bind(&isInt32);
  {
    masm.unboxInt32(input, scratch);
    masm.convertInt32ToDouble(scratch, floatReg);
    masm.jump(&hashDouble);
  }

  masm.bind(&isDouble);
  {
    masm.unboxDouble(input, floatReg);
    masm.jump(&hashDouble);
  }

  masm.bind(&isNull);
  {
    masm.loadConstantDouble(1.0, floatReg);
    masm.jump(&hashDouble);
  }

  masm.bind(&isUndefined);
  {
    masm.loadConstantDouble(2.0, floatReg);
    masm.jump(&hashDouble);
  }

  masm.bind(&isBoolean);
  {
    masm.unboxBoolean(input, scratch);
    masm.add32(Imm32(3), scratch);
    masm.convertInt32ToDouble(scratch, floatReg);
    masm.jump(&hashDouble);
  }

  masm.bind(&isBigInt);
  {
    masm.unboxBigInt(input, scratch);

    LiveRegisterSet volatileRegs = liveVolatileRegs();
    masm.PushRegsInMask(volatileRegs);

    using Fn = uint32_t (*)(BigInt* bigInt);
    masm.setupUnalignedABICall(scratchJSContext);
    masm.loadJSContext(scratchJSContext);
    masm.passABIArg(scratch);
    masm.callWithABI<Fn, js::FuzzilliHashBigInt>();
    masm.storeCallInt32Result(scratch);

    LiveRegisterSet ignore;
    ignore.add(scratch);
    ignore.add(scratchJSContext);
    masm.PopRegsInMaskIgnore(volatileRegs, ignore);
    masm.jump(&updateHash);
  }

  masm.bind(&isObject);
  {
    masm.unboxObject(input, scratch);

    callvm.prepare();
    masm.Push(scratch);

    using Fn = void (*)(JSContext* cx, JSObject* o);
    callvm.callNoResult<Fn, js::FuzzilliHashObject>();

    masm.jump(&done);
  }

  masm.bind(&hashDouble);
  masm.fuzzilliHashDouble(floatReg, scratch, scratch2);

  masm.bind(&updateHash);
  masm.fuzzilliStoreHash(scratch, scratchJSContext, scratch2);

  masm.bind(&done);

  masm.moveValue(UndefinedValue(), output.valueReg());
  return true;
}
#endif

template <typename Fn, Fn fn>
void CacheIRCompiler::callVM(MacroAssembler& masm) {
  VMFunctionId id = VMFunctionToId<Fn, fn>::id;
  callVMInternal(masm, id);
}

void CacheIRCompiler::callVMInternal(MacroAssembler& masm, VMFunctionId id) {
  MOZ_ASSERT(enteredStubFrame_);
  if (mode_ == Mode::Ion) {
    TrampolinePtr code = cx_->runtime()->jitRuntime()->getVMWrapper(id);
    const VMFunctionData& fun = GetVMFunction(id);
    uint32_t frameSize = fun.explicitStackSlots() * sizeof(void*);
    masm.PushFrameDescriptor(FrameType::IonICCall);
    masm.callJit(code);

    // Pop rest of the exit frame and the arguments left on the stack.
    int framePop =
        sizeof(ExitFrameLayout) - ExitFrameLayout::bytesPoppedAfterCall();
    masm.implicitPop(frameSize + framePop);

    masm.freeStack(asIon()->localTracingSlots() * sizeof(Value));

    // Pop IonICCallFrameLayout.
    masm.Pop(FramePointer);
    masm.freeStack(IonICCallFrameLayout::Size() - sizeof(void*));
    return;
  }

  MOZ_ASSERT(mode_ == Mode::Baseline);

  TrampolinePtr code = cx_->runtime()->jitRuntime()->getVMWrapper(id);

  EmitBaselineCallVM(code, masm);
}

bool CacheIRCompiler::isBaseline() { return mode_ == Mode::Baseline; }

bool CacheIRCompiler::isIon() { return mode_ == Mode::Ion; }

BaselineCacheIRCompiler* CacheIRCompiler::asBaseline() {
  MOZ_ASSERT(this->isBaseline());
  return static_cast<BaselineCacheIRCompiler*>(this);
}

IonCacheIRCompiler* CacheIRCompiler::asIon() {
  MOZ_ASSERT(this->isIon());
  return static_cast<IonCacheIRCompiler*>(this);
}

#ifdef DEBUG
void CacheIRCompiler::assertFloatRegisterAvailable(FloatRegister reg) {
  if (isBaseline()) {
    // Baseline does not have any FloatRegisters live when calling an IC stub.
    return;
  }

  asIon()->assertFloatRegisterAvailable(reg);
}
#endif

AutoCallVM::AutoCallVM(MacroAssembler& masm, CacheIRCompiler* compiler,
                       CacheRegisterAllocator& allocator)
    : masm_(masm), compiler_(compiler), allocator_(allocator) {
  // Ion needs to `enterStubFrame` before it can callVM and it also needs to
  // initialize AutoSaveLiveRegisters.
  if (compiler_->mode_ == CacheIRCompiler::Mode::Ion) {
    // Will need to use a downcast here as well, in order to pass the
    // stub to AutoSaveLiveRegisters
    save_.emplace(*compiler_->asIon());
  }

  if (compiler->outputUnchecked_.isSome()) {
    output_.emplace(*compiler);
  }

  if (compiler_->mode_ == CacheIRCompiler::Mode::Baseline) {
    stubFrame_.emplace(*compiler_->asBaseline());
    if (output_.isSome()) {
      scratch_.emplace(allocator_, masm_, output_.ref());
    } else {
      scratch_.emplace(allocator_, masm_);
    }
  }
}

void AutoCallVM::prepare() {
  allocator_.discardStack(masm_);
  MOZ_ASSERT(compiler_ != nullptr);
  if (compiler_->mode_ == CacheIRCompiler::Mode::Ion) {
    compiler_->asIon()->enterStubFrame(masm_, *save_.ptr());
    return;
  }
  MOZ_ASSERT(compiler_->mode_ == CacheIRCompiler::Mode::Baseline);
  stubFrame_->enter(masm_, scratch_.ref());
}

void AutoCallVM::storeResult(JSValueType returnType) {
  MOZ_ASSERT(returnType != JSVAL_TYPE_DOUBLE);

  if (returnType == JSVAL_TYPE_UNKNOWN) {
    masm_.storeCallResultValue(output_.ref());
  } else {
    if (output_->hasValue()) {
      masm_.tagValue(returnType, ReturnReg, output_->valueReg());
    } else {
      masm_.storeCallPointerResult(output_->typedReg().gpr());
    }
  }
}

void AutoCallVM::leaveBaselineStubFrame() {
  if (compiler_->mode_ == CacheIRCompiler::Mode::Baseline) {
    stubFrame_->leave(masm_);
  }
}

template <typename...>
struct VMFunctionReturnType;

template <class R, typename... Args>
struct VMFunctionReturnType<R (*)(JSContext*, Args...)> {
  using LastArgument = typename LastArg<Args...>::Type;

  // By convention VMFunctions returning `bool` use an output parameter.
  using ReturnType =
      std::conditional_t<std::is_same_v<R, bool>, LastArgument, R>;
};

template <class>
struct ReturnTypeToJSValueType;

// Definitions for the currently used return types.
template <>
struct ReturnTypeToJSValueType<MutableHandleValue> {
  static constexpr JSValueType result = JSVAL_TYPE_UNKNOWN;
};
template <>
struct ReturnTypeToJSValueType<bool*> {
  static constexpr JSValueType result = JSVAL_TYPE_BOOLEAN;
};
template <>
struct ReturnTypeToJSValueType<int32_t*> {
  static constexpr JSValueType result = JSVAL_TYPE_INT32;
};
template <>
struct ReturnTypeToJSValueType<JSString*> {
  static constexpr JSValueType result = JSVAL_TYPE_STRING;
};
template <>
struct ReturnTypeToJSValueType<JSLinearString*> {
  static constexpr JSValueType result = JSVAL_TYPE_STRING;
};
template <>
struct ReturnTypeToJSValueType<JSAtom*> {
  static constexpr JSValueType result = JSVAL_TYPE_STRING;
};
template <>
struct ReturnTypeToJSValueType<BigInt*> {
  static constexpr JSValueType result = JSVAL_TYPE_BIGINT;
};
template <>
struct ReturnTypeToJSValueType<JSObject*> {
  static constexpr JSValueType result = JSVAL_TYPE_OBJECT;
};
template <>
struct ReturnTypeToJSValueType<PropertyIteratorObject*> {
  static constexpr JSValueType result = JSVAL_TYPE_OBJECT;
};
template <>
struct ReturnTypeToJSValueType<ArrayIteratorObject*> {
  static constexpr JSValueType result = JSVAL_TYPE_OBJECT;
};
template <>
struct ReturnTypeToJSValueType<StringIteratorObject*> {
  static constexpr JSValueType result = JSVAL_TYPE_OBJECT;
};
template <>
struct ReturnTypeToJSValueType<RegExpStringIteratorObject*> {
  static constexpr JSValueType result = JSVAL_TYPE_OBJECT;
};
template <>
struct ReturnTypeToJSValueType<PlainObject*> {
  static constexpr JSValueType result = JSVAL_TYPE_OBJECT;
};
template <>
struct ReturnTypeToJSValueType<ArrayObject*> {
  static constexpr JSValueType result = JSVAL_TYPE_OBJECT;
};
template <>
struct ReturnTypeToJSValueType<TypedArrayObject*> {
  static constexpr JSValueType result = JSVAL_TYPE_OBJECT;
};
template <>
struct ReturnTypeToJSValueType<MapObject*> {
  static constexpr JSValueType result = JSVAL_TYPE_OBJECT;
};
template <>
struct ReturnTypeToJSValueType<SetObject*> {
  static constexpr JSValueType result = JSVAL_TYPE_OBJECT;
};

template <typename Fn>
void AutoCallVM::storeResult() {
  using ReturnType = typename VMFunctionReturnType<Fn>::ReturnType;
  storeResult(ReturnTypeToJSValueType<ReturnType>::result);
}

AutoScratchFloatRegister::AutoScratchFloatRegister(CacheIRCompiler* compiler,
                                                   FailurePath* failure)
    : compiler_(compiler), failure_(failure) {
  // If we're compiling a Baseline IC, FloatReg0 is always available.
  if (!compiler_->isBaseline()) {
    MacroAssembler& masm = compiler_->masm;
    masm.push(FloatReg0);
    compiler->allocator.setHasAutoScratchFloatRegisterSpill(true);
  }

  if (failure_) {
    failure_->setHasAutoScratchFloatRegister();
  }
}

AutoScratchFloatRegister::~AutoScratchFloatRegister() {
  if (failure_) {
    failure_->clearHasAutoScratchFloatRegister();
  }

  if (!compiler_->isBaseline()) {
    MacroAssembler& masm = compiler_->masm;
    masm.pop(FloatReg0);
    compiler_->allocator.setHasAutoScratchFloatRegisterSpill(false);

    if (failure_) {
      Label done;
      masm.jump(&done);
      masm.bind(&failurePopReg_);
      masm.pop(FloatReg0);
      masm.jump(failure_->label());
      masm.bind(&done);
    }
  }
}

Label* AutoScratchFloatRegister::failure() {
  MOZ_ASSERT(failure_);

  if (!compiler_->isBaseline()) {
    return &failurePopReg_;
  }
  return failure_->labelUnchecked();
}

Messung V0.5 in Prozent
C=97 H=99 G=97

¤ Dauer der Verarbeitung: 0.377 Sekunden  (vorverarbeitet am  2026-04-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge