PBIResult PortableBaselineInterpret()

in js/src/vm/PortableBaselineInterpret.cpp [5957:9308]


PBIResult PortableBaselineInterpret(
    JSContext* cx_, State& state, Stack& stack, StackVal* sp,
    JSObject* envChain, Value* ret, jsbytecode* pc, ImmutableScriptData* isd,
    jsbytecode* restartEntryPC, BaselineFrame* restartFrame,
    StackVal* restartEntryFrame, PBIResult restartCode) {
#define RESTART(code)                                                 \
  if (!IsRestart) {                                                   \
    TRACE_PRINTF("Restarting (code %d sp %p fp %p)\n", int(code), sp, \
                 ctx.stack.fp);                                       \
    SYNCSP();                                                         \
    restartCode = code;                                               \
    goto restart;                                                     \
  }

#define GOTO_ERROR()           \
  do {                         \
    SYNCSP();                  \
    RESTART(PBIResult::Error); \
    goto error;                \
  } while (0)

  // Update local state when we switch to a new script with a new PC.
#define RESET_PC(new_pc, new_script)                                \
  pc = new_pc;                                                      \
  entryPC = new_script->code();                                     \
  isd = new_script->immutableScriptData();                          \
  icEntries = frame->icScript()->icEntries();                       \
  icEntry = frame->interpreterICEntry();                            \
  argsObjAliasesFormals = frame->script()->argsObjAliasesFormals(); \
  resumeOffsets = isd->resumeOffsets().data();

#define OPCODE_LABEL(op, ...) LABEL(op),
#define TRAILING_LABEL(v) LABEL(default),

  static const void* const addresses[EnableInterruptsPseudoOpcode + 1] = {
      FOR_EACH_OPCODE(OPCODE_LABEL)
          FOR_EACH_TRAILING_UNUSED_OPCODE(TRAILING_LABEL)};

#undef OPCODE_LABEL
#undef TRAILING_LABEL

  BaselineFrame* frame = restartFrame;
  StackVal* entryFrame = restartEntryFrame;
  jsbytecode* entryPC = restartEntryPC;

  if (!IsRestart) {
    PUSHNATIVE(StackValNative(nullptr));  // Fake return address.
    frame = stack.pushFrame(sp, cx_, envChain);
    MOZ_ASSERT(frame);  // safety: stack margin.
    sp = reinterpret_cast<StackVal*>(frame);
    // Save the entry frame so that when unwinding, we know when to
    // return from this C++ frame.
    entryFrame = sp;
    // Save the entry PC so that we can compute offsets locally.
    entryPC = pc;
  }

  bool from_unwind = false;
  uint32_t nfixed = frame->script()->nfixed();
  bool argsObjAliasesFormals = frame->script()->argsObjAliasesFormals();

  PBIResult ic_result = PBIResult::Ok;
  uint64_t ic_arg0 = 0, ic_arg1 = 0, ic_arg2 = 0, ic_ret = 0;

  ICCtx ctx(cx_, frame, state, stack);
  auto* icEntries = frame->icScript()->icEntries();
  auto* icEntry = icEntries;
  const uint32_t* resumeOffsets = isd->resumeOffsets().data();

  if (IsRestart) {
    ic_result = restartCode;
    TRACE_PRINTF(
        "Enter from restart: sp = %p ctx.stack.fp = %p ctx.frame = %p\n", sp,
        ctx.stack.fp, ctx.frame);
    goto ic_fail;
  } else {
    AutoCheckRecursionLimit recursion(ctx.frameMgr.cxForLocalUseOnly());
    if (!recursion.checkDontReport(ctx.frameMgr.cxForLocalUseOnly())) {
      PUSH_EXIT_FRAME();
      ReportOverRecursed(ctx.frameMgr.cxForLocalUseOnly());
      return PBIResult::Error;
    }
  }

  // Check max stack depth once, so we don't need to check it
  // otherwise below for ordinary stack-manipulation opcodes (just for
  // exit frames).
  if (!ctx.stack.check(sp, sizeof(StackVal) * frame->script()->nslots())) {
    PUSH_EXIT_FRAME();
    ReportOverRecursed(ctx.frameMgr.cxForLocalUseOnly());
    return PBIResult::Error;
  }

  SYNCSP();
  sp -= nfixed;
  for (uint32_t i = 0; i < nfixed; i++) {
    sp[i] = StackVal(UndefinedValue());
  }
  ret->setUndefined();

  // Check if we are being debugged, and set a flag in the frame if so. This
  // flag must be set before calling InitFunctionEnvironmentObjects.
  if (frame->script()->isDebuggee()) {
    TRACE_PRINTF("Script is debuggee\n");
    frame->setIsDebuggee();
  }

  if (CalleeTokenIsFunction(frame->calleeToken())) {
    JSFunction* func = CalleeTokenToFunction(frame->calleeToken());
    frame->setEnvironmentChain(func->environment());
    if (func->needsFunctionEnvironmentObjects()) {
      PUSH_EXIT_FRAME();
      if (!js::InitFunctionEnvironmentObjects(cx, frame)) {
        GOTO_ERROR();
      }
      TRACE_PRINTF("callee is func %p; created environment object: %p\n", func,
                   frame->environmentChain());
    }
  }

  // The debug prologue can't run until the function environment is set up.
  if (frame->script()->isDebuggee()) {
    PUSH_EXIT_FRAME();
    if (!DebugPrologue(cx, frame)) {
      GOTO_ERROR();
    }
  }

  if (!frame->script()->hasScriptCounts()) {
    if (ctx.frameMgr.cxForLocalUseOnly()->realm()->collectCoverageForDebug()) {
      PUSH_EXIT_FRAME();
      if (!frame->script()->initScriptCounts(cx)) {
        GOTO_ERROR();
      }
    }
  }
  COUNT_COVERAGE_MAIN();

#ifdef ENABLE_INTERRUPT_CHECKS
  if (ctx.frameMgr.cxForLocalUseOnly()->hasAnyPendingInterrupt()) {
    PUSH_EXIT_FRAME();
    if (!InterruptCheck(cx)) {
      GOTO_ERROR();
    }
  }
#endif

  TRACE_PRINTF("Entering: sp = %p fp = %p frame = %p, script = %p, pc = %p\n",
               sp, ctx.stack.fp, frame, frame->script(), pc);
  TRACE_PRINTF("nslots = %d nfixed = %d\n", int(frame->script()->nslots()),
               int(frame->script()->nfixed()));

  while (true) {
    DEBUG_CHECK();

#if !defined(ENABLE_COMPUTED_GOTO_DISPATCH) || !defined(__wasi__)
  dispatch:
#endif

#ifdef TRACE_INTERP
  {
    JSOp op = JSOp(*pc);
    printf("sp[0] = %" PRIx64 " sp[1] = %" PRIx64 " sp[2] = %" PRIx64 "\n",
           sp[0].asUInt64(), sp[1].asUInt64(), sp[2].asUInt64());
    printf("script = %p pc = %p: %s (ic %d entry %p) pending = %d\n",
           frame->script(), pc, CodeName(op), (int)(icEntry - icEntries),
           icEntry, ctx.frameMgr.cxForLocalUseOnly()->isExceptionPending());
    printf("sp = %p fp = %p\n", sp, ctx.stack.fp);
    printf("TOS tag: %d\n", int(sp[0].asValue().asRawBits() >> 47));
    fflush(stdout);
  }
#endif

#ifdef ENABLE_COMPUTED_GOTO_DISPATCH
    goto* addresses[*pc];
#else
    (void)addresses;  // Avoid unused-local error. We keep the table
                      // itself to avoid warnings (see note in IC
                      // interpreter above).
    switch (JSOp(*pc))
#endif
    {
      CASE(Nop) { END_OP(Nop); }
      CASE(NopIsAssignOp) { END_OP(NopIsAssignOp); }
      CASE(Undefined) {
        VIRTPUSH(StackVal(UndefinedValue()));
        END_OP(Undefined);
      }
      CASE(Null) {
        VIRTPUSH(StackVal(NullValue()));
        END_OP(Null);
      }
      CASE(False) {
        VIRTPUSH(StackVal(BooleanValue(false)));
        END_OP(False);
      }
      CASE(True) {
        VIRTPUSH(StackVal(BooleanValue(true)));
        END_OP(True);
      }
      CASE(Int32) {
        VIRTPUSH(StackVal(Int32Value(GET_INT32(pc))));
        END_OP(Int32);
      }
      CASE(Zero) {
        VIRTPUSH(StackVal(Int32Value(0)));
        END_OP(Zero);
      }
      CASE(One) {
        VIRTPUSH(StackVal(Int32Value(1)));
        END_OP(One);
      }
      CASE(Int8) {
        VIRTPUSH(StackVal(Int32Value(GET_INT8(pc))));
        END_OP(Int8);
      }
      CASE(Uint16) {
        VIRTPUSH(StackVal(Int32Value(GET_UINT16(pc))));
        END_OP(Uint16);
      }
      CASE(Uint24) {
        VIRTPUSH(StackVal(Int32Value(GET_UINT24(pc))));
        END_OP(Uint24);
      }
      CASE(Double) {
        VIRTPUSH(StackVal(GET_INLINE_VALUE(pc)));
        END_OP(Double);
      }
      CASE(BigInt) {
        VIRTPUSH(StackVal(JS::BigIntValue(frame->script()->getBigInt(pc))));
        END_OP(BigInt);
      }
      CASE(String) {
        VIRTPUSH(StackVal(StringValue(frame->script()->getString(pc))));
        END_OP(String);
      }
      CASE(Symbol) {
        VIRTPUSH(StackVal(SymbolValue(
            ctx.frameMgr.cxForLocalUseOnly()->wellKnownSymbols().get(
                GET_UINT8(pc)))));
        END_OP(Symbol);
      }
      CASE(Void) {
        VIRTSPWRITE(0, StackVal(JS::UndefinedValue()));
        END_OP(Void);
      }

      CASE(Typeof)
      CASE(TypeofExpr) {
        static_assert(JSOpLength_Typeof == JSOpLength_TypeofExpr);
        if (HybridICs) {
          SYNCSP();
          VIRTSPWRITE(
              0,
              StackVal(StringValue(TypeOfOperation(
                  SPHANDLE(0), ctx.frameMgr.cxForLocalUseOnly()->runtime()))));
          NEXT_IC();
        } else {
          IC_POP_ARG(0);
          IC_ZERO_ARG(1);
          IC_ZERO_ARG(2);
          INVOKE_IC_AND_PUSH(TypeOf, false);
        }
        END_OP(Typeof);
      }

      CASE(TypeofEq) {
        if (HybridICs) {
          TypeofEqOperand operand =
              TypeofEqOperand::fromRawValue(GET_UINT8(pc));
          bool result = js::TypeOfValue(SPHANDLE(0)) == operand.type();
          if (operand.compareOp() == JSOp::Ne) {
            result = !result;
          }
          VIRTSPWRITE(0, StackVal(BooleanValue(result)));
          NEXT_IC();
        } else {
          IC_POP_ARG(0);
          IC_ZERO_ARG(1);
          IC_ZERO_ARG(2);
          INVOKE_IC_AND_PUSH(TypeOfEq, false);
        }
        END_OP(TypeofEq);
      }

      CASE(Pos) {
        if (VIRTSP(0).asValue().isNumber()) {
          // Nothing!
          NEXT_IC();
          END_OP(Pos);
        } else {
          goto generic_unary;
        }
      }
      CASE(Neg) {
        Value v = VIRTSP(0).asValue();
        if (v.isInt32()) {
          int32_t i = v.toInt32();
          if (i != 0 && i != INT32_MIN) {
            VIRTSPWRITE(0, StackVal(Int32Value(-i)));
            NEXT_IC();
            END_OP(Neg);
          }
        }
        if (v.isNumber()) {
          VIRTSPWRITE(0, StackVal(NumberValue(-v.toNumber())));
          NEXT_IC();
          END_OP(Neg);
        }
        goto generic_unary;
      }

      CASE(Inc) {
        Value v = VIRTSP(0).asValue();
        if (v.isInt32()) {
          int32_t i = v.toInt32();
          if (i != INT32_MAX) {
            VIRTSPWRITE(0, StackVal(Int32Value(i + 1)));
            NEXT_IC();
            END_OP(Inc);
          }
        }
        if (v.isNumber()) {
          VIRTSPWRITE(0, StackVal(NumberValue(v.toNumber() + 1)));
          NEXT_IC();
          END_OP(Inc);
        }
        goto generic_unary;
      }
      CASE(Dec) {
        Value v = VIRTSP(0).asValue();
        if (v.isInt32()) {
          int32_t i = v.toInt32();
          if (i != INT32_MIN) {
            VIRTSPWRITE(0, StackVal(Int32Value(i - 1)));
            NEXT_IC();
            END_OP(Dec);
          }
        }
        if (v.isNumber()) {
          VIRTSPWRITE(0, StackVal(NumberValue(v.toNumber() - 1)));
          NEXT_IC();
          END_OP(Dec);
        }
        goto generic_unary;
      }

      CASE(BitNot) {
        Value v = VIRTSP(0).asValue();
        if (v.isInt32()) {
          int32_t i = v.toInt32();
          VIRTSPWRITE(0, StackVal(Int32Value(~i)));
          NEXT_IC();
          END_OP(Inc);
        }
        goto generic_unary;
      }

      CASE(ToNumeric) {
        if (VIRTSP(0).asValue().isNumeric()) {
          NEXT_IC();
        } else if (HybridICs) {
          SYNCSP();
          MutableHandleValue val = SPHANDLEMUT(0);
          PUSH_EXIT_FRAME();
          if (!ToNumeric(cx, val)) {
            GOTO_ERROR();
          }
          NEXT_IC();
        } else {
          goto generic_unary;
        }
        END_OP(ToNumeric);
      }

    generic_unary:;
      {
        static_assert(JSOpLength_Pos == JSOpLength_Neg);
        static_assert(JSOpLength_Pos == JSOpLength_BitNot);
        static_assert(JSOpLength_Pos == JSOpLength_Inc);
        static_assert(JSOpLength_Pos == JSOpLength_Dec);
        static_assert(JSOpLength_Pos == JSOpLength_ToNumeric);
        IC_POP_ARG(0);
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(UnaryArith, false);
        END_OP(Pos);
      }

      CASE(Not) {
        Value v = VIRTSP(0).asValue();
        if (v.isBoolean()) {
          VIRTSPWRITE(0, StackVal(BooleanValue(!v.toBoolean())));
          NEXT_IC();
        } else if (HybridICs) {
          SYNCSP();
          VIRTSPWRITE(0, StackVal(BooleanValue(!ToBoolean(SPHANDLE(0)))));
          NEXT_IC();
        } else {
          IC_POP_ARG(0);
          IC_ZERO_ARG(1);
          IC_ZERO_ARG(2);
          INVOKE_IC(ToBool, false);
          VIRTPUSH(
              StackVal(BooleanValue(!Value::fromRawBits(ic_ret).toBoolean())));
        }
        END_OP(Not);
      }

      CASE(And) {
        bool result;
        Value v = VIRTSP(0).asValue();
        if (v.isBoolean()) {
          result = v.toBoolean();
          NEXT_IC();
        } else if (HybridICs) {
          result = ToBoolean(SPHANDLE(0));
          NEXT_IC();
        } else {
          IC_SET_ARG_FROM_STACK(0, 0);
          IC_ZERO_ARG(1);
          IC_ZERO_ARG(2);
          INVOKE_IC(ToBool, false);
          result = Value::fromRawBits(ic_ret).toBoolean();
        }
        int32_t jumpOffset = GET_JUMP_OFFSET(pc);
        if (!result) {
          ADVANCE(jumpOffset);
          PREDICT_NEXT(JumpTarget);
          PREDICT_NEXT(LoopHead);
        } else {
          ADVANCE(JSOpLength_And);
        }
        DISPATCH();
      }
      CASE(Or) {
        bool result;
        Value v = VIRTSP(0).asValue();
        if (v.isBoolean()) {
          result = v.toBoolean();
          NEXT_IC();
        } else if (HybridICs) {
          result = ToBoolean(SPHANDLE(0));
          NEXT_IC();
        } else {
          IC_SET_ARG_FROM_STACK(0, 0);
          IC_ZERO_ARG(1);
          IC_ZERO_ARG(2);
          INVOKE_IC(ToBool, false);
          result = Value::fromRawBits(ic_ret).toBoolean();
        }
        int32_t jumpOffset = GET_JUMP_OFFSET(pc);
        if (result) {
          ADVANCE(jumpOffset);
          PREDICT_NEXT(JumpTarget);
          PREDICT_NEXT(LoopHead);
        } else {
          ADVANCE(JSOpLength_Or);
        }
        DISPATCH();
      }
      CASE(JumpIfTrue) {
        bool result;
        Value v = VIRTSP(0).asValue();
        if (v.isBoolean()) {
          result = v.toBoolean();
          VIRTPOP();
          NEXT_IC();
        } else if (HybridICs) {
          result = ToBoolean(SPHANDLE(0));
          VIRTPOP();
          NEXT_IC();
        } else {
          IC_POP_ARG(0);
          IC_ZERO_ARG(1);
          IC_ZERO_ARG(2);
          INVOKE_IC(ToBool, false);
          result = Value::fromRawBits(ic_ret).toBoolean();
        }
        int32_t jumpOffset = GET_JUMP_OFFSET(pc);
        if (result) {
          ADVANCE(jumpOffset);
          PREDICT_NEXT(JumpTarget);
          PREDICT_NEXT(LoopHead);
        } else {
          ADVANCE(JSOpLength_JumpIfTrue);
        }
        DISPATCH();
      }
      CASE(JumpIfFalse) {
        bool result;
        Value v = VIRTSP(0).asValue();
        if (v.isBoolean()) {
          result = v.toBoolean();
          VIRTPOP();
          NEXT_IC();
        } else if (HybridICs) {
          result = ToBoolean(SPHANDLE(0));
          VIRTPOP();
          NEXT_IC();
        } else {
          IC_POP_ARG(0);
          IC_ZERO_ARG(1);
          IC_ZERO_ARG(2);
          INVOKE_IC(ToBool, false);
          result = Value::fromRawBits(ic_ret).toBoolean();
        }
        int32_t jumpOffset = GET_JUMP_OFFSET(pc);
        if (!result) {
          ADVANCE(jumpOffset);
          PREDICT_NEXT(JumpTarget);
          PREDICT_NEXT(LoopHead);
        } else {
          ADVANCE(JSOpLength_JumpIfFalse);
        }
        DISPATCH();
      }

      CASE(Add) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            int64_t lhs = v1.toInt32();
            int64_t rhs = v0.toInt32();
            int64_t result = lhs + rhs;
            if (result >= int64_t(INT32_MIN) && result <= int64_t(INT32_MAX)) {
              VIRTPOP();
              VIRTSPWRITE(0, StackVal(Int32Value(int32_t(result))));
              NEXT_IC();
              END_OP(Add);
            }
          }
          if (v0.isNumber() && v1.isNumber()) {
            double lhs = v1.toNumber();
            double rhs = v0.toNumber();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(NumberValue(lhs + rhs)));
            NEXT_IC();
            END_OP(Add);
          }

          MutableHandleValue lhs = SPHANDLEMUT(1);
          MutableHandleValue rhs = SPHANDLEMUT(0);
          MutableHandleValue result = SPHANDLEMUT(1);
          {
            PUSH_EXIT_FRAME();
            if (!AddOperation(cx, lhs, rhs, result)) {
              GOTO_ERROR();
            }
          }
          VIRTPOP();
          NEXT_IC();
          END_OP(Add);
        }
        goto generic_binary;
      }

      CASE(Sub) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            int64_t lhs = v1.toInt32();
            int64_t rhs = v0.toInt32();
            int64_t result = lhs - rhs;
            if (result >= int64_t(INT32_MIN) && result <= int64_t(INT32_MAX)) {
              VIRTPOP();
              VIRTSPWRITE(0, StackVal(Int32Value(int32_t(result))));
              NEXT_IC();
              END_OP(Sub);
            }
          }
          if (v0.isNumber() && v1.isNumber()) {
            double lhs = v1.toNumber();
            double rhs = v0.toNumber();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(NumberValue(lhs - rhs)));
            NEXT_IC();
            END_OP(Add);
          }

          MutableHandleValue lhs = SPHANDLEMUT(1);
          MutableHandleValue rhs = SPHANDLEMUT(0);
          MutableHandleValue result = SPHANDLEMUT(1);
          {
            PUSH_EXIT_FRAME();
            if (!SubOperation(cx, lhs, rhs, result)) {
              GOTO_ERROR();
            }
          }
          VIRTPOP();
          NEXT_IC();
          END_OP(Sub);
        }
        goto generic_binary;
      }

      CASE(Mul) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            int64_t lhs = v1.toInt32();
            int64_t rhs = v0.toInt32();
            int64_t product = lhs * rhs;
            if (product >= int64_t(INT32_MIN) &&
                product <= int64_t(INT32_MAX) &&
                (product != 0 || !((lhs < 0) ^ (rhs < 0)))) {
              VIRTPOP();
              VIRTSPWRITE(0, StackVal(Int32Value(int32_t(product))));
              NEXT_IC();
              END_OP(Mul);
            }
          }
          if (v0.isNumber() && v1.isNumber()) {
            double lhs = v1.toNumber();
            double rhs = v0.toNumber();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(NumberValue(lhs * rhs)));
            NEXT_IC();
            END_OP(Mul);
          }

          MutableHandleValue lhs = SPHANDLEMUT(1);
          MutableHandleValue rhs = SPHANDLEMUT(0);
          MutableHandleValue result = SPHANDLEMUT(1);
          {
            PUSH_EXIT_FRAME();
            if (!MulOperation(cx, lhs, rhs, result)) {
              GOTO_ERROR();
            }
          }
          VIRTPOP();
          NEXT_IC();
          END_OP(Mul);
        }
        goto generic_binary;
      }
      CASE(Div) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isNumber() && v1.isNumber()) {
            double lhs = v1.toNumber();
            double rhs = v0.toNumber();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(NumberValue(NumberDiv(lhs, rhs))));
            NEXT_IC();
            END_OP(Div);
          }

          MutableHandleValue lhs = SPHANDLEMUT(1);
          MutableHandleValue rhs = SPHANDLEMUT(0);
          MutableHandleValue result = SPHANDLEMUT(1);
          {
            PUSH_EXIT_FRAME();
            if (!DivOperation(cx, lhs, rhs, result)) {
              GOTO_ERROR();
            }
          }
          VIRTPOP();
          NEXT_IC();
          END_OP(Div);
        }
        goto generic_binary;
      }
      CASE(Mod) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            int64_t lhs = v1.toInt32();
            int64_t rhs = v0.toInt32();
            if (lhs > 0 && rhs > 0) {
              int64_t mod = lhs % rhs;
              VIRTPOP();
              VIRTSPWRITE(0, StackVal(Int32Value(int32_t(mod))));
              NEXT_IC();
              END_OP(Mod);
            }
          }
          if (v0.isNumber() && v1.isNumber()) {
            double lhs = v1.toNumber();
            double rhs = v0.toNumber();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(DoubleValue(NumberMod(lhs, rhs))));
            NEXT_IC();
            END_OP(Mod);
          }

          MutableHandleValue lhs = SPHANDLEMUT(1);
          MutableHandleValue rhs = SPHANDLEMUT(0);
          MutableHandleValue result = SPHANDLEMUT(1);
          {
            PUSH_EXIT_FRAME();
            if (!ModOperation(cx, lhs, rhs, result)) {
              GOTO_ERROR();
            }
          }
          VIRTPOP();
          NEXT_IC();
          END_OP(Mod);
        }
        goto generic_binary;
      }
      CASE(Pow) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isNumber() && v1.isNumber()) {
            double lhs = v1.toNumber();
            double rhs = v0.toNumber();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(NumberValue(ecmaPow(lhs, rhs))));
            NEXT_IC();
            END_OP(Pow);
          }

          MutableHandleValue lhs = SPHANDLEMUT(1);
          MutableHandleValue rhs = SPHANDLEMUT(0);
          MutableHandleValue result = SPHANDLEMUT(1);
          {
            PUSH_EXIT_FRAME();
            if (!PowOperation(cx, lhs, rhs, result)) {
              GOTO_ERROR();
            }
          }
          VIRTPOP();
          NEXT_IC();
          END_OP(Pow);
        }
        goto generic_binary;
      }
      CASE(BitOr) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            int32_t lhs = v1.toInt32();
            int32_t rhs = v0.toInt32();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(Int32Value(lhs | rhs)));
            NEXT_IC();
            END_OP(BitOr);
          }
        }
        goto generic_binary;
      }
      CASE(BitAnd) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            int32_t lhs = v1.toInt32();
            int32_t rhs = v0.toInt32();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(Int32Value(lhs & rhs)));
            NEXT_IC();
            END_OP(BitAnd);
          }
        }
        goto generic_binary;
      }
      CASE(BitXor) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            int32_t lhs = v1.toInt32();
            int32_t rhs = v0.toInt32();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(Int32Value(lhs ^ rhs)));
            NEXT_IC();
            END_OP(BitXor);
          }
        }
        goto generic_binary;
      }
      CASE(Lsh) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            // Unsigned to avoid undefined behavior on left-shift overflow
            // (see comment in BitLshOperation in Interpreter.cpp).
            uint32_t lhs = uint32_t(v1.toInt32());
            uint32_t rhs = uint32_t(v0.toInt32());
            VIRTPOP();
            rhs &= 31;
            VIRTSPWRITE(0, StackVal(Int32Value(int32_t(lhs << rhs))));
            NEXT_IC();
            END_OP(Lsh);
          }
        }
        goto generic_binary;
      }
      CASE(Rsh) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            int32_t lhs = v1.toInt32();
            int32_t rhs = v0.toInt32();
            VIRTPOP();
            rhs &= 31;
            VIRTSPWRITE(0, StackVal(Int32Value(lhs >> rhs)));
            NEXT_IC();
            END_OP(Rsh);
          }
        }
        goto generic_binary;
      }
      CASE(Ursh) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            uint32_t lhs = uint32_t(v1.toInt32());
            int32_t rhs = v0.toInt32();
            VIRTPOP();
            rhs &= 31;
            uint32_t result = lhs >> rhs;
            StackVal stackResult(0);
            if (result <= uint32_t(INT32_MAX)) {
              stackResult = StackVal(Int32Value(int32_t(result)));
            } else {
              stackResult = StackVal(NumberValue(double(result)));
            }
            VIRTSPWRITE(0, stackResult);
            NEXT_IC();
            END_OP(Ursh);
          }
        }
        goto generic_binary;
      }

    generic_binary:;
      {
        static_assert(JSOpLength_BitOr == JSOpLength_BitXor);
        static_assert(JSOpLength_BitOr == JSOpLength_BitAnd);
        static_assert(JSOpLength_BitOr == JSOpLength_Lsh);
        static_assert(JSOpLength_BitOr == JSOpLength_Rsh);
        static_assert(JSOpLength_BitOr == JSOpLength_Ursh);
        static_assert(JSOpLength_BitOr == JSOpLength_Add);
        static_assert(JSOpLength_BitOr == JSOpLength_Sub);
        static_assert(JSOpLength_BitOr == JSOpLength_Mul);
        static_assert(JSOpLength_BitOr == JSOpLength_Div);
        static_assert(JSOpLength_BitOr == JSOpLength_Mod);
        static_assert(JSOpLength_BitOr == JSOpLength_Pow);
        IC_POP_ARG(1);
        IC_POP_ARG(0);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(BinaryArith, false);
        END_OP(Div);
      }

      CASE(Eq) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            bool result = v0.toInt32() == v1.toInt32();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Eq);
          }
          if (v0.isNumber() && v1.isNumber()) {
            double lhs = v1.toNumber();
            double rhs = v0.toNumber();
            bool result = lhs == rhs;
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Eq);
          }
          if (v0.isNumber() && v1.isNumber()) {
            bool result = v0.toNumber() == v1.toNumber();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Eq);
          }
        }
        goto generic_cmp;
      }

      CASE(Ne) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            bool result = v0.toInt32() != v1.toInt32();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Ne);
          }
          if (v0.isNumber() && v1.isNumber()) {
            double lhs = v1.toNumber();
            double rhs = v0.toNumber();
            bool result = lhs != rhs;
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Ne);
          }
          if (v0.isNumber() && v1.isNumber()) {
            bool result = v0.toNumber() != v1.toNumber();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Eq);
          }
        }
        goto generic_cmp;
      }

      CASE(Lt) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            bool result = v1.toInt32() < v0.toInt32();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Lt);
          }
          if (v0.isNumber() && v1.isNumber()) {
            double lhs = v1.toNumber();
            double rhs = v0.toNumber();
            bool result = lhs < rhs;
            if (std::isnan(lhs) || std::isnan(rhs)) {
              result = false;
            }
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Lt);
          }
        }
        goto generic_cmp;
      }
      CASE(Le) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            bool result = v1.toInt32() <= v0.toInt32();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Le);
          }
          if (v0.isNumber() && v1.isNumber()) {
            double lhs = v1.toNumber();
            double rhs = v0.toNumber();
            bool result = lhs <= rhs;
            if (std::isnan(lhs) || std::isnan(rhs)) {
              result = false;
            }
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Le);
          }
        }
        goto generic_cmp;
      }
      CASE(Gt) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            bool result = v1.toInt32() > v0.toInt32();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Gt);
          }
          if (v0.isNumber() && v1.isNumber()) {
            double lhs = v1.toNumber();
            double rhs = v0.toNumber();
            bool result = lhs > rhs;
            if (std::isnan(lhs) || std::isnan(rhs)) {
              result = false;
            }
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Gt);
          }
        }
        goto generic_cmp;
      }
      CASE(Ge) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          if (v0.isInt32() && v1.isInt32()) {
            bool result = v1.toInt32() >= v0.toInt32();
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Ge);
          }
          if (v0.isNumber() && v1.isNumber()) {
            double lhs = v1.toNumber();
            double rhs = v0.toNumber();
            bool result = lhs >= rhs;
            if (std::isnan(lhs) || std::isnan(rhs)) {
              result = false;
            }
            VIRTPOP();
            VIRTSPWRITE(0, StackVal(BooleanValue(result)));
            NEXT_IC();
            END_OP(Ge);
          }
        }
        goto generic_cmp;
      }

      CASE(StrictEq)
      CASE(StrictNe) {
        if (HybridICs) {
          Value v0 = VIRTSP(0).asValue();
          Value v1 = VIRTSP(1).asValue();
          bool result;
          HandleValue lval = SPHANDLE(1);
          HandleValue rval = SPHANDLE(0);
          if (v0.isString() && v1.isString()) {
            PUSH_EXIT_FRAME();
            if (!js::StrictlyEqual(cx, lval, rval, &result)) {
              GOTO_ERROR();
            }
          } else {
            if (!js::StrictlyEqual(nullptr, lval, rval, &result)) {
              GOTO_ERROR();
            }
          }
          VIRTPOP();
          VIRTSPWRITE(0,
                      StackVal(BooleanValue(
                          (JSOp(*pc) == JSOp::StrictEq) ? result : !result)));
          NEXT_IC();
          END_OP(StrictEq);
        } else {
          goto generic_cmp;
        }
      }

    generic_cmp:;
      {
        static_assert(JSOpLength_Eq == JSOpLength_Ne);
        static_assert(JSOpLength_Eq == JSOpLength_StrictEq);
        static_assert(JSOpLength_Eq == JSOpLength_StrictNe);
        static_assert(JSOpLength_Eq == JSOpLength_Lt);
        static_assert(JSOpLength_Eq == JSOpLength_Gt);
        static_assert(JSOpLength_Eq == JSOpLength_Le);
        static_assert(JSOpLength_Eq == JSOpLength_Ge);
        IC_POP_ARG(1);
        IC_POP_ARG(0);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(Compare, false);
        END_OP(Eq);
      }

      CASE(StrictConstantNe)
      CASE(StrictConstantEq) {
        JSOp op = JSOp(*pc);
        uint16_t operand = GET_UINT16(pc);
        {
          ReservedRooted<JS::Value> val(&state.value0, VIRTPOP().asValue());
          bool result;
          {
            PUSH_EXIT_FRAME();
            if (!js::ConstantStrictEqual(cx, val, operand, &result)) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(
              BooleanValue(op == JSOp::StrictConstantEq ? result : !result)));
        }
        END_OP(StrictConstantEq);
      }

      CASE(Instanceof) {
        IC_POP_ARG(1);
        IC_POP_ARG(0);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(InstanceOf, false);
        END_OP(Instanceof);
      }

      CASE(In) {
        IC_POP_ARG(1);
        IC_POP_ARG(0);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(In, false);
        END_OP(In);
      }

      CASE(ToPropertyKey) {
        IC_POP_ARG(0);
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(ToPropertyKey, false);
        END_OP(ToPropertyKey);
      }

      CASE(ToString) {
        if (VIRTSP(0).asValue().isString()) {
          END_OP(ToString);
        }
        {
          ReservedRooted<Value> value0(&state.value0, VIRTPOP().asValue());
          if (JSString* result = ToStringSlow<NoGC>(
                  ctx.frameMgr.cxForLocalUseOnly(), value0)) {
            VIRTPUSH(StackVal(StringValue(result)));
          } else {
            {
              PUSH_EXIT_FRAME();
              result = ToString<CanGC>(cx, value0);
              if (!result) {
                GOTO_ERROR();
              }
            }
            VIRTPUSH(StackVal(StringValue(result)));
          }
        }
        END_OP(ToString);
      }

      CASE(IsNullOrUndefined) {
        Value v = VIRTSP(0).asValue();
        bool result = v.isNull() || v.isUndefined();
        VIRTPUSH(StackVal(BooleanValue(result)));
        END_OP(IsNullOrUndefined);
      }

      CASE(GlobalThis) {
        VIRTPUSH(StackVal(ObjectValue(*ctx.frameMgr.cxForLocalUseOnly()
                                           ->global()
                                           ->lexicalEnvironment()
                                           .thisObject())));
        END_OP(GlobalThis);
      }

      CASE(NonSyntacticGlobalThis) {
        {
          ReservedRooted<JSObject*> obj0(&state.obj0,
                                         frame->environmentChain());
          ReservedRooted<Value> value0(&state.value0);
          {
            PUSH_EXIT_FRAME();
            js::GetNonSyntacticGlobalThis(cx, obj0, &value0);
          }
          VIRTPUSH(StackVal(value0));
        }
        END_OP(NonSyntacticGlobalThis);
      }

      CASE(NewTarget) {
        VIRTPUSH(StackVal(frame->newTarget()));
        END_OP(NewTarget);
      }

      CASE(DynamicImport) {
        {
          ReservedRooted<Value> value0(&state.value0,
                                       VIRTPOP().asValue());  // options
          ReservedRooted<Value> value1(&state.value1,
                                       VIRTPOP().asValue());  // specifier
          JSObject* promise;
          {
            PUSH_EXIT_FRAME();
            ReservedRooted<JSScript*> script0(&state.script0, frame->script());
            promise = StartDynamicModuleImport(cx, script0, value1, value0);
            if (!promise) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(ObjectValue(*promise)));
        }
        END_OP(DynamicImport);
      }

      CASE(ImportMeta) {
        IC_ZERO_ARG(0);
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(LazyConstant, false);
        END_OP(ImportMeta);
      }

      CASE(NewInit) {
        if (HybridICs) {
          JSObject* obj;
          {
            PUSH_EXIT_FRAME();
            ReservedRooted<JSScript*> script0(&state.script0, frame->script());
            obj = NewObjectOperation(cx, script0, pc);
            if (!obj) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(ObjectValue(*obj)));
          NEXT_IC();
          END_OP(NewInit);
        } else {
          IC_ZERO_ARG(0);
          IC_ZERO_ARG(1);
          IC_ZERO_ARG(2);
          INVOKE_IC_AND_PUSH(NewObject, false);
          END_OP(NewInit);
        }
      }
      CASE(NewObject) {
        if (HybridICs) {
          JSObject* obj;
          {
            PUSH_EXIT_FRAME();
            ReservedRooted<JSScript*> script0(&state.script0, frame->script());
            obj = NewObjectOperation(cx, script0, pc);
            if (!obj) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(ObjectValue(*obj)));
          NEXT_IC();
          END_OP(NewObject);
        } else {
          IC_ZERO_ARG(0);
          IC_ZERO_ARG(1);
          IC_ZERO_ARG(2);
          INVOKE_IC_AND_PUSH(NewObject, false);
          END_OP(NewObject);
        }
      }
      CASE(Object) {
        VIRTPUSH(StackVal(ObjectValue(*frame->script()->getObject(pc))));
        END_OP(Object);
      }
      CASE(ObjWithProto) {
        {
          ReservedRooted<Value> value0(&state.value0, VIRTSP(0).asValue());
          JSObject* obj;
          {
            PUSH_EXIT_FRAME();
            obj = ObjectWithProtoOperation(cx, value0);
            if (!obj) {
              GOTO_ERROR();
            }
          }
          VIRTSPWRITE(0, StackVal(ObjectValue(*obj)));
        }
        END_OP(ObjWithProto);
      }

      CASE(InitElem)
      CASE(InitHiddenElem)
      CASE(InitLockedElem)
      CASE(InitElemInc)
      CASE(SetElem)
      CASE(StrictSetElem) {
        static_assert(JSOpLength_InitElem == JSOpLength_InitHiddenElem);
        static_assert(JSOpLength_InitElem == JSOpLength_InitLockedElem);
        static_assert(JSOpLength_InitElem == JSOpLength_InitElemInc);
        static_assert(JSOpLength_InitElem == JSOpLength_SetElem);
        static_assert(JSOpLength_InitElem == JSOpLength_StrictSetElem);
        StackVal val = VIRTSP(0);
        IC_POP_ARG(2);
        IC_POP_ARG(1);
        IC_SET_ARG_FROM_STACK(0, 0);
        if (JSOp(*pc) == JSOp::SetElem || JSOp(*pc) == JSOp::StrictSetElem) {
          VIRTSPWRITE(0, val);
        }
        INVOKE_IC(SetElem, true);
        if (JSOp(*pc) == JSOp::InitElemInc) {
          VIRTPUSH(
              StackVal(Int32Value(Value::fromRawBits(ic_arg1).toInt32() + 1)));
        }
        END_OP(InitElem);
      }

      CASE(InitPropGetter)
      CASE(InitHiddenPropGetter)
      CASE(InitPropSetter)
      CASE(InitHiddenPropSetter) {
        static_assert(JSOpLength_InitPropGetter ==
                      JSOpLength_InitHiddenPropGetter);
        static_assert(JSOpLength_InitPropGetter == JSOpLength_InitPropSetter);
        static_assert(JSOpLength_InitPropGetter ==
                      JSOpLength_InitHiddenPropSetter);
        {
          ReservedRooted<JSObject*> obj1(
              &state.obj1,
              &VIRTPOP().asValue().toObject());  // val
          ReservedRooted<JSObject*> obj0(
              &state.obj0,
              &VIRTSP(0).asValue().toObject());  // obj; leave on stack
          ReservedRooted<PropertyName*> name0(&state.name0,
                                              frame->script()->getName(pc));
          {
            PUSH_EXIT_FRAME();
            if (!InitPropGetterSetterOperation(cx, pc, obj0, name0, obj1)) {
              GOTO_ERROR();
            }
          }
        }
        END_OP(InitPropGetter);
      }

      CASE(InitElemGetter)
      CASE(InitHiddenElemGetter)
      CASE(InitElemSetter)
      CASE(InitHiddenElemSetter) {
        static_assert(JSOpLength_InitElemGetter ==
                      JSOpLength_InitHiddenElemGetter);
        static_assert(JSOpLength_InitElemGetter == JSOpLength_InitElemSetter);
        static_assert(JSOpLength_InitElemGetter ==
                      JSOpLength_InitHiddenElemSetter);
        {
          ReservedRooted<JSObject*> obj1(
              &state.obj1,
              &VIRTPOP().asValue().toObject());  // val
          ReservedRooted<Value> value0(&state.value0,
                                       VIRTPOP().asValue());  // idval
          ReservedRooted<JSObject*> obj0(
              &state.obj0,
              &VIRTSP(0).asValue().toObject());  // obj; leave on stack
          {
            PUSH_EXIT_FRAME();
            if (!InitElemGetterSetterOperation(cx, pc, obj0, value0, obj1)) {
              GOTO_ERROR();
            }
          }
        }
        END_OP(InitElemGetter);
      }

      CASE(GetProp)
      CASE(GetBoundName) {
        static_assert(JSOpLength_GetProp == JSOpLength_GetBoundName);
        IC_POP_ARG(0);
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(GetProp, false);
        END_OP(GetProp);
      }
      CASE(GetPropSuper) {
        IC_POP_ARG(0);
        IC_POP_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(GetPropSuper, false);
        END_OP(GetPropSuper);
      }

      CASE(GetElem) {
        if (HybridICs && VIRTSP(1).asValue().isString()) {
          HandleValue lhs = SPHANDLE(1);
          HandleValue rhs = SPHANDLE(0);
          uint32_t index;
          if (IsDefinitelyIndex(rhs, &index)) {
            JSString* str = lhs.toString();
            if (index < str->length() && str->isLinear()) {
              JSLinearString* linear = &str->asLinear();
              char16_t c = linear->latin1OrTwoByteChar(index);
              StaticStrings& sstr =
                  ctx.frameMgr.cxForLocalUseOnly()->staticStrings();
              if (sstr.hasUnit(c)) {
                VIRTPOP();
                VIRTSPWRITE(0, StackVal(StringValue(sstr.getUnit(c))));
                NEXT_IC();
                END_OP(GetElem);
              }
            }
          }
        }

        IC_POP_ARG(1);
        IC_POP_ARG(0);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(GetElem, false);
        END_OP(GetElem);
      }

      CASE(GetElemSuper) {
        // N.B.: second and third args are out of order! See the saga at
        // https://bugzilla.mozilla.org/show_bug.cgi?id=1709328; this is
        // an echo of that issue.
        IC_POP_ARG(1);
        IC_POP_ARG(2);
        IC_POP_ARG(0);
        INVOKE_IC_AND_PUSH(GetElemSuper, true);
        END_OP(GetElemSuper);
      }

      CASE(DelProp) {
        {
          ReservedRooted<Value> value0(&state.value0, VIRTPOP().asValue());
          ReservedRooted<PropertyName*> name0(&state.name0,
                                              frame->script()->getName(pc));
          bool res = false;
          {
            PUSH_EXIT_FRAME();
            if (!DelPropOperation<false>(cx, value0, name0, &res)) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(BooleanValue(res)));
        }
        END_OP(DelProp);
      }
      CASE(StrictDelProp) {
        {
          ReservedRooted<Value> value0(&state.value0, VIRTPOP().asValue());
          ReservedRooted<PropertyName*> name0(&state.name0,
                                              frame->script()->getName(pc));
          bool res = false;
          {
            PUSH_EXIT_FRAME();
            if (!DelPropOperation<true>(cx, value0, name0, &res)) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(BooleanValue(res)));
        }
        END_OP(StrictDelProp);
      }
      CASE(DelElem) {
        {
          ReservedRooted<Value> value1(&state.value1, VIRTPOP().asValue());
          ReservedRooted<Value> value0(&state.value0, VIRTPOP().asValue());
          bool res = false;
          {
            PUSH_EXIT_FRAME();
            if (!DelElemOperation<false>(cx, value0, value1, &res)) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(BooleanValue(res)));
        }
        END_OP(DelElem);
      }
      CASE(StrictDelElem) {
        {
          ReservedRooted<Value> value1(&state.value1, VIRTPOP().asValue());
          ReservedRooted<Value> value0(&state.value0, VIRTPOP().asValue());
          bool res = false;
          {
            PUSH_EXIT_FRAME();
            if (!DelElemOperation<true>(cx, value0, value1, &res)) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(BooleanValue(res)));
        }
        END_OP(StrictDelElem);
      }

      CASE(HasOwn) {
        IC_POP_ARG(1);
        IC_POP_ARG(0);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(HasOwn, false);
        END_OP(HasOwn);
      }

      CASE(CheckPrivateField) {
        IC_SET_ARG_FROM_STACK(1, 0);
        IC_SET_ARG_FROM_STACK(0, 1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(CheckPrivateField, false);
        END_OP(CheckPrivateField);
      }

      CASE(NewPrivateName) {
        {
          ReservedRooted<JSAtom*> atom0(&state.atom0,
                                        frame->script()->getAtom(pc));
          JS::Symbol* symbol;
          {
            PUSH_EXIT_FRAME();
            symbol = NewPrivateName(cx, atom0);
            if (!symbol) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(SymbolValue(symbol)));
        }
        END_OP(NewPrivateName);
      }

      CASE(SuperBase) {
        JSFunction& superEnvFunc =
            VIRTPOP().asValue().toObject().as<JSFunction>();
        MOZ_ASSERT(superEnvFunc.allowSuperProperty());
        MOZ_ASSERT(superEnvFunc.baseScript()->needsHomeObject());
        const Value& homeObjVal = superEnvFunc.getExtendedSlot(
            FunctionExtended::METHOD_HOMEOBJECT_SLOT);

        JSObject* homeObj = &homeObjVal.toObject();
        JSObject* superBase = HomeObjectSuperBase(homeObj);

        VIRTPUSH(StackVal(ObjectOrNullValue(superBase)));
        END_OP(SuperBase);
      }

      CASE(SetPropSuper)
      CASE(StrictSetPropSuper) {
        // stack signature: receiver, lval, rval => rval
        static_assert(JSOpLength_SetPropSuper == JSOpLength_StrictSetPropSuper);
        bool strict = JSOp(*pc) == JSOp::StrictSetPropSuper;
        {
          ReservedRooted<Value> value2(&state.value2,
                                       VIRTPOP().asValue());  // rval
          ReservedRooted<Value> value1(&state.value1,
                                       VIRTPOP().asValue());  // lval
          ReservedRooted<Value> value0(&state.value0,
                                       VIRTPOP().asValue());  // recevier
          ReservedRooted<PropertyName*> name0(&state.name0,
                                              frame->script()->getName(pc));
          {
            PUSH_EXIT_FRAME();
            // SetPropertySuper(cx, lval, receiver, name, rval, strict)
            // (N.B.: lval and receiver are transposed!)
            if (!SetPropertySuper(cx, value1, value0, name0, value2, strict)) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(value2));
        }
        END_OP(SetPropSuper);
      }

      CASE(SetElemSuper)
      CASE(StrictSetElemSuper) {
        // stack signature: receiver, key, lval, rval => rval
        static_assert(JSOpLength_SetElemSuper == JSOpLength_StrictSetElemSuper);
        bool strict = JSOp(*pc) == JSOp::StrictSetElemSuper;
        {
          ReservedRooted<Value> value3(&state.value3,
                                       VIRTPOP().asValue());  // rval
          ReservedRooted<Value> value2(&state.value2,
                                       VIRTPOP().asValue());  // lval
          ReservedRooted<Value> value1(&state.value1,
                                       VIRTPOP().asValue());  // index
          ReservedRooted<Value> value0(&state.value0,
                                       VIRTPOP().asValue());  // receiver
          {
            PUSH_EXIT_FRAME();
            // SetElementSuper(cx, lval, receiver, index, rval, strict)
            // (N.B.: lval, receiver and index are rotated!)
            if (!SetElementSuper(cx, value2, value0, value1, value3, strict)) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(value3));  // value
        }
        END_OP(SetElemSuper);
      }

      CASE(Iter) {
        IC_POP_ARG(0);
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(GetIterator, false);
        END_OP(Iter);
      }

      CASE(MoreIter) {
        // iter => iter, name
        Value v = IteratorMore(&VIRTSP(0).asValue().toObject());
        VIRTPUSH(StackVal(v));
        END_OP(MoreIter);
      }

      CASE(IsNoIter) {
        // iter => iter, bool
        bool result = VIRTSP(0).asValue().isMagic(JS_NO_ITER_VALUE);
        VIRTPUSH(StackVal(BooleanValue(result)));
        END_OP(IsNoIter);
      }

      CASE(EndIter) {
        // iter, interval =>
        VIRTPOP();
        CloseIterator(&VIRTPOP().asValue().toObject());
        END_OP(EndIter);
      }

      CASE(CloseIter) {
        IC_SET_OBJ_ARG(0, &VIRTPOP().asValue().toObject());
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC(CloseIter, false);
        END_OP(CloseIter);
      }

      CASE(CheckIsObj) {
        if (!VIRTSP(0).asValue().isObject()) {
          PUSH_EXIT_FRAME();
          MOZ_ALWAYS_FALSE(
              js::ThrowCheckIsObject(cx, js::CheckIsObjectKind(GET_UINT8(pc))));
          /* abandon frame; error handler will re-establish sp */
          GOTO_ERROR();
        }
        END_OP(CheckIsObj);
      }

      CASE(CheckObjCoercible) {
        {
          ReservedRooted<Value> value0(&state.value0, VIRTSP(0).asValue());
          if (value0.isNullOrUndefined()) {
            PUSH_EXIT_FRAME();
            MOZ_ALWAYS_FALSE(ThrowObjectCoercible(cx, value0));
            /* abandon frame; error handler will re-establish sp */
            GOTO_ERROR();
          }
        }
        END_OP(CheckObjCoercible);
      }

      CASE(ToAsyncIter) {
        // iter, next => asynciter
        {
          ReservedRooted<Value> value0(&state.value0,
                                       VIRTPOP().asValue());  // next
          ReservedRooted<JSObject*> obj0(
              &state.obj0,
              &VIRTPOP().asValue().toObject());  // iter
          JSObject* result;
          {
            PUSH_EXIT_FRAME();
            result = CreateAsyncFromSyncIterator(cx, obj0, value0);
            if (!result) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(ObjectValue(*result)));
        }
        END_OP(ToAsyncIter);
      }

      CASE(MutateProto) {
        // obj, protoVal => obj
        {
          ReservedRooted<Value> value0(&state.value0, VIRTPOP().asValue());
          ReservedRooted<JSObject*> obj0(&state.obj0,
                                         &VIRTSP(0).asValue().toObject());
          {
            PUSH_EXIT_FRAME();
            if (!MutatePrototype(cx, obj0.as<PlainObject>(), value0)) {
              GOTO_ERROR();
            }
          }
        }
        END_OP(MutateProto);
      }

      CASE(NewArray) {
        if (HybridICs) {
          ArrayObject* obj;
          {
            PUSH_EXIT_FRAME();
            uint32_t length = GET_UINT32(pc);
            obj = NewArrayOperation(cx, length);
            if (!obj) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(ObjectValue(*obj)));
          NEXT_IC();
          END_OP(NewArray);
        } else {
          IC_ZERO_ARG(0);
          IC_ZERO_ARG(1);
          IC_ZERO_ARG(2);
          INVOKE_IC_AND_PUSH(NewArray, false);
          END_OP(NewArray);
        }
      }

      CASE(InitElemArray) {
        // array, val => array
        {
          ReservedRooted<Value> value0(&state.value0, VIRTPOP().asValue());
          ReservedRooted<JSObject*> obj0(&state.obj0,
                                         &VIRTSP(0).asValue().toObject());
          {
            PUSH_EXIT_FRAME();
            InitElemArrayOperation(cx, pc, obj0.as<ArrayObject>(), value0);
          }
        }
        END_OP(InitElemArray);
      }

      CASE(Hole) {
        VIRTPUSH(StackVal(MagicValue(JS_ELEMENTS_HOLE)));
        END_OP(Hole);
      }

      CASE(RegExp) {
        JSObject* obj;
        {
          PUSH_EXIT_FRAME();
          ReservedRooted<JSObject*> obj0(&state.obj0,
                                         frame->script()->getRegExp(pc));
          obj = CloneRegExpObject(cx, obj0.as<RegExpObject>());
          if (!obj) {
            GOTO_ERROR();
          }
        }
        VIRTPUSH(StackVal(ObjectValue(*obj)));
        END_OP(RegExp);
      }

      CASE(Lambda) {
        if (HybridICs) {
          JSObject* clone;
          {
            PUSH_EXIT_FRAME();
            ReservedRooted<JSFunction*> fun0(&state.fun0,
                                             frame->script()->getFunction(pc));
            ReservedRooted<JSObject*> obj0(&state.obj0,
                                           frame->environmentChain());
            clone = Lambda(cx, fun0, obj0);
            if (!clone) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(ObjectValue(*clone)));
          NEXT_IC();
          END_OP(Lambda);
        } else {
          IC_ZERO_ARG(0);
          IC_ZERO_ARG(1);
          IC_ZERO_ARG(2);
          INVOKE_IC_AND_PUSH(Lambda, false);
          END_OP(Lambda);
        }
      }

      CASE(SetFunName) {
        // fun, name => fun
        {
          ReservedRooted<Value> value0(&state.value0,
                                       VIRTPOP().asValue());  // name
          ReservedRooted<JSFunction*> fun0(
              &state.fun0, &VIRTSP(0).asValue().toObject().as<JSFunction>());
          FunctionPrefixKind prefixKind = FunctionPrefixKind(GET_UINT8(pc));
          {
            PUSH_EXIT_FRAME();
            if (!SetFunctionName(cx, fun0, value0, prefixKind)) {
              GOTO_ERROR();
            }
          }
        }
        END_OP(SetFunName);
      }

      CASE(InitHomeObject) {
        // fun, homeObject => fun
        {
          ReservedRooted<JSObject*> obj0(
              &state.obj0, &VIRTPOP().asValue().toObject());  // homeObject
          ReservedRooted<JSFunction*> fun0(
              &state.fun0, &VIRTSP(0).asValue().toObject().as<JSFunction>());
          MOZ_ASSERT(fun0->allowSuperProperty());
          MOZ_ASSERT(obj0->is<PlainObject>() || obj0->is<JSFunction>());
          fun0->setExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT,
                                ObjectValue(*obj0));
        }
        END_OP(InitHomeObject);
      }

      CASE(CheckClassHeritage) {
        {
          ReservedRooted<Value> value0(&state.value0, VIRTSP(0).asValue());
          {
            PUSH_EXIT_FRAME();
            if (!CheckClassHeritageOperation(cx, value0)) {
              GOTO_ERROR();
            }
          }
        }
        END_OP(CheckClassHeritage);
      }

      CASE(FunWithProto) {
        // proto => obj
        {
          ReservedRooted<JSObject*> obj0(
              &state.obj0,
              &VIRTPOP().asValue().toObject());  // proto
          ReservedRooted<JSObject*> obj1(&state.obj1,
                                         frame->environmentChain());
          ReservedRooted<JSFunction*> fun0(&state.fun0,
                                           frame->script()->getFunction(pc));
          JSObject* obj;
          {
            PUSH_EXIT_FRAME();
            obj = FunWithProtoOperation(cx, fun0, obj1, obj0);
            if (!obj) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(ObjectValue(*obj)));
        }
        END_OP(FunWithProto);
      }

      CASE(BuiltinObject) {
        IC_ZERO_ARG(0);
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(LazyConstant, false);
        END_OP(BuiltinObject);
      }

      CASE(Call)
      CASE(CallIgnoresRv)
      CASE(CallContent)
      CASE(CallIter)
      CASE(CallContentIter)
      CASE(Eval)
      CASE(StrictEval)
      CASE(SuperCall)
      CASE(New)
      CASE(NewContent) {
        static_assert(JSOpLength_Call == JSOpLength_CallIgnoresRv);
        static_assert(JSOpLength_Call == JSOpLength_CallContent);
        static_assert(JSOpLength_Call == JSOpLength_CallIter);
        static_assert(JSOpLength_Call == JSOpLength_CallContentIter);
        static_assert(JSOpLength_Call == JSOpLength_Eval);
        static_assert(JSOpLength_Call == JSOpLength_StrictEval);
        static_assert(JSOpLength_Call == JSOpLength_SuperCall);
        static_assert(JSOpLength_Call == JSOpLength_New);
        static_assert(JSOpLength_Call == JSOpLength_NewContent);
        JSOp op = JSOp(*pc);
        bool constructing = (op == JSOp::New || op == JSOp::NewContent ||
                             op == JSOp::SuperCall);
        uint32_t argc = GET_ARGC(pc);
        do {
          {
            // CallArgsFromSp would be called with
            // - numValues = argc + 2 + constructing
            // - stackSlots = argc + constructing
            // - sp = vp + numValues
            // CallArgs::create then gets
            // - argc_ = stackSlots - constructing = argc
            // - argv_ = sp - stackSlots = vp + 2
            // our arguments are in reverse order compared to what CallArgs
            // expects so we should subtract any array subscripts from (sp +
            // stackSlots - 1)
            StackVal* firstArg = sp + argc + constructing - 1;

            // callee is argv_[-2] -> sp + argc + constructing + 1
            // this is   argv_[-1] -> sp + argc + constructing
            // newTarget is argv_[argc_] -> sp + constructing - 1
            // but this/newTarget are only used when constructing is 1 so we can
            // simplify this is   argv_[-1] -> sp + argc + 1 newTarget is
            // argv_[argc_] -> sp

            HandleValue callee = Stack::handle(firstArg + 2);
            if (!callee.isObject() || !callee.toObject().is<JSFunction>()) {
              TRACE_PRINTF("missed fastpath: not a function\n");
              break;
            }
            ReservedRooted<JSFunction*> func(
                &state.fun0, &callee.toObject().as<JSFunction>());
            if (!func->hasBaseScript() || !func->isInterpreted()) {
              TRACE_PRINTF("missed fastpath: not an interpreted script\n");
              break;
            }
            if (!constructing && func->isClassConstructor()) {
              TRACE_PRINTF(
                  "missed fastpath: constructor called without `new`\n");
              break;
            }
            if (constructing && !func->isConstructor()) {
              TRACE_PRINTF(
                  "missed fastpath: constructing with a non-constructor\n");
              break;
            }
            if (!func->baseScript()->hasBytecode()) {
              TRACE_PRINTF("missed fastpath: no bytecode\n");
              break;
            }
            ReservedRooted<JSScript*> calleeScript(
                &state.script0, func->baseScript()->asJSScript());
            if (!calleeScript->hasJitScript()) {
              TRACE_PRINTF("missed fastpath: no jit-script\n");
              break;
            }
            if (ctx.frameMgr.cxForLocalUseOnly()->realm() !=
                calleeScript->realm()) {
              TRACE_PRINTF("missed fastpath: mismatched realm\n");
              break;
            }
            if (argc < func->nargs()) {
              TRACE_PRINTF("missed fastpath: not enough arguments\n");
              break;
            }

            // Fast-path: function, interpreted, has JitScript, same realm, no
            // argument underflow.

            // Include newTarget in the args if it exists; exclude callee
            uint32_t totalArgs = argc + 1 + constructing;
            StackVal* origArgs = sp;

            TRACE_PRINTF(
                "Call fastpath: argc = %d origArgs = %p callee = %" PRIx64 "\n",
                argc, origArgs, callee.get().asRawBits());

            if (!ctx.stack.check(sp, sizeof(StackVal) * (totalArgs + 3))) {
              TRACE_PRINTF("missed fastpath: would cause stack overrun\n");
              break;
            }

            if (constructing) {
              MutableHandleValue thisv = Stack::handleMut(firstArg + 1);
              if (!thisv.isObject()) {
                HandleValue newTarget = Stack::handle(firstArg - argc);
                ReservedRooted<JSObject*> obj0(&state.obj0,
                                               &newTarget.toObject());

                PUSH_EXIT_FRAME();
                // CreateThis might discard the JitScript but we're counting on
                // it continuing to exist while we evaluate the fastpath.
                AutoKeepJitScripts keepJitScript(cx);
                if (!CreateThis(cx, func, obj0, GenericObject, thisv)) {
                  GOTO_ERROR();
                }

                TRACE_PRINTF("created %" PRIx64 "\n", thisv.get().asRawBits());
              }
            }

            // 0. Save current PC and interpreter IC pointer in
            // current frame, so we can retrieve them later.
            frame->interpreterPC() = pc;
            frame->interpreterICEntry() = icEntry;

            // 1. Push a baseline stub frame. Don't use the frame manager
            // -- we don't want the frame to be auto-freed when we leave
            // this scope, and we don't want to shadow `sp`.
            StackVal* exitFP = ctx.stack.pushExitFrame(sp, frame);
            MOZ_ASSERT(exitFP);  // safety: stack margin.
            sp = exitFP;
            TRACE_PRINTF("exit frame at %p\n", exitFP);

            // 2. Modify exit code to nullptr (this is where ICStubReg is
            // normally saved; the tracing code can skip if null).
            PUSHNATIVE(StackValNative(nullptr));

            // 3. Push args in proper order (they are reversed in our
            // downward-growth stack compared to what the calling
            // convention expects).
            for (uint32_t i = 0; i < totalArgs; i++) {
              VIRTPUSH(origArgs[i]);
            }

            // 4. Push inter-frame content: callee token, descriptor for
            // above.
            PUSHNATIVE(StackValNative(CalleeToToken(func, constructing)));
            PUSHNATIVE(StackValNative(
                MakeFrameDescriptorForJitCall(FrameType::BaselineStub, argc)));

            // 5. Push fake return address, set script, push baseline frame.
            PUSHNATIVE(StackValNative(nullptr));
            BaselineFrame* newFrame =
                ctx.stack.pushFrame(sp, ctx.frameMgr.cxForLocalUseOnly(),
                                    /* envChain = */ func->environment());
            MOZ_ASSERT(newFrame);  // safety: stack margin.
            TRACE_PRINTF("callee frame at %p\n", newFrame);
            frame = newFrame;
            ctx.frameMgr.switchToFrame(frame);
            ctx.frame = frame;
            // 6. Set up PC and SP for callee.
            sp = reinterpret_cast<StackVal*>(frame);
            RESET_PC(calleeScript->code(), calleeScript);
            // 7. Check callee stack space for max stack depth.
            if (!stack.check(sp, sizeof(StackVal) * calleeScript->nslots())) {
              PUSH_EXIT_FRAME();
              ReportOverRecursed(ctx.frameMgr.cxForLocalUseOnly());
              GOTO_ERROR();
            }
            // 8. Push local slots, and set return value to `undefined` by
            // default.
            uint32_t nfixed = calleeScript->nfixed();
            for (uint32_t i = 0; i < nfixed; i++) {
              VIRTPUSH(StackVal(UndefinedValue()));
            }
            ret->setUndefined();
            // 9. Initialize environment objects.
            if (func->needsFunctionEnvironmentObjects()) {
              PUSH_EXIT_FRAME();
              if (!js::InitFunctionEnvironmentObjects(cx, frame)) {
                GOTO_ERROR();
              }
            }
            // 10. Set debug flag, if appropriate.
            if (frame->script()->isDebuggee()) {
              TRACE_PRINTF("Script is debuggee\n");
              frame->setIsDebuggee();

              PUSH_EXIT_FRAME();
              if (!DebugPrologue(cx, frame)) {
                GOTO_ERROR();
              }
            }
            // 11. Check for interrupts.
#ifdef ENABLE_INTERRUPT_CHECKS
            if (ctx.frameMgr.cxForLocalUseOnly()->hasAnyPendingInterrupt()) {
              PUSH_EXIT_FRAME();
              if (!InterruptCheck(cx)) {
                GOTO_ERROR();
              }
            }
#endif
            // 12. Initialize coverage tables, if needed.
            if (!frame->script()->hasScriptCounts()) {
              if (ctx.frameMgr.cxForLocalUseOnly()
                      ->realm()
                      ->collectCoverageForDebug()) {
                PUSH_EXIT_FRAME();
                if (!frame->script()->initScriptCounts(cx)) {
                  GOTO_ERROR();
                }
              }
            }
            COUNT_COVERAGE_MAIN();
          }

          // Everything is switched to callee context now -- dispatch!
          DISPATCH();
        } while (0);

        // Slow path: use the IC!
        ic_arg0 = argc;
        ctx.icregs.extraArgs = 2 + constructing;
        INVOKE_IC(Call, false);
        VIRTPOPN(argc + 2 + constructing);
        VIRTPUSH(StackVal(Value::fromRawBits(ic_ret)));
        END_OP(Call);
      }

      CASE(SpreadCall)
      CASE(SpreadEval)
      CASE(StrictSpreadEval) {
        static_assert(JSOpLength_SpreadCall == JSOpLength_SpreadEval);
        static_assert(JSOpLength_SpreadCall == JSOpLength_StrictSpreadEval);
        ic_arg0 = 1;
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        ctx.icregs.extraArgs = 2;
        INVOKE_IC(SpreadCall, false);
        VIRTPOPN(3);
        VIRTPUSH(StackVal(Value::fromRawBits(ic_ret)));
        END_OP(SpreadCall);
      }

      CASE(SpreadSuperCall)
      CASE(SpreadNew) {
        static_assert(JSOpLength_SpreadSuperCall == JSOpLength_SpreadNew);
        ic_arg0 = 1;
        ctx.icregs.extraArgs = 3;
        INVOKE_IC(SpreadCall, false);
        VIRTPOPN(4);
        VIRTPUSH(StackVal(Value::fromRawBits(ic_ret)));
        END_OP(SpreadSuperCall);
      }

      CASE(OptimizeSpreadCall) {
        IC_POP_ARG(0);
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(OptimizeSpreadCall, false);
        END_OP(OptimizeSpreadCall);
      }

      CASE(OptimizeGetIterator) {
        IC_POP_ARG(0);
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(OptimizeGetIterator, false);
        END_OP(OptimizeGetIterator);
      }

      CASE(ImplicitThis) {
        {
          ReservedRooted<JSObject*> env(&state.obj0,
                                        &VIRTSP(0).asValue().toObject());
          VIRTPOP();
          PUSH_EXIT_FRAME();
          ImplicitThisOperation(cx, env, &state.res);
        }
        VIRTPUSH(StackVal(state.res));
        state.res.setUndefined();
        END_OP(ImplicitThis);
      }

      CASE(CallSiteObj) {
        JSObject* cso = frame->script()->getObject(pc);
        MOZ_ASSERT(!cso->as<ArrayObject>().isExtensible());
        MOZ_ASSERT(cso->as<ArrayObject>().containsPure(
            ctx.frameMgr.cxForLocalUseOnly()->names().raw));
        VIRTPUSH(StackVal(ObjectValue(*cso)));
        END_OP(CallSiteObj);
      }

      CASE(IsConstructing) {
        VIRTPUSH(StackVal(MagicValue(JS_IS_CONSTRUCTING)));
        END_OP(IsConstructing);
      }

      CASE(SuperFun) {
        JSObject* superEnvFunc = &VIRTPOP().asValue().toObject();
        JSObject* superFun = SuperFunOperation(superEnvFunc);
        VIRTPUSH(StackVal(ObjectOrNullValue(superFun)));
        END_OP(SuperFun);
      }

      CASE(CheckThis) {
        if (VIRTSP(0).asValue().isMagic(JS_UNINITIALIZED_LEXICAL)) {
          PUSH_EXIT_FRAME();
          MOZ_ALWAYS_FALSE(ThrowUninitializedThis(cx));
          GOTO_ERROR();
        }
        END_OP(CheckThis);
      }

      CASE(CheckThisReinit) {
        if (!VIRTSP(0).asValue().isMagic(JS_UNINITIALIZED_LEXICAL)) {
          PUSH_EXIT_FRAME();
          MOZ_ALWAYS_FALSE(ThrowInitializedThis(cx));
          GOTO_ERROR();
        }
        END_OP(CheckThisReinit);
      }

      CASE(Generator) {
        JSObject* generator;
        {
          PUSH_EXIT_FRAME();
          generator = CreateGeneratorFromFrame(cx, frame);
          if (!generator) {
            GOTO_ERROR();
          }
        }
        VIRTPUSH(StackVal(ObjectValue(*generator)));
        END_OP(Generator);
      }

      CASE(InitialYield) {
        // gen => rval, gen, resumeKind
        ReservedRooted<JSObject*> obj0(&state.obj0,
                                       &VIRTSP(0).asValue().toObject());
        uint32_t frameSize = ctx.stack.frameSize(sp, frame);
        {
          PUSH_EXIT_FRAME();
          if (!NormalSuspend(cx, obj0, frame, frameSize, pc)) {
            GOTO_ERROR();
          }
        }
        frame->setReturnValue(VIRTSP(0).asValue());
        goto do_return;
      }

      CASE(Await)
      CASE(Yield) {
        // rval1, gen => rval2, gen, resumeKind
        ReservedRooted<JSObject*> obj0(&state.obj0,
                                       &VIRTPOP().asValue().toObject());
        uint32_t frameSize = ctx.stack.frameSize(sp, frame);
        {
          PUSH_EXIT_FRAME();
          if (!NormalSuspend(cx, obj0, frame, frameSize, pc)) {
            GOTO_ERROR();
          }
        }
        frame->setReturnValue(VIRTSP(0).asValue());
        goto do_return;
      }

      CASE(FinalYieldRval) {
        // gen =>
        ReservedRooted<JSObject*> obj0(&state.obj0,
                                       &VIRTPOP().asValue().toObject());
        {
          PUSH_EXIT_FRAME();
          if (!FinalSuspend(cx, obj0, pc)) {
            GOTO_ERROR();
          }
        }
        goto do_return;
      }

      CASE(IsGenClosing) {
        bool result = VIRTSP(0).asValue() == MagicValue(JS_GENERATOR_CLOSING);
        VIRTPUSH(StackVal(BooleanValue(result)));
        END_OP(IsGenClosing);
      }

      CASE(AsyncAwait) {
        // value, gen => promise
        JSObject* promise;
        {
          ReservedRooted<JSObject*> obj0(
              &state.obj0,
              &VIRTPOP().asValue().toObject());  // gen
          ReservedRooted<Value> value0(&state.value0,
                                       VIRTPOP().asValue());  // value
          PUSH_EXIT_FRAME();
          promise = AsyncFunctionAwait(
              cx, obj0.as<AsyncFunctionGeneratorObject>(), value0);
          if (!promise) {
            GOTO_ERROR();
          }
        }
        VIRTPUSH(StackVal(ObjectValue(*promise)));
        END_OP(AsyncAwait);
      }

      CASE(AsyncResolve) {
        // value, gen => promise
        JSObject* promise;
        {
          ReservedRooted<JSObject*> obj0(
              &state.obj0,
              &VIRTPOP().asValue().toObject());  // gen
          ReservedRooted<Value> value0(&state.value0,
                                       VIRTPOP().asValue());  // value
          PUSH_EXIT_FRAME();
          promise = AsyncFunctionResolve(
              cx, obj0.as<AsyncFunctionGeneratorObject>(), value0);
          if (!promise) {
            GOTO_ERROR();
          }
        }
        VIRTPUSH(StackVal(ObjectValue(*promise)));
        END_OP(AsyncResolve);
      }

      CASE(AsyncReject) {
        // reason, gen => promise
        JSObject* promise;
        {
          ReservedRooted<JSObject*> obj0(
              &state.obj0,
              &VIRTPOP().asValue().toObject());  // gen
          ReservedRooted<Value> value0(&state.value0,
                                       VIRTPOP().asValue());  // stack
          ReservedRooted<Value> value1(&state.value1,
                                       VIRTPOP().asValue());  // reason
          PUSH_EXIT_FRAME();
          promise = AsyncFunctionReject(
              cx, obj0.as<AsyncFunctionGeneratorObject>(), value1, value0);
          if (!promise) {
            GOTO_ERROR();
          }
        }
        VIRTPUSH(StackVal(ObjectValue(*promise)));
        END_OP(AsyncReject);
      }

      CASE(CanSkipAwait) {
        // value => value, can_skip
        bool result = false;
        {
          ReservedRooted<Value> value0(&state.value0, VIRTSP(0).asValue());
          PUSH_EXIT_FRAME();
          if (!CanSkipAwait(cx, value0, &result)) {
            GOTO_ERROR();
          }
        }
        VIRTPUSH(StackVal(BooleanValue(result)));
        END_OP(CanSkipAwait);
      }

      CASE(MaybeExtractAwaitValue) {
        // value, can_skip => value_or_resolved, can_skip
        {
          Value can_skip = VIRTPOP().asValue();
          ReservedRooted<Value> value0(&state.value0,
                                       VIRTPOP().asValue());  // value
          if (can_skip.toBoolean()) {
            PUSH_EXIT_FRAME();
            if (!ExtractAwaitValue(cx, value0, &value0)) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(value0));
          VIRTPUSH(StackVal(can_skip));
        }
        END_OP(MaybeExtractAwaitValue);
      }

      CASE(ResumeKind) {
        GeneratorResumeKind resumeKind = ResumeKindFromPC(pc);
        VIRTPUSH(StackVal(Int32Value(int32_t(resumeKind))));
        END_OP(ResumeKind);
      }

      CASE(CheckResumeKind) {
        // rval, gen, resumeKind => rval
        {
          GeneratorResumeKind resumeKind =
              IntToResumeKind(VIRTPOP().asValue().toInt32());
          ReservedRooted<JSObject*> obj0(
              &state.obj0,
              &VIRTPOP().asValue().toObject());  // gen
          ReservedRooted<Value> value0(&state.value0,
                                       VIRTSP(0).asValue());  // rval
          if (resumeKind != GeneratorResumeKind::Next) {
            PUSH_EXIT_FRAME();
            MOZ_ALWAYS_FALSE(GeneratorThrowOrReturn(
                cx, frame, obj0.as<AbstractGeneratorObject>(), value0,
                resumeKind));
            GOTO_ERROR();
          }
        }
        END_OP(CheckResumeKind);
      }

      CASE(Resume) {
        SYNCSP();
        Value gen = VIRTSP(2).asValue();
        Value* callerSP = reinterpret_cast<Value*>(sp);
        {
          ReservedRooted<Value> value0(&state.value0);
          ReservedRooted<JSObject*> obj0(&state.obj0, &gen.toObject());
          {
            PUSH_EXIT_FRAME();
            TRACE_PRINTF("Going to C++ interp for Resume\n");
            if (!InterpretResume(cx, obj0, callerSP, &value0)) {
              GOTO_ERROR();
            }
          }
          VIRTPOPN(2);
          VIRTSPWRITE(0, StackVal(value0));
        }
        END_OP(Resume);
      }

      CASE(JumpTarget) {
        int32_t icIndex = GET_INT32(pc);
        icEntry = icEntries + icIndex;
        COUNT_COVERAGE_PC(pc);
        END_OP(JumpTarget);
      }
      CASE(LoopHead) {
        int32_t icIndex = GET_INT32(pc);
        icEntry = icEntries + icIndex;
#ifdef ENABLE_INTERRUPT_CHECKS
        if (ctx.frameMgr.cxForLocalUseOnly()->hasAnyPendingInterrupt()) {
          PUSH_EXIT_FRAME();
          if (!InterruptCheck(cx)) {
            GOTO_ERROR();
          }
        }
#endif
        COUNT_COVERAGE_PC(pc);
        END_OP(LoopHead);
      }
      CASE(AfterYield) {
        int32_t icIndex = GET_INT32(pc);
        icEntry = icEntries + icIndex;
        if (frame->script()->isDebuggee()) {
          TRACE_PRINTF("doing DebugAfterYield\n");
          PUSH_EXIT_FRAME();
          ReservedRooted<JSScript*> script0(&state.script0, frame->script());
          if (DebugAPI::hasAnyBreakpointsOrStepMode(script0) &&
              !HandleDebugTrap(cx, frame, pc)) {
            TRACE_PRINTF("HandleDebugTrap returned error\n");
            GOTO_ERROR();
          }
          if (!DebugAfterYield(cx, frame)) {
            TRACE_PRINTF("DebugAfterYield returned error\n");
            GOTO_ERROR();
          }
        }
        COUNT_COVERAGE_PC(pc);
        END_OP(AfterYield);
      }

      CASE(Goto) {
        ADVANCE(GET_JUMP_OFFSET(pc));
        PREDICT_NEXT(JumpTarget);
        PREDICT_NEXT(LoopHead);
        DISPATCH();
      }

      CASE(Coalesce) {
        if (!VIRTSP(0).asValue().isNullOrUndefined()) {
          ADVANCE(GET_JUMP_OFFSET(pc));
          DISPATCH();
        } else {
          END_OP(Coalesce);
        }
      }

      CASE(Case) {
        bool cond = VIRTPOP().asValue().toBoolean();
        if (cond) {
          VIRTPOP();
          ADVANCE(GET_JUMP_OFFSET(pc));
          DISPATCH();
        } else {
          END_OP(Case);
        }
      }

      CASE(Default) {
        VIRTPOP();
        ADVANCE(GET_JUMP_OFFSET(pc));
        DISPATCH();
      }

      CASE(TableSwitch) {
        int32_t len = GET_JUMP_OFFSET(pc);
        int32_t low = GET_JUMP_OFFSET(pc + 1 * JUMP_OFFSET_LEN);
        int32_t high = GET_JUMP_OFFSET(pc + 2 * JUMP_OFFSET_LEN);
        Value v = VIRTPOP().asValue();
        int32_t i = 0;
        if (v.isInt32()) {
          i = v.toInt32();
        } else if (!v.isDouble() ||
                   !mozilla::NumberEqualsInt32(v.toDouble(), &i)) {
          ADVANCE(len);
          DISPATCH();
        }

        if (i >= low && i <= high) {
          uint32_t idx = uint32_t(i) - uint32_t(low);
          uint32_t firstResumeIndex = GET_RESUMEINDEX(pc + 3 * JUMP_OFFSET_LEN);
          pc = entryPC + resumeOffsets[firstResumeIndex + idx];
          DISPATCH();
        }
        ADVANCE(len);
        DISPATCH();
      }

      CASE(Return) {
        frame->setReturnValue(VIRTPOP().asValue());
        goto do_return;
      }

      CASE(GetRval) {
        VIRTPUSH(StackVal(frame->returnValue()));
        END_OP(GetRval);
      }

      CASE(SetRval) {
        frame->setReturnValue(VIRTPOP().asValue());
        END_OP(SetRval);
      }

    do_return:
      CASE(RetRval) {
        SYNCSP();
        bool ok = true;
        if (frame->isDebuggee() && !from_unwind) {
          TRACE_PRINTF("doing DebugEpilogueOnBaselineReturn\n");
          PUSH_EXIT_FRAME();
          ok = DebugEpilogueOnBaselineReturn(cx, frame, pc);
        }
        from_unwind = false;

        uint32_t argc = frame->numActualArgs();
        sp = ctx.stack.popFrame();

        // If FP is higher than the entry frame now, return; otherwise,
        // do an inline state update.
        if (stack.fp > entryFrame) {
          *ret = frame->returnValue();
          TRACE_PRINTF("ret = %" PRIx64 "\n", ret->asRawBits());
          return ok ? PBIResult::Ok : PBIResult::Error;
        } else {
          TRACE_PRINTF("Return fastpath\n");
          Value ret = frame->returnValue();
          TRACE_PRINTF("ret = %" PRIx64 "\n", ret.asRawBits());

          // Pop exit frame as well.
          sp = ctx.stack.popFrame();
          // Pop fake return address and descriptor.
          POPNNATIVE(2);

          // Set PC, frame, and current script.
          frame = reinterpret_cast<BaselineFrame*>(
              reinterpret_cast<uintptr_t>(stack.fp) - BaselineFrame::Size());
          TRACE_PRINTF(" sp -> %p, fp -> %p, frame -> %p\n", sp, ctx.stack.fp,
                       frame);
          ctx.frameMgr.switchToFrame(frame);
          ctx.frame = frame;
          RESET_PC(frame->interpreterPC(), frame->script());

          // Adjust caller's stack to complete the call op that PC still points
          // to in that frame (pop args, push return value).
          JSOp op = JSOp(*pc);
          bool constructing = (op == JSOp::New || op == JSOp::NewContent ||
                               op == JSOp::SuperCall);
          // Fix-up return value; EnterJit would do this if we hadn't bypassed
          // it.
          if (constructing && ret.isPrimitive()) {
            ret = sp[argc + constructing].asValue();
            TRACE_PRINTF("updated ret = %" PRIx64 "\n", ret.asRawBits());
          }
          // Pop args -- this is 1 more than how many are pushed in the
          // `totalArgs` count during the call fastpath because it includes
          // the callee.
          VIRTPOPN(argc + 2 + constructing);
          // Push return value.
          VIRTPUSH(StackVal(ret));

          if (!ok) {
            GOTO_ERROR();
          }

          // Advance past call instruction, and advance past IC.
          NEXT_IC();
          ADVANCE(JSOpLength_Call);

          DISPATCH();
        }
      }

      CASE(CheckReturn) {
        Value thisval = VIRTPOP().asValue();
        // inlined version of frame->checkReturn(thisval, result)
        // (js/src/vm/Stack.cpp).
        HandleValue retVal = frame->returnValue();
        if (retVal.isObject()) {
          VIRTPUSH(StackVal(retVal));
        } else if (!retVal.isUndefined()) {
          PUSH_EXIT_FRAME();
          MOZ_ALWAYS_FALSE(ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN,
                                            JSDVG_IGNORE_STACK, retVal,
                                            nullptr));
          GOTO_ERROR();
        } else if (thisval.isMagic(JS_UNINITIALIZED_LEXICAL)) {
          PUSH_EXIT_FRAME();
          MOZ_ALWAYS_FALSE(ThrowUninitializedThis(cx));
          GOTO_ERROR();
        } else {
          VIRTPUSH(StackVal(thisval));
        }
        END_OP(CheckReturn);
      }

      CASE(Throw) {
        {
          ReservedRooted<Value> value0(&state.value0, VIRTPOP().asValue());
          PUSH_EXIT_FRAME();
          MOZ_ALWAYS_FALSE(ThrowOperation(cx, value0));
          GOTO_ERROR();
        }
        END_OP(Throw);
      }

      CASE(ThrowWithStack) {
        {
          ReservedRooted<Value> value0(&state.value0, VIRTPOP().asValue());
          ReservedRooted<Value> value1(&state.value1, VIRTPOP().asValue());
          PUSH_EXIT_FRAME();
          MOZ_ALWAYS_FALSE(ThrowWithStackOperation(cx, value1, value0));
          GOTO_ERROR();
        }
        END_OP(ThrowWithStack);
      }

      CASE(ThrowMsg) {
        {
          PUSH_EXIT_FRAME();
          MOZ_ALWAYS_FALSE(ThrowMsgOperation(cx, GET_UINT8(pc)));
          GOTO_ERROR();
        }
        END_OP(ThrowMsg);
      }

      CASE(ThrowSetConst) {
        {
          PUSH_EXIT_FRAME();
          ReservedRooted<JSScript*> script0(&state.script0, frame->script());
          ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, script0, pc);
          GOTO_ERROR();
        }
        END_OP(ThrowSetConst);
      }

      CASE(Try)
      CASE(TryDestructuring) {
        static_assert(JSOpLength_Try == JSOpLength_TryDestructuring);
        END_OP(Try);
      }

      CASE(Exception) {
        {
          PUSH_EXIT_FRAME();
          if (!GetAndClearException(cx, &state.res)) {
            GOTO_ERROR();
          }
        }
        VIRTPUSH(StackVal(state.res));
        state.res.setUndefined();
        END_OP(Exception);
      }

      CASE(ExceptionAndStack) {
        {
          ReservedRooted<Value> value0(&state.value0);
          {
            PUSH_EXIT_FRAME();
            if (!cx.getCx()->getPendingExceptionStack(&value0)) {
              GOTO_ERROR();
            }
            if (!GetAndClearException(cx, &state.res)) {
              GOTO_ERROR();
            }
          }
          VIRTPUSH(StackVal(state.res));
          VIRTPUSH(StackVal(value0));
          state.res.setUndefined();
        }
        END_OP(ExceptionAndStack);
      }

      CASE(Finally) {
#ifdef ENABLE_INTERRUPT_CHECKS
        if (ctx.frameMgr.cxForLocalUseOnly()->hasAnyPendingInterrupt()) {
          PUSH_EXIT_FRAME();
          if (!InterruptCheck(cx)) {
            GOTO_ERROR();
          }
        }
#endif
        END_OP(Finally);
      }

      CASE(Uninitialized) {
        VIRTPUSH(StackVal(MagicValue(JS_UNINITIALIZED_LEXICAL)));
        END_OP(Uninitialized);
      }
      CASE(InitLexical) {
        uint32_t i = GET_LOCALNO(pc);
        frame->unaliasedLocal(i) = VIRTSP(0).asValue();
        END_OP(InitLexical);
      }

      CASE(InitAliasedLexical) {
        EnvironmentCoordinate ec = EnvironmentCoordinate(pc);
        EnvironmentObject& obj = getEnvironmentFromCoordinate(frame, ec);
        obj.setAliasedBinding(ec, VIRTSP(0).asValue());
        END_OP(InitAliasedLexical);
      }
      CASE(CheckLexical) {
        if (VIRTSP(0).asValue().isMagic(JS_UNINITIALIZED_LEXICAL)) {
          PUSH_EXIT_FRAME();
          ReservedRooted<JSScript*> script0(&state.script0, frame->script());
          ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, script0,
                                    pc);
          GOTO_ERROR();
        }
        END_OP(CheckLexical);
      }
      CASE(CheckAliasedLexical) {
        if (VIRTSP(0).asValue().isMagic(JS_UNINITIALIZED_LEXICAL)) {
          PUSH_EXIT_FRAME();
          ReservedRooted<JSScript*> script0(&state.script0, frame->script());
          ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, script0,
                                    pc);
          GOTO_ERROR();
        }
        END_OP(CheckAliasedLexical);
      }

      CASE(BindUnqualifiedGName) {
        IC_SET_OBJ_ARG(
            0,
            &ctx.frameMgr.cxForLocalUseOnly()->global()->lexicalEnvironment());
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(BindName, false);
        END_OP(BindUnqualifiedGName);
      }
      CASE(BindName) {
        IC_SET_OBJ_ARG(0, frame->environmentChain());
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(BindName, false);
        END_OP(BindName);
      }
      CASE(BindUnqualifiedName) {
        IC_SET_OBJ_ARG(0, frame->environmentChain());
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(BindName, false);
        END_OP(BindUnqualifiedName);
      }
      CASE(GetGName) {
        IC_SET_OBJ_ARG(
            0,
            &ctx.frameMgr.cxForLocalUseOnly()->global()->lexicalEnvironment());
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(GetName, false);
        END_OP(GetGName);
      }
      CASE(GetName) {
        IC_SET_OBJ_ARG(0, frame->environmentChain());
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(GetName, false);
        END_OP(GetName);
      }

      CASE(GetArg) {
        unsigned i = GET_ARGNO(pc);
        if (argsObjAliasesFormals) {
          VIRTPUSH(StackVal(frame->argsObj().arg(i)));
        } else {
          VIRTPUSH(StackVal(frame->argv()[i]));
        }
        END_OP(GetArg);
      }

      CASE(GetFrameArg) {
        uint32_t i = GET_ARGNO(pc);
        VIRTPUSH(StackVal(frame->argv()[i]));
        END_OP(GetFrameArg);
      }

      CASE(GetLocal) {
        uint32_t i = GET_LOCALNO(pc);
        TRACE_PRINTF(" -> local: %d\n", int(i));
        VIRTPUSH(StackVal(GETLOCAL(i)));
        END_OP(GetLocal);
      }

      CASE(ArgumentsLength) {
        VIRTPUSH(StackVal(Int32Value(frame->numActualArgs())));
        END_OP(ArgumentsLength);
      }

      CASE(GetActualArg) {
        MOZ_ASSERT(!frame->script()->needsArgsObj());
        uint32_t index = VIRTSP(0).asValue().toInt32();
        VIRTSPWRITE(0, StackVal(frame->unaliasedActual(index)));
        END_OP(GetActualArg);
      }

      CASE(GetAliasedVar)
      CASE(GetAliasedDebugVar) {
        static_assert(JSOpLength_GetAliasedVar ==
                      JSOpLength_GetAliasedDebugVar);
        EnvironmentCoordinate ec = EnvironmentCoordinate(pc);
        EnvironmentObject& obj = getEnvironmentFromCoordinate(frame, ec);
        VIRTPUSH(StackVal(obj.aliasedBinding(ec)));
        END_OP(GetAliasedVar);
      }

      CASE(GetImport) {
        IC_ZERO_ARG(0);
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(GetImport, false);
        END_OP(GetImport);
      }

      CASE(GetIntrinsic) {
        IC_ZERO_ARG(0);
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(LazyConstant, false);
        END_OP(GetIntrinsic);
      }

      CASE(Callee) {
        VIRTPUSH(StackVal(frame->calleev()));
        END_OP(Callee);
      }

      CASE(EnvCallee) {
        uint8_t numHops = GET_UINT8(pc);
        JSObject* env = &frame->environmentChain()->as<EnvironmentObject>();
        for (unsigned i = 0; i < numHops; i++) {
          env = &env->as<EnvironmentObject>().enclosingEnvironment();
        }
        VIRTPUSH(StackVal(ObjectValue(env->as<CallObject>().callee())));
        END_OP(EnvCallee);
      }

      CASE(SetProp)
      CASE(StrictSetProp)
      CASE(SetName)
      CASE(StrictSetName)
      CASE(SetGName)
      CASE(StrictSetGName) {
        static_assert(JSOpLength_SetProp == JSOpLength_StrictSetProp);
        static_assert(JSOpLength_SetProp == JSOpLength_SetName);
        static_assert(JSOpLength_SetProp == JSOpLength_StrictSetName);
        static_assert(JSOpLength_SetProp == JSOpLength_SetGName);
        static_assert(JSOpLength_SetProp == JSOpLength_StrictSetGName);
        IC_POP_ARG(1);
        IC_POP_ARG(0);
        IC_ZERO_ARG(2);
        VIRTPUSH(StackVal(ic_arg1));
        INVOKE_IC(SetProp, false);
        END_OP(SetProp);
      }

      CASE(InitProp)
      CASE(InitHiddenProp)
      CASE(InitLockedProp) {
        static_assert(JSOpLength_InitProp == JSOpLength_InitHiddenProp);
        static_assert(JSOpLength_InitProp == JSOpLength_InitLockedProp);
        IC_POP_ARG(1);
        IC_SET_ARG_FROM_STACK(0, 0);
        IC_ZERO_ARG(2);
        INVOKE_IC(SetProp, false);
        END_OP(InitProp);
      }
      CASE(InitGLexical) {
        IC_SET_ARG_FROM_STACK(1, 0);
        IC_SET_OBJ_ARG(
            0,
            &ctx.frameMgr.cxForLocalUseOnly()->global()->lexicalEnvironment());
        IC_ZERO_ARG(2);
        INVOKE_IC(SetProp, false);
        END_OP(InitGLexical);
      }

      CASE(SetArg) {
        unsigned i = GET_ARGNO(pc);
        if (argsObjAliasesFormals) {
          frame->argsObj().setArg(i, VIRTSP(0).asValue());
        } else {
          frame->argv()[i] = VIRTSP(0).asValue();
        }
        END_OP(SetArg);
      }

      CASE(SetLocal) {
        uint32_t i = GET_LOCALNO(pc);
        TRACE_PRINTF(" -> local: %d\n", int(i));
        SETLOCAL(i, VIRTSP(0).asValue());
        END_OP(SetLocal);
      }

      CASE(SetAliasedVar) {
        EnvironmentCoordinate ec = EnvironmentCoordinate(pc);
        EnvironmentObject& obj = getEnvironmentFromCoordinate(frame, ec);
        MOZ_ASSERT(!IsUninitializedLexical(obj.aliasedBinding(ec)));
        obj.setAliasedBinding(ec, VIRTSP(0).asValue());
        END_OP(SetAliasedVar);
      }

      CASE(SetIntrinsic) {
        {
          ReservedRooted<Value> value0(&state.value0, VIRTSP(0).asValue());
          ReservedRooted<JSScript*> script0(&state.script0, frame->script());
          {
            PUSH_EXIT_FRAME();
            if (!SetIntrinsicOperation(cx, script0, pc, value0)) {
              GOTO_ERROR();
            }
          }
        }
        END_OP(SetIntrinsic);
      }

      CASE(PushLexicalEnv) {
        {
          ReservedRooted<Scope*> scope0(&state.scope0,
                                        frame->script()->getScope(pc));
          {
            PUSH_EXIT_FRAME();
            if (!frame->pushLexicalEnvironment(cx, scope0.as<LexicalScope>())) {
              GOTO_ERROR();
            }
          }
        }
        END_OP(PushLexicalEnv);
      }
      CASE(PopLexicalEnv) {
        if (frame->isDebuggee()) {
          TRACE_PRINTF("doing DebugLeaveThenPopLexicalEnv\n");
          PUSH_EXIT_FRAME();
          if (!DebugLeaveThenPopLexicalEnv(cx, frame, pc)) {
            GOTO_ERROR();
          }
        } else {
          frame->popOffEnvironmentChain<LexicalEnvironmentObject>();
        }
        END_OP(PopLexicalEnv);
      }
      CASE(DebugLeaveLexicalEnv) {
        if (frame->isDebuggee()) {
          TRACE_PRINTF("doing DebugLeaveLexicalEnv\n");
          PUSH_EXIT_FRAME();
          if (!DebugLeaveLexicalEnv(cx, frame, pc)) {
            GOTO_ERROR();
          }
        }
        END_OP(DebugLeaveLexicalEnv);
      }

      CASE(RecreateLexicalEnv) {
        {
          PUSH_EXIT_FRAME();
          if (frame->isDebuggee()) {
            TRACE_PRINTF("doing DebuggeeRecreateLexicalEnv\n");
            if (!DebuggeeRecreateLexicalEnv(cx, frame, pc)) {
              GOTO_ERROR();
            }
          } else {
            if (!frame->recreateLexicalEnvironment<false>(cx)) {
              GOTO_ERROR();
            }
          }
        }
        END_OP(RecreateLexicalEnv);
      }

      CASE(FreshenLexicalEnv) {
        {
          PUSH_EXIT_FRAME();
          if (frame->isDebuggee()) {
            TRACE_PRINTF("doing DebuggeeFreshenLexicalEnv\n");
            if (!DebuggeeFreshenLexicalEnv(cx, frame, pc)) {
              GOTO_ERROR();
            }
          } else {
            if (!frame->freshenLexicalEnvironment<false>(cx)) {
              GOTO_ERROR();
            }
          }
        }
        END_OP(FreshenLexicalEnv);
      }
      CASE(PushClassBodyEnv) {
        {
          ReservedRooted<Scope*> scope0(&state.scope0,
                                        frame->script()->getScope(pc));
          PUSH_EXIT_FRAME();
          if (!frame->pushClassBodyEnvironment(cx,
                                               scope0.as<ClassBodyScope>())) {
            GOTO_ERROR();
          }
        }
        END_OP(PushClassBodyEnv);
      }
      CASE(PushVarEnv) {
        {
          ReservedRooted<Scope*> scope0(&state.scope0,
                                        frame->script()->getScope(pc));
          PUSH_EXIT_FRAME();
          if (!frame->pushVarEnvironment(cx, scope0)) {
            GOTO_ERROR();
          }
        }
        END_OP(PushVarEnv);
      }
      CASE(EnterWith) {
        {
          ReservedRooted<Scope*> scope0(&state.scope0,
                                        frame->script()->getScope(pc));
          ReservedRooted<Value> value0(&state.value0, VIRTPOP().asValue());
          PUSH_EXIT_FRAME();
          if (!EnterWithOperation(cx, frame, value0, scope0.as<WithScope>())) {
            GOTO_ERROR();
          }
        }
        END_OP(EnterWith);
      }
      CASE(LeaveWith) {
        frame->popOffEnvironmentChain<WithEnvironmentObject>();
        END_OP(LeaveWith);
      }
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
      CASE(AddDisposable) {
        {
          ReservedRooted<JSObject*> env(&state.obj0, frame->environmentChain());

          ReservedRooted<JS::Value> needsClosure(&state.value0,
                                                 VIRTPOP().asValue());
          ReservedRooted<JS::Value> method(&state.value1, VIRTPOP().asValue());
          ReservedRooted<JS::Value> val(&state.value2, VIRTPOP().asValue());
          UsingHint hint = UsingHint(GET_UINT8(pc));
          PUSH_EXIT_FRAME();
          if (!AddDisposableResourceToCapability(
                  cx, env, val, method, needsClosure.toBoolean(), hint)) {
            GOTO_ERROR();
          }
        }
        END_OP(AddDisposable);
      }

      CASE(TakeDisposeCapability) {
        {
          ReservedRooted<JSObject*> env(&state.obj0, frame->environmentChain());
          JS::Value maybeDisposables =
              env->as<DisposableEnvironmentObject>().getDisposables();

          MOZ_ASSERT(maybeDisposables.isObject() ||
                     maybeDisposables.isUndefined());

          if (maybeDisposables.isUndefined()) {
            VIRTPUSH(StackVal(UndefinedValue()));
          } else {
            VIRTPUSH(StackVal(maybeDisposables));
            env->as<DisposableEnvironmentObject>().clearDisposables();
          }
        }
        END_OP(TakeDisposeCapability);
      }

      CASE(CreateSuppressedError) {
        ErrorObject* errorObj;
        {
          ReservedRooted<JS::Value> error(&state.value0, VIRTPOP().asValue());
          ReservedRooted<JS::Value> suppressed(&state.value1,
                                               VIRTPOP().asValue());
          PUSH_EXIT_FRAME();
          errorObj = CreateSuppressedError(cx, error, suppressed);
          if (!errorObj) {
            GOTO_ERROR();
          }
        }
        VIRTPUSH(StackVal(ObjectValue(*errorObj)));
        END_OP(CreateSuppressedError);
      }
#endif
      CASE(BindVar) {
        JSObject* varObj;
        {
          ReservedRooted<JSObject*> obj0(&state.obj0,
                                         frame->environmentChain());
          PUSH_EXIT_FRAME();
          varObj = BindVarOperation(cx, obj0);
        }
        VIRTPUSH(StackVal(ObjectValue(*varObj)));
        END_OP(BindVar);
      }

      CASE(GlobalOrEvalDeclInstantiation) {
        GCThingIndex lastFun = GET_GCTHING_INDEX(pc);
        {
          ReservedRooted<JSObject*> obj0(&state.obj0,
                                         frame->environmentChain());
          ReservedRooted<JSScript*> script0(&state.script0, frame->script());
          PUSH_EXIT_FRAME();
          if (!GlobalOrEvalDeclInstantiation(cx, obj0, script0, lastFun)) {
            GOTO_ERROR();
          }
        }
        END_OP(GlobalOrEvalDeclInstantiation);
      }

      CASE(DelName) {
        {
          ReservedRooted<PropertyName*> name0(&state.name0,
                                              frame->script()->getName(pc));
          ReservedRooted<JSObject*> obj0(&state.obj0,
                                         frame->environmentChain());
          PUSH_EXIT_FRAME();
          if (!DeleteNameOperation(cx, name0, obj0, &state.res)) {
            GOTO_ERROR();
          }
        }
        VIRTPUSH(StackVal(state.res));
        state.res.setUndefined();
        END_OP(DelName);
      }

      CASE(Arguments) {
        {
          PUSH_EXIT_FRAME();
          if (!NewArgumentsObject(cx, frame, &state.res)) {
            GOTO_ERROR();
          }
        }
        VIRTPUSH(StackVal(state.res));
        state.res.setUndefined();
        END_OP(Arguments);
      }

      CASE(Rest) {
        IC_ZERO_ARG(0);
        IC_ZERO_ARG(1);
        IC_ZERO_ARG(2);
        INVOKE_IC_AND_PUSH(Rest, false);
        END_OP(Rest);
      }

      CASE(FunctionThis) {
        {
          PUSH_EXIT_FRAME();
          if (!js::GetFunctionThis(cx, frame, &state.res)) {
            GOTO_ERROR();
          }
        }
        VIRTPUSH(StackVal(state.res));
        state.res.setUndefined();
        END_OP(FunctionThis);
      }

      CASE(Pop) {
        VIRTPOP();
        END_OP(Pop);
      }
      CASE(PopN) {
        SYNCSP();
        uint32_t n = GET_UINT16(pc);
        VIRTPOPN(n);
        END_OP(PopN);
      }
      CASE(Dup) {
        StackVal value = VIRTSP(0);
        VIRTPUSH(value);
        END_OP(Dup);
      }
      CASE(Dup2) {
        StackVal value1 = VIRTSP(0);
        StackVal value2 = VIRTSP(1);
        VIRTPUSH(value2);
        VIRTPUSH(value1);
        END_OP(Dup2);
      }
      CASE(DupAt) {
        unsigned i = GET_UINT24(pc);
        StackVal value = VIRTSP(i);
        VIRTPUSH(value);
        END_OP(DupAt);
      }
      CASE(Swap) {
        StackVal v0 = VIRTSP(0);
        StackVal v1 = VIRTSP(1);
        VIRTSPWRITE(0, v1);
        VIRTSPWRITE(1, v0);
        END_OP(Swap);
      }
      CASE(Pick) {
        unsigned i = GET_UINT8(pc);
        SYNCSP();
        StackVal tmp = sp[i];
        memmove(&sp[1], &sp[0], sizeof(StackVal) * i);
        VIRTSPWRITE(0, tmp);
        END_OP(Pick);
      }
      CASE(Unpick) {
        unsigned i = GET_UINT8(pc);
        StackVal tmp = VIRTSP(0);
        SYNCSP();
        memmove(&sp[0], &sp[1], sizeof(StackVal) * i);
        sp[i] = tmp;
        END_OP(Unpick);
      }
      CASE(DebugCheckSelfHosted) {
        HandleValue val = SPHANDLE(0);
        {
          PUSH_EXIT_FRAME();
          if (!Debug_CheckSelfHosted(cx, val)) {
            GOTO_ERROR();
          }
        }
        END_OP(DebugCheckSelfHosted);
      }
      CASE(Lineno) { END_OP(Lineno); }
      CASE(NopDestructuring) { END_OP(NopDestructuring); }
      CASE(ForceInterpreter) { END_OP(ForceInterpreter); }
      CASE(Debugger) {
        {
          PUSH_EXIT_FRAME();
          if (!OnDebuggerStatement(cx, frame)) {
            GOTO_ERROR();
          }
        }
        END_OP(Debugger);
      }

    label_default:
#ifndef ENABLE_COMPUTED_GOTO_DISPATCH
    default:
#endif
      MOZ_CRASH("Bad opcode");
    }
  }

restart:
  // This is a `goto` target so that we exit any on-stack exit frames
  // before restarting, to match previous behavior.
  return PortableBaselineInterpret<true, HybridICs>(
      ctx.frameMgr.cxForLocalUseOnly(), ctx.state, ctx.stack, sp, envChain, ret,
      pc, isd, entryPC, frame, entryFrame, restartCode);

error:
  TRACE_PRINTF("HandleException: frame %p\n", frame);
  {
    ResumeFromException rfe;
    {
      PUSH_EXIT_FRAME();
      HandleException(&rfe);
    }

    switch (rfe.kind) {
      case ExceptionResumeKind::EntryFrame:
        TRACE_PRINTF(" -> Return from entry frame\n");
        frame->setReturnValue(MagicValue(JS_ION_ERROR));
        ctx.stack.fp = reinterpret_cast<StackVal*>(rfe.framePointer);
        ctx.stack.unwindingSP = reinterpret_cast<StackVal*>(rfe.stackPointer);
        ctx.stack.unwindingFP = reinterpret_cast<StackVal*>(rfe.framePointer);
        goto unwind_error;
      case ExceptionResumeKind::Catch:
        RESET_PC(frame->interpreterPC(), frame->script());
        ctx.stack.fp = reinterpret_cast<StackVal*>(rfe.framePointer);
        ctx.stack.unwindingSP = reinterpret_cast<StackVal*>(rfe.stackPointer);
        ctx.stack.unwindingFP = reinterpret_cast<StackVal*>(rfe.framePointer);
        TRACE_PRINTF(" -> catch to pc %p\n", pc);
        goto unwind;
      case ExceptionResumeKind::Finally:
        RESET_PC(frame->interpreterPC(), frame->script());
        ctx.stack.fp = reinterpret_cast<StackVal*>(rfe.framePointer);
        sp = reinterpret_cast<StackVal*>(rfe.stackPointer);
        TRACE_PRINTF(" -> finally to pc %p\n", pc);
        VIRTPUSH(StackVal(rfe.exception));
        VIRTPUSH(StackVal(rfe.exceptionStack));
        VIRTPUSH(StackVal(BooleanValue(true)));
        ctx.stack.unwindingSP = sp;
        ctx.stack.unwindingFP = ctx.stack.fp;
        goto unwind;
      case ExceptionResumeKind::ForcedReturnBaseline:
        RESET_PC(frame->interpreterPC(), frame->script());
        ctx.stack.fp = reinterpret_cast<StackVal*>(rfe.framePointer);
        ctx.stack.unwindingSP = reinterpret_cast<StackVal*>(rfe.stackPointer);
        ctx.stack.unwindingFP = reinterpret_cast<StackVal*>(rfe.framePointer);
        TRACE_PRINTF(" -> forced return\n");
        goto unwind_ret;
      case ExceptionResumeKind::ForcedReturnIon:
        MOZ_CRASH(
            "Unexpected ForcedReturnIon exception-resume kind in Portable "
            "Baseline");
      case ExceptionResumeKind::Bailout:
        MOZ_CRASH(
            "Unexpected Bailout exception-resume kind in Portable Baseline");
      case ExceptionResumeKind::WasmInterpEntry:
        MOZ_CRASH(
            "Unexpected WasmInterpEntry exception-resume kind in Portable "
            "Baseline");
      case ExceptionResumeKind::WasmCatch:
        MOZ_CRASH(
            "Unexpected WasmCatch exception-resume kind in Portable "
            "Baseline");
    }
  }

  DISPATCH();

ic_fail:
  RESTART(ic_result);
  switch (ic_result) {
    case PBIResult::Ok:
      MOZ_CRASH("Unreachable: ic_result must be an error if we reach ic_fail");
    case PBIResult::Error:
      goto error;
    case PBIResult::Unwind:
      goto unwind;
    case PBIResult::UnwindError:
      goto unwind_error;
    case PBIResult::UnwindRet:
      goto unwind_ret;
  }

unwind:
  TRACE_PRINTF("unwind: fp = %p entryFrame = %p\n", ctx.stack.fp, entryFrame);
  if (reinterpret_cast<uintptr_t>(ctx.stack.unwindingFP) >
      reinterpret_cast<uintptr_t>(entryFrame) + BaselineFrame::Size()) {
    TRACE_PRINTF(" -> returning\n");
    return PBIResult::Unwind;
  }
  sp = ctx.stack.unwindingSP;
  ctx.stack.fp = ctx.stack.unwindingFP;
  frame = reinterpret_cast<BaselineFrame*>(
      reinterpret_cast<uintptr_t>(ctx.stack.fp) - BaselineFrame::Size());
  TRACE_PRINTF(" -> setting sp to %p, frame to %p\n", sp, frame);
  ctx.frameMgr.switchToFrame(frame);
  ctx.frame = frame;
  RESET_PC(frame->interpreterPC(), frame->script());
  DISPATCH();
unwind_error:
  TRACE_PRINTF("unwind_error: fp = %p entryFrame = %p\n", ctx.stack.fp,
               entryFrame);
  if (reinterpret_cast<uintptr_t>(ctx.stack.unwindingFP) >
      reinterpret_cast<uintptr_t>(entryFrame) + BaselineFrame::Size()) {
    return PBIResult::UnwindError;
  }
  if (reinterpret_cast<uintptr_t>(ctx.stack.unwindingFP) ==
      reinterpret_cast<uintptr_t>(entryFrame) + BaselineFrame::Size()) {
    return PBIResult::Error;
  }
  sp = ctx.stack.unwindingSP;
  ctx.stack.fp = ctx.stack.unwindingFP;
  frame = reinterpret_cast<BaselineFrame*>(
      reinterpret_cast<uintptr_t>(ctx.stack.fp) - BaselineFrame::Size());
  TRACE_PRINTF(" -> setting sp to %p, frame to %p\n", sp, frame);
  ctx.frameMgr.switchToFrame(frame);
  ctx.frame = frame;
  RESET_PC(frame->interpreterPC(), frame->script());
  goto error;
unwind_ret:
  TRACE_PRINTF("unwind_ret: fp = %p entryFrame = %p\n", ctx.stack.fp,
               entryFrame);
  if (reinterpret_cast<uintptr_t>(ctx.stack.unwindingFP) >
      reinterpret_cast<uintptr_t>(entryFrame) + BaselineFrame::Size()) {
    return PBIResult::UnwindRet;
  }
  if (reinterpret_cast<uintptr_t>(ctx.stack.unwindingFP) ==
      reinterpret_cast<uintptr_t>(entryFrame) + BaselineFrame::Size()) {
    *ret = frame->returnValue();
    return PBIResult::Ok;
  }
  sp = ctx.stack.unwindingSP;
  ctx.stack.fp = ctx.stack.unwindingFP;
  frame = reinterpret_cast<BaselineFrame*>(
      reinterpret_cast<uintptr_t>(ctx.stack.fp) - BaselineFrame::Size());
  TRACE_PRINTF(" -> setting sp to %p, frame to %p\n", sp, frame);
  ctx.frameMgr.switchToFrame(frame);
  ctx.frame = frame;
  RESET_PC(frame->interpreterPC(), frame->script());
  from_unwind = true;
  goto do_return;

#ifndef __wasi__
debug:;
  {
    TRACE_PRINTF("hit debug point\n");
    PUSH_EXIT_FRAME();
    if (!HandleDebugTrap(cx, frame, pc)) {
      TRACE_PRINTF("HandleDebugTrap returned error\n");
      goto error;
    }
    RESET_PC(frame->interpreterPC(), frame->script());
    TRACE_PRINTF("HandleDebugTrap done\n");
  }
  goto dispatch;
#endif
}