text: // Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
  // for details. All rights reserved. Use of this source code is governed by a
  // BSD-style license that can be found in the LICENSE file.
  
  #include "vm/globals.h"
  
  // For `AllocateObjectInstr::WillAllocateNewOrRemembered`
  // For `GenericCheckBoundInstr::UseUnboxedRepresentation`
  #include "vm/compiler/backend/il.h"
  
  #define SHOULD_NOT_INCLUDE_RUNTIME
  
  #include "vm/compiler/stub_code_compiler.h"
  
  #if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64)
  
  #include "vm/class_id.h"
  #include "vm/code_entry_kind.h"
  #include "vm/compiler/api/type_check_mode.h"
  #include "vm/compiler/assembler/assembler.h"
  #include "vm/compiler/backend/locations.h"
  #include "vm/constants.h"
  #include "vm/instructions.h"
  #include "vm/static_type_exactness_state.h"
  #include "vm/tags.h"
  
  #define __ assembler->
  
  namespace dart {
  namespace compiler {
  
  // Ensures that [A0] is a new object, if not it will be added to the remembered
  // set via a leaf runtime call.
  //
  // WARNING: This might clobber all registers except for [A0], [THR] and [FP].
  // The caller should simply call LeaveStubFrame() and return.
  void StubCodeCompiler::EnsureIsNewOrRemembered(Assembler* assembler,
                                                 bool preserve_registers) {
    // If the object is not remembered we call a leaf-runtime to add it to the
    // remembered set.
    Label done;
    __ andi(TMP2, A0, 1 << target::ObjectAlignment::kNewObjectBitPosition);
    __ bnez(TMP2, &done);
  
    {
      Assembler::CallRuntimeScope scope(
          assembler, kEnsureRememberedAndMarkingDeferredRuntimeEntry,
          /*frame_size=*/0, /*preserve_registers=*/preserve_registers);
      __ mv(A1, THR);
      scope.Call(/*argument_count=*/2);
    }
  
    __ Bind(&done);
  }
  
  // Input parameters:
  //   RA : return address.
  //   SP : address of last argument in argument array.
  //   SP + 8*T4 - 8 : address of first argument in argument array.
  //   SP + 8*T4 : address of return value.
  //   T5 : address of the runtime function to call.
  //   T4 : number of arguments to the call.
  void StubCodeCompiler::GenerateCallToRuntimeStub(Assembler* assembler) {
    const intptr_t thread_offset = target::NativeArguments::thread_offset();
    const intptr_t argc_tag_offset = target::NativeArguments::argc_tag_offset();
    const intptr_t argv_offset = target::NativeArguments::argv_offset();
    const intptr_t retval_offset = target::NativeArguments::retval_offset();
  
    __ Comment("CallToRuntimeStub");
    __ lx(CODE_REG, Address(THR, target::Thread::call_to_runtime_stub_offset()));
    __ SetPrologueOffset();
    __ EnterStubFrame();
  
    // Save exit frame information to enable stack walking as we are about
    // to transition to Dart VM C++ code.
    __ StoreToOffset(FP, THR, target::Thread::top_exit_frame_info_offset());
  
    // Mark that the thread exited generated code through a runtime call.
    __ LoadImmediate(TMP, target::Thread::exit_through_runtime_call());
    __ StoreToOffset(TMP, THR, target::Thread::exit_through_ffi_offset());
  
  #if defined(DEBUG)
    {
      Label ok;
      // Check that we are always entering from Dart code.
      __ LoadFromOffset(TMP, THR, target::Thread::vm_tag_offset());
      __ CompareImmediate(TMP, VMTag::kDartTagId);
      __ BranchIf(EQ, &ok);
      __ Stop("Not coming from Dart code.");
      __ Bind(&ok);
    }
  #endif
  
    // Mark that the thread is executing VM code.
    __ StoreToOffset(T5, THR, target::Thread::vm_tag_offset());
  
    // Reserve space for arguments and align frame before entering C++ world.
    // target::NativeArguments are passed in registers.
    __ Comment("align stack");
    // Reserve space for arguments.
    ASSERT(target::NativeArguments::StructSize() == 4 * target::kWordSize);
    __ ReserveAlignedFrameSpace(target::NativeArguments::StructSize());
  
    // Pass target::NativeArguments structure by value and call runtime.
    // Registers R0, R1, R2, and R3 are used.
  
    ASSERT(thread_offset == 0 * target::kWordSize);
    // There are no runtime calls to closures, so we do not need to set the tag
    // bits kClosureFunctionBit and kInstanceFunctionBit in argc_tag_.
    ASSERT(argc_tag_offset == 1 * target::kWordSize);
    ASSERT(argv_offset == 2 * target::kWordSize);
    __ slli(T2, T4, target::kWordSizeLog2);
    __ add(T2, FP, T2);  // Compute argv.
    // Set argv in target::NativeArguments.
    __ AddImmediate(T2,
                    target::frame_layout.param_end_from_fp * target::kWordSize);
  
    ASSERT(retval_offset == 3 * target::kWordSize);
    __ AddImmediate(T3, T2, target::kWordSize);
  
    __ StoreToOffset(THR, SP, thread_offset);
    __ StoreToOffset(T4, SP, argc_tag_offset);
    __ StoreToOffset(T2, SP, argv_offset);
    __ StoreToOffset(T3, SP, retval_offset);
    __ mv(A0, SP);  // Pass the pointer to the target::NativeArguments.
  
    ASSERT(IsAbiPreservedRegister(THR));
    __ jalr(T5);
    __ Comment("CallToRuntimeStub return");
  
    // Refresh pinned registers values (inc. write barrier mask and null object).
    __ RestorePinnedRegisters();
  
    // Retval is next to 1st argument.
    // Mark that the thread is executing Dart code.
    __ LoadImmediate(TMP, VMTag::kDartTagId);
    __ StoreToOffset(TMP, THR, target::Thread::vm_tag_offset());
  
    // Mark that the thread has not exited generated Dart code.
    __ StoreToOffset(ZR, THR, target::Thread::exit_through_ffi_offset());
  
    // Reset exit frame information in Isolate's mutator thread structure.
    __ StoreToOffset(ZR, THR, target::Thread::top_exit_frame_info_offset());
  
    // Restore the global object pool after returning from runtime (old space is
    // moving, so the GOP could have been relocated).
    if (FLAG_precompiled_mode) {
      __ SetupGlobalPoolAndDispatchTable();
    }
  
    __ LeaveStubFrame();
  
    // The following return can jump to a lazy-deopt stub, which assumes A0
    // contains a return value and will save it in a GC-visible way.  We therefore
    // have to ensure A0 does not contain any garbage value left from the C
    // function we called (which has return type "void").
    // (See GenerateDeoptimizationSequence::saved_result_slot_from_fp.)
    __ LoadImmediate(A0, 0);
    __ ret();
  }
  
  void StubCodeCompiler::GenerateSharedStubGeneric(
      Assembler* assembler,
      bool save_fpu_registers,
      intptr_t self_code_stub_offset_from_thread,
      bool allow_return,
      std::function<void()> perform_runtime_call) {
    // We want the saved registers to appear like part of the caller's frame, so
    // we push them before calling EnterStubFrame.
    RegisterSet all_registers;
    all_registers.AddAllNonReservedRegisters(save_fpu_registers);
  
    // To make the stack map calculation architecture independent we do the same
    // as on intel.
    __ PushRegister(RA);
    __ PushRegisters(all_registers);
    __ lx(CODE_REG, Address(THR, self_code_stub_offset_from_thread));
    __ EnterStubFrame();
    perform_runtime_call();
    if (!allow_return) {
      __ Breakpoint();
      return;
    }
    __ LeaveStubFrame();
    __ PopRegisters(all_registers);
    __ Drop(1);  // We use the RA restored via LeaveStubFrame.
    __ ret();
  }
  
  void StubCodeCompiler::GenerateSharedStub(
      Assembler* assembler,
      bool save_fpu_registers,
      const RuntimeEntry* target,
      intptr_t self_code_stub_offset_from_thread,
      bool allow_return,
      bool store_runtime_result_in_result_register) {
    ASSERT(!store_runtime_result_in_result_register || allow_return);
    auto perform_runtime_call = [&]() {
      if (store_runtime_result_in_result_register) {
        __ PushRegister(NULL_REG);
      }
      __ CallRuntime(*target, /*argument_count=*/0);
      if (store_runtime_result_in_result_register) {
        __ PopRegister(A0);
        __ sx(A0, Address(FP, target::kWordSize *
                                  StubCodeCompiler::WordOffsetFromFpToCpuRegister(
                                      SharedSlowPathStubABI::kResultReg)));
      }
    };
    GenerateSharedStubGeneric(assembler, save_fpu_registers,
                              self_code_stub_offset_from_thread, allow_return,
                              perform_runtime_call);
  }
  
  void StubCodeCompiler::GenerateEnterSafepointStub(Assembler* assembler) {
    RegisterSet all_registers;
    all_registers.AddAllGeneralRegisters();
  
    __ EnterFrame(0);
    __ PushRegisters(all_registers);
  
    __ ReserveAlignedFrameSpace(0);
  
    __ lx(TMP, Address(THR, kEnterSafepointRuntimeEntry.OffsetFromThread()));
    __ jalr(TMP);
  
    __ PopRegisters(all_registers);
    __ LeaveFrame();
    __ ret();
  }
  
  static void GenerateExitSafepointStubCommon(Assembler* assembler,
                                              uword runtime_entry_offset) {
    RegisterSet all_registers;
    all_registers.AddAllGeneralRegisters();
  
    __ EnterFrame(0);
    __ PushRegisters(all_registers);
  
    __ ReserveAlignedFrameSpace(0);
  
    // Set the execution state to VM while waiting for the safepoint to end.
    // This isn't strictly necessary but enables tests to check that we're not
    // in native code anymore. See tests/ffi/function_gc_test.dart for example.
    __ LoadImmediate(TMP, target::Thread::vm_execution_state());
    __ sx(TMP, Address(THR, target::Thread::execution_state_offset()));
  
    __ lx(TMP, Address(THR, runtime_entry_offset));
    __ jalr(TMP);
  
    __ PopRegisters(all_registers);
    __ LeaveFrame();
    __ ret();
  }
  
  void StubCodeCompiler::GenerateExitSafepointStub(Assembler* assembler) {
    GenerateExitSafepointStubCommon(
        assembler, kExitSafepointRuntimeEntry.OffsetFromThread());
  }
  
  void StubCodeCompiler::GenerateExitSafepointIgnoreUnwindInProgressStub(
      Assembler* assembler) {
    GenerateExitSafepointStubCommon(
        assembler,
        kExitSafepointIgnoreUnwindInProgressRuntimeEntry.OffsetFromThread());
  }
  
  // Calls native code within a safepoint.
  //
  // On entry:
  //   T0: target to call
  //   Stack: set up for native call (SP), aligned, CSP < SP
  //
  // On exit:
  //   S2: clobbered, although normally callee-saved
  //   Stack: preserved, CSP == SP
  void StubCodeCompiler::GenerateCallNativeThroughSafepointStub(
      Assembler* assembler) {
    COMPILE_ASSERT(IsAbiPreservedRegister(S2));
    __ mv(S2, RA);
    __ LoadImmediate(T1, target::Thread::exit_through_ffi());
    __ TransitionGeneratedToNative(T0, FPREG, T1 /*volatile*/,
                                   /*enter_safepoint=*/true);
  
  #if defined(DEBUG)
    // Check SP alignment.
    __ andi(T2 /*volatile*/, SP, ~(OS::ActivationFrameAlignment() - 1));
    Label done;
    __ beq(T2, SP, &done);
    __ Breakpoint();
    __ Bind(&done);
  #endif
  
    __ jalr(T0);
  
    __ TransitionNativeToGenerated(T1, /*leave_safepoint=*/true);
    __ jr(S2);
  }
  
  #if !defined(DART_PRECOMPILER)
  void StubCodeCompiler::GenerateJITCallbackTrampolines(
      Assembler* assembler,
      intptr_t next_callback_id) {
  #if defined(USING_SIMULATOR)
    // TODO(37299): FFI is not support in SIMRISCV32/64.
    __ ebreak();
  #else
    Label loaded_callback_id_hi;
  
    // T1 is volatile and not used for passing any arguments.
    COMPILE_ASSERT(!IsCalleeSavedRegister(T1) && !IsArgumentRegister(T1));
    for (intptr_t i = 0;
         i < NativeCallbackTrampolines::NumCallbackTrampolinesPerPage(); ++i) {
      // We don't use LoadImmediate because we need the trampoline size to be
      // fixed independently of the callback ID.
      // lui has 20 bits of range.
      __ lui_fixed(T1, (next_callback_id + i) << 12);
      __ j(&loaded_callback_id_hi);
    }
  
    ASSERT(__ CodeSize() ==
           kNativeCallbackTrampolineSize *
               NativeCallbackTrampolines::NumCallbackTrampolinesPerPage());
  
    const intptr_t shared_stub_start = __ CodeSize();
  
    __ Bind(&loaded_callback_id_hi);
    __ srai(T1, T1, 12);
  
    // Save THR (callee-saved) and RA. Keeps stack aligned.
    COMPILE_ASSERT(StubCodeCompiler::kNativeCallbackTrampolineStackDelta == 2);
    __ PushRegisterPair(RA, THR);
    COMPILE_ASSERT(!IsArgumentRegister(THR));
  
    RegisterSet all_registers;
    all_registers.AddAllArgumentRegisters();
  
    // The call below might clobber T1 (volatile, holding callback_id).
    all_registers.Add(Location::RegisterLocation(T1));
  
    // Load the thread, verify the callback ID and exit the safepoint.
    //
    // We exit the safepoint inside DLRT_GetThreadForNativeCallbackTrampoline
    // in order to safe code size on this shared stub.
    {
      __ PushRegisters(all_registers);
      __ EnterFrame(0);
      __ ReserveAlignedFrameSpace(0);
  
      // Since DLRT_GetThreadForNativeCallbackTrampoline can theoretically be
      // loaded anywhere, we use the same trick as before to ensure a predictable
      // instruction sequence.
      Label call;
      __ mv(A0, T1);
  
      const intptr_t kPCRelativeLoadOffset = 12;
      intptr_t start = __ CodeSize();
      __ auipc(T1, 0);
      __ lx(T1, Address(T1, kPCRelativeLoadOffset));
      __ j(&call);
  
      ASSERT_EQUAL(__ CodeSize() - start, kPCRelativeLoadOffset);
  #if XLEN == 32
      __ Emit32(
          reinterpret_cast<int32_t>(&DLRT_GetThreadForNativeCallbackTrampoline));
  #else
      __ Emit64(
          reinterpret_cast<int64_t>(&DLRT_GetThreadForNativeCallbackTrampoline));
  #endif
  
      __ Bind(&call);
      __ jalr(T1);
      __ mv(THR, A0);
  
      __ LeaveFrame();
  
      __ PopRegisters(all_registers);
    }
  
    COMPILE_ASSERT(!IsCalleeSavedRegister(T2) && !IsArgumentRegister(T2));
    COMPILE_ASSERT(!IsCalleeSavedRegister(T3) && !IsArgumentRegister(T3));
  
    // Load the code object.
    __ LoadFromOffset(T2, THR, compiler::target::Thread::callback_code_offset());
    __ LoadCompressedFieldFromOffset(
        T2, T2, compiler::target::GrowableObjectArray::data_offset());
    __ LoadCompressed(
        T2,
        __ ElementAddressForRegIndex(
            /*external=*/false,
            /*array_cid=*/kArrayCid,
            /*index_scale, smi-tagged=*/compiler::target::kCompressedWordSize * 2,
            /*index_unboxed=*/false,
            /*array=*/T2,
            /*index=*/T1,
            /*temp=*/T3));
    __ LoadFieldFromOffset(T2, T2, compiler::target::Code::entry_point_offset());
  
    // Clobbers all volatile registers, including the callback ID in T1.
    __ jalr(T2);
  
    // Clobbers TMP, TMP2 and T1 -- all volatile and not holding return values.
    __ EnterFullSafepoint(/*scratch=*/T1);
  
    __ PopRegisterPair(RA, THR);
    __ ret();
  
    ASSERT_EQUAL((__ CodeSize() - shared_stub_start),
                 kNativeCallbackSharedStubSize);
    ASSERT(__ CodeSize() <= VirtualMemory::PageSize());
  
  #if defined(DEBUG)
    while (__ CodeSize() < VirtualMemory::PageSize()) {
      __ ebreak();
    }
  #endif
  #endif
  }
  #endif  // !defined(DART_PRECOMPILER)
  
  // T1: The extracted method.
  // T4: The type_arguments_field_offset (or 0)
  void StubCodeCompiler::GenerateBuildMethodExtractorStub(
      Assembler* assembler,
      const Code& closure_allocation_stub,
      const Code& context_allocation_stub,
      bool generic) {
    const intptr_t kReceiverOffset = target::frame_layout.param_end_from_fp + 1;
  
    __ EnterStubFrame();
  
    // Build type_arguments vector (or null)
    Label no_type_args;
    __ lx(T3, Address(THR, target::Thread::object_null_offset()));
    __ CompareImmediate(T4, 0);
    __ BranchIf(EQ, &no_type_args);
    __ lx(T0, Address(FP, kReceiverOffset * target::kWordSize));
    __ add(TMP, T0, T4);
    __ LoadCompressed(T3, Address(TMP, 0));
    __ Bind(&no_type_args);
  
    // Push type arguments & extracted method.
    __ PushRegister(T3);
    __ PushRegister(T1);
  
    // Allocate context.
    {
      Label done, slow_path;
      if (!FLAG_use_slow_path && FLAG_inline_alloc) {
        __ TryAllocateArray(kContextCid, target::Context::InstanceSize(1),
                            &slow_path,
                            A0,  // instance
                            T1,  // end address
                            T2, T3);
        __ StoreCompressedIntoObjectNoBarrier(
            A0, FieldAddress(A0, target::Context::parent_offset()), NULL_REG);
        __ LoadImmediate(T1, 1);
        __ sw(T1, FieldAddress(A0, target::Context::num_variables_offset()));
        __ j(&done, compiler::Assembler::kNearJump);
      }
  
      __ Bind(&slow_path);
  
      __ LoadImmediate(/*num_vars=*/T1, 1);
      __ LoadObject(CODE_REG, context_allocation_stub);
      __ lx(RA, FieldAddress(CODE_REG, target::Code::entry_point_offset()));
      __ jalr(RA);
  
      __ Bind(&done);
    }
  
    // Put context in right register for AllocateClosure call.
    __ MoveRegister(AllocateClosureABI::kContextReg, A0);
  
    // Store receiver in context
    __ lx(AllocateClosureABI::kScratchReg,
          Address(FP, target::kWordSize * kReceiverOffset));
    __ StoreCompressedIntoObject(
        AllocateClosureABI::kContextReg,
        FieldAddress(AllocateClosureABI::kContextReg,
                     target::Context::variable_offset(0)),
        AllocateClosureABI::kScratchReg);
  
    // Pop function before pushing context.
    __ PopRegister(AllocateClosureABI::kFunctionReg);
  
    // Allocate closure. After this point, we only use the registers in
    // AllocateClosureABI.
    __ LoadObject(CODE_REG, closure_allocation_stub);
    __ lx(AllocateClosureABI::kScratchReg,
          FieldAddress(CODE_REG, target::Code::entry_point_offset()));
    __ jalr(AllocateClosureABI::kScratchReg);
  
    // Populate closure object.
    __ PopRegister(AllocateClosureABI::kScratchReg);  // Pop type arguments.
    __ StoreCompressedIntoObjectNoBarrier(
        AllocateClosureABI::kResultReg,
        FieldAddress(AllocateClosureABI::kResultReg,
                     target::Closure::instantiator_type_arguments_offset()),
        AllocateClosureABI::kScratchReg);
    // Keep delayed_type_arguments as null if non-generic (see Closure::New).
    if (generic) {
      __ LoadObject(AllocateClosureABI::kScratchReg, EmptyTypeArguments());
      __ StoreCompressedIntoObjectNoBarrier(
          AllocateClosureABI::kResultReg,
          FieldAddress(AllocateClosureABI::kResultReg,
                       target::Closure::delayed_type_arguments_offset()),
          AllocateClosureABI::kScratchReg);
    }
  
    __ LeaveStubFrame();
    // No-op if the two are the same.
    __ MoveRegister(A0, AllocateClosureABI::kResultReg);
    __ Ret();
  }
  
  void StubCodeCompiler::GenerateDispatchTableNullErrorStub(
      Assembler* assembler) {
    __ EnterStubFrame();
    __ SmiTag(DispatchTableNullErrorABI::kClassIdReg);
    __ PushRegister(DispatchTableNullErrorABI::kClassIdReg);
    __ CallRuntime(kDispatchTableNullErrorRuntimeEntry, /*argument_count=*/1);
    // The NullError runtime entry does not return.
    __ Breakpoint();
  }
  
  void StubCodeCompiler::GenerateRangeError(Assembler* assembler,
                                            bool with_fpu_regs) {
    auto perform_runtime_call = [&]() {
    // If the generated code has unboxed index/length we need to box them before
    // calling the runtime entry.
  #if XLEN == 32
      ASSERT(!GenericCheckBoundInstr::UseUnboxedRepresentation());
  #else
      if (GenericCheckBoundInstr::UseUnboxedRepresentation()) {
        Label length, smi_case;
  
        // The user-controlled index might not fit into a Smi.
        __ mv(TMP, RangeErrorABI::kIndexReg);
        __ SmiTag(RangeErrorABI::kIndexReg, RangeErrorABI::kIndexReg);
        __ SmiUntag(TMP2, RangeErrorABI::kIndexReg);
        __ beq(TMP, TMP2, &length);  // No overflow.
        {
          // Allocate a mint, reload the two registers and popualte the mint.
          __ PushRegister(NULL_REG);
          __ CallRuntime(kAllocateMintRuntimeEntry, /*argument_count=*/0);
          __ PopRegister(RangeErrorABI::kIndexReg);
          __ lx(TMP,
                Address(FP, target::kWordSize *
                                StubCodeCompiler::WordOffsetFromFpToCpuRegister(
                                    RangeErrorABI::kIndexReg)));
          __ sx(TMP, FieldAddress(RangeErrorABI::kIndexReg,
                                  target::Mint::value_offset()));
          __ lx(RangeErrorABI::kLengthReg,
                Address(FP, target::kWordSize *
                                StubCodeCompiler::WordOffsetFromFpToCpuRegister(
                                    RangeErrorABI::kLengthReg)));
        }
  
        // Length is guaranteed to be in positive Smi range (it comes from a load
        // of a vm recognized array).
        __ Bind(&length);
        __ SmiTag(RangeErrorABI::kLengthReg);
      }
  #endif  // XLEN != 32
      __ PushRegister(RangeErrorABI::kLengthReg);
      __ PushRegister(RangeErrorABI::kIndexReg);
      __ CallRuntime(kRangeErrorRuntimeEntry, /*argument_count=*/2);
      __ Breakpoint();
    };
  
    GenerateSharedStubGeneric(
        assembler, /*save_fpu_registers=*/with_fpu_regs,
        with_fpu_regs
            ? target::Thread::range_error_shared_with_fpu_regs_stub_offset()
            : target::Thread::range_error_shared_without_fpu_regs_stub_offset(),
        /*allow_return=*/false, perform_runtime_call);
  }
  
  // Input parameters:
  //   RA : return address.
  //   SP : address of return value.
  //   T5 : address of the native function to call.
  //   T2 : address of first argument in argument array.
  //   T1 : argc_tag including number of arguments and function kind.
  static void GenerateCallNativeWithWrapperStub(Assembler* assembler,
                                                Address wrapper) {
    const intptr_t thread_offset = target::NativeArguments::thread_offset();
    const intptr_t argc_tag_offset = target::NativeArguments::argc_tag_offset();
    const intptr_t argv_offset = target::NativeArguments::argv_offset();
    const intptr_t retval_offset = target::NativeArguments::retval_offset();
  
    __ EnterStubFrame();
  
    // Save exit frame information to enable stack walking as we are about
    // to transition to native code.
    __ StoreToOffset(FP, THR, target::Thread::top_exit_frame_info_offset());
  
    // Mark that the thread exited generated code through a runtime call.
    __ LoadImmediate(TMP, target::Thread::exit_through_runtime_call());
    __ StoreToOffset(TMP, THR, target::Thread::exit_through_ffi_offset());
  
  #if defined(DEBUG)
    {
      Label ok;
      // Check that we are always entering from Dart code.
      __ LoadFromOffset(TMP, THR, target::Thread::vm_tag_offset());
      __ CompareImmediate(TMP, VMTag::kDartTagId);
      __ BranchIf(EQ, &ok);
      __ Stop("Not coming from Dart code.");
      __ Bind(&ok);
    }
  #endif
  
    // Mark that the thread is executing native code.
    __ StoreToOffset(T5, THR, target::Thread::vm_tag_offset());
  
    // Reserve space for the native arguments structure passed on the stack (the
    // outgoing pointer parameter to the native arguments structure is passed in
    // R0) and align frame before entering the C++ world.
    __ ReserveAlignedFrameSpace(target::NativeArguments::StructSize());
  
    // Initialize target::NativeArguments structure and call native function.
    ASSERT(thread_offset == 0 * target::kWordSize);
    // There are no native calls to closures, so we do not need to set the tag
    // bits kClosureFunctionBit and kInstanceFunctionBit in argc_tag_.
    ASSERT(argc_tag_offset == 1 * target::kWordSize);
    // Set argc in target::NativeArguments: R1 already contains argc.
    ASSERT(argv_offset == 2 * target::kWordSize);
    // Set argv in target::NativeArguments: R2 already contains argv.
    // Set retval in NativeArgs.
    ASSERT(retval_offset == 3 * target::kWordSize);
    __ AddImmediate(
        T3, FP, (target::frame_layout.param_end_from_fp + 1) * target::kWordSize);
  
    // Passing the structure by value as in runtime calls would require changing
    // Dart API for native functions.
    // For now, space is reserved on the stack and we pass a pointer to it.
    __ StoreToOffset(THR, SP, thread_offset);
    __ StoreToOffset(T1, SP, argc_tag_offset);
    __ StoreToOffset(T2, SP, argv_offset);
    __ StoreToOffset(T3, SP, retval_offset);
    __ mv(A0, SP);  // Pass the pointer to the target::NativeArguments.
    __ mv(A1, T5);  // Pass the function entrypoint to call.
  
    // Call native function invocation wrapper or redirection via simulator.
    ASSERT(IsAbiPreservedRegister(THR));
    __ Call(wrapper);
  
    // Refresh pinned registers values (inc. write barrier mask and null object).
    __ RestorePinnedRegisters();
  
    // Mark that the thread is executing Dart code.
    __ LoadImmediate(TMP, VMTag::kDartTagId);
    __ StoreToOffset(TMP, THR, target::Thread::vm_tag_offset());
  
    // Mark that the thread has not exited generated Dart code.
    __ StoreToOffset(ZR, THR, target::Thread::exit_through_ffi_offset());
  
    // Reset exit frame information in Isolate's mutator thread structure.
    __ StoreToOffset(ZR, THR, target::Thread::top_exit_frame_info_offset());
  
    // Restore the global object pool after returning from runtime (old space is
    // moving, so the GOP could have been relocated).
    if (FLAG_precompiled_mode) {
      __ SetupGlobalPoolAndDispatchTable();
    }
  
    __ LeaveStubFrame();
    __ ret();
  }
  
  void StubCodeCompiler::GenerateCallNoScopeNativeStub(Assembler* assembler) {
    GenerateCallNativeWithWrapperStub(
        assembler,
        Address(THR,
                target::Thread::no_scope_native_wrapper_entry_point_offset()));
  }
  
  void StubCodeCompiler::GenerateCallAutoScopeNativeStub(Assembler* assembler) {
    GenerateCallNativeWithWrapperStub(
        assembler,
        Address(THR,
                target::Thread::auto_scope_native_wrapper_entry_point_offset()));
  }
  
  // Input parameters:
  //   RA : return address.
  //   SP : address of return value.
  //   R5 : address of the native function to call.
  //   R2 : address of first argument in argument array.
  //   R1 : argc_tag including number of arguments and function kind.
  void StubCodeCompiler::GenerateCallBootstrapNativeStub(Assembler* assembler) {
    GenerateCallNativeWithWrapperStub(
        assembler,
        Address(THR,
                target::Thread::bootstrap_native_wrapper_entry_point_offset()));
  }
  
  // Input parameters:
  //   S4: arguments descriptor array.
  void StubCodeCompiler::GenerateCallStaticFunctionStub(Assembler* assembler) {
    // Create a stub frame as we are pushing some objects on the stack before
    // calling into the runtime.
    __ EnterStubFrame();
    __ subi(SP, SP, 2 * target::kWordSize);
    __ sx(S4, Address(SP, 1 * target::kWordSize));  // Preserve args descriptor.
    __ sx(ZR, Address(SP, 0 * target::kWordSize));  // Result slot.
    __ CallRuntime(kPatchStaticCallRuntimeEntry, 0);
    __ lx(CODE_REG, Address(SP, 0 * target::kWordSize));  // Result.
    __ lx(S4, Address(SP, 1 * target::kWordSize));  // Restore args descriptor.
    __ addi(SP, SP, 2 * target::kWordSize);
    __ LeaveStubFrame();
    // Jump to the dart function.
    __ LoadFieldFromOffset(TMP, CODE_REG, target::Code::entry_point_offset());
    __ jr(TMP);
  }
  
  // Called from a static call only when an invalid code has been entered
  // (invalid because its function was optimized or deoptimized).
  // S4: arguments descriptor array.
  void StubCodeCompiler::GenerateFixCallersTargetStub(Assembler* assembler) {
    Label monomorphic;
    __ BranchOnMonomorphicCheckedEntryJIT(&monomorphic);
  
    // Load code pointer to this stub from the thread:
    // The one that is passed in, is not correct - it points to the code object
    // that needs to be replaced.
    __ lx(CODE_REG,
          Address(THR, target::Thread::fix_callers_target_code_offset()));
    // Create a stub frame as we are pushing some objects on the stack before
    // calling into the runtime.
    __ EnterStubFrame();
    // Setup space on stack for return value and preserve arguments descriptor.
    __ PushRegister(S4);
    __ PushRegister(ZR);
    __ CallRuntime(kFixCallersTargetRuntimeEntry, 0);
    // Get Code object result and restore arguments descriptor array.
    __ PopRegister(CODE_REG);
    __ PopRegister(S4);
    // Remove the stub frame.
    __ LeaveStubFrame();
    // Jump to the dart function.
    __ LoadFieldFromOffset(TMP, CODE_REG, target::Code::entry_point_offset());
    __ jr(TMP);
  
    __ Bind(&monomorphic);
    // Load code pointer to this stub from the thread:
    // The one that is passed in, is not correct - it points to the code object
    // that needs to be replaced.
    __ lx(CODE_REG,
          Address(THR, target::Thread::fix_callers_target_code_offset()));
    // Create a stub frame as we are pushing some objects on the stack before
    // calling into the runtime.
    __ EnterStubFrame();
    __ PushRegister(ZR);  // Result slot.
    __ PushRegister(A0);  // Preserve receiver.
    __ PushRegister(S5);  // Old cache value (also 2nd return value).
    __ CallRuntime(kFixCallersTargetMonomorphicRuntimeEntry, 2);
    __ PopRegister(S5);        // Get target cache object.
    __ PopRegister(A0);        // Restore receiver.
    __ PopRegister(CODE_REG);  // Get target Code object.
    // Remove the stub frame.
    __ LeaveStubFrame();
    // Jump to the dart function.
    __ LoadFieldFromOffset(
        TMP, CODE_REG,
        target::Code::entry_point_offset(CodeEntryKind::kMonomorphic));
    __ jr(TMP);
  }
  
  // Called from object allocate instruction when the allocation stub has been
  // disabled.
  void StubCodeCompiler::GenerateFixAllocationStubTargetStub(
      Assembler* assembler) {
    // Load code pointer to this stub from the thread:
    // The one that is passed in, is not correct - it points to the code object
    // that needs to be replaced.
    __ lx(CODE_REG,
          Address(THR, target::Thread::fix_allocation_stub_code_offset()));
    __ EnterStubFrame();
    // Setup space on stack for return value.
    __ PushRegister(ZR);
    __ CallRuntime(kFixAllocationStubTargetRuntimeEntry, 0);
    // Get Code object result.
    __ PopRegister(CODE_REG);
    // Remove the stub frame.
    __ LeaveStubFrame();
    // Jump to the dart function.
    __ LoadFieldFromOffset(TMP, CODE_REG, target::Code::entry_point_offset());
    __ jr(TMP);
  }
  
  // Input parameters:
  //   T2: smi-tagged argument count, may be zero.
  //   FP[target::frame_layout.param_end_from_fp + 1]: last argument.
  static void PushArrayOfArguments(Assembler* assembler) {
    COMPILE_ASSERT(AllocateArrayABI::kLengthReg == T2);
    COMPILE_ASSERT(AllocateArrayABI::kTypeArgumentsReg == T1);
  
    // Allocate array to store arguments of caller.
    __ LoadObject(T1, NullObject());
    // T1: null element type for raw Array.
    // T2: smi-tagged argument count, may be zero.
    __ JumpAndLink(StubCodeAllocateArray());
    // A0: newly allocated array.
    // T2: smi-tagged argument count, may be zero (was preserved by the stub).
    __ PushRegister(A0);  // Array is in A0 and on top of stack.
    __ SmiUntag(T2);
    __ slli(T1, T2, target::kWordSizeLog2);
    __ add(T1, T1, FP);
    __ AddImmediate(T1,
                    target::frame_layout.param_end_from_fp * target::kWordSize);
    __ AddImmediate(T3, A0, target::Array::data_offset() - kHeapObjectTag);
    // T1: address of first argument on stack.
    // T3: address of first argument in array.
  
    Label loop, loop_exit;
    __ Bind(&loop);
    __ beqz(T2, &loop_exit);
    __ lx(T6, Address(T1, 0));
    __ addi(T1, T1, -target::kWordSize);
    __ StoreCompressedIntoObject(A0, Address(T3, 0), T6);
    __ addi(T3, T3, target::kCompressedWordSize);
    __ addi(T2, T2, -1);
    __ j(&loop);
    __ Bind(&loop_exit);
  }
  
  // Used by eager and lazy deoptimization. Preserve result in RAX if necessary.
  // This stub translates optimized frame into unoptimized frame. The optimized
  // frame can contain values in registers and on stack, the unoptimized
  // frame contains all values on stack.
  // Deoptimization occurs in following steps:
  // - Push all registers that can contain values.
  // - Call C routine to copy the stack and saved registers into temporary buffer.
  // - Adjust caller's frame to correct unoptimized frame size.
  // - Fill the unoptimized frame.
  // - Materialize objects that require allocation (e.g. Double instances).
  // GC can occur only after frame is fully rewritten.
  // Stack after TagAndPushPP() below:
  //   +------------------+
  //   | Saved PP         | <- PP
  //   +------------------+
  //   | PC marker        | <- TOS
  //   +------------------+
  //   | Saved FP         |
  //   +------------------+
  //   | return-address   |  (deoptimization point)
  //   +------------------+
  //   | Saved CODE_REG   | <- FP of stub
  //   +------------------+
  //   | ...              | <- SP of optimized frame
  //
  // Parts of the code cannot GC, part of the code can GC.
  static void GenerateDeoptimizationSequence(Assembler* assembler,
                                             DeoptStubKind kind) {
    // DeoptimizeCopyFrame expects a Dart frame, i.e. EnterDartFrame(0), but there
    // is no need to set the correct PC marker or load PP, since they get patched.
    __ EnterStubFrame();
  
    // The code in this frame may not cause GC. kDeoptimizeCopyFrameRuntimeEntry
    // and kDeoptimizeFillFrameRuntimeEntry are leaf runtime calls.
    const intptr_t saved_result_slot_from_fp =
        target::frame_layout.first_local_from_fp + 1 -
        (kNumberOfCpuRegisters - A0);
    const intptr_t saved_exception_slot_from_fp =
        target::frame_layout.first_local_from_fp + 1 -
        (kNumberOfCpuRegisters - A0);
    const intptr_t saved_stacktrace_slot_from_fp =
        target::frame_layout.first_local_from_fp + 1 -
        (kNumberOfCpuRegisters - A1);
    // Result in A0 is preserved as part of pushing all registers below.
  
    // Push registers in their enumeration order: lowest register number at
    // lowest address.
    __ subi(SP, SP, kNumberOfCpuRegisters * target::kWordSize);
    for (intptr_t i = kNumberOfCpuRegisters - 1; i >= 0; i--) {
      const Register r = static_cast<Register>(i);
      if (r == CODE_REG) {
        // Save the original value of CODE_REG pushed before invoking this stub
        // instead of the value used to call this stub.
        COMPILE_ASSERT(TMP > CODE_REG);  // TMP saved first
        __ lx(TMP, Address(FP, 0 * target::kWordSize));
        __ sx(TMP, Address(SP, i * target::kWordSize));
      } else {
        __ sx(r, Address(SP, i * target::kWordSize));
      }
    }
  
    __ subi(SP, SP, kNumberOfFpuRegisters * kFpuRegisterSize);
    for (intptr_t i = kNumberOfFpuRegisters - 1; i >= 0; i--) {
      FRegister freg = static_cast<FRegister>(i);
      __ fsd(freg, Address(SP, i * kFpuRegisterSize));
    }
  
    __ mv(A0, SP);  // Pass address of saved registers block.
    bool is_lazy =
        (kind == kLazyDeoptFromReturn) || (kind == kLazyDeoptFromThrow);
    __ li(A1, is_lazy ? 1 : 0);
    __ ReserveAlignedFrameSpace(0);
    __ CallRuntime(kDeoptimizeCopyFrameRuntimeEntry, 2);
    // Result (A0) is stack-size (FP - SP) in bytes.
  
    if (kind == kLazyDeoptFromReturn) {
      // Restore result into T1 temporarily.
      __ LoadFromOffset(T1, FP, saved_result_slot_from_fp * target::kWordSize);
    } else if (kind == kLazyDeoptFromThrow) {
      // Restore result into T1 temporarily.
      __ LoadFromOffset(T1, FP, saved_exception_slot_from_fp * target::kWordSize);
      __ LoadFromOffset(T2, FP,
                        saved_stacktrace_slot_from_fp * target::kWordSize);
    }
  
    // There is a Dart Frame on the stack. We must restore PP and leave frame.
    __ RestoreCodePointer();
    __ LeaveStubFrame();
    __ sub(SP, FP, A0);
  
    // DeoptimizeFillFrame expects a Dart frame, i.e. EnterDartFrame(0), but there
    // is no need to set the correct PC marker or load PP, since they get patched.
    __ EnterStubFrame();
  
    if (kind == kLazyDeoptFromReturn) {
      __ PushRegister(T1);  // Preserve result as first local.
    } else if (kind == kLazyDeoptFromThrow) {
      __ PushRegister(T1);  // Preserve exception as first local.
      __ PushRegister(T2);  // Preserve stacktrace as second local.
    }
    __ ReserveAlignedFrameSpace(0);
    __ mv(A0, FP);  // Pass last FP as parameter in R0.
    __ CallRuntime(kDeoptimizeFillFrameRuntimeEntry, 1);
    if (kind == kLazyDeoptFromReturn) {
      // Restore result into T1.
      __ LoadFromOffset(
          T1, FP, target::frame_layout.first_local_from_fp * target::kWordSize);
    } else if (kind == kLazyDeoptFromThrow) {
      // Restore result into T1.
      __ LoadFromOffset(
          T1, FP, target::frame_layout.first_local_from_fp * target::kWordSize);
      __ LoadFromOffset(
          T2, FP,
          (target::frame_layout.first_local_from_fp - 1) * target::kWordSize);
    }
    // Code above cannot cause GC.
    // There is a Dart Frame on the stack. We must restore PP and leave frame.
    __ RestoreCodePointer();
    __ LeaveStubFrame();
  
    // Frame is fully rewritten at this point and it is safe to perform a GC.
    // Materialize any objects that were deferred by FillFrame because they
    // require allocation.
    // Enter stub frame with loading PP. The caller's PP is not materialized yet.
    __ EnterStubFrame();
    if (kind == kLazyDeoptFromReturn) {
      __ PushRegister(T1);  // Preserve result, it will be GC-d here.
    } else if (kind == kLazyDeoptFromThrow) {
      __ PushRegister(T1);  // Preserve exception, it will be GC-d here.
      __ PushRegister(T2);  // Preserve stacktrace, it will be GC-d here.
    }
  
    __ PushRegister(ZR);  // Space for the result.
    __ CallRuntime(kDeoptimizeMaterializeRuntimeEntry, 0);
    // Result tells stub how many bytes to remove from the expression stack
    // of the bottom-most frame. They were used as materialization arguments.
    __ PopRegister(T2);
    __ SmiUntag(T2);
    if (kind == kLazyDeoptFromReturn) {
      __ PopRegister(A0);  // Restore result.
    } else if (kind == kLazyDeoptFromThrow) {
      __ PopRegister(A1);  // Restore stacktrace.
      __ PopRegister(A0);  // Restore exception.
    }
    __ LeaveStubFrame();
    // Remove materialization arguments.
    __ add(SP, SP, T2);
    // The caller is responsible for emitting the return instruction.
  }
  
  // A0: result, must be preserved
  void StubCodeCompiler::GenerateDeoptimizeLazyFromReturnStub(
      Assembler* assembler) {
    // Push zap value instead of CODE_REG for lazy deopt.
    __ LoadImmediate(TMP, kZapCodeReg);
    __ PushRegister(TMP);
    // Return address for "call" to deopt stub.
    __ LoadImmediate(RA, kZapReturnAddress);
    __ lx(CODE_REG,
          Address(THR, target::Thread::lazy_deopt_from_return_stub_offset()));
    GenerateDeoptimizationSequence(assembler, kLazyDeoptFromReturn);
    __ ret();
  }
  
  // A0: exception, must be preserved
  // A1: stacktrace, must be preserved
  void StubCodeCompiler::GenerateDeoptimizeLazyFromThrowStub(
      Assembler* assembler) {
    // Push zap value instead of CODE_REG for lazy deopt.
    __ LoadImmediate(TMP, kZapCodeReg);
    __ PushRegister(TMP);
    // Return address for "call" to deopt stub.
    __ LoadImmediate(RA, kZapReturnAddress);
    __ lx(CODE_REG,
          Address(THR, target::Thread::lazy_deopt_from_throw_stub_offset()));
    GenerateDeoptimizationSequence(assembler, kLazyDeoptFromThrow);
    __ ret();
  }
  
  void StubCodeCompiler::GenerateDeoptimizeStub(Assembler* assembler) {
    __ PushRegister(CODE_REG);
    __ lx(CODE_REG, Address(THR, target::Thread::deoptimize_stub_offset()));
    GenerateDeoptimizationSequence(assembler, kEagerDeopt);
    __ ret();
  }
  
  // S5: ICData/MegamorphicCache
  static void GenerateNoSuchMethodDispatcherBody(Assembler* assembler) {
    __ EnterStubFrame();
  
    __ lx(S4,
          FieldAddress(S5, target::CallSiteData::arguments_descriptor_offset()));
  
    // Load the receiver.
    __ LoadCompressedSmiFieldFromOffset(
        T2, S4, target::ArgumentsDescriptor::size_offset());
    __ slli(TMP, T2, target::kWordSizeLog2 - 1);  // T2 is Smi.
    __ add(TMP, TMP, FP);
    __ LoadFromOffset(A0, TMP,
                      target::frame_layout.param_end_from_fp * target::kWordSize);
    __ PushRegister(ZR);  // Result slot.
    __ PushRegister(A0);  // Receiver.
    __ PushRegister(S5);  // ICData/MegamorphicCache.
    __ PushRegister(S4);  // Arguments descriptor.
  
    // Adjust arguments count.
    __ LoadCompressedSmiFieldFromOffset(
        T3, S4, target::ArgumentsDescriptor::type_args_len_offset());
    Label args_count_ok;
    __ beqz(T3, &args_count_ok, Assembler::kNearJump);
    // Include the type arguments.
    __ addi(T2, T2, target::ToRawSmi(1));
    __ Bind(&args_count_ok);
  
    // T2: Smi-tagged arguments array length.
    PushArrayOfArguments(assembler);
    const intptr_t kNumArgs = 4;
    __ CallRuntime(kNoSuchMethodFromCallStubRuntimeEntry, kNumArgs);
    __ Drop(4);
    __ PopRegister(A0);  // Return value.
    __ LeaveStubFrame();
    __ ret();
  }
  
  static void GenerateDispatcherCode(Assembler* assembler,
                                     Label* call_target_function) {
    __ Comment("NoSuchMethodDispatch");
    // When lazily generated invocation dispatchers are disabled, the
    // miss-handler may return null.
    __ bne(T0, NULL_REG, call_target_function);
  
    GenerateNoSuchMethodDispatcherBody(assembler);
  }
  
  // Input:
  //   S4 - arguments descriptor
  //   S5 - icdata/megamorphic_cache
  void StubCodeCompiler::GenerateNoSuchMethodDispatcherStub(
      Assembler* assembler) {
    GenerateNoSuchMethodDispatcherBody(assembler);
  }
  
  // Called for inline allocation of arrays.
  // Input registers (preserved):
  //   RA: return address.
  //   AllocateArrayABI::kLengthReg: array length as Smi.
  //   AllocateArrayABI::kTypeArgumentsReg: type arguments of array.
  // Output registers:
  //   AllocateArrayABI::kResultReg: newly allocated array.
  // Clobbered:
  //   T3, T4, T5
  void StubCodeCompiler::GenerateAllocateArrayStub(Assembler* assembler) {
    if (!FLAG_use_slow_path && FLAG_inline_alloc) {
      Label slow_case;
      // Compute the size to be allocated, it is based on the array length
      // and is computed as:
      // RoundedAllocationSize(
      //     (array_length * kCompressedWordSize) + target::Array::header_size()).
      // Check that length is a Smi.
      __ BranchIfNotSmi(AllocateArrayABI::kLengthReg, &slow_case);
  
      // Check length >= 0 && length <= kMaxNewSpaceElements
      const intptr_t max_len =
          target::ToRawSmi(target::Array::kMaxNewSpaceElements);
      __ CompareImmediate(AllocateArrayABI::kLengthReg, max_len, kObjectBytes);
      __ BranchIf(HI, &slow_case);
  
      const intptr_t cid = kArrayCid;
      NOT_IN_PRODUCT(__ MaybeTraceAllocation(kArrayCid, T4, &slow_case));
  
      // Calculate and align allocation size.
      // Load new object start and calculate next object start.
      // AllocateArrayABI::kTypeArgumentsReg: type arguments of array.
      // AllocateArrayABI::kLengthReg: array length as Smi.
      __ lx(AllocateArrayABI::kResultReg,
            Address(THR, target::Thread::top_offset()));
      intptr_t fixed_size_plus_alignment_padding =
          target::Array::header_size() +
          target::ObjectAlignment::kObjectAlignment - 1;
      // AllocateArrayABI::kLengthReg is Smi.
      __ slli(T3, AllocateArrayABI::kLengthReg,
              target::kWordSizeLog2 - kSmiTagSize);
      __ AddImmediate(T3, fixed_size_plus_alignment_padding);
      __ andi(T3, T3, ~(target::ObjectAlignment::kObjectAlignment - 1));
      // AllocateArrayABI::kResultReg: potential new object start.
      // T3: object size in bytes.
      __ add(T4, AllocateArrayABI::kResultReg, T3);
      // Branch if unsigned overflow.
      __ bltu(T4, AllocateArrayABI::kResultReg, &slow_case);
  
      // Check if the allocation fits into the remaining space.
      // AllocateArrayABI::kResultReg: potential new object start.
      // AllocateArrayABI::kTypeArgumentsReg: type arguments of array.
      // AllocateArrayABI::kLengthReg: array length as Smi.
      // T3: array size.
      // T4: potential next object start.
      __ LoadFromOffset(TMP, THR, target::Thread::end_offset());
      __ bgeu(T4, TMP, &slow_case);  // Branch if unsigned higher or equal.
  
      // Successfully allocated the object(s), now update top to point to
      // next object start and initialize the object.
      // AllocateArrayABI::kResultReg: potential new object start.
      // T3: array size.
      // T4: potential next object start.
      __ sx(T4, Address(THR, target::Thread::top_offset()));
      __ addi(AllocateArrayABI::kResultReg, AllocateArrayABI::kResultReg,
              kHeapObjectTag);
  
      // AllocateArrayABI::kResultReg: new object start as a tagged pointer.
      // AllocateArrayABI::kTypeArgumentsReg: type arguments of array.
      // AllocateArrayABI::kLengthReg: array length as Smi.
      // R3: array size.
      // R7: new object end address.
  
      // Store the type argument field.
      __ StoreCompressedIntoObjectOffsetNoBarrier(
          AllocateArrayABI::kResultReg, target::Array::type_arguments_offset(),
          AllocateArrayABI::kTypeArgumentsReg);
  
      // Set the length field.
      __ StoreCompressedIntoObjectOffsetNoBarrier(AllocateArrayABI::kResultReg,
                                                  target::Array::length_offset(),
                                                  AllocateArrayABI::kLengthReg);
  
      // Calculate the size tag.
      // AllocateArrayABI::kResultReg: new object start as a tagged pointer.
      // AllocateArrayABI::kLengthReg: array length as Smi.
      // T3: array size.
      // T4: new object end address.
      const intptr_t shift = target::UntaggedObject::kTagBitsSizeTagPos -
                             target::ObjectAlignment::kObjectAlignmentLog2;
      __ li(T5, 0);
      __ CompareImmediate(T3, target::UntaggedObject::kSizeTagMaxSizeTag);
      compiler::Label zero_tag;
      __ BranchIf(UNSIGNED_GREATER, &zero_tag);
      __ slli(T5, T3, shift);
      __ Bind(&zero_tag);
  
      // Get the class index and insert it into the tags.
      const uword tags =
          target::MakeTagWordForNewSpaceObject(cid, /*instance_size=*/0);
  
      __ OrImmediate(T5, T5, tags);
      __ StoreFieldToOffset(T5, AllocateArrayABI::kResultReg,
                            target::Array::tags_offset());
  
      // Initialize all array elements to raw_null.
      // AllocateArrayABI::kResultReg: new object start as a tagged pointer.
      // R7: new object end address.
      // AllocateArrayABI::kLengthReg: array length as Smi.
      __ AddImmediate(T3, AllocateArrayABI::kResultReg,
                      target::Array::data_offset() - kHeapObjectTag);
      // R3: iterator which initially points to the start of the variable
      // data area to be initialized.
      Label loop, done;
      __ Bind(&loop);
      // TODO(cshapiro): StoreIntoObjectNoBarrier
      __ bgeu(T3, T4, &done);
      __ sx(NULL_REG, Address(T3, 0));
      __ sx(NULL_REG, Address(T3, target::kCompressedWordSize));
      __ AddImmediate(T3, 2 * target::kCompressedWordSize);
      __ j(&loop);  // Loop until T3 == T4.
      __ Bind(&done);
  
      // Done allocating and initializing the array.
      // AllocateArrayABI::kResultReg: new object.
      // AllocateArrayABI::kLengthReg: array length as Smi (preserved).
      __ ret();
  
      // Unable to allocate the array using the fast inline code, just call
      // into the runtime.
      __ Bind(&slow_case);
    }
  
    // Create a stub frame as we are pushing some objects on the stack before
    // calling into the runtime.
    __ EnterStubFrame();
    __ subi(SP, SP, 3 * target::kWordSize);
    __ sx(ZR, Address(SP, 2 * target::kWordSize));  // Result slot.
    __ sx(AllocateArrayABI::kLengthReg, Address(SP, 1 * target::kWordSize));
    __ sx(AllocateArrayABI::kTypeArgumentsReg,
          Address(SP, 0 * target::kWordSize));
    __ CallRuntime(kAllocateArrayRuntimeEntry, 2);
    __ lx(AllocateArrayABI::kTypeArgumentsReg,
          Address(SP, 0 * target::kWordSize));
    __ lx(AllocateArrayABI::kLengthReg, Address(SP, 1 * target::kWordSize));
    __ lx(AllocateArrayABI::kResultReg, Address(SP, 2 * target::kWordSize));
    __ addi(SP, SP, 3 * target::kWordSize);
    __ LeaveStubFrame();
  
    // Write-barrier elimination might be enabled for this array (depending on the
    // array length). To be sure we will check if the allocated object is in old
    // space and if so call a leaf runtime to add it to the remembered set.
    ASSERT(AllocateArrayABI::kResultReg == A0);
    EnsureIsNewOrRemembered(assembler);
  
    __ ret();
  }
  
  void StubCodeCompiler::GenerateAllocateMintSharedWithFPURegsStub(
      Assembler* assembler) {
    // For test purpose call allocation stub without inline allocation attempt.
    if (!FLAG_use_slow_path && FLAG_inline_alloc) {
      Label slow_case;
      __ TryAllocate(compiler::MintClass(), &slow_case, Assembler::kNearJump,
                     AllocateMintABI::kResultReg, AllocateMintABI::kTempReg);
      __ ret();
  
      __ Bind(&slow_case);
    }
    COMPILE_ASSERT(AllocateMintABI::kResultReg ==
                   SharedSlowPathStubABI::kResultReg);
    GenerateSharedStub(assembler, /*save_fpu_registers=*/true,
                       &kAllocateMintRuntimeEntry,
                       target::Thread::allocate_mint_with_fpu_regs_stub_offset(),
                       /*allow_return=*/true,
                       /*store_runtime_result_in_result_register=*/true);
  }
  
  void StubCodeCompiler::GenerateAllocateMintSharedWithoutFPURegsStub(
      Assembler* assembler) {
    // For test purpose call allocation stub without inline allocation attempt.
    if (!FLAG_use_slow_path && FLAG_inline_alloc) {
      Label slow_case;
      __ TryAllocate(compiler::MintClass(), &slow_case, Assembler::kNearJump,
                     AllocateMintABI::kResultReg, AllocateMintABI::kTempReg);
      __ ret();
  
      __ Bind(&slow_case);
    }
    COMPILE_ASSERT(AllocateMintABI::kResultReg ==
                   SharedSlowPathStubABI::kResultReg);
    GenerateSharedStub(
        assembler, /*save_fpu_registers=*/false, &kAllocateMintRuntimeEntry,
        target::Thread::allocate_mint_without_fpu_regs_stub_offset(),
        /*allow_return=*/true,
        /*store_runtime_result_in_result_register=*/true);
  }
  
  // Called when invoking Dart code from C++ (VM code).
  // Input parameters:
  //   RA : points to return address.
  //   A0 : target code or entry point (in bare instructions mode).
  //   A1 : arguments descriptor array.
  //   A2 : arguments array.
  //   A3 : current thread.
  // Beware!  TMP == A3
  void StubCodeCompiler::GenerateInvokeDartCodeStub(Assembler* assembler) {
    __ Comment("InvokeDartCodeStub");
  
    __ EnterFrame(0);
  
    // Push code object to PC marker slot.
    __ lx(TMP2, Address(A3, target::Thread::invoke_dart_code_stub_offset()));
    __ PushRegister(TMP2);
  
  #if defined(USING_SHADOW_CALL_STACK)
  #error Unimplemented
  #endif
  
    // TODO(riscv): Consider using only volatile FPU registers in Dart code so we
    // don't need to save the preserved FPU registers here.
    __ PushNativeCalleeSavedRegisters();
  
    // Set up THR, which caches the current thread in Dart code.
    if (THR != A3) {
      __ mv(THR, A3);
    }
  
    // Refresh pinned registers values (inc. write barrier mask and null object).
    __ RestorePinnedRegisters();
  
    // Save the current VMTag on the stack.
    __ LoadFromOffset(TMP, THR, target::Thread::vm_tag_offset());
    __ PushRegister(TMP);
  
    // Save top resource and top exit frame info. Use R6 as a temporary register.
    // StackFrameIterator reads the top exit frame info saved in this frame.
    __ LoadFromOffset(TMP, THR, target::Thread::top_resource_offset());
    __ StoreToOffset(ZR, THR, target::Thread::top_resource_offset());
    __ PushRegister(TMP);
  
    __ LoadFromOffset(TMP, THR, target::Thread::exit_through_ffi_offset());
    __ StoreToOffset(ZR, THR, target::Thread::exit_through_ffi_offset());
    __ PushRegister(TMP);
  
    __ LoadFromOffset(TMP, THR, target::Thread::top_exit_frame_info_offset());
    __ StoreToOffset(ZR, THR, target::Thread::top_exit_frame_info_offset());
    __ PushRegister(TMP);
    // target::frame_layout.exit_link_slot_from_entry_fp must be kept in sync
    // with the code below.
  #if XLEN == 32
    ASSERT_EQUAL(target::frame_layout.exit_link_slot_from_entry_fp, -42);
  #elif XLEN == 64
    ASSERT_EQUAL(target::frame_layout.exit_link_slot_from_entry_fp, -30);
  #endif
    // In debug mode, verify that we've pushed the top exit frame info at the
    // correct offset from FP.
    __ EmitEntryFrameVerification();
  
    // Mark that the thread is executing Dart code. Do this after initializing the
    // exit link for the profiler.
    __ LoadImmediate(TMP, VMTag::kDartTagId);
    __ StoreToOffset(TMP, THR, target::Thread::vm_tag_offset());
  
    // Load arguments descriptor array, which is passed to Dart code.
    __ LoadFromOffset(ARGS_DESC_REG, A1, VMHandles::kOffsetOfRawPtrInHandle);
  
    // Load number of arguments into T5 and adjust count for type arguments.
    __ LoadFieldFromOffset(T5, ARGS_DESC_REG,
                           target::ArgumentsDescriptor::count_offset());
    __ LoadFieldFromOffset(T3, ARGS_DESC_REG,
                           target::ArgumentsDescriptor::type_args_len_offset());
    __ SmiUntag(T5);
    // Include the type arguments.
    __ snez(T3, T3);  // T3 <- T3 == 0 ? 0 : 1
    __ add(T5, T5, T3);
  
    // Compute address of 'arguments array' data area into A2.
    __ LoadFromOffset(A2, A2, VMHandles::kOffsetOfRawPtrInHandle);
    __ AddImmediate(A2, target::Array::data_offset() - kHeapObjectTag);
  
    // Set up arguments for the Dart call.
    Label push_arguments;
    Label done_push_arguments;
    __ beqz(T5, &done_push_arguments);  // check if there are arguments.
    __ LoadImmediate(T2, 0);
    __ Bind(&push_arguments);
    __ lx(T3, Address(A2, 0));
    __ PushRegister(T3);
    __ addi(T2, T2, 1);
    __ addi(A2, A2, target::kWordSize);
    __ blt(T2, T5, &push_arguments, compiler::Assembler::kNearJump);
    __ Bind(&done_push_arguments);
  
    if (FLAG_precompiled_mode) {
      __ SetupGlobalPoolAndDispatchTable();
      __ mv(CODE_REG, ZR);  // GC-safe value into CODE_REG.
    } else {
      // We now load the pool pointer(PP) with a GC safe value as we are about to
      // invoke dart code. We don't need a real object pool here.
      __ li(PP, 1);  // PP is untagged, callee will tag and spill PP.
      __ lx(CODE_REG, Address(A0, VMHandles::kOffsetOfRawPtrInHandle));
      __ lx(A0, FieldAddress(CODE_REG, target::Code::entry_point_offset()));
    }
  
    // Call the Dart code entrypoint.
    __ jalr(A0);  // ARGS_DESC_REG is the arguments descriptor array.
    __ Comment("InvokeDartCodeStub return");
  
    // Get rid of arguments pushed on the stack.
    __ addi(
        SP, FP,
        target::frame_layout.exit_link_slot_from_entry_fp * target::kWordSize);
  
    // Restore the saved top exit frame info and top resource back into the
    // Isolate structure. Uses R6 as a temporary register for this.
    __ PopRegister(TMP);
    __ StoreToOffset(TMP, THR, target::Thread::top_exit_frame_info_offset());
    __ PopRegister(TMP);
    __ StoreToOffset(TMP, THR, target::Thread::exit_through_ffi_offset());
    __ PopRegister(TMP);
    __ StoreToOffset(TMP, THR, target::Thread::top_resource_offset());
  
    // Restore the current VMTag from the stack.
    __ PopRegister(TMP);
    __ StoreToOffset(TMP, THR, target::Thread::vm_tag_offset());
  
    __ PopNativeCalleeSavedRegisters();
  
    // Restore the frame pointer and C stack pointer and return.
    __ LeaveFrame();
    __ ret();
  }
  
  // Helper to generate space allocation of context stub.
  // This does not initialise the fields of the context.
  // Input:
  //   T1: number of context variables.
  // Output:
  //   A0: new allocated Context object.
  // Clobbered:
  //   T2, T3, T4, TMP
  static void GenerateAllocateContextSpaceStub(Assembler* assembler,
                                               Label* slow_case) {
    // First compute the rounded instance size.
    // R1: number of context variables.
    intptr_t fixed_size_plus_alignment_padding =
        target::Context::header_size() +
        target::ObjectAlignment::kObjectAlignment - 1;
    __ slli(T2, T1, kCompressedWordSizeLog2);
    __ AddImmediate(T2, fixed_size_plus_alignment_padding);
    __ andi(T2, T2, ~(target::ObjectAlignment::kObjectAlignment - 1));
  
    NOT_IN_PRODUCT(__ MaybeTraceAllocation(kContextCid, T4, slow_case));
    // Now allocate the object.
    // T1: number of context variables.
    // T2: object size.
    __ lx(A0, Address(THR, target::Thread::top_offset()));
    __ add(T3, T2, A0);
    // Check if the allocation fits into the remaining space.
    // A0: potential new object.
    // T1: number of context variables.
    // T2: object size.
    // T3: potential next object start.
    __ lx(TMP, Address(THR, target::Thread::end_offset()));
    __ CompareRegisters(T3, TMP);
    __ BranchIf(CS, slow_case);  // Branch if unsigned higher or equal.
  
    // Successfully allocated the object, now update top to point to
    // next object start and initialize the object.
    // A0: new object.
    // T1: number of context variables.
    // T2: object size.
    // T3: next object start.
    __ sx(T3, Address(THR, target::Thread::top_offset()));
    __ addi(A0, A0, kHeapObjectTag);
  
    // Calculate the size tag.
    // A0: new object.
    // T1: number of context variables.
    // T2: object size.
    const intptr_t shift = target::UntaggedObject::kTagBitsSizeTagPos -
                           target::ObjectAlignment::kObjectAlignmentLog2;
    __ li(T3, 0);
    __ CompareImmediate(T2, target::UntaggedObject::kSizeTagMaxSizeTag);
    // If no size tag overflow, shift R2 left, else set R2 to zero.
    compiler::Label zero_tag;
    __ BranchIf(HI, &zero_tag);
    __ slli(T3, T2, shift);
    __ Bind(&zero_tag);
  
    // Get the class index and insert it into the tags.
    // T3: size and bit tags.
    const uword tags =
        target::MakeTagWordForNewSpaceObject(kContextCid, /*instance_size=*/0);
  
    __ OrImmediate(T3, T3, tags);
    __ StoreFieldToOffset(T3, A0, target::Object::tags_offset());
  
    // Setup up number of context variables field.
    // A0: new object.
    // T1: number of context variables as integer value (not object).
    __ StoreFieldToOffset(T1, A0, target::Context::num_variables_offset(),
                          kFourBytes);
  }
  
  // Called for inline allocation of contexts.
  // Input:
  //   T1: number of context variables.
  // Output:
  //   A0: new allocated Context object.
  void StubCodeCompiler::GenerateAllocateContextStub(Assembler* assembler) {
    if (!FLAG_use_slow_path && FLAG_inline_alloc) {
      Label slow_case;
  
      GenerateAllocateContextSpaceStub(assembler, &slow_case);
  
      // Setup the parent field.
      // A0: new object.
      // T1: number of context variables.
      __ StoreCompressedIntoObjectOffset(A0, target::Context::parent_offset(),
                                         NULL_REG);
  
      // Initialize the context variables.
      // A0: new object.
      // T1: number of context variables.
      {
        Label loop, done;
        __ AddImmediate(T3, A0,
                        target::Context::variable_offset(0) - kHeapObjectTag);
        __ Bind(&loop);
        __ subi(T1, T1, 1);
        __ bltz(T1, &done);
        __ sx(NULL_REG, Address(T3, 0));
        __ addi(T3, T3, target::kCompressedWordSize);
        __ j(&loop);
        __ Bind(&done);
      }
  
      // Done allocating and initializing the context.
      // A0: new object.
      __ ret();
  
      __ Bind(&slow_case);
    }
  
    // Create a stub frame as we are pushing some objects on the stack before
    // calling into the runtime.
    __ EnterStubFrame();
    // Setup space on stack for return value.
    __ SmiTag(T1);
    __ PushObject(NullObject());
    __ PushRegister(T1);
    __ CallRuntime(kAllocateContextRuntimeEntry, 1);  // Allocate context.
    __ Drop(1);          // Pop number of context variables argument.
    __ PopRegister(A0);  // Pop the new context object.
  
    // Write-barrier elimination might be enabled for this context (depending on
    // the size). To be sure we will check if the allocated object is in old
    // space and if so call a leaf runtime to add it to the remembered set.
    EnsureIsNewOrRemembered(assembler, /*preserve_registers=*/false);
  
    // A0: new object
    // Restore the frame pointer.
    __ LeaveStubFrame();
    __ ret();
  }
  
  // Called for clone of contexts.
  // Input:
  //   T5: context variable to clone.
  // Output:
  //   A0: new allocated Context object.
  void StubCodeCompiler::GenerateCloneContextStub(Assembler* assembler) {
    if (!FLAG_use_slow_path && FLAG_inline_alloc) {
      Label slow_case;
  
      // Load num. variable (int32) in the existing context.
      __ lw(T1, FieldAddress(T5, target::Context::num_variables_offset()));
  
      GenerateAllocateContextSpaceStub(assembler, &slow_case);
  
      // Load parent in the existing context.
      __ LoadCompressed(T3, FieldAddress(T5, target::Context::parent_offset()));
      // Setup the parent field.
      // A0: new context.
      __ StoreCompressedIntoObjectNoBarrier(
          A0, FieldAddress(A0, target::Context::parent_offset()), T3);
  
      // Clone the context variables.
      // A0: new context.
      // T1: number of context variables.
      {
        Label loop, done;
        // T3: Variable array address, new context.
        __ AddImmediate(T3, A0,
                        target::Context::variable_offset(0) - kHeapObjectTag);
        // T4: Variable array address, old context.
        __ AddImmediate(T4, T5,
                        target::Context::variable_offset(0) - kHeapObjectTag);
  
        __ Bind(&loop);
        __ subi(T1, T1, 1);
        __ bltz(T1, &done);
        __ lx(T5, Address(T4, 0));
        __ addi(T4, T4, target::kCompressedWordSize);
        __ sx(T5, Address(T3, 0));
        __ addi(T3, T3, target::kCompressedWordSize);
        __ j(&loop);
  
        __ Bind(&done);
      }
  
      // Done allocating and initializing the context.
      // A0: new object.
      __ ret();
  
      __ Bind(&slow_case);
    }
  
    __ EnterStubFrame();
  
    __ subi(SP, SP, 2 * target::kWordSize);
    __ sx(NULL_REG, Address(SP, 1 * target::kWordSize));  // Result slot.
    __ sx(T5, Address(SP, 0 * target::kWordSize));        // Context argument.
    __ CallRuntime(kCloneContextRuntimeEntry, 1);
    __ lx(A0, Address(SP, 1 * target::kWordSize));  // Context result.
    __ subi(SP, SP, 2 * target::kWordSize);
  
    // Write-barrier elimination might be enabled for this context (depending on
    // the size). To be sure we will check if the allocated object is in old
    // space and if so call a leaf runtime to add it to the remembered set.
    EnsureIsNewOrRemembered(assembler, /*preserve_registers=*/false);
  
    // A0: new object
    __ LeaveStubFrame();
    __ ret();
  }
  
  void StubCodeCompiler::GenerateWriteBarrierWrappersStub(Assembler* assembler) {
    for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
      if ((kDartAvailableCpuRegs & (1 << i)) == 0) continue;
  
      Register reg = static_cast<Register>(i);
      intptr_t start = __ CodeSize();
      __ addi(SP, SP, -3 * target::kWordSize);
      __ sx(RA, Address(SP, 2 * target::kWordSize));
      __ sx(TMP, Address(SP, 1 * target::kWordSize));
      __ sx(kWriteBarrierObjectReg, Address(SP, 0 * target::kWordSize));
      __ mv(kWriteBarrierObjectReg, reg);
      __ Call(Address(THR, target::Thread::write_barrier_entry_point_offset()));
      __ lx(kWriteBarrierObjectReg, Address(SP, 0 * target::kWordSize));
      __ lx(TMP, Address(SP, 1 * target::kWordSize));
      __ lx(RA, Address(SP, 2 * target::kWordSize));
      __ addi(SP, SP, 3 * target::kWordSize);
      __ jr(TMP);  // Return.
      intptr_t end = __ CodeSize();
      ASSERT_EQUAL(end - start, kStoreBufferWrapperSize);
    }
  }
  
  // Helper stub to implement Assembler::StoreIntoObject/Array.
  // Input parameters:
  //   A0: Object (old)
  //   A1: Value (old or new)
  //   A6: Slot
  // If A1 is new, add A0 to the store buffer. Otherwise A1 is old, mark A1
  // and add it to the mark list.
  COMPILE_ASSERT(kWriteBarrierObjectReg == A0);
  COMPILE_ASSERT(kWriteBarrierValueReg == A1);
  COMPILE_ASSERT(kWriteBarrierSlotReg == A6);
  static void GenerateWriteBarrierStubHelper(Assembler* assembler,
                                             Address stub_code,
                                             bool cards) {
    Label add_to_mark_stack, remember_card, lost_race;
    __ andi(TMP2, A1, 1 << target::ObjectAlignment::kNewObjectBitPosition);
    __ beqz(TMP2, &add_to_mark_stack);
  
    if (cards) {
      __ lbu(TMP2, FieldAddress(A0, target::Object::tags_offset()));
      __ andi(TMP2, TMP2, 1 << target::UntaggedObject::kCardRememberedBit);
      __ bnez(TMP2, &remember_card);
    } else {
  #if defined(DEBUG)
      Label ok;
      __ lbu(TMP2, FieldAddress(A0, target::Object::tags_offset()));
      __ andi(TMP2, TMP2, 1 << target::UntaggedObject::kCardRememberedBit);
      __ beqz(TMP2, &ok, Assembler::kNearJump);
      __ Stop("Wrong barrier!");
      __ Bind(&ok);
  #endif
    }
  
    // Spill T2, T3, T4.
    __ subi(SP, SP, 3 * target::kWordSize);
    __ sx(T2, Address(SP, 2 * target::kWordSize));
    __ sx(T3, Address(SP, 1 * target::kWordSize));
    __ sx(T4, Address(SP, 0 * target::kWordSize));
  
    // Atomically clear kOldAndNotRememberedBit.
    // TODO(riscv): Use amoand instead of lr/sc.
    ASSERT(target::Object::tags_offset() == 0);
    __ subi(T3, A0, kHeapObjectTag);
    // T3: Untagged address of header word (lr/sc do not support offsets).
    Label retry;
    __ Bind(&retry);
    __ lr(T2, Address(T3, 0));
    __ andi(TMP2, T2, 1 << target::UntaggedObject::kOldAndNotRememberedBit);
    __ beqz(TMP2, &lost_race);
    __ andi(T2, T2, ~(1 << target::UntaggedObject::kOldAndNotRememberedBit));
    __ sc(T4, T2, Address(T3, 0));
    __ bnez(T4, &retry);
  
    // Load the StoreBuffer block out of the thread. Then load top_ out of the
    // StoreBufferBlock and add the address to the pointers_.
    __ LoadFromOffset(T4, THR, target::Thread::store_buffer_block_offset());
    __ LoadFromOffset(T2, T4, target::StoreBufferBlock::top_offset(),
                      kUnsignedFourBytes);
    __ slli(T3, T2, target::kWordSizeLog2);
    __ add(T3, T4, T3);
    __ StoreToOffset(A0, T3, target::StoreBufferBlock::pointers_offset());
  
    // Increment top_ and check for overflow.
    // T2: top_.
    // T4: StoreBufferBlock.
    Label overflow;
    __ addi(T2, T2, 1);
    __ StoreToOffset(T2, T4, target::StoreBufferBlock::top_offset(),
                     kUnsignedFourBytes);
    __ CompareImmediate(T2, target::StoreBufferBlock::kSize);
    // Restore values.
    __ BranchIf(EQ, &overflow);
  
    // Restore T2, T3, T4.
    __ lx(T4, Address(SP, 0 * target::kWordSize));
    __ lx(T3, Address(SP, 1 * target::kWordSize));
    __ lx(T2, Address(SP, 2 * target::kWordSize));
    __ addi(SP, SP, 3 * target::kWordSize);
    __ ret();
  
    // Handle overflow: Call the runtime leaf function.
    __ Bind(&overflow);
    // Restore T2, T3, T4.
    __ lx(T4, Address(SP, 0 * target::kWordSize));
    __ lx(T3, Address(SP, 1 * target::kWordSize));
    __ lx(T2, Address(SP, 2 * target::kWordSize));
    __ addi(SP, SP, 3 * target::kWordSize);
    {
      Assembler::CallRuntimeScope scope(assembler,
                                        kStoreBufferBlockProcessRuntimeEntry,
                                        /*frame_size=*/0, stub_code);
      __ mv(A0, THR);
      scope.Call(/*argument_count=*/1);
    }
    __ ret();
  
    __ Bind(&add_to_mark_stack);
    // Spill T2, T3, T4.
    __ subi(SP, SP, 3 * target::kWordSize);
    __ sx(T2, Address(SP, 2 * target::kWordSize));
    __ sx(T3, Address(SP, 1 * target::kWordSize));
    __ sx(T4, Address(SP, 0 * target::kWordSize));
  
    // Atomically clear kOldAndNotMarkedBit.
    // TODO(riscv): Use amoand instead of lr/sc.
    Label marking_retry, marking_overflow;
    ASSERT(target::Object::tags_offset() == 0);
    __ subi(T3, A1, kHeapObjectTag);
    // T3: Untagged address of header word (lr/sc do not support offsets).
    __ Bind(&marking_retry);
    __ lr(T2, Address(T3, 0));
    __ andi(TMP2, T2, 1 << target::UntaggedObject::kOldAndNotMarkedBit);
    __ beqz(TMP2, &lost_race);
    __ andi(T2, T2, ~(1 << target::UntaggedObject::kOldAndNotMarkedBit));
    __ sc(T4, T2, Address(T3, 0));
    __ bnez(T4, &marking_retry);
  
    __ LoadFromOffset(T4, THR, target::Thread::marking_stack_block_offset());
    __ LoadFromOffset(T2, T4, target::MarkingStackBlock::top_offset(),
                      kUnsignedFourBytes);
    __ slli(T3, T2, target::kWordSizeLog2);
    __ add(T3, T4, T3);
    __ StoreToOffset(A1, T3, target::MarkingStackBlock::pointers_offset());
    __ addi(T2, T2, 1);
    __ StoreToOffset(T2, T4, target::MarkingStackBlock::top_offset(),
                     kUnsignedFourBytes);
    __ CompareImmediate(T2, target::MarkingStackBlock::kSize);
    __ BranchIf(EQ, &marking_overflow);
    // Restore T2, T3, T4.
    __ lx(T4, Address(SP, 0 * target::kWordSize));
    __ lx(T3, Address(SP, 1 * target::kWordSize));
    __ lx(T2, Address(SP, 2 * target::kWordSize));
    __ addi(SP, SP, 3 * target::kWordSize);
    __ ret();
  
    __ Bind(&marking_overflow);
    // Restore T2, T3, T4.
    __ lx(T4, Address(SP, 0 * target::kWordSize));
    __ lx(T3, Address(SP, 1 * target::kWordSize));
    __ lx(T2, Address(SP, 2 * target::kWordSize));
    __ addi(SP, SP, 3 * target::kWordSize);
    {
      Assembler::CallRuntimeScope scope(assembler,
                                        kMarkingStackBlockProcessRuntimeEntry,
                                        /*frame_size=*/0, stub_code);
      __ mv(A0, THR);
      scope.Call(/*argument_count=*/1);
    }
    __ ret();
  
    __ Bind(&lost_race);
    // Restore T2, T3, T4.
    __ lx(T4, Address(SP, 0 * target::kWordSize));
    __ lx(T3, Address(SP, 1 * target::kWordSize));
    __ lx(T2, Address(SP, 2 * target::kWordSize));
    __ addi(SP, SP, 3 * target::kWordSize);
    __ ret();
  
    if (cards) {
      Label remember_card_slow;
  
      // Get card table.
      __ Bind(&remember_card);
      __ AndImmediate(TMP, A0, target::kOldPageMask);  // OldPage.
      __ lx(TMP,
            Address(TMP, target::OldPage::card_table_offset()));  // Card table.
      __ beqz(TMP, &remember_card_slow);
  
      // Dirty the card.
      __ AndImmediate(TMP, A0, target::kOldPageMask);  // OldPage.
      __ sub(A6, A6, TMP);                             // Offset in page.
      __ lx(TMP,
            Address(TMP, target::OldPage::card_table_offset()));  // Card table.
      __ srli(A6, A6, target::OldPage::kBytesPerCardLog2);
      __ add(TMP, TMP, A6);        // Card address.
      __ sb(A0, Address(TMP, 0));  // Low byte of A0 is non-zero from object tag.
      __ ret();
  
      // Card table not yet allocated.
      __ Bind(&remember_card_slow);
      {
        Assembler::CallRuntimeScope scope(assembler, kRememberCardRuntimeEntry,
                                          /*frame_size=*/0, stub_code);
        __ mv(A0, A0);  // Arg0 = Object
        __ mv(A1, A6);  // Arg1 = Slot
        scope.Call(/*argument_count=*/2);
      }
      __ ret();
    }
  }
  
  void StubCodeCompiler::GenerateWriteBarrierStub(Assembler* assembler) {
    GenerateWriteBarrierStubHelper(
        assembler, Address(THR, target::Thread::write_barrier_code_offset()),
        false);
  }
  
  void StubCodeCompiler::GenerateArrayWriteBarrierStub(Assembler* assembler) {
    GenerateWriteBarrierStubHelper(
        assembler,
        Address(THR, target::Thread::array_write_barrier_code_offset()), true);
  }
  
  static void GenerateAllocateObjectHelper(Assembler* assembler,
                                           bool is_cls_parameterized) {
    const Register kTagsReg = AllocateObjectABI::kTagsReg;
  
    {
      Label slow_case;
  
      const Register kNewTopReg = T3;
  
      // Bump allocation.
      {
        const Register kInstanceSizeReg = T4;
        const Register kEndReg = T5;
  
        __ ExtractInstanceSizeFromTags(kInstanceSizeReg, kTagsReg);
  
        // Load two words from Thread::top: top and end.
        // AllocateObjectABI::kResultReg: potential next object start.
        __ lx(AllocateObjectABI::kResultReg,
              Address(THR, target::Thread::top_offset()));
        __ lx(kEndReg, Address(THR, target::Thread::end_offset()));
  
        __ add(kNewTopReg, AllocateObjectABI::kResultReg, kInstanceSizeReg);
  
        __ CompareRegisters(kEndReg, kNewTopReg);
        __ BranchIf(UNSIGNED_LESS_EQUAL, &slow_case);
  
        // Successfully allocated the object, now update top to point to
        // next object start and store the class in the class field of object.
        __ sx(kNewTopReg, Address(THR, target::Thread::top_offset()));
      }  // kInstanceSizeReg = R4, kEndReg = R5
  
      // Tags.
      __ sx(kTagsReg, Address(AllocateObjectABI::kResultReg,
                              target::Object::tags_offset()));
  
      // Initialize the remaining words of the object.
      {
        const Register kFieldReg = T4;
  
        __ AddImmediate(kFieldReg, AllocateObjectABI::kResultReg,
                        target::Instance::first_field_offset());
        Label done, init_loop;
        __ Bind(&init_loop);
        __ CompareRegisters(kFieldReg, kNewTopReg);
        __ BranchIf(UNSIGNED_GREATER_EQUAL, &done);
        __ sx(NULL_REG, Address(kFieldReg, 0));
        __ addi(kFieldReg, kFieldReg, target::kCompressedWordSize);
        __ j(&init_loop);
  
        __ Bind(&done);
      }  // kFieldReg = T4
  
      if (is_cls_parameterized) {
        Label not_parameterized_case;
  
        const Register kClsIdReg = T4;
        const Register kTypeOffsetReg = T5;
  
        __ ExtractClassIdFromTags(kClsIdReg, kTagsReg);
  
        // Load class' type_arguments_field offset in words.
        __ LoadClassById(kTypeOffsetReg, kClsIdReg);
        __ lw(
            kTypeOffsetReg,
            FieldAddress(kTypeOffsetReg,
                         target::Class::
                             host_type_arguments_field_offset_in_words_offset()));
  
        // Set the type arguments in the new object.
        __ slli(kTypeOffsetReg, kTypeOffsetReg, target::kWordSizeLog2);
        __ add(kTypeOffsetReg, kTypeOffsetReg, AllocateObjectABI::kResultReg);
        __ sx(AllocateObjectABI::kTypeArgumentsReg, Address(kTypeOffsetReg, 0));
  
        __ Bind(&not_parameterized_case);
      }  // kClsIdReg = R4, kTypeOffestReg = R5
  
      __ AddImmediate(AllocateObjectABI::kResultReg,
                      AllocateObjectABI::kResultReg, kHeapObjectTag);
  
      __ ret();
  
      __ Bind(&slow_case);
    }  // kNewTopReg = R3
  
    // Fall back on slow case:
    if (!is_cls_parameterized) {
      __ mv(AllocateObjectABI::kTypeArgumentsReg, NULL_REG);
    }
    // Tail call to generic allocation stub.
    __ lx(
        TMP,
        Address(THR, target::Thread::allocate_object_slow_entry_point_offset()));
    __ jr(TMP);
  }
  
  // Called for inline allocation of objects (any class).
  void StubCodeCompiler::GenerateAllocateObjectStub(Assembler* assembler) {
    GenerateAllocateObjectHelper(assembler, /*is_cls_parameterized=*/false);
  }
  
  void StubCodeCompiler::GenerateAllocateObjectParameterizedStub(
      Assembler* assembler) {
    GenerateAllocateObjectHelper(assembler, /*is_cls_parameterized=*/true);
  }
  
  void StubCodeCompiler::GenerateAllocateObjectSlowStub(Assembler* assembler) {
    if (!FLAG_precompiled_mode) {
      __ lx(CODE_REG,
            Address(THR, target::Thread::call_to_runtime_stub_offset()));
    }
  
    // Create a stub frame as we are pushing some objects on the stack before
    // calling into the runtime.
    __ EnterStubFrame();
  
    __ ExtractClassIdFromTags(AllocateObjectABI::kTagsReg,
                              AllocateObjectABI::kTagsReg);
    __ LoadClassById(A0, AllocateObjectABI::kTagsReg);
  
    __ subi(SP, SP, 3 * target::kWordSize);
    __ sx(ZR, Address(SP, 2 * target::kWordSize));  // Result slot.
    __ sx(A0, Address(SP, 1 * target::kWordSize));  // Arg0: Class object.
    __ sx(AllocateObjectABI::kTypeArgumentsReg,
          Address(SP, 0 * target::kWordSize));  // Arg1: Type args or null.
    __ CallRuntime(kAllocateObjectRuntimeEntry, 2);
    __ lx(AllocateObjectABI::kResultReg, Address(SP, 2 * target::kWordSize));
    __ addi(SP, SP, 3 * target::kWordSize);
  
    // Write-barrier elimination is enabled for [cls] and we therefore need to
    // ensure that the object is in new-space or has remembered bit set.
    EnsureIsNewOrRemembered(assembler, /*preserve_registers=*/false);
  
    __ LeaveStubFrame();
  
    __ ret();
  }
  
  // Called for inline allocation of objects.
  void StubCodeCompiler::GenerateAllocationStubForClass(
      Assembler* assembler,
      UnresolvedPcRelativeCalls* unresolved_calls,
      const Class& cls,
      const Code& allocate_object,
      const Code& allocat_object_parametrized) {
    classid_t cls_id = target::Class::GetId(cls);
    ASSERT(cls_id != kIllegalCid);
  
    RELEASE_ASSERT(AllocateObjectInstr::WillAllocateNewOrRemembered(cls));
  
    // The generated code is different if the class is parameterized.
    const bool is_cls_parameterized = target::Class::NumTypeArguments(cls) > 0;
    ASSERT(!is_cls_parameterized || target::Class::TypeArgumentsFieldOffset(
                                        cls) != target::Class::kNoTypeArguments);
  
    const intptr_t instance_size = target::Class::GetInstanceSize(cls);
    ASSERT(instance_size > 0);
    RELEASE_ASSERT(target::Heap::IsAllocatableInNewSpace(instance_size));
  
    const uword tags =
        target::MakeTagWordForNewSpaceObject(cls_id, instance_size);
  
    // Note: Keep in sync with helper function.
    const Register kTagsReg = AllocateObjectABI::kTagsReg;
    ASSERT(kTagsReg != AllocateObjectABI::kTypeArgumentsReg);
  
    __ LoadImmediate(kTagsReg, tags);
  
    if (!FLAG_use_slow_path && FLAG_inline_alloc &&
        !target::Class::TraceAllocation(cls) &&
        target::SizeFitsInSizeTag(instance_size)) {
      if (is_cls_parameterized) {
        // TODO(41974): Assign all allocation stubs to the root loading unit?
        if (false &&
            !IsSameObject(NullObject(),
                          CastHandle<Object>(allocat_object_parametrized))) {
          __ GenerateUnRelocatedPcRelativeTailCall();
          unresolved_calls->Add(new UnresolvedPcRelativeCall(
              __ CodeSize(), allocat_object_parametrized, /*is_tail_call=*/true));
        } else {
          __ lx(TMP,
                Address(THR,
                        target::Thread::
                            allocate_object_parameterized_entry_point_offset()));
          __ jr(TMP);
        }
      } else {
        // TODO(41974): Assign all allocation stubs to the root loading unit?
        if (false &&
            !IsSameObject(NullObject(), CastHandle<Object>(allocate_object))) {
          __ GenerateUnRelocatedPcRelativeTailCall();
          unresolved_calls->Add(new UnresolvedPcRelativeCall(
              __ CodeSize(), allocate_object, /*is_tail_call=*/true));
        } else {
          __ lx(
              TMP,
              Address(THR, target::Thread::allocate_object_entry_point_offset()));
          __ jr(TMP);
        }
      }
    } else {
      if (!is_cls_parameterized) {
        __ LoadObject(AllocateObjectABI::kTypeArgumentsReg, NullObject());
      }
      __ lx(TMP,
            Address(THR,
                    target::Thread::allocate_object_slow_entry_point_offset()));
      __ jr(TMP);
    }
  }
  
  // Called for invoking "dynamic noSuchMethod(Invocation invocation)" function
  // from the entry code of a dart function after an error in passed argument
  // name or number is detected.
  // Input parameters:
  //  RA : return address.
  //  SP : address of last argument.
  //  S4: arguments descriptor array.
  void StubCodeCompiler::GenerateCallClosureNoSuchMethodStub(
      Assembler* assembler) {
    __ EnterStubFrame();
  
    // Load the receiver.
    __ LoadCompressedSmiFieldFromOffset(
        T2, S4, target::ArgumentsDescriptor::size_offset());
    __ slli(TMP, T2, target::kWordSizeLog2 - 1);  // T2 is Smi
    __ add(TMP, TMP, FP);
    __ LoadFromOffset(A0, TMP,
                      target::frame_layout.param_end_from_fp * target::kWordSize);
  
    // Load the function.
    __ LoadCompressedFieldFromOffset(TMP, A0, target::Closure::function_offset());
  
    __ PushRegister(ZR);   // Result slot.
    __ PushRegister(A0);   // Receiver.
    __ PushRegister(TMP);  // Function
    __ PushRegister(S4);   // Arguments descriptor.
  
    // Adjust arguments count.
    __ LoadCompressedSmiFieldFromOffset(
        T3, S4, target::ArgumentsDescriptor::type_args_len_offset());
    Label args_count_ok;
    __ beqz(T3, &args_count_ok, Assembler::kNearJump);
    // Include the type arguments.
    __ addi(T2, T2, target::ToRawSmi(1));
    __ Bind(&args_count_ok);
  
    // T2: Smi-tagged arguments array length.
    PushArrayOfArguments(assembler);
  
    const intptr_t kNumArgs = 4;
    __ CallRuntime(kNoSuchMethodFromPrologueRuntimeEntry, kNumArgs);
    // noSuchMethod on closures always throws an error, so it will never return.
    __ ebreak();
  }
  
  //  A6: function object.
  //  S5: inline cache data object.
  // Cannot use function object from ICData as it may be the inlined
  // function and not the top-scope function.
  void StubCodeCompiler::GenerateOptimizedUsageCounterIncrement(
      Assembler* assembler) {
    if (FLAG_precompiled_mode) {
      __ Breakpoint();
      return;
    }
    if (FLAG_trace_optimized_ic_calls) {
      __ Stop("Unimplemented");
    }
    __ LoadFieldFromOffset(TMP, A6, target::Function::usage_counter_offset(),
                           kFourBytes);
    __ addi(TMP, TMP, 1);
    __ StoreFieldToOffset(TMP, A6, target::Function::usage_counter_offset(),
                          kFourBytes);
  }
  
  // Loads function into 'func_reg'.
  void StubCodeCompiler::GenerateUsageCounterIncrement(Assembler* assembler,
                                                       Register func_reg) {
    if (FLAG_precompiled_mode) {
      __ trap();
      return;
    }
    if (FLAG_optimization_counter_threshold >= 0) {
      __ Comment("Increment function counter");
      __ LoadFieldFromOffset(func_reg, IC_DATA_REG,
                             target::ICData::owner_offset());
      __ LoadFieldFromOffset(
          A1, func_reg, target::Function::usage_counter_offset(), kFourBytes);
      __ AddImmediate(A1, 1);
      __ StoreFieldToOffset(A1, func_reg,
                            target::Function::usage_counter_offset(), kFourBytes);
    }
  }
  
  // Note: S5 must be preserved.
  // Attempt a quick Smi operation for known operations ('kind'). The ICData
  // must have been primed with a Smi/Smi check that will be used for counting
  // the invocations.
  static void EmitFastSmiOp(Assembler* assembler,
                            Token::Kind kind,
                            intptr_t num_args,
                            Label* not_smi_or_overflow) {
    __ Comment("Fast Smi op");
    __ lx(A0, Address(SP, +1 * target::kWordSize));  // Left.
    __ lx(A1, Address(SP, +0 * target::kWordSize));  // Right.
    __ or_(TMP2, A0, A1);
    __ andi(TMP2, TMP2, kSmiTagMask);
    __ bnez(TMP2, not_smi_or_overflow);
    switch (kind) {
      case Token::kADD: {
        __ AddBranchOverflow(A0, A0, A1, not_smi_or_overflow);
        break;
      }
      case Token::kLT: {
        // TODO(riscv): Bit tricks with stl and NULL_REG.
        Label load_true, done;
        __ blt(A0, A1, &load_true, compiler::Assembler::kNearJump);
        __ LoadObject(A0, CastHandle<Object>(FalseObject()));
        __ j(&done, Assembler::kNearJump);
        __ Bind(&load_true);
        __ LoadObject(A0, CastHandle<Object>(TrueObject()));
        __ Bind(&done);
        break;
      }
      case Token::kEQ: {
        // TODO(riscv): Bit tricks with stl and NULL_REG.
        Label load_true, done;
        __ beq(A0, A1, &load_true, Assembler::kNearJump);
        __ LoadObject(A0, CastHandle<Object>(FalseObject()));
        __ j(&done, Assembler::kNearJump);
        __ Bind(&load_true);
        __ LoadObject(A0, CastHandle<Object>(TrueObject()));
        __ Bind(&done);
        break;
      }
      default:
        UNIMPLEMENTED();
    }
  
    // S5: IC data object (preserved).
    __ LoadFieldFromOffset(A6, IC_DATA_REG, target::ICData::entries_offset());
    // R6: ic_data_array with check entries: classes and target functions.
    __ AddImmediate(A6, target::Array::data_offset() - kHeapObjectTag);
  // R6: points directly to the first ic data array element.
  #if defined(DEBUG)
    // Check that first entry is for Smi/Smi.
    Label error, ok;
    const intptr_t imm_smi_cid = target::ToRawSmi(kSmiCid);
    __ LoadCompressedSmiFromOffset(TMP, A6, 0);
    __ CompareImmediate(TMP, imm_smi_cid);
    __ BranchIf(NE, &error);
    __ LoadCompressedSmiFromOffset(TMP, A6, target::kCompressedWordSize);
    __ CompareImmediate(TMP, imm_smi_cid);
    __ BranchIf(EQ, &ok);
    __ Bind(&error);
    __ Stop("Incorrect IC data");
    __ Bind(&ok);
  #endif
    if (FLAG_optimization_counter_threshold >= 0) {
      const intptr_t count_offset =
          target::ICData::CountIndexFor(num_args) * target::kCompressedWordSize;
      // Update counter, ignore overflow.
      __ LoadCompressedSmiFromOffset(A1, A6, count_offset);
      __ addi(A1, A1, target::ToRawSmi(1));
      __ StoreToOffset(A1, A6, count_offset);
    }
  
    __ ret();
  }
  
  // Saves the offset of the target entry-point (from the Function) into T6.
  //
  // Must be the first code generated, since any code before will be skipped in
  // the unchecked entry-point.
  static void GenerateRecordEntryPoint(Assembler* assembler) {
    Label done;
    __ LoadImmediate(T6, target::Function::entry_point_offset() - kHeapObjectTag);
    __ j(&done, Assembler::kNearJump);
    __ BindUncheckedEntryPoint();
    __ LoadImmediate(
        T6, target::Function::entry_point_offset(CodeEntryKind::kUnchecked) -
                kHeapObjectTag);
    __ Bind(&done);
  }
  
  // Generate inline cache check for 'num_args'.
  //  A0: receiver (if instance call)
  //  S5: ICData
  //  RA: return address
  // Control flow:
  // - If receiver is null -> jump to IC miss.
  // - If receiver is Smi -> load Smi class.
  // - If receiver is not-Smi -> load receiver's class.
  // - Check if 'num_args' (including receiver) match any IC data group.
  // - Match found -> jump to target.
  // - Match not found -> jump to IC miss.
  void StubCodeCompiler::GenerateNArgsCheckInlineCacheStub(
      Assembler* assembler,
      intptr_t num_args,
      const RuntimeEntry& handle_ic_miss,
      Token::Kind kind,
      Optimized optimized,
      CallType type,
      Exactness exactness) {
    const bool save_entry_point = kind == Token::kILLEGAL;
    if (FLAG_precompiled_mode) {
      __ Breakpoint();
      return;
    }
  
    if (save_entry_point) {
      GenerateRecordEntryPoint(assembler);
      // T6: untagged entry point offset
    }
  
    if (optimized == kOptimized) {
      GenerateOptimizedUsageCounterIncrement(assembler);
    } else {
      GenerateUsageCounterIncrement(assembler, /*scratch=*/T0);
    }
  
    ASSERT(exactness == kIgnoreExactness);  // Unimplemented.
    ASSERT(num_args == 1 || num_args == 2);
  #if defined(DEBUG)
    {
      Label ok;
      // Check that the IC data array has NumArgsTested() == num_args.
      // 'NumArgsTested' is stored in the least significant bits of 'state_bits'.
      __ LoadFromOffset(TMP, IC_DATA_REG,
                        target::ICData::state_bits_offset() - kHeapObjectTag,
                        kUnsignedFourBytes);
      ASSERT(target::ICData::NumArgsTestedShift() == 0);  // No shift needed.
      __ andi(TMP, TMP, target::ICData::NumArgsTestedMask());
      __ CompareImmediate(TMP2, num_args);
      __ BranchIf(EQ, &ok, Assembler::kNearJump);
      __ Stop("Incorrect stub for IC data");
      __ Bind(&ok);
    }
  #endif  // DEBUG
  
  #if !defined(PRODUCT)
    Label stepping, done_stepping;
    if (optimized == kUnoptimized) {
      __ Comment("Check single stepping");
      __ LoadIsolate(TMP);
      __ LoadFromOffset(TMP, TMP, target::Isolate::single_step_offset(),
                        kUnsignedByte);
      __ bnez(TMP, &stepping);
      __ Bind(&done_stepping);
    }
  #endif
  
    Label not_smi_or_overflow;
    if (kind != Token::kILLEGAL) {
      EmitFastSmiOp(assembler, kind, num_args, &not_smi_or_overflow);
    }
    __ Bind(&not_smi_or_overflow);
  
    __ Comment("Extract ICData initial values and receiver cid");
    // S5: IC data object (preserved).
    __ LoadFieldFromOffset(A1, IC_DATA_REG, target::ICData::entries_offset());
    // A1: ic_data_array with check entries: classes and target functions.
    __ AddImmediate(A1, target::Array::data_offset() - kHeapObjectTag);
    // A1: points directly to the first ic data array element.
  
    if (type == kInstanceCall) {
      __ LoadTaggedClassIdMayBeSmi(T1, A0);
      __ LoadFieldFromOffset(ARGS_DESC_REG, IC_DATA_REG,
                             target::CallSiteData::arguments_descriptor_offset());
      if (num_args == 2) {
        __ LoadCompressedSmiFieldFromOffset(
            A7, ARGS_DESC_REG, target::ArgumentsDescriptor::count_offset());
        __ slli(A7, A7, target::kWordSizeLog2 - kSmiTagSize);
        __ add(A7, SP, A7);
        __ lx(A6, Address(A7, -2 * target::kWordSize));
        __ LoadTaggedClassIdMayBeSmi(T2, A6);
      }
    } else {
      __ LoadFieldFromOffset(ARGS_DESC_REG, IC_DATA_REG,
                             target::CallSiteData::arguments_descriptor_offset());
      __ LoadCompressedSmiFieldFromOffset(
          A7, ARGS_DESC_REG, target::ArgumentsDescriptor::count_offset());
      __ slli(A7, A7, target::kWordSizeLog2 - kSmiTagSize);
      __ add(A7, A7, SP);
      __ lx(A6, Address(A7, -1 * target::kWordSize));
      __ LoadTaggedClassIdMayBeSmi(T1, A6);
      if (num_args == 2) {
        __ lx(A6, Address(A7, -2 * target::kWordSize));
        __ LoadTaggedClassIdMayBeSmi(T2, A6);
      }
    }
    // T1: first argument class ID as Smi.
    // T2: second argument class ID as Smi.
    // S4: args descriptor
  
    // We unroll the generic one that is generated once more than the others.
    const bool optimize = kind == Token::kILLEGAL;
  
    // Loop that checks if there is an IC data match.
    Label loop, found, miss;
    __ Comment("ICData loop");
  
    __ Bind(&loop);
    for (int unroll = optimize ? 4 : 2; unroll >= 0; unroll--) {
      Label update;
  
      __ LoadCompressedSmiFromOffset(A7, A1, 0);
      if (num_args == 1) {
        __ beq(A7, T1, &found);  // Class id match?
      } else {
        __ bne(A7, T1, &update);  // Continue.
        __ LoadCompressedSmiFromOffset(A7, A1, target::kCompressedWordSize);
        __ beq(A7, T2, &found);  // Class id match?
      }
      __ Bind(&update);
  
      const intptr_t entry_size = target::ICData::TestEntryLengthFor(
                                      num_args, exactness == kCheckExactness) *
                                  target::kCompressedWordSize;
      __ AddImmediate(A1, entry_size);  // Next entry.
  
      __ CompareImmediate(A7, target::ToRawSmi(kIllegalCid));  // Done?
      if (unroll == 0) {
        __ BranchIf(NE, &loop);
      } else {
        __ BranchIf(EQ, &miss);
      }
    }
  
    __ Bind(&miss);
    __ Comment("IC miss");
  
    // Compute address of arguments.
    __ LoadCompressedSmiFieldFromOffset(
        A7, ARGS_DESC_REG, target::ArgumentsDescriptor::count_offset());
    __ slli(A7, A7, target::kWordSizeLog2 - kSmiTagSize);
    __ add(A7, A7, SP);
    __ subi(A7, A7, 1 * target::kWordSize);
  
    // A7: address of receiver
    // Create a stub frame as we are pushing some objects on the stack before
    // calling into the runtime.
    __ EnterStubFrame();
    // Preserve IC data object and arguments descriptor array and
    // setup space on stack for result (target code object).
    __ PushRegister(ARGS_DESC_REG);  // Preserve arguments descriptor array.
    __ PushRegister(IC_DATA_REG);    // Preserve IC Data.
    if (save_entry_point) {
      __ SmiTag(T6);
      __ PushRegister(T6);
    }
    // Setup space on stack for the result (target code object).
    __ PushRegister(ZR);
    // Push call arguments.
    for (intptr_t i = 0; i < num_args; i++) {
      __ LoadFromOffset(TMP, A7, -target::kWordSize * i);
      __ PushRegister(TMP);
    }
    // Pass IC data object.
    __ PushRegister(IC_DATA_REG);
    __ CallRuntime(handle_ic_miss, num_args + 1);
    // Remove the call arguments pushed earlier, including the IC data object.
    __ Drop(num_args + 1);
    // Pop returned function object into R0.
    // Restore arguments descriptor array and IC data array.
    __ PopRegister(T0);  // Pop returned function object into T0.
    if (save_entry_point) {
      __ PopRegister(T6);
      __ SmiUntag(T6);
    }
    __ PopRegister(IC_DATA_REG);    // Restore IC Data.
    __ PopRegister(ARGS_DESC_REG);  // Restore arguments descriptor array.
    __ RestoreCodePointer();
    __ LeaveStubFrame();
    Label call_target_function;
    if (!FLAG_lazy_dispatchers) {
      GenerateDispatcherCode(assembler, &call_target_function);
    } else {
      __ j(&call_target_function);
    }
  
    __ Bind(&found);
    __ Comment("Update caller's counter");
    // A1: pointer to an IC data check group.
    const intptr_t target_offset =
        target::ICData::TargetIndexFor(num_args) * target::kCompressedWordSize;
    const intptr_t count_offset =
        target::ICData::CountIndexFor(num_args) * target::kCompressedWordSize;
    __ LoadCompressedFromOffset(T0, A1, target_offset);
  
    if (FLAG_optimization_counter_threshold >= 0) {
      // Update counter, ignore overflow.
      __ LoadCompressedSmiFromOffset(TMP, A1, count_offset);
      __ addi(TMP, TMP, target::ToRawSmi(1));
      __ StoreToOffset(TMP, A1, count_offset);
    }
  
    __ Comment("Call target");
    __ Bind(&call_target_function);
    // T0: target function.
    __ LoadCompressedFieldFromOffset(CODE_REG, T0,
                                     target::Function::code_offset());
    if (save_entry_point) {
      __ add(A7, T0, T6);
      __ lx(A7, Address(A7, 0));
    } else {
      __ LoadFieldFromOffset(A7, T0, target::Function::entry_point_offset());
    }
    __ jr(A7);  // T0: Function, argument to lazy compile stub.
  
  #if !defined(PRODUCT)
    if (optimized == kUnoptimized) {
      __ Bind(&stepping);
      __ EnterStubFrame();
      if (type == kInstanceCall) {
        __ PushRegister(A0);  // Preserve receiver.
      }
      if (save_entry_point) {
        __ SmiTag(T6);
        __ PushRegister(T6);
      }
      __ PushRegister(IC_DATA_REG);  // Preserve IC data.
      __ CallRuntime(kSingleStepHandlerRuntimeEntry, 0);
      __ PopRegister(IC_DATA_REG);
      if (save_entry_point) {
        __ PopRegister(T6);
        __ SmiUntag(T6);
      }
      if (type == kInstanceCall) {
        __ PopRegister(A0);
      }
      __ RestoreCodePointer();
      __ LeaveStubFrame();
      __ j(&done_stepping);
    }
  #endif
  }
  
  // A0: receiver
  // S5: ICData
  // RA: return address
  void StubCodeCompiler::GenerateOneArgCheckInlineCacheStub(
      Assembler* assembler) {
    GenerateNArgsCheckInlineCacheStub(
        assembler, 1, kInlineCacheMissHandlerOneArgRuntimeEntry, Token::kILLEGAL,
        kUnoptimized, kInstanceCall, kIgnoreExactness);
  }
  
  // A0: receiver
  // S5: ICData
  // RA: return address
  void StubCodeCompiler::GenerateOneArgCheckInlineCacheWithExactnessCheckStub(
      Assembler* assembler) {
    __ Stop("Unimplemented");
  }
  
  // A0: receiver
  // S5: ICData
  // RA: return address
  void StubCodeCompiler::GenerateTwoArgsCheckInlineCacheStub(
      Assembler* assembler) {
    GenerateNArgsCheckInlineCacheStub(
        assembler, 2, kInlineCacheMissHandlerTwoArgsRuntimeEntry, Token::kILLEGAL,
        kUnoptimized, kInstanceCall, kIgnoreExactness);
  }
  
  // A0: receiver
  // S5: ICData
  // RA: return address
  void StubCodeCompiler::GenerateSmiAddInlineCacheStub(Assembler* assembler) {
    GenerateNArgsCheckInlineCacheStub(
        assembler, 2, kInlineCacheMissHandlerTwoArgsRuntimeEntry, Token::kADD,
        kUnoptimized, kInstanceCall, kIgnoreExactness);
  }
  
  // A0: receiver
  // S5: ICData
  // RA: return address
  void StubCodeCompiler::GenerateSmiLessInlineCacheStub(Assembler* assembler) {
    GenerateNArgsCheckInlineCacheStub(
        assembler, 2, kInlineCacheMissHandlerTwoArgsRuntimeEntry, Token::kLT,
        kUnoptimized, kInstanceCall, kIgnoreExactness);
  }
  
  // A0: receiver
  // S5: ICData
  // RA: return address
  void StubCodeCompiler::GenerateSmiEqualInlineCacheStub(Assembler* assembler) {
    GenerateNArgsCheckInlineCacheStub(
        assembler, 2, kInlineCacheMissHandlerTwoArgsRuntimeEntry, Token::kEQ,
        kUnoptimized, kInstanceCall, kIgnoreExactness);
  }
  
  // A0: receiver
  // S5: ICData
  // A6: Function
  // RA: return address
  void StubCodeCompiler::GenerateOneArgOptimizedCheckInlineCacheStub(
      Assembler* assembler) {
    GenerateNArgsCheckInlineCacheStub(
        assembler, 1, kInlineCacheMissHandlerOneArgRuntimeEntry, Token::kILLEGAL,
        kOptimized, kInstanceCall, kIgnoreExactness);
  }
  
  // A0: receiver
  // S5: ICData
  // A6: Function
  // RA: return address
  void StubCodeCompiler::
      GenerateOneArgOptimizedCheckInlineCacheWithExactnessCheckStub(
          Assembler* assembler) {
    __ Stop("Unimplemented");
  }
  
  // A0: receiver
  // S5: ICData
  // A6: Function
  // RA: return address
  void StubCodeCompiler::GenerateTwoArgsOptimizedCheckInlineCacheStub(
      Assembler* assembler) {
    GenerateNArgsCheckInlineCacheStub(
        assembler, 2, kInlineCacheMissHandlerTwoArgsRuntimeEntry, Token::kILLEGAL,
        kOptimized, kInstanceCall, kIgnoreExactness);
  }
  
  // S5: ICData
  // RA: return address
  void StubCodeCompiler::GenerateZeroArgsUnoptimizedStaticCallStub(
      Assembler* assembler) {
    GenerateRecordEntryPoint(assembler);
    GenerateUsageCounterIncrement(assembler, /* scratch */ T0);
  
  #if defined(DEBUG)
    {
      Label ok;
      // Check that the IC data array has NumArgsTested() == 0.
      // 'NumArgsTested' is stored in the least significant bits of 'state_bits'.
      __ LoadFromOffset(TMP, IC_DATA_REG,
                        target::ICData::state_bits_offset() - kHeapObjectTag,
                        kUnsignedFourBytes);
      ASSERT(target::ICData::NumArgsTestedShift() == 0);  // No shift needed.
      __ andi(TMP, TMP, target::ICData::NumArgsTestedMask());
      __ CompareImmediate(TMP, 0);
      __ BranchIf(EQ, &ok);
      __ Stop("Incorrect IC data for unoptimized static call");
      __ Bind(&ok);
    }
  #endif  // DEBUG
  
    // Check single stepping.
  #if !defined(PRODUCT)
    Label stepping, done_stepping;
    __ LoadIsolate(TMP);
    __ LoadFromOffset(TMP, TMP, target::Isolate::single_step_offset(),
                      kUnsignedByte);
    __ bnez(TMP, &stepping, Assembler::kNearJump);
    __ Bind(&done_stepping);
  #endif
  
    // T5: IC data object (preserved).
    __ LoadFieldFromOffset(A0, IC_DATA_REG, target::ICData::entries_offset());
    // A0: ic_data_array with entries: target functions and count.
    __ AddImmediate(A0, target::Array::data_offset() - kHeapObjectTag);
    // A0: points directly to the first ic data array element.
    const intptr_t target_offset =
        target::ICData::TargetIndexFor(0) * target::kCompressedWordSize;
    const intptr_t count_offset =
        target::ICData::CountIndexFor(0) * target::kCompressedWordSize;
  
    if (FLAG_optimization_counter_threshold >= 0) {
      // Increment count for this call, ignore overflow.
      __ LoadCompressedSmiFromOffset(TMP, A0, count_offset);
      __ addi(TMP, TMP, target::ToRawSmi(1));
      __ StoreToOffset(TMP, A0, count_offset);
    }
  
    // Load arguments descriptor into T4.
    __ LoadFieldFromOffset(ARGS_DESC_REG, IC_DATA_REG,
                           target::CallSiteData::arguments_descriptor_offset());
  
    // Get function and call it, if possible.
    __ LoadCompressedFromOffset(T0, A0, target_offset);
    __ LoadCompressedFieldFromOffset(CODE_REG, T0,
                                     target::Function::code_offset());
    __ add(A0, T0, T6);
    __ lx(TMP, Address(A0, 0));
    __ jr(TMP);  // T0: Function, argument to lazy compile stub.
  
  #if !defined(PRODUCT)
    __ Bind(&stepping);
    __ EnterStubFrame();
    __ PushRegister(IC_DATA_REG);  // Preserve IC data.
    __ SmiTag(T6);
    __ PushRegister(T6);
    __ CallRuntime(kSingleStepHandlerRuntimeEntry, 0);
    __ PopRegister(T6);
    __ SmiUntag(T6);
    __ PopRegister(IC_DATA_REG);
    __ RestoreCodePointer();
    __ LeaveStubFrame();
    __ j(&done_stepping);
  #endif
  }
  
  // S5: ICData
  // RA: return address
  void StubCodeCompiler::GenerateOneArgUnoptimizedStaticCallStub(
      Assembler* assembler) {
    GenerateUsageCounterIncrement(assembler, /* scratch */ T0);
    GenerateNArgsCheckInlineCacheStub(
        assembler, 1, kStaticCallMissHandlerOneArgRuntimeEntry, Token::kILLEGAL,
        kUnoptimized, kStaticCall, kIgnoreExactness);
  }
  
  // S5: ICData
  // RA: return address
  void StubCodeCompiler::GenerateTwoArgsUnoptimizedStaticCallStub(
      Assembler* assembler) {
    GenerateUsageCounterIncrement(assembler, /* scratch */ T0);
    GenerateNArgsCheckInlineCacheStub(
        assembler, 2, kStaticCallMissHandlerTwoArgsRuntimeEntry, Token::kILLEGAL,
        kUnoptimized, kStaticCall, kIgnoreExactness);
  }
  
  // Stub for compiling a function and jumping to the compiled code.
  // S4: Arguments descriptor.
  // T0: Function.
  void StubCodeCompiler::GenerateLazyCompileStub(Assembler* assembler) {
    // Preserve arg desc.
    __ EnterStubFrame();
    __ PushRegister(ARGS_DESC_REG);  // Save arg. desc.
    __ PushRegister(T0);             // Pass function.
    __ CallRuntime(kCompileFunctionRuntimeEntry, 1);
    __ PopRegister(T0);             // Restore argument.
    __ PopRegister(ARGS_DESC_REG);  // Restore arg desc.
    __ LeaveStubFrame();
  
    __ LoadCompressedFieldFromOffset(CODE_REG, T0,
                                     target::Function::code_offset());
    __ LoadFieldFromOffset(TMP, T0, target::Function::entry_point_offset());
    __ jr(TMP);
  }
  
  // A0: Receiver
  // S5: ICData
  void StubCodeCompiler::GenerateICCallBreakpointStub(Assembler* assembler) {
  #if defined(PRODUCT)
    __ Stop("No debugging in PRODUCT mode");
  #else
    __ EnterStubFrame();
    __ subi(SP, SP, 3 * target::kWordSize);
    __ sx(A0, Address(SP, 2 * target::kWordSize));  // Preserve receiver.
    __ sx(S5, Address(SP, 1 * target::kWordSize));  // Preserve IC data.
    __ sx(ZR, Address(SP, 0 * target::kWordSize));  // Space for result.
    __ CallRuntime(kBreakpointRuntimeHandlerRuntimeEntry, 0);
    __ lx(CODE_REG, Address(SP, 0 * target::kWordSize));  // Original stub.
    __ lx(S5, Address(SP, 1 * target::kWordSize));        // Restore IC data.
    __ lx(A0, Address(SP, 2 * target::kWordSize));        // Restore receiver.
    __ LeaveStubFrame();
    __ LoadFieldFromOffset(TMP, CODE_REG, target::Code::entry_point_offset());
    __ jr(TMP);
  #endif
  }
  
  // S5: ICData
  void StubCodeCompiler::GenerateUnoptStaticCallBreakpointStub(
      Assembler* assembler) {
  #if defined(PRODUCT)
    __ Stop("No debugging in PRODUCT mode");
  #else
    __ EnterStubFrame();
    __ subi(SP, SP, 2 * target::kWordSize);
    __ sx(S5, Address(SP, 1 * target::kWordSize));  // Preserve IC data.
    __ sx(ZR, Address(SP, 0 * target::kWordSize));  // Space for result.
    __ CallRuntime(kBreakpointRuntimeHandlerRuntimeEntry, 0);
    __ lx(CODE_REG, Address(SP, 0 * target::kWordSize));  // Original stub.
    __ lx(S5, Address(SP, 1 * target::kWordSize));        // Restore IC data.
    __ LeaveStubFrame();
    __ LoadFieldFromOffset(TMP, CODE_REG, target::Code::entry_point_offset());
    __ jr(TMP);
  #endif  // defined(PRODUCT)
  }
  
  void StubCodeCompiler::GenerateRuntimeCallBreakpointStub(Assembler* assembler) {
  #if defined(PRODUCT)
    __ Stop("No debugging in PRODUCT mode");
  #else
    __ EnterStubFrame();
    __ subi(SP, SP, 1 * target::kWordSize);
    __ sx(ZR, Address(SP, 0 * target::kWordSize));  // Space for result.
    __ CallRuntime(kBreakpointRuntimeHandlerRuntimeEntry, 0);
    __ lx(CODE_REG, Address(SP, 0 * target::kWordSize));
    __ LeaveStubFrame();
    __ LoadFieldFromOffset(TMP, CODE_REG, target::Code::entry_point_offset());
    __ jr(TMP);
  #endif  // defined(PRODUCT)
  }
  
  // Called only from unoptimized code. All relevant registers have been saved.
  void StubCodeCompiler::GenerateDebugStepCheckStub(Assembler* assembler) {
  #if defined(PRODUCT)
    __ Stop("No debugging in PRODUCT mode");
  #else
    // Check single stepping.
    Label stepping, done_stepping;
    __ LoadIsolate(A1);
    __ LoadFromOffset(A1, A1, target::Isolate::single_step_offset(),
                      kUnsignedByte);
    __ bnez(A1, &stepping, compiler::Assembler::kNearJump);
    __ Bind(&done_stepping);
    __ ret();
  
    __ Bind(&stepping);
    __ EnterStubFrame();
    __ CallRuntime(kSingleStepHandlerRuntimeEntry, 0);
    __ LeaveStubFrame();
    __ j(&done_stepping);
  #endif  // defined(PRODUCT)
  }
  
  // Used to check class and type arguments. Arguments passed in registers:
  //
  // Inputs (mostly from TypeTestABI struct):
  //   - kSubtypeTestCacheReg: UntaggedSubtypeTestCache
  //   - kInstanceReg: instance to test against.
  //   - kDstTypeReg: destination type (for n>=3).
  //   - kInstantiatorTypeArgumentsReg: instantiator type arguments (for n=5).
  //   - kFunctionTypeArgumentsReg: function type arguments (for n=5).
  //   - RA: return address.
  //
  // All input registers are preserved except for kSubtypeTestCacheReg, which
  // should be saved by the caller if needed.
  //
  // Result in SubtypeTestCacheABI::kResultReg: null -> not found, otherwise
  // result (true or false).
  static void GenerateSubtypeNTestCacheStub(Assembler* assembler, int n) {
    ASSERT(n == 1 || n == 3 || n == 5 || n == 7);
  
    // Until we have the result, we use the result register to store the null
    // value for quick access. This has the side benefit of initializing the
    // result to null, so it only needs to be changed if found.
    const Register kNullReg = TypeTestABI::kSubtypeTestCacheResultReg;
    __ LoadObject(kNullReg, NullObject());
  
    const Register kCacheArrayReg = TypeTestABI::kSubtypeTestCacheReg;
    const Register kScratchReg = TypeTestABI::kScratchReg;
  
    // All of these must be distinct from TypeTestABI::kSubtypeTestCacheResultReg
    // since it is used for kNullReg as well.
  
    // Loop initialization (moved up here to avoid having all dependent loads
    // after each other).
  
    // We avoid a load-acquire barrier here by relying on the fact that all other
    // loads from the array are data-dependent loads.
    __ lx(kCacheArrayReg, FieldAddress(TypeTestABI::kSubtypeTestCacheReg,
                                       target::SubtypeTestCache::cache_offset()));
    __ AddImmediate(kCacheArrayReg,
                    target::Array::data_offset() - kHeapObjectTag);
  
    Label loop, not_closure;
    if (n >= 5) {
      __ LoadClassIdMayBeSmi(STCInternalRegs::kInstanceCidOrSignatureReg,
                             TypeTestABI::TypeTestABI::kInstanceReg);
    } else {
      __ LoadClassId(STCInternalRegs::kInstanceCidOrSignatureReg,
                     TypeTestABI::kInstanceReg);
    }
    __ CompareImmediate(STCInternalRegs::kInstanceCidOrSignatureReg, kClosureCid);
    __ BranchIf(NE, &not_closure);
  
    // Closure handling.
    {
      __ Comment("Closure");
      __ LoadCompressed(STCInternalRegs::kInstanceCidOrSignatureReg,
                        FieldAddress(TypeTestABI::kInstanceReg,
                                     target::Closure::function_offset()));
      __ LoadCompressed(STCInternalRegs::kInstanceCidOrSignatureReg,
                        FieldAddress(STCInternalRegs::kInstanceCidOrSignatureReg,
                                     target::Function::signature_offset()));
      if (n >= 3) {
        __ LoadCompressed(
            STCInternalRegs::kInstanceInstantiatorTypeArgumentsReg,
            FieldAddress(TypeTestABI::kInstanceReg,
                         target::Closure::instantiator_type_arguments_offset()));
        if (n >= 7) {
          __ LoadCompressed(
              STCInternalRegs::kInstanceParentFunctionTypeArgumentsReg,
              FieldAddress(TypeTestABI::kInstanceReg,
                           target::Closure::function_type_arguments_offset()));
          __ LoadCompressed(
              STCInternalRegs::kInstanceDelayedFunctionTypeArgumentsReg,
              FieldAddress(TypeTestABI::kInstanceReg,
                           target::Closure::delayed_type_arguments_offset()));
        }
      }
      __ j(&loop);
    }
  
    // Non-Closure handling.
    {
      __ Comment("Non-Closure");
      __ Bind(&not_closure);
      if (n >= 3) {
        Label has_no_type_arguments;
        __ LoadClassById(kScratchReg,
                         STCInternalRegs::kInstanceCidOrSignatureReg);
        __ mv(STCInternalRegs::kInstanceInstantiatorTypeArgumentsReg, kNullReg);
        __ LoadFieldFromOffset(
            kScratchReg, kScratchReg,
            target::Class::host_type_arguments_field_offset_in_words_offset(),
            kFourBytes);
        __ CompareImmediate(kScratchReg, target::Class::kNoTypeArguments);
        __ BranchIf(EQ, &has_no_type_arguments);
        __ slli(kScratchReg, kScratchReg, kCompressedWordSizeLog2);
        __ add(kScratchReg, kScratchReg, TypeTestABI::kInstanceReg);
        __ LoadCompressed(STCInternalRegs::kInstanceInstantiatorTypeArgumentsReg,
                          FieldAddress(kScratchReg, 0));
        __ Bind(&has_no_type_arguments);
        __ Comment("No type arguments");
  
        if (n >= 7) {
          __ mv(STCInternalRegs::kInstanceParentFunctionTypeArgumentsReg,
                kNullReg);
          __ mv(STCInternalRegs::kInstanceDelayedFunctionTypeArgumentsReg,
                kNullReg);
        }
      }
      __ SmiTag(STCInternalRegs::kInstanceCidOrSignatureReg);
    }
  
    Label found, done, next_iteration;
  
    // Loop header
    __ Bind(&loop);
    __ Comment("Loop");
    __ LoadCompressed(
        kScratchReg,
        Address(kCacheArrayReg,
                target::kCompressedWordSize *
                    target::SubtypeTestCache::kInstanceCidOrSignature));
    __ CompareObjectRegisters(kScratchReg, kNullReg);
    __ BranchIf(EQ, &done);
    __ CompareObjectRegisters(kScratchReg,
                              STCInternalRegs::kInstanceCidOrSignatureReg);
    if (n == 1) {
      __ BranchIf(EQ, &found);
    } else {
      __ BranchIf(NE, &next_iteration);
      __ LoadCompressed(kScratchReg,
                        Address(kCacheArrayReg,
                                target::kCompressedWordSize *
                                    target::SubtypeTestCache::kDestinationType));
      __ CompareRegisters(kScratchReg, TypeTestABI::kDstTypeReg);
      __ BranchIf(NE, &next_iteration);
      __ LoadCompressed(
          kScratchReg,
          Address(kCacheArrayReg,
                  target::kCompressedWordSize *
                      target::SubtypeTestCache::kInstanceTypeArguments));
      __ CompareRegisters(kScratchReg,
                          STCInternalRegs::kInstanceInstantiatorTypeArgumentsReg);
      if (n == 3) {
        __ BranchIf(EQ, &found);
      } else {
        __ BranchIf(NE, &next_iteration);
        __ LoadCompressed(
            kScratchReg,
            Address(kCacheArrayReg,
                    target::kCompressedWordSize *
                        target::SubtypeTestCache::kInstantiatorTypeArguments));
        __ CompareRegisters(kScratchReg,
                            TypeTestABI::kInstantiatorTypeArgumentsReg);
        __ BranchIf(NE, &next_iteration);
        __ LoadCompressed(
            kScratchReg,
            Address(kCacheArrayReg,
                    target::kCompressedWordSize *
                        target::SubtypeTestCache::kFunctionTypeArguments));
        __ CompareRegisters(kScratchReg, TypeTestABI::kFunctionTypeArgumentsReg);
        if (n == 5) {
          __ BranchIf(EQ, &found);
        } else {
          ASSERT(n == 7);
          __ BranchIf(NE, &next_iteration);
  
          __ LoadCompressed(
              kScratchReg, Address(kCacheArrayReg,
                                   target::kCompressedWordSize *
                                       target::SubtypeTestCache::
                                           kInstanceParentFunctionTypeArguments));
          __ CompareRegisters(
              kScratchReg,
              STCInternalRegs::kInstanceParentFunctionTypeArgumentsReg);
          __ BranchIf(NE, &next_iteration);
  
          __ LoadCompressed(
              kScratchReg,
              Address(kCacheArrayReg,
                      target::kCompressedWordSize *
                          target::SubtypeTestCache::
                              kInstanceDelayedFunctionTypeArguments));
          __ CompareRegisters(
              kScratchReg,
              STCInternalRegs::kInstanceDelayedFunctionTypeArgumentsReg);
          __ BranchIf(EQ, &found);
        }
      }
    }
    __ Bind(&next_iteration);
    __ Comment("Next iteration");
    __ AddImmediate(
        kCacheArrayReg,
        target::kCompressedWordSize * target::SubtypeTestCache::kTestEntryLength);
    __ j(&loop);
  
    __ Bind(&found);
    __ Comment("Found");
    __ LoadCompressed(
        TypeTestABI::kSubtypeTestCacheResultReg,
        Address(kCacheArrayReg, target::kCompressedWordSize *
                                    target::SubtypeTestCache::kTestResult));
    __ Bind(&done);
    __ Comment("Done");
    __ ret();
  }
  
  // See comment on [GenerateSubtypeNTestCacheStub].
  void StubCodeCompiler::GenerateSubtype1TestCacheStub(Assembler* assembler) {
    GenerateSubtypeNTestCacheStub(assembler, 1);
  }
  
  // See comment on [GenerateSubtypeNTestCacheStub].
  void StubCodeCompiler::GenerateSubtype3TestCacheStub(Assembler* assembler) {
    GenerateSubtypeNTestCacheStub(assembler, 3);
  }
  
  // See comment on [GenerateSubtypeNTestCacheStub].
  void StubCodeCompiler::GenerateSubtype5TestCacheStub(Assembler* assembler) {
    GenerateSubtypeNTestCacheStub(assembler, 5);
  }
  
  // See comment on [GenerateSubtypeNTestCacheStub].
  void StubCodeCompiler::GenerateSubtype7TestCacheStub(Assembler* assembler) {
    GenerateSubtypeNTestCacheStub(assembler, 7);
  }
  
  void StubCodeCompiler::GenerateGetCStackPointerStub(Assembler* assembler) {
    __ mv(A0, SP);
    __ ret();
  }
  
  // Jump to a frame on the call stack.
  // RA: return address.
  // A0: program_counter.
  // A1: stack_pointer.
  // A2: frame_pointer.
  // A3: thread.
  // Does not return.
  //
  // Notice: We need to keep this in sync with `Simulator::JumpToFrame()`.
  void StubCodeCompiler::GenerateJumpToFrameStub(Assembler* assembler) {
    ASSERT(kExceptionObjectReg == A0);
    ASSERT(kStackTraceObjectReg == A1);
    __ mv(CALLEE_SAVED_TEMP, A0);  // Program counter.
    __ mv(SP, A1);                 // Stack pointer.
    __ mv(FP, A2);                 // Frame_pointer.
    __ mv(THR, A3);
  #if defined(USING_SHADOW_CALL_STACK)
  #error Unimplemented
  #endif
    Label exit_through_non_ffi;
    // Check if we exited generated from FFI. If so do transition.
    __ LoadFromOffset(TMP, THR,
                      compiler::target::Thread::exit_through_ffi_offset());
    __ LoadImmediate(TMP2, target::Thread::exit_through_ffi());
    __ bne(TMP, TMP2, &exit_through_non_ffi);
    __ Stop("Unimplemented");
    __ Bind(&exit_through_non_ffi);
  
    // Refresh pinned registers values (inc. write barrier mask and null object).
    __ RestorePinnedRegisters();
    // Set the tag.
    __ LoadImmediate(TMP, VMTag::kDartTagId);
    __ StoreToOffset(TMP, THR, target::Thread::vm_tag_offset());
    // Clear top exit frame.
    __ StoreToOffset(ZR, THR, target::Thread::top_exit_frame_info_offset());
    // Restore the pool pointer.
    __ RestoreCodePointer();
    if (FLAG_precompiled_mode) {
      __ SetupGlobalPoolAndDispatchTable();
    } else {
      __ LoadPoolPointer();
    }
    __ jr(CALLEE_SAVED_TEMP);  // Jump to continuation point.
  }
  
  // Run an exception handler.  Execution comes from JumpToFrame
  // stub or from the simulator.
  //
  // The arguments are stored in the Thread object.
  // Does not return.
  void StubCodeCompiler::GenerateRunExceptionHandlerStub(Assembler* assembler) {
    // Exception object.
    ASSERT(kExceptionObjectReg == A0);
    __ LoadFromOffset(A0, THR, target::Thread::active_exception_offset());
    __ StoreToOffset(NULL_REG, THR, target::Thread::active_exception_offset());
  
    // StackTrace object.
    ASSERT(kStackTraceObjectReg == A1);
    __ LoadFromOffset(A1, THR, target::Thread::active_stacktrace_offset());
    __ StoreToOffset(NULL_REG, THR, target::Thread::active_stacktrace_offset());
  
    __ LoadFromOffset(RA, THR, target::Thread::resume_pc_offset());
    __ ret();  // Jump to the exception handler code.
  }
  
  // Deoptimize a frame on the call stack before rewinding.
  // The arguments are stored in the Thread object.
  // No result.
  void StubCodeCompiler::GenerateDeoptForRewindStub(Assembler* assembler) {
    // Push zap value instead of CODE_REG.
    __ LoadImmediate(TMP, kZapCodeReg);
    __ PushRegister(TMP);
  
    // Load the deopt pc into RA.
    __ LoadFromOffset(RA, THR, target::Thread::resume_pc_offset());
    GenerateDeoptimizationSequence(assembler, kEagerDeopt);
  
    // After we have deoptimized, jump to the correct frame.
    __ EnterStubFrame();
    __ CallRuntime(kRewindPostDeoptRuntimeEntry, 0);
    __ LeaveStubFrame();
    __ ebreak();
  }
  
  // Calls to the runtime to optimize the given function.
  // A0: function to be re-optimized.
  // S4: argument descriptor (preserved).
  void StubCodeCompiler::GenerateOptimizeFunctionStub(Assembler* assembler) {
    __ LoadFromOffset(CODE_REG, THR, target::Thread::optimize_stub_offset());
    __ EnterStubFrame();
  
    __ subi(SP, SP, 3 * target::kWordSize);
    __ sx(S4, Address(SP, 2 * target::kWordSize));  // Preserves args descriptor.
    __ sx(ZR, Address(SP, 1 * target::kWordSize));  // Result slot.
    __ sx(A0, Address(SP, 0 * target::kWordSize));  // Function argument.
    __ CallRuntime(kOptimizeInvokedFunctionRuntimeEntry, 1);
    __ lx(T0, Address(SP, 1 * target::kWordSize));  // Function result.
    __ lx(S4, Address(SP, 2 * target::kWordSize));  // Restore args descriptor.
    __ addi(SP, SP, 3 * target::kWordSize);
  
    __ LoadCompressedFieldFromOffset(CODE_REG, T0,
                                     target::Function::code_offset());
    __ LoadFieldFromOffset(A1, T0, target::Function::entry_point_offset());
    __ LeaveStubFrame();
    __ jr(A1);
    __ ebreak();
  }
  
  // Does identical check (object references are equal or not equal) with special
  // checks for boxed numbers and returns with TMP = 0 iff left and right are
  // identical.
  static void GenerateIdenticalWithNumberCheckStub(Assembler* assembler,
                                                   const Register left,
                                                   const Register right) {
    Label reference_compare, check_mint, done;
    // If any of the arguments is Smi do reference compare.
    // Note: A Mint cannot contain a value that would fit in Smi.
    __ BranchIfSmi(left, &reference_compare, Assembler::kNearJump);
    __ BranchIfSmi(right, &reference_compare, Assembler::kNearJump);
  
    // Value compare for two doubles.
    __ CompareClassId(left, kDoubleCid, /*scratch*/ TMP);
    __ BranchIf(NOT_EQUAL, &check_mint, Assembler::kNearJump);
    __ CompareClassId(right, kDoubleCid, /*scratch*/ TMP);
    __ BranchIf(NOT_EQUAL, &reference_compare, Assembler::kNearJump);
  
    // Double values bitwise compare.
  #if XLEN == 32
    __ lw(T0, FieldAddress(left, target::Double::value_offset()));
    __ lw(T1, FieldAddress(right, target::Double::value_offset()));
    __ xor_(TMP, T0, T1);
    __ lw(T0, FieldAddress(left, target::Double::value_offset() + 4));
    __ lw(T1, FieldAddress(right, target::Double::value_offset() + 4));
    __ xor_(TMP2, T0, T1);
    __ or_(TMP, TMP, TMP2);
  #else
    __ ld(T0, FieldAddress(left, target::Double::value_offset()));
    __ ld(T1, FieldAddress(right, target::Double::value_offset()));
    __ xor_(TMP, T0, T1);
  #endif
    __ j(&done, Assembler::kNearJump);
  
    __ Bind(&check_mint);
    __ CompareClassId(left, kMintCid, /*scratch*/ TMP);
    __ BranchIf(NOT_EQUAL, &reference_compare, Assembler::kNearJump);
    __ CompareClassId(right, kMintCid, /*scratch*/ TMP);
    __ BranchIf(NOT_EQUAL, &reference_compare, Assembler::kNearJump);
  #if XLEN == 32
    __ lw(T0, FieldAddress(left, target::Mint::value_offset()));
    __ lw(T1, FieldAddress(right, target::Mint::value_offset()));
    __ xor_(TMP, T0, T1);
    __ lw(T0, FieldAddress(left, target::Mint::value_offset() + 4));
    __ lw(T1, FieldAddress(right, target::Mint::value_offset() + 4));
    __ xor_(TMP2, T0, T1);
    __ or_(TMP, TMP, TMP2);
  #else
    __ ld(T0, FieldAddress(left, target::Mint::value_offset()));
    __ ld(T1, FieldAddress(right, target::Mint::value_offset()));
    __ xor_(TMP, T0, T1);
  #endif
    __ j(&done, Assembler::kNearJump);
  
    __ Bind(&reference_compare);
    __ xor_(TMP, left, right);
    __ Bind(&done);
  }
  
  // Called only from unoptimized code. All relevant registers have been saved.
  // RA: return address.
  // SP + 4: left operand.
  // SP + 0: right operand.
  // Return TMP set to 0 if equal.
  void StubCodeCompiler::GenerateUnoptimizedIdenticalWithNumberCheckStub(
      Assembler* assembler) {
  #if !defined(PRODUCT)
    // Check single stepping.
    Label stepping, done_stepping;
    __ LoadIsolate(TMP);
    __ LoadFromOffset(TMP, TMP, target::Isolate::single_step_offset(),
                      kUnsignedByte);
    __ bnez(TMP, &stepping);
    __ Bind(&done_stepping);
  #endif
  
    const Register left = A0;
    const Register right = A1;
    __ LoadFromOffset(left, SP, 1 * target::kWordSize);
    __ LoadFromOffset(right, SP, 0 * target::kWordSize);
    GenerateIdenticalWithNumberCheckStub(assembler, left, right);
    __ ret();
  
  #if !defined(PRODUCT)
    __ Bind(&stepping);
    __ EnterStubFrame();
    __ CallRuntime(kSingleStepHandlerRuntimeEntry, 0);
    __ RestoreCodePointer();
    __ LeaveStubFrame();
    __ j(&done_stepping);
  #endif
  }
  
  // Called from optimized code only.
  // RA: return address.
  // SP + 4: left operand.
  // SP + 0: right operand.
  // Return TMP set to 0 if equal.
  void StubCodeCompiler::GenerateOptimizedIdenticalWithNumberCheckStub(
      Assembler* assembler) {
    const Register left = A0;
    const Register right = A1;
    __ LoadFromOffset(left, SP, 1 * target::kWordSize);
    __ LoadFromOffset(right, SP, 0 * target::kWordSize);
    GenerateIdenticalWithNumberCheckStub(assembler, left, right);
    __ ret();
  }
  
  // Called from megamorphic call sites.
  //  A0: receiver (passed to target)
  //  S5: MegamorphicCache (preserved)
  // Passed to target:
  //  A0: receiver
  //  CODE_REG: target Code
  //  S4: arguments descriptor
  //  S5: MegamorphicCache
  void StubCodeCompiler::GenerateMegamorphicCallStub(Assembler* assembler) {
    // Jump if receiver is a smi.
    Label smi_case;
    __ BranchIfSmi(A0, &smi_case);
  
    // Loads the cid of the object.
    __ LoadClassId(T5, A0);
  
    Label cid_loaded;
    __ Bind(&cid_loaded);
    __ lx(T2, FieldAddress(S5, target::MegamorphicCache::buckets_offset()));
    __ lx(T1, FieldAddress(S5, target::MegamorphicCache::mask_offset()));
    // T2: cache buckets array.
    // T1: mask as a smi.
  
    // Make the cid into a smi.
    __ SmiTag(T5);
    // T5: class ID of the receiver (smi).
  
    // Compute the table index.
    ASSERT(target::MegamorphicCache::kSpreadFactor == 7);
    // Use lsl and sub to multiply with 7 == 8 - 1.
    __ slli(T3, T5, 3);
    __ sub(T3, T3, T5);
    // T3: probe.
    Label loop;
    __ Bind(&loop);
    __ and_(T3, T3, T1);
  
    const intptr_t base = target::Array::data_offset();
    // T3 is smi tagged, but table entries are 16 bytes, so LSL 3.
    __ slli(TMP, T3, kCompressedWordSizeLog2);
    __ add(TMP, TMP, T2);
    __ LoadCompressedSmiFieldFromOffset(T4, TMP, base);
    Label probe_failed;
    __ CompareObjectRegisters(T4, T5);
    __ BranchIf(NE, &probe_failed);
  
    Label load_target;
    __ Bind(&load_target);
    // Call the target found in the cache.  For a class id match, this is a
    // proper target for the given name and arguments descriptor.  If the
    // illegal class id was found, the target is a cache miss handler that can
    // be invoked as a normal Dart function.
    __ LoadCompressed(T0, FieldAddress(TMP, base + target::kCompressedWordSize));
    __ lx(A1, FieldAddress(T0, target::Function::entry_point_offset()));
    __ lx(ARGS_DESC_REG,
          FieldAddress(S5, target::CallSiteData::arguments_descriptor_offset()));
    if (!FLAG_precompiled_mode) {
      __ LoadCompressed(CODE_REG,
                        FieldAddress(T0, target::Function::code_offset()));
    }
    __ jr(A1);  // T0: Function, argument to lazy compile stub.
  
    // Probe failed, check if it is a miss.
    __ Bind(&probe_failed);
    ASSERT(kIllegalCid == 0);
    Label miss;
    __ beqz(T4, &miss);  // branch if miss.
  
    // Try next extry in the table.
    __ AddImmediate(T3, target::ToRawSmi(1));
    __ j(&loop);
  
    // Load cid for the Smi case.
    __ Bind(&smi_case);
    __ LoadImmediate(T5, kSmiCid);
    __ j(&cid_loaded);
  
    __ Bind(&miss);
    GenerateSwitchableCallMissStub(assembler);
  }
  
  // Input:
  //   A0 - receiver
  //   S5 - icdata
  void StubCodeCompiler::GenerateICCallThroughCodeStub(Assembler* assembler) {
    Label loop, found, miss;
    __ lx(T1, FieldAddress(S5, target::ICData::entries_offset()));
    __ lx(S4,
          FieldAddress(S5, target::CallSiteData::arguments_descriptor_offset()));
    __ AddImmediate(T1, target::Array::data_offset() - kHeapObjectTag);
    // T1: first IC entry
    __ LoadTaggedClassIdMayBeSmi(A1, A0);
    // A1: receiver cid as Smi
  
    __ Bind(&loop);
    __ LoadCompressedSmi(T2, Address(T1, 0));
    __ beq(A1, T2, &found);
    __ CompareImmediate(T2, target::ToRawSmi(kIllegalCid));
    __ BranchIf(EQ, &miss);
  
    const intptr_t entry_length =
        target::ICData::TestEntryLengthFor(1, /*tracking_exactness=*/false) *
        target::kCompressedWordSize;
    __ AddImmediate(T1, entry_length);  // Next entry.
    __ j(&loop);
  
    __ Bind(&found);
    if (FLAG_precompiled_mode) {
      const intptr_t entry_offset =
          target::ICData::EntryPointIndexFor(1) * target::kCompressedWordSize;
      __ LoadCompressed(A1, Address(T1, entry_offset));
      __ lx(A1, FieldAddress(A1, target::Function::entry_point_offset()));
    } else {
      const intptr_t code_offset =
          target::ICData::CodeIndexFor(1) * target::kCompressedWordSize;
      __ LoadCompressed(CODE_REG, Address(T1, code_offset));
      __ lx(A1, FieldAddress(CODE_REG, target::Code::entry_point_offset()));
    }
    __ jr(A1);
  
    __ Bind(&miss);
    __ LoadIsolate(A1);
    __ lx(CODE_REG, Address(A1, target::Isolate::ic_miss_code_offset()));
    __ lx(A1, FieldAddress(CODE_REG, target::Code::entry_point_offset()));
    __ jr(A1);
  }
  
  // Implement the monomorphic entry check for call-sites where the receiver
  // might be a Smi.
  //
  //   A0: receiver
  //   S5: MonomorphicSmiableCall object
  //
  //   T1,T2: clobbered
  void StubCodeCompiler::GenerateMonomorphicSmiableCheckStub(
      Assembler* assembler) {
    Label miss;
    __ LoadClassIdMayBeSmi(T1, A0);
  
    // Note: this stub is only used in AOT mode, hence the direct (bare) call.
    __ LoadField(
        T2,
        FieldAddress(S5, target::MonomorphicSmiableCall::expected_cid_offset()));
    __ LoadField(
        TMP,
        FieldAddress(S5, target::MonomorphicSmiableCall::entrypoint_offset()));
    __ bne(T1, T2, &miss);
    __ jr(TMP);
  
    __ Bind(&miss);
    __ lx(TMP, Address(THR, target::Thread::switchable_call_miss_entry_offset()));
    __ jr(TMP);
  }
  
  // Called from switchable IC calls.
  //  A0: receiver
  void StubCodeCompiler::GenerateSwitchableCallMissStub(Assembler* assembler) {
    __ lx(CODE_REG,
          Address(THR, target::Thread::switchable_call_miss_stub_offset()));
    __ EnterStubFrame();
    __ PushRegister(A0);  // Preserve receiver.
  
    __ PushRegister(ZR);  // Result slot.
    __ PushRegister(ZR);  // Arg0: stub out.
    __ PushRegister(A0);  // Arg1: Receiver
    __ CallRuntime(kSwitchableCallMissRuntimeEntry, 2);
    __ Drop(1);
    __ PopRegister(CODE_REG);     // result = stub
    __ PopRegister(IC_DATA_REG);  // result = IC
  
    __ PopRegister(A0);  // Restore receiver.
    __ LeaveStubFrame();
  
    __ lx(TMP, FieldAddress(CODE_REG, target::Code::entry_point_offset(
                                          CodeEntryKind::kNormal)));
    __ jr(TMP);
  }
  
  // Called from switchable IC calls.
  //  A0: receiver
  //  S5: SingleTargetCache
  // Passed to target:
  //  CODE_REG: target Code object
  void StubCodeCompiler::GenerateSingleTargetCallStub(Assembler* assembler) {
    Label miss;
    __ LoadClassIdMayBeSmi(A1, A0);
    __ lhu(T2, FieldAddress(S5, target::SingleTargetCache::lower_limit_offset()));
    __ lhu(T3, FieldAddress(S5, target::SingleTargetCache::upper_limit_offset()));
  
    __ blt(A1, T2, &miss);
    __ bgt(A1, T3, &miss);
  
    __ lx(TMP, FieldAddress(S5, target::SingleTargetCache::entry_point_offset()));
    __ lx(CODE_REG, FieldAddress(S5, target::SingleTargetCache::target_offset()));
    __ jr(TMP);
  
    __ Bind(&miss);
    __ EnterStubFrame();
    __ PushRegister(A0);  // Preserve receiver.
  
    __ PushRegister(ZR);  // Result slot.
    __ PushRegister(ZR);  // Arg0: Stub out.
    __ PushRegister(A0);  // Arg1: Receiver
    __ CallRuntime(kSwitchableCallMissRuntimeEntry, 2);
    __ Drop(1);
    __ PopRegister(CODE_REG);  // result = stub
    __ PopRegister(S5);        // result = IC
  
    __ PopRegister(A0);  // Restore receiver.
    __ LeaveStubFrame();
  
    __ lx(TMP, FieldAddress(CODE_REG, target::Code::entry_point_offset(
                                          CodeEntryKind::kMonomorphic)));
    __ jr(TMP);
  }
  
  // Instantiate type arguments from instantiator and function type args.
  // T1 uninstantiated type arguments.
  // T2 instantiator type arguments.
  // T3: function type arguments.
  // Returns instantiated type arguments in T5.
  void StubCodeCompiler::GenerateInstantiateTypeArgumentsStub(
      Assembler* assembler) {
    // Lookup cache before calling runtime.
    __ LoadCompressedFieldFromOffset(
        A1, InstantiationABI::kUninstantiatedTypeArgumentsReg,
        target::TypeArguments::instantiations_offset());
    __ AddImmediate(A1, Array::data_offset() - kHeapObjectTag);
    // The instantiations cache is initialized with Object::zero_array() and is
    // therefore guaranteed to contain kNoInstantiator. No length check needed.
    compiler::Label loop, next, found, call_runtime;
    __ Bind(&loop);
  
    // Use load-acquire to test for sentinel, if we found non-sentinel it is safe
    // to access the other entries. If we found a sentinel we go to runtime.
    __ LoadAcquireCompressed(
        A6, A1,
        TypeArguments::Instantiation::kInstantiatorTypeArgsIndex *
            target::kCompressedWordSize);
    __ CompareImmediate(A6, Smi::RawValue(TypeArguments::kNoInstantiator),
                        kObjectBytes);
    __ BranchIf(EQ, &call_runtime);
  
    __ CompareRegisters(A6, InstantiationABI::kInstantiatorTypeArgumentsReg);
    __ BranchIf(NE, &next);
    __ LoadCompressedFromOffset(
        A7, A1,
        TypeArguments::Instantiation::kFunctionTypeArgsIndex *
            target::kCompressedWordSize);
    __ CompareRegisters(A7, InstantiationABI::kFunctionTypeArgumentsReg);
    __ BranchIf(EQ, &found);
    __ Bind(&next);
    __ AddImmediate(A1, TypeArguments::Instantiation::kSizeInWords *
                            target::kCompressedWordSize);
    __ j(&loop);
  
    // Instantiate non-null type arguments.
    // A runtime call to instantiate the type arguments is required.
    __ Bind(&call_runtime);
    __ EnterStubFrame();
    __ PushRegisterPair(InstantiationABI::kUninstantiatedTypeArgumentsReg,
                        NULL_REG);
    __ PushRegisterPair(InstantiationABI::kFunctionTypeArgumentsReg,
                        InstantiationABI::kInstantiatorTypeArgumentsReg);
    __ CallRuntime(kInstantiateTypeArgumentsRuntimeEntry, 3);
    __ Drop(3);  // Drop 2 type vectors, and uninstantiated type.
    __ PopRegister(InstantiationABI::kResultTypeArgumentsReg);
    __ LeaveStubFrame();
    __ Ret();
  
    __ Bind(&found);
    __ LoadCompressedFromOffset(
        InstantiationABI::kResultTypeArgumentsReg, A1,
        TypeArguments::Instantiation::kInstantiatedTypeArgsIndex *
            target::kCompressedWordSize);
    __ Ret();
  }
  
  void StubCodeCompiler::
      GenerateInstantiateTypeArgumentsMayShareInstantiatorTAStub(
          Assembler* assembler) {
    // Return the instantiator type arguments if its nullability is compatible for
    // sharing, otherwise proceed to instantiation cache lookup.
    compiler::Label cache_lookup;
    __ LoadCompressedSmi(
        A6, FieldAddress(InstantiationABI::kUninstantiatedTypeArgumentsReg,
                         target::TypeArguments::nullability_offset()));
    __ LoadCompressedSmi(
        A7, FieldAddress(InstantiationABI::kInstantiatorTypeArgumentsReg,
                         target::TypeArguments::nullability_offset()));
    __ and_(A7, A7, A6);
    __ CompareRegisters(A7, A6);
    __ BranchIf(NE, &cache_lookup);
    __ mv(InstantiationABI::kResultTypeArgumentsReg,
          InstantiationABI::kInstantiatorTypeArgumentsReg);
    __ Ret();
  
    __ Bind(&cache_lookup);
    GenerateInstantiateTypeArgumentsStub(assembler);
  }
  
  void StubCodeCompiler::GenerateInstantiateTypeArgumentsMayShareFunctionTAStub(
      Assembler* assembler) {
    // Return the function type arguments if its nullability is compatible for
    // sharing, otherwise proceed to instantiation cache lookup.
    compiler::Label cache_lookup;
    __ LoadCompressedSmi(
        A6, FieldAddress(InstantiationABI::kUninstantiatedTypeArgumentsReg,
                         target::TypeArguments::nullability_offset()));
    __ LoadCompressedSmi(
        A7, FieldAddress(InstantiationABI::kFunctionTypeArgumentsReg,
                         target::TypeArguments::nullability_offset()));
    __ and_(A7, A7, A6);
    __ CompareRegisters(A7, A6);
    __ BranchIf(NE, &cache_lookup);
    __ mv(InstantiationABI::kResultTypeArgumentsReg,
          InstantiationABI::kFunctionTypeArgumentsReg);
    __ Ret();
  
    __ Bind(&cache_lookup);
    GenerateInstantiateTypeArgumentsStub(assembler);
  }
  
  static int GetScaleFactor(intptr_t size) {
    switch (size) {
      case 1:
        return 0;
      case 2:
        return 1;
      case 4:
        return 2;
      case 8:
        return 3;
      case 16:
        return 4;
    }
    UNREACHABLE();
    return -1;
  }
  
  void StubCodeCompiler::GenerateAllocateTypedDataArrayStub(Assembler* assembler,
                                                            intptr_t cid) {
    const intptr_t element_size = TypedDataElementSizeInBytes(cid);
    const intptr_t max_len = TypedDataMaxNewSpaceElements(cid);
    const intptr_t scale_shift = GetScaleFactor(element_size);
  
    COMPILE_ASSERT(AllocateTypedDataArrayABI::kLengthReg == T2);
    COMPILE_ASSERT(AllocateTypedDataArrayABI::kResultReg == A0);
  
    if (!FLAG_use_slow_path && FLAG_inline_alloc) {
      Label call_runtime;
      NOT_IN_PRODUCT(__ MaybeTraceAllocation(cid, T3, &call_runtime));
      __ mv(T3, AllocateTypedDataArrayABI::kLengthReg);
      /* Check that length is a positive Smi. */
      /* T3: requested array length argument. */
      __ BranchIfNotSmi(T3, &call_runtime);
      __ SmiUntag(T3);
      /* Check for length >= 0 && length <= max_len. */
      /* T3: untagged array length. */
      __ CompareImmediate(T3, max_len, kObjectBytes);
      __ BranchIf(UNSIGNED_GREATER, &call_runtime);
      if (scale_shift != 0) {
        __ slli(T3, T3, scale_shift);
      }
      const intptr_t fixed_size_plus_alignment_padding =
          target::TypedData::HeaderSize() +
          target::ObjectAlignment::kObjectAlignment - 1;
      __ AddImmediate(T3, fixed_size_plus_alignment_padding);
      __ andi(T3, T3, ~(target::ObjectAlignment::kObjectAlignment - 1));
      __ lx(A0, Address(THR, target::Thread::top_offset()));
  
      /* T3: allocation size. */
      __ add(T4, A0, T3);
      __ bltu(T4, A0, &call_runtime); /* Fail on unsigned overflow. */
  
      /* Check if the allocation fits into the remaining space. */
      /* A0: potential new object start. */
      /* T4: potential next object start. */
      /* T3: allocation size. */
      __ lx(TMP, Address(THR, target::Thread::end_offset()));
      __ bgeu(T4, TMP, &call_runtime);
  
      /* Successfully allocated the object(s), now update top to point to */
      /* next object start and initialize the object. */
      __ sx(T4, Address(THR, target::Thread::top_offset()));
      __ AddImmediate(A0, kHeapObjectTag);
      /* Initialize the tags. */
      /* A0: new object start as a tagged pointer. */
      /* T4: new object end address. */
      /* T3: allocation size. */
      {
        __ li(T5, 0);
        __ CompareImmediate(T3, target::UntaggedObject::kSizeTagMaxSizeTag);
        compiler::Label zero_tags;
        __ BranchIf(HI, &zero_tags);
        __ slli(T5, T3,
                target::UntaggedObject::kTagBitsSizeTagPos -
                    target::ObjectAlignment::kObjectAlignmentLog2);
        __ Bind(&zero_tags);
  
        /* Get the class index and insert it into the tags. */
        uword tags =
            target::MakeTagWordForNewSpaceObject(cid, /*instance_size=*/0);
        __ OrImmediate(T5, T5, tags);
        __ sx(T5, FieldAddress(A0, target::Object::tags_offset())); /* Tags. */
      }
      /* Set the length field. */
      /* A0: new object start as a tagged pointer. */
      /* T4: new object end address. */
      __ mv(T3, AllocateTypedDataArrayABI::kLengthReg); /* Array length. */
      __ StoreCompressedIntoObjectNoBarrier(
          A0, FieldAddress(A0, target::TypedDataBase::length_offset()), T3);
      /* Initialize all array elements to 0. */
      /* A0: new object start as a tagged pointer. */
      /* T4: new object end address. */
      /* T3: iterator which initially points to the start of the variable */
      /* R3: scratch register. */
      /* data area to be initialized. */
      __ AddImmediate(T3, A0, target::TypedData::HeaderSize() - 1);
      __ StoreInternalPointer(
          A0, FieldAddress(A0, target::PointerBase::data_offset()), T3);
      Label init_loop, done;
      __ Bind(&init_loop);
      __ bgeu(T3, T4, &done);
      __ sx(ZR, Address(T3, 0));
      __ addi(T3, T3, target::kWordSize);
      __ j(&init_loop);
      __ Bind(&done);
  
      __ Ret();
  
      __ Bind(&call_runtime);
    }
  
    __ EnterStubFrame();
    __ PushRegister(ZR);                                     // Result slot.
    __ PushImmediate(target::ToRawSmi(cid));                 // Cid
    __ PushRegister(AllocateTypedDataArrayABI::kLengthReg);  // Array length
    __ CallRuntime(kAllocateTypedDataRuntimeEntry, 2);
    __ Drop(2);  // Drop arguments.
    __ PopRegister(AllocateTypedDataArrayABI::kResultReg);
    __ LeaveStubFrame();
    __ Ret();
  }
  
  }  // namespace compiler
  
  }  // namespace dart
  
  #endif  // defined(TARGET_ARCH_RISCV)
  