// Copyright (c) 2020, 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/compiler/runtime_api.h"
#include "vm/flags.h"
#include "vm/globals.h"

// For `StubCodeCompiler::GenerateAllocateUnhandledExceptionStub`
#include "vm/compiler/backend/il.h"

#define SHOULD_NOT_INCLUDE_RUNTIME

#include "vm/compiler/stub_code_compiler.h"

#include "vm/code_descriptors.h"
#include "vm/compiler/api/type_check_mode.h"
#include "vm/compiler/assembler/assembler.h"
#include "vm/compiler/backend/locations.h"
#include "vm/stack_frame.h"

#define __ assembler->

namespace dart {
namespace compiler {

intptr_t StubCodeCompiler::WordOffsetFromFpToCpuRegister(
    Register cpu_register) {
  ASSERT(RegisterSet::Contains(kDartAvailableCpuRegs, cpu_register));

  intptr_t slots_from_fp = target::frame_layout.param_end_from_fp + 1;
  for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) {
    Register reg = static_cast<Register>(i);
    if (reg == cpu_register) break;
    if (RegisterSet::Contains(kDartAvailableCpuRegs, reg)) {
      slots_from_fp++;
    }
  }
  return slots_from_fp;
}

void StubCodeCompiler::GenerateInitStaticFieldStub() {
  __ EnterStubFrame();
  __ PushObject(NullObject());  // Make room for result.
  __ PushRegister(InitStaticFieldABI::kFieldReg);
  __ CallRuntime(kInitStaticFieldRuntimeEntry, /*argument_count=*/1);
  __ Drop(1);
  __ PopRegister(InitStaticFieldABI::kResultReg);
  __ LeaveStubFrame();
  __ Ret();
}

void StubCodeCompiler::GenerateCheckIsolateFieldAccessStub() {
  const Register kFieldReg = InitStaticFieldABI::kFieldReg;
  const Register kScratchReg = InitLateStaticFieldInternalRegs::kScratchReg;

  __ EnterStubFrame();

  Label throw_since_no_isolate_is_present;
  if (!FLAG_experimental_shared_data) {
    // Should not be invoked
    __ Breakpoint();
  }
  // This stub is also called from mutator thread running without an
  // isolate and attempts to load value from isolate static field.
  __ LoadIsolate(kScratchReg);
  __ BranchIfZero(kScratchReg, &throw_since_no_isolate_is_present);
  __ LeaveStubFrame();
  __ Ret();

#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
  // We are jumping over LeaveStubFrame so restore LR state to match one
  // at the jump point.
  __ set_lr_state(compiler::LRState::OnEntry().EnterFrame());
#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
  // Throw FieldAccessError
  __ Bind(&throw_since_no_isolate_is_present);
  __ PushObject(NullObject());  // Make room for (unused) result.
  __ PushRegister(kFieldReg);
  __ CallRuntime(kStaticFieldAccessedWithoutIsolateErrorRuntimeEntry,
                 /*argument_count=*/1);
  __ Breakpoint();
}

void StubCodeCompiler::GenerateInitLateStaticFieldStub(bool is_final,
                                                       bool is_shared) {
  const Register kResultReg = InitStaticFieldABI::kResultReg;
  const Register kFieldReg = InitStaticFieldABI::kFieldReg;
  const Register kAddressReg = InitLateStaticFieldInternalRegs::kAddressReg;
  const Register kScratchReg = InitLateStaticFieldInternalRegs::kScratchReg;

  __ EnterStubFrame();

  if (is_shared) {
    // Since initialization of shared fields has to be guarded by
    // a mutex, do the initialization in the runtime.
    __ PushObject(NullObject());  // Make room for the result
    __ PushRegister(kFieldReg);
    __ CallRuntime(kInitializeSharedFieldRuntimeEntry, /*argument_count=*/1);
    __ PopRegister(kFieldReg);
    __ PopRegister(kResultReg);
    __ LeaveStubFrame();
    __ Ret();
    return;
  }

  Label throw_since_no_isolate_is_present;
  if (FLAG_experimental_shared_data) {
    ASSERT(!is_shared);
    // This stub is also called from mutator thread running without an
    // isolate and attempts to load value from isolate static field.
    __ LoadIsolate(kScratchReg);
    __ BranchIfZero(kScratchReg, &throw_since_no_isolate_is_present);
  }

  __ Comment("Calling initializer function");
  __ PushRegister(kFieldReg);
  __ LoadCompressedFieldFromOffset(
      FUNCTION_REG, kFieldReg, target::Field::initializer_function_offset());
  if (!FLAG_precompiled_mode) {
    __ LoadCompressedFieldFromOffset(CODE_REG, FUNCTION_REG,
                                     target::Function::code_offset());
    // Load a GC-safe value for the arguments descriptor (unused but tagged).
    __ LoadImmediate(ARGS_DESC_REG, 0);
  }
  if (FLAG_target_thread_sanitizer) {
    __ TsanFuncEntry();
  }
  __ Call(FieldAddress(FUNCTION_REG, target::Function::entry_point_offset()));
  if (FLAG_target_thread_sanitizer) {
    __ TsanFuncExit();
  }
  __ MoveRegister(kResultReg, CallingConventions::kReturnReg);
  __ PopRegister(kFieldReg);
  __ LoadStaticFieldAddress(kAddressReg, kFieldReg, kScratchReg, is_shared);

  Label throw_exception;
  if (is_final) {
    __ Comment("Checking that initializer did not set late final field");
    if (is_shared) {
      __ LoadAcquireFromOffset(kScratchReg, kAddressReg, 0);
    } else {
      __ LoadFromOffset(kScratchReg, kAddressReg, 0);
    }
    __ CompareObject(kScratchReg, SentinelObject());
    __ BranchIf(NOT_EQUAL, &throw_exception);
  }

  if (is_shared) {
    __ StoreReleaseToOffset(kResultReg, kAddressReg, 0);
  } else {
    __ StoreToOffset(kResultReg, kAddressReg, 0);
  }
  __ LeaveStubFrame();
  __ Ret();

  if (is_final) {
#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
    // We are jumping over LeaveStubFrame so restore LR state to match one
    // at the jump point.
    __ set_lr_state(compiler::LRState::OnEntry().EnterFrame());
#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
    __ Bind(&throw_exception);
    __ PushObject(NullObject());  // Make room for (unused) result.
    __ PushRegister(kFieldReg);
    __ CallRuntime(kLateFieldAssignedDuringInitializationErrorRuntimeEntry,
                   /*argument_count=*/1);
    __ Breakpoint();
  }

  if (FLAG_experimental_shared_data) {
    ASSERT(!is_shared);
#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
    // We are jumping over LeaveStubFrame so restore LR state to match one
    // at the jump point.
    __ set_lr_state(compiler::LRState::OnEntry().EnterFrame());
#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
    // Throw FieldAccessError
    __ Bind(&throw_since_no_isolate_is_present);
    __ PushObject(NullObject());  // Make room for (unused) result.
    __ PushRegister(kFieldReg);
    __ CallRuntime(kStaticFieldAccessedWithoutIsolateErrorRuntimeEntry,
                   /*argument_count=*/1);
    __ Breakpoint();
  }
}

void StubCodeCompiler::GenerateInitLateStaticFieldStub() {
  GenerateInitLateStaticFieldStub(/*is_final=*/false, /*is_shared=*/false);
}

void StubCodeCompiler::GenerateInitLateFinalStaticFieldStub() {
  GenerateInitLateStaticFieldStub(/*is_final=*/true, /*is_shared=*/false);
}

void StubCodeCompiler::GenerateInitSharedLateStaticFieldStub() {
  GenerateInitLateStaticFieldStub(/*is_final=*/false, /*is_shared=*/true);
}

void StubCodeCompiler::GenerateInitInstanceFieldStub() {
  __ EnterStubFrame();
  __ PushObject(NullObject());  // Make room for result.
  __ PushRegistersInOrder(
      {InitInstanceFieldABI::kInstanceReg, InitInstanceFieldABI::kFieldReg});
  __ CallRuntime(kInitInstanceFieldRuntimeEntry, /*argument_count=*/2);
  __ Drop(2);
  __ PopRegister(InitInstanceFieldABI::kResultReg);
  __ LeaveStubFrame();
  __ Ret();
}

void StubCodeCompiler::GenerateInitLateInstanceFieldStub(bool is_final) {
  const Register kInstanceReg = InitInstanceFieldABI::kInstanceReg;
  const Register kFieldReg = InitInstanceFieldABI::kFieldReg;
  const Register kAddressReg = InitLateInstanceFieldInternalRegs::kAddressReg;
  const Register kScratchReg = InitLateInstanceFieldInternalRegs::kScratchReg;

  __ EnterStubFrame();
  // Save kFieldReg and kInstanceReg for later.
  // Call initializer function.
  __ PushRegistersInOrder({kFieldReg, kInstanceReg, kInstanceReg});

  static_assert(
      InitInstanceFieldABI::kResultReg == CallingConventions::kReturnReg,
      "Result is a return value from initializer");

  __ LoadCompressedFieldFromOffset(
      FUNCTION_REG, InitInstanceFieldABI::kFieldReg,
      target::Field::initializer_function_offset());
  if (!FLAG_precompiled_mode) {
    __ LoadCompressedFieldFromOffset(CODE_REG, FUNCTION_REG,
                                     target::Function::code_offset());
#if defined(DART_DYNAMIC_MODULES)
    // InterpretCall stub needs arguments descriptor for all function calls.
    __ LoadObject(ARGS_DESC_REG, ArgumentsDescriptorBoxed(/*type_args_len=*/0,
                                                          /*num_arguments=*/1));
#else
    // Load a GC-safe value for the arguments descriptor (unused but tagged).
    __ LoadImmediate(ARGS_DESC_REG, 0);
#endif  // defined(DART_DYNAMIC_MODULES)
  }
  if (FLAG_target_thread_sanitizer) {
    __ TsanFuncEntry();
  }
  __ Call(FieldAddress(FUNCTION_REG, target::Function::entry_point_offset()));
  if (FLAG_target_thread_sanitizer) {
    __ TsanFuncExit();
  }
  __ Drop(1);  // Drop argument.

  __ PopRegisterPair(kInstanceReg, kFieldReg);
  __ LoadCompressedFieldFromOffset(
      kScratchReg, kFieldReg, target::Field::host_offset_or_field_id_offset());
#if defined(DART_COMPRESSED_POINTERS)
  // TODO(compressed-pointers): Variant of LoadFieldAddressForRegOffset that
  // ignores upper bits?
  __ SmiUntag(kScratchReg);
  __ SmiTag(kScratchReg);
#endif
  __ LoadCompressedFieldAddressForRegOffset(kAddressReg, kInstanceReg,
                                            kScratchReg);

  Label throw_exception;
  if (is_final) {
    __ LoadCompressed(kScratchReg, Address(kAddressReg, 0));
    __ CompareObject(kScratchReg, SentinelObject());
    __ BranchIf(NOT_EQUAL, &throw_exception);
  }

#if defined(TARGET_ARCH_IA32)
  // On IA32 StoreIntoObject clobbers value register, so scratch
  // register is used in StoreIntoObject to preserve kResultReg.
  __ MoveRegister(kScratchReg, InitInstanceFieldABI::kResultReg);
  __ StoreIntoObject(kInstanceReg, Address(kAddressReg, 0), kScratchReg);
#else
  __ StoreCompressedIntoObject(kInstanceReg, Address(kAddressReg, 0),
                               InitInstanceFieldABI::kResultReg);
#endif  // defined(TARGET_ARCH_IA32)

  __ LeaveStubFrame();
  __ Ret();

  if (is_final) {
#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
    // We are jumping over LeaveStubFrame so restore LR state to match one
    // at the jump point.
    __ set_lr_state(compiler::LRState::OnEntry().EnterFrame());
#endif  // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
    __ Bind(&throw_exception);
    __ PushObject(NullObject());  // Make room for (unused) result.
    __ PushRegister(kFieldReg);
    __ CallRuntime(kLateFieldAssignedDuringInitializationErrorRuntimeEntry,
                   /*argument_count=*/1);
    __ Breakpoint();
  }
}

void StubCodeCompiler::GenerateInitLateInstanceFieldStub() {
  GenerateInitLateInstanceFieldStub(/*is_final=*/false);
}

void StubCodeCompiler::GenerateInitLateFinalInstanceFieldStub() {
  GenerateInitLateInstanceFieldStub(/*is_final=*/true);
}

void StubCodeCompiler::GenerateThrowStub() {
  __ EnterStubFrame();
  __ PushObject(NullObject());  // Make room for (unused) result.
  __ PushRegister(ThrowABI::kExceptionReg);
  __ CallRuntime(kThrowRuntimeEntry, /*argument_count=*/1);
  __ Breakpoint();
}

void StubCodeCompiler::GenerateReThrowStub() {
  __ EnterStubFrame();
  __ PushObject(NullObject());  // Make room for (unused) result.
  __ PushRegistersInOrder(
      {ReThrowABI::kExceptionReg, ReThrowABI::kStackTraceReg});
  __ PushImmediate(Smi::RawValue(0));  // Do not bypass debugger.
  __ CallRuntime(kReThrowRuntimeEntry, /*argument_count=*/3);
  __ Breakpoint();
}

void StubCodeCompiler::GenerateAssertSubtypeStub() {
  __ EnterStubFrame();
  __ PushRegistersInOrder({AssertSubtypeABI::kInstantiatorTypeArgumentsReg,
                           AssertSubtypeABI::kFunctionTypeArgumentsReg,
                           AssertSubtypeABI::kSubTypeReg,
                           AssertSubtypeABI::kSuperTypeReg,
                           AssertSubtypeABI::kDstNameReg});
  __ CallRuntime(kSubtypeCheckRuntimeEntry, /*argument_count=*/5);
  __ Drop(5);  // Drop unused result as well as arguments.
  __ LeaveStubFrame();
  __ Ret();
}

void StubCodeCompiler::GenerateAssertAssignableStub() {
#if !defined(TARGET_ARCH_IA32)
  __ Breakpoint();
#else
  __ EnterStubFrame();
  __ PushObject(Object::null_object());  // Make room for the result.
  __ pushl(Address(
      EBP, target::kWordSize * AssertAssignableStubABI::kInstanceSlotFromFp));
  __ pushl(Address(
      EBP, target::kWordSize * AssertAssignableStubABI::kDstTypeSlotFromFp));
  __ pushl(Address(
      EBP,
      target::kWordSize * AssertAssignableStubABI::kInstantiatorTAVSlotFromFp));
  __ pushl(Address(EBP, target::kWordSize *
                            AssertAssignableStubABI::kFunctionTAVSlotFromFp));
  __ PushRegistersInOrder({AssertAssignableStubABI::kDstNameReg,
                           AssertAssignableStubABI::kSubtypeTestReg});
  __ PushObject(Smi::ZoneHandle(Smi::New(kTypeCheckFromInline)));
  __ CallRuntime(kTypeCheckRuntimeEntry, /*argument_count=*/7);
  __ Drop(8);
  __ LeaveStubFrame();
  __ Ret();
#endif
}

// Instantiate type arguments from instantiator and function type args.
// Inputs:
// - InstantiationABI::kUninstantiatedTypeArgumentsReg: tav to instantiate
// - InstantiationABI::kInstantiatorTypeArgumentsReg: instantiator tav
// - InstantiationABI::kFunctionTypeArgumentsReg: function tav
// Outputs:
// - InstantiationABI::kResultTypeArgumentsReg: instantiated tav
// Clobbers:
// - InstantiationABI::kScratchReg
void StubCodeCompiler::GenerateInstantiateTypeArgumentsStub() {
  // We only need the offset of the current entry up until we either call
  // the runtime or until we retrieve the instantiated type arguments out of it
  // to put in the result register, so we use the result register to store it.
  const Register kEntryReg = InstantiationABI::kResultTypeArgumentsReg;

  // The registers that need spilling prior to traversing a hash-based cache.
  const RegisterSet saved_registers(InstantiateTAVInternalRegs::kSavedRegisters,
                                    /*fpu_register_mask=*/0);

  static_assert(((1 << InstantiationABI::kInstantiatorTypeArgumentsReg) &
                 InstantiateTAVInternalRegs::kSavedRegisters) == 0,
                "Must handle possibility of inst tav reg being spilled");
  static_assert(((1 << InstantiationABI::kFunctionTypeArgumentsReg) &
                 InstantiateTAVInternalRegs::kSavedRegisters) == 0,
                "Must handle possibility of function tav reg being spilled");

  // Takes labels for the cache hit/miss cases (to allow for restoring spilled
  // registers).
  auto check_entry = [&](compiler::Label* found, compiler::Label* not_found) {
    __ Comment("Check cache entry");
    // Use load-acquire to get the entry.
    static_assert(TypeArguments::Cache::kSentinelIndex ==
                      TypeArguments::Cache::kInstantiatorTypeArgsIndex,
                  "sentinel is not same index as instantiator type args");
    __ LoadAcquireCompressedFromOffset(
        InstantiationABI::kScratchReg, kEntryReg,
        TypeArguments::Cache::kInstantiatorTypeArgsIndex *
            target::kCompressedWordSize);
    // Test for an unoccupied entry by checking for the Smi sentinel.
    __ BranchIfSmi(InstantiationABI::kScratchReg, not_found);
    // Otherwise it must be occupied and contain TypeArguments objects.
    compiler::Label next;
    __ CompareRegisters(InstantiationABI::kScratchReg,
                        InstantiationABI::kInstantiatorTypeArgumentsReg);
    __ BranchIf(NOT_EQUAL, &next, compiler::Assembler::kNearJump);
    __ LoadCompressed(
        InstantiationABI::kScratchReg,
        compiler::Address(kEntryReg,
                          TypeArguments::Cache::kFunctionTypeArgsIndex *
                              target::kCompressedWordSize));
    __ CompareRegisters(InstantiationABI::kScratchReg,
                        InstantiationABI::kFunctionTypeArgumentsReg);
    __ BranchIf(EQUAL, found);
    __ Bind(&next);
  };

  // Lookup cache before calling runtime.
  __ LoadAcquireCompressedFromOffset(
      InstantiationABI::kScratchReg,
      InstantiationABI::kUninstantiatedTypeArgumentsReg,
      target::TypeArguments::instantiations_offset() - kHeapObjectTag);
  // Go ahead and load the backing array data address into kEntryReg.
  __ LoadFieldAddressForOffset(kEntryReg, InstantiationABI::kScratchReg,
                               target::Array::data_offset());

  compiler::Label linear_cache_loop, hash_cache_search, cache_hit, call_runtime;

  // There is a maximum size for linear caches that is smaller than the size
  // of any hash-based cache, so we check the size of the backing array to
  // determine if this is a linear or hash-based cache.
  __ LoadFromSlot(InstantiationABI::kScratchReg, InstantiationABI::kScratchReg,
                  Slot::Array_length());
  __ CompareImmediate(
      InstantiationABI::kScratchReg,
      target::ToRawSmi(TypeArguments::Cache::kMaxLinearCacheSize));
#if defined(TARGET_ARCH_IA32)
  // We just don't have enough registers to do hash-based cache searching in a
  // way that doesn't overly complicate the generation code, so just go to
  // runtime.
  __ BranchIf(GREATER, &call_runtime);
#else
  __ BranchIf(GREATER, &hash_cache_search);
#endif

  __ Comment("Check linear cache");
  // Move kEntryReg to the start of the first entry.
  __ AddImmediate(kEntryReg, TypeArguments::Cache::kHeaderSize *
                                 target::kCompressedWordSize);
  __ Bind(&linear_cache_loop);
  check_entry(&cache_hit, &call_runtime);
  __ AddImmediate(kEntryReg, TypeArguments::Cache::kEntrySize *
                                 target::kCompressedWordSize);
  __ Jump(&linear_cache_loop, compiler::Assembler::kNearJump);

#if !defined(TARGET_ARCH_IA32)
  __ Bind(&hash_cache_search);
  __ Comment("Check hash-based cache");

  compiler::Label pop_before_success, pop_before_failure;
  if (!saved_registers.IsEmpty()) {
    __ Comment("Spills due to register pressure");
    __ PushRegisters(saved_registers);
  }

  __ Comment("Calculate address of first entry");
  __ AddImmediate(
      InstantiateTAVInternalRegs::kEntryStartReg, kEntryReg,
      TypeArguments::Cache::kHeaderSize * target::kCompressedWordSize);

  __ Comment("Calculate probe mask");
  __ LoadAcquireCompressedFromOffset(
      InstantiationABI::kScratchReg, kEntryReg,
      TypeArguments::Cache::kMetadataIndex * target::kCompressedWordSize);
  __ LsrImmediate(
      InstantiationABI::kScratchReg,
      TypeArguments::Cache::EntryCountLog2Bits::shift() + kSmiTagShift);
  __ LoadImmediate(InstantiateTAVInternalRegs::kProbeMaskReg, 1);
  __ LslRegister(InstantiateTAVInternalRegs::kProbeMaskReg,
                 InstantiationABI::kScratchReg);
  __ AddImmediate(InstantiateTAVInternalRegs::kProbeMaskReg, -1);
  // Can use kEntryReg as scratch now until we're entering the loop.

  // Retrieve the hash from the TAV. If the retrieved hash is 0, jumps to
  // not_found, otherwise falls through.
  auto retrieve_hash = [&](Register dst, Register src) {
    Label is_not_null, done;
    __ CompareObject(src, NullObject());
    __ BranchIf(NOT_EQUAL, &is_not_null, compiler::Assembler::kNearJump);
    __ LoadImmediate(dst, TypeArguments::kAllDynamicHash);
    __ Jump(&done, compiler::Assembler::kNearJump);
    __ Bind(&is_not_null);
    __ LoadFromSlot(dst, src, Slot::TypeArguments_hash());
    __ SmiUntag(dst);
    // If the retrieved hash is 0, then it hasn't been computed yet.
    __ BranchIfZero(dst, &pop_before_failure);
    __ Bind(&done);
  };

  __ Comment("Calculate initial probe from type argument vector hashes");
  retrieve_hash(InstantiateTAVInternalRegs::kCurrentEntryIndexReg,
                InstantiationABI::kInstantiatorTypeArgumentsReg);
  retrieve_hash(InstantiationABI::kScratchReg,
                InstantiationABI::kFunctionTypeArgumentsReg);
  __ CombineHashes(InstantiateTAVInternalRegs::kCurrentEntryIndexReg,
                   InstantiationABI::kScratchReg);
  __ FinalizeHash(InstantiateTAVInternalRegs::kCurrentEntryIndexReg,
                  InstantiationABI::kScratchReg);
  // Use the probe mask to get a valid entry index.
  __ AndRegisters(InstantiateTAVInternalRegs::kCurrentEntryIndexReg,
                  InstantiateTAVInternalRegs::kProbeMaskReg);

  // Start off the probing distance at zero (will increment prior to use).
  __ LoadImmediate(InstantiateTAVInternalRegs::kProbeDistanceReg, 0);

  compiler::Label loop;
  __ Bind(&loop);
  __ Comment("Loop over hash cache entries");
  // Convert the current entry index into the entry address.
  __ MoveRegister(kEntryReg, InstantiateTAVInternalRegs::kCurrentEntryIndexReg);
  __ MulImmediate(kEntryReg, TypeArguments::Cache::kEntrySize *
                                 target::kCompressedWordSize);
  __ AddRegisters(kEntryReg, InstantiateTAVInternalRegs::kEntryStartReg);
  check_entry(&pop_before_success, &pop_before_failure);
  // Increment the probing distance and then add it to the current entry
  // index, then mask the result with the probe mask.
  __ AddImmediate(InstantiateTAVInternalRegs::kProbeDistanceReg, 1);
  __ AddRegisters(InstantiateTAVInternalRegs::kCurrentEntryIndexReg,
                  InstantiateTAVInternalRegs::kProbeDistanceReg);
  __ AndRegisters(InstantiateTAVInternalRegs::kCurrentEntryIndexReg,
                  InstantiateTAVInternalRegs::kProbeMaskReg);
  __ Jump(&loop);

  __ Bind(&pop_before_failure);
  if (!saved_registers.IsEmpty()) {
    __ Comment("Restore spilled registers on cache miss");
    __ PopRegisters(saved_registers);
  }
#endif

  // Instantiate non-null type arguments.
  // A runtime call to instantiate the type arguments is required.
  __ Bind(&call_runtime);
  __ Comment("Cache miss");
  __ EnterStubFrame();
#if !defined(DART_ASSEMBLER_HAS_NULL_REG)
  __ PushObject(Object::null_object());  // Make room for the result.
#endif
#if defined(TARGET_ARCH_ARM)
  static_assert((InstantiationABI::kUninstantiatedTypeArgumentsReg >
                 InstantiationABI::kInstantiatorTypeArgumentsReg) &&
                    (InstantiationABI::kInstantiatorTypeArgumentsReg >
                     InstantiationABI::kFunctionTypeArgumentsReg),
                "Should be ordered to push arguments with one instruction");
#endif
  __ PushRegistersInOrder({
#if defined(DART_ASSEMBLER_HAS_NULL_REG)
      NULL_REG,
#endif
      InstantiationABI::kUninstantiatedTypeArgumentsReg,
      InstantiationABI::kInstantiatorTypeArgumentsReg,
      InstantiationABI::kFunctionTypeArgumentsReg,
  });
  __ CallRuntime(kInstantiateTypeArgumentsRuntimeEntry, 3);
  __ Drop(3);  // Drop 2 type vectors, and uninstantiated type.
  __ PopRegister(InstantiationABI::kResultTypeArgumentsReg);
  __ LeaveStubFrame();
  __ Ret();

#if !defined(TARGET_ARCH_IA32)
  __ Bind(&pop_before_success);
  if (!saved_registers.IsEmpty()) {
    __ Comment("Restore spilled registers on cache hit");
    __ PopRegisters(saved_registers);
  }
#endif

  __ Bind(&cache_hit);
  __ Comment("Cache hit");
  __ LoadCompressed(
      InstantiationABI::kResultTypeArgumentsReg,
      compiler::Address(kEntryReg,
                        TypeArguments::Cache::kInstantiatedTypeArgsIndex *
                            target::kCompressedWordSize));
  __ Ret();
}

void StubCodeCompiler::
    GenerateInstantiateTypeArgumentsMayShareInstantiatorTAStub() {
  const Register kScratch1Reg = InstantiationABI::kResultTypeArgumentsReg;
  const Register kScratch2Reg = InstantiationABI::kScratchReg;
  // Return the instantiator type arguments if its nullability is compatible for
  // sharing, otherwise proceed to instantiation cache lookup.
  compiler::Label cache_lookup;
  __ LoadCompressedSmi(
      kScratch1Reg,
      compiler::FieldAddress(InstantiationABI::kUninstantiatedTypeArgumentsReg,
                             target::TypeArguments::nullability_offset()));
  __ LoadCompressedSmi(
      kScratch2Reg,
      compiler::FieldAddress(InstantiationABI::kInstantiatorTypeArgumentsReg,
                             target::TypeArguments::nullability_offset()));
  __ AndRegisters(kScratch2Reg, kScratch1Reg);
  __ CompareRegisters(kScratch2Reg, kScratch1Reg);
  __ BranchIf(NOT_EQUAL, &cache_lookup, compiler::Assembler::kNearJump);
  __ MoveRegister(InstantiationABI::kResultTypeArgumentsReg,
                  InstantiationABI::kInstantiatorTypeArgumentsReg);
  __ Ret();

  __ Bind(&cache_lookup);
  GenerateInstantiateTypeArgumentsStub();
}

void StubCodeCompiler::
    GenerateInstantiateTypeArgumentsMayShareFunctionTAStub() {
  const Register kScratch1Reg = InstantiationABI::kResultTypeArgumentsReg;
  const Register kScratch2Reg = InstantiationABI::kScratchReg;
  // Return the function type arguments if its nullability is compatible for
  // sharing, otherwise proceed to instantiation cache lookup.
  compiler::Label cache_lookup;
  __ LoadCompressedSmi(
      kScratch1Reg,
      compiler::FieldAddress(InstantiationABI::kUninstantiatedTypeArgumentsReg,
                             target::TypeArguments::nullability_offset()));
  __ LoadCompressedSmi(
      kScratch2Reg,
      compiler::FieldAddress(InstantiationABI::kFunctionTypeArgumentsReg,
                             target::TypeArguments::nullability_offset()));
  __ AndRegisters(kScratch2Reg, kScratch1Reg);
  __ CompareRegisters(kScratch2Reg, kScratch1Reg);
  __ BranchIf(NOT_EQUAL, &cache_lookup, compiler::Assembler::kNearJump);
  __ MoveRegister(InstantiationABI::kResultTypeArgumentsReg,
                  InstantiationABI::kFunctionTypeArgumentsReg);
  __ Ret();

  __ Bind(&cache_lookup);
  GenerateInstantiateTypeArgumentsStub();
}

static void BuildInstantiateTypeRuntimeCall(Assembler* assembler) {
  __ EnterStubFrame();
  __ PushObject(Object::null_object());
  __ PushRegistersInOrder({InstantiateTypeABI::kTypeReg,
                           InstantiateTypeABI::kInstantiatorTypeArgumentsReg,
                           InstantiateTypeABI::kFunctionTypeArgumentsReg});
  __ CallRuntime(kInstantiateTypeRuntimeEntry, /*argument_count=*/3);
  __ Drop(3);
  __ PopRegister(InstantiateTypeABI::kResultTypeReg);
  __ LeaveStubFrame();
  __ Ret();
}

static void BuildInstantiateTypeParameterStub(Assembler* assembler,
                                              Nullability nullability,
                                              bool is_function_parameter) {
  Label runtime_call, return_dynamic, type_parameter_value_is_not_type;

  if (is_function_parameter) {
    __ CompareObject(InstantiateTypeABI::kFunctionTypeArgumentsReg,
                     TypeArguments::null_object());
    __ BranchIf(EQUAL, &return_dynamic);
    __ LoadFieldFromOffset(
        InstantiateTypeABI::kResultTypeReg, InstantiateTypeABI::kTypeReg,
        target::TypeParameter::index_offset(), kUnsignedTwoBytes);
    __ LoadIndexedCompressed(InstantiateTypeABI::kResultTypeReg,
                             InstantiateTypeABI::kFunctionTypeArgumentsReg,
                             target::TypeArguments::types_offset(),
                             InstantiateTypeABI::kResultTypeReg);
  } else {
    __ CompareObject(InstantiateTypeABI::kInstantiatorTypeArgumentsReg,
                     TypeArguments::null_object());
    __ BranchIf(EQUAL, &return_dynamic);
    __ LoadFieldFromOffset(
        InstantiateTypeABI::kResultTypeReg, InstantiateTypeABI::kTypeReg,
        target::TypeParameter::index_offset(), kUnsignedTwoBytes);
    __ LoadIndexedCompressed(InstantiateTypeABI::kResultTypeReg,
                             InstantiateTypeABI::kInstantiatorTypeArgumentsReg,
                             target::TypeArguments::types_offset(),
                             InstantiateTypeABI::kResultTypeReg);
  }

  switch (nullability) {
    case Nullability::kNonNullable:
      __ Ret();
      break;
    case Nullability::kNullable:
      __ CompareAbstractTypeNullabilityWith(
          InstantiateTypeABI::kResultTypeReg,
          static_cast<int8_t>(Nullability::kNullable),
          InstantiateTypeABI::kScratchReg);
      __ BranchIf(NOT_EQUAL, &runtime_call);
      __ Ret();
      break;
  }

  // The TAV was null, so the value of the type parameter is "dynamic".
  __ Bind(&return_dynamic);
  __ LoadObject(InstantiateTypeABI::kResultTypeReg, Type::dynamic_type());
  __ Ret();

  __ Bind(&runtime_call);
  BuildInstantiateTypeRuntimeCall(assembler);
}

void StubCodeCompiler::
    GenerateInstantiateTypeNonNullableClassTypeParameterStub() {
  BuildInstantiateTypeParameterStub(assembler, Nullability::kNonNullable,
                                    /*is_function_parameter=*/false);
}

void StubCodeCompiler::GenerateInstantiateTypeNullableClassTypeParameterStub() {
  BuildInstantiateTypeParameterStub(assembler, Nullability::kNullable,
                                    /*is_function_parameter=*/false);
}

void StubCodeCompiler::
    GenerateInstantiateTypeNonNullableFunctionTypeParameterStub() {
  BuildInstantiateTypeParameterStub(assembler, Nullability::kNonNullable,
                                    /*is_function_parameter=*/true);
}

void StubCodeCompiler::
    GenerateInstantiateTypeNullableFunctionTypeParameterStub() {
  BuildInstantiateTypeParameterStub(assembler, Nullability::kNullable,
                                    /*is_function_parameter=*/true);
}

void StubCodeCompiler::GenerateInstantiateTypeStub() {
  BuildInstantiateTypeRuntimeCall(assembler);
}

void StubCodeCompiler::GenerateInstanceOfStub() {
  __ EnterStubFrame();
  __ PushObject(NullObject());  // Make room for the result.
  __ PushRegistersInOrder({TypeTestABI::kInstanceReg, TypeTestABI::kDstTypeReg,
                           TypeTestABI::kInstantiatorTypeArgumentsReg,
                           TypeTestABI::kFunctionTypeArgumentsReg,
                           TypeTestABI::kSubtypeTestCacheReg});
  __ CallRuntime(kInstanceofRuntimeEntry, /*argument_count=*/5);
  __ Drop(5);
  __ PopRegister(TypeTestABI::kInstanceOfResultReg);
  __ LeaveStubFrame();
  __ Ret();
}

// For use in GenerateTypeIsTopTypeForSubtyping and
// GenerateNullIsAssignableToType.
static void EnsureIsSomeKindOfType(Assembler* assembler,
                                   Register type_reg,
                                   Register scratch_reg) {
#if defined(DEBUG)
  compiler::Label is_type_param_or_type_or_function_type;
  __ LoadClassIdMayBeSmi(scratch_reg, type_reg);
  __ CompareImmediate(scratch_reg, kTypeParameterCid);
  __ BranchIf(EQUAL, &is_type_param_or_type_or_function_type,
              compiler::Assembler::kNearJump);
  __ CompareImmediate(scratch_reg, kTypeCid);
  __ BranchIf(EQUAL, &is_type_param_or_type_or_function_type,
              compiler::Assembler::kNearJump);
  __ CompareImmediate(scratch_reg, kFunctionTypeCid);
  __ BranchIf(EQUAL, &is_type_param_or_type_or_function_type,
              compiler::Assembler::kNearJump);
  __ CompareImmediate(scratch_reg, kRecordTypeCid);
  __ BranchIf(EQUAL, &is_type_param_or_type_or_function_type,
              compiler::Assembler::kNearJump);
  __ Stop("not a type, function type, record type or type parameter");
  __ Bind(&is_type_param_or_type_or_function_type);
#endif
}

// Version of AbstractType::IsTopTypeForSubtyping() used when the type is not
// known at compile time. Must be kept in sync.
//
// Inputs:
// - TypeTestABI::kDstTypeReg: Destination type.
//
// Non-preserved scratch registers:
// - TypeTestABI::kScratchReg (only on non-IA32 architectures)
//
// Outputs:
// - TypeTestABI::kSubtypeTestCacheReg: 0 if the value is guaranteed assignable,
//   non-zero otherwise.
//
// All registers other than outputs and non-preserved scratches are preserved.
void StubCodeCompiler::GenerateTypeIsTopTypeForSubtypingStub() {
  // The only case where the original value of kSubtypeTestCacheReg is needed
  // after the stub call is on IA32, where it's currently preserved on the stack
  // before calling the stub (as it's also CODE_REG on that architecture), so we
  // both use it as a scratch and clobber it for the return value.
  const Register scratch1_reg = TypeTestABI::kSubtypeTestCacheReg;
  // We reuse the first scratch register as the output register because we're
  // always guaranteed to have a type in it (starting with kDstType), and all
  // non-Smi ObjectPtrs are non-zero values.
  const Register output_reg = scratch1_reg;
#if defined(TARGET_ARCH_IA32)
  // The remaining scratch registers are preserved and restored before exit on
  // IA32. Because  we have few registers to choose from (which are all used in
  // TypeTestABI), use specific TestTypeABI registers.
  const Register scratch2_reg = TypeTestABI::kFunctionTypeArgumentsReg;
  // Preserve non-output scratch registers.
  __ PushRegister(scratch2_reg);
#else
  const Register scratch2_reg = TypeTestABI::kScratchReg;
#endif
  static_assert(scratch1_reg != scratch2_reg,
                "both scratch registers are the same");

  compiler::Label check_top_type, is_top_type, done;
  // Initialize scratch1_reg with the type to check (which also sets the
  // output register to a non-zero value). scratch1_reg (and thus the output
  // register) will always have a type in it from here on out.
  __ MoveRegister(scratch1_reg, TypeTestABI::kDstTypeReg);
  __ Bind(&check_top_type);
  // scratch1_reg: Current type to check.
  EnsureIsSomeKindOfType(assembler, scratch1_reg, scratch2_reg);
  compiler::Label is_type_ref;
  __ CompareClassId(scratch1_reg, kTypeCid, scratch2_reg);
  // Type parameters can't be top types themselves, though a particular
  // instantiation may result in a top type.
  // Function types cannot be top types.
  __ BranchIf(NOT_EQUAL, &done);
  __ LoadTypeClassId(scratch2_reg, scratch1_reg);
  __ CompareImmediate(scratch2_reg, kDynamicCid);
  __ BranchIf(EQUAL, &is_top_type, compiler::Assembler::kNearJump);
  __ CompareImmediate(scratch2_reg, kVoidCid);
  __ BranchIf(EQUAL, &is_top_type, compiler::Assembler::kNearJump);
  compiler::Label unwrap_future_or;
  __ CompareImmediate(scratch2_reg, kFutureOrCid);
  __ BranchIf(EQUAL, &unwrap_future_or, compiler::Assembler::kNearJump);
  __ CompareImmediate(scratch2_reg, kInstanceCid);
  __ BranchIf(NOT_EQUAL, &done, compiler::Assembler::kNearJump);
  // Instance type isn't a top type if non-nullable.
  __ CompareAbstractTypeNullabilityWith(
      scratch1_reg, static_cast<int8_t>(Nullability::kNonNullable),
      scratch2_reg);
  __ BranchIf(EQUAL, &done, compiler::Assembler::kNearJump);
  __ Bind(&is_top_type);
  __ LoadImmediate(output_reg, 0);
  __ Bind(&done);
#if defined(TARGET_ARCH_IA32)
  // Restore preserved scratch registers.
  __ PopRegister(scratch2_reg);
#endif
  __ Ret();
  // An uncommon case, so off the main trunk of the function.
  __ Bind(&unwrap_future_or);
  __ LoadCompressedField(
      scratch2_reg,
      compiler::FieldAddress(scratch1_reg,
                             compiler::target::Type::arguments_offset()));
  __ CompareObject(scratch2_reg, Object::null_object());
  // If the arguments are null, then unwrapping gives dynamic, a top type.
  __ BranchIf(EQUAL, &is_top_type, compiler::Assembler::kNearJump);
  __ LoadCompressedField(
      scratch1_reg,
      compiler::FieldAddress(
          scratch2_reg, compiler::target::TypeArguments::type_at_offset(0)));
  __ Jump(&check_top_type, compiler::Assembler::kNearJump);
}

// Version of Instance::NullIsAssignableTo(other, inst_tav, fun_tav) used when
// the destination type was not known at compile time. Must be kept in sync.
//
// Inputs:
// - TypeTestABI::kInstanceReg: Object to check for assignability.
// - TypeTestABI::kDstTypeReg: Destination type.
// - TypeTestABI::kInstantiatorTypeArgumentsReg: Instantiator TAV.
// - TypeTestABI::kFunctionTypeArgumentsReg: Function TAV.
//
// Non-preserved non-output scratch registers:
// - TypeTestABI::kScratchReg (only on non-IA32 architectures)
//
// Outputs:
// - TypeTestABI::kSubtypeTestCacheReg: 0 if the value is guaranteed assignable,
//   non-zero otherwise.
//
// All registers other than outputs and non-preserved scratches are preserved.
void StubCodeCompiler::GenerateNullIsAssignableToTypeStub() {
  // The only case where the original value of kSubtypeTestCacheReg is needed
  // after the stub call is on IA32, where it's currently preserved on the stack
  // before calling the stub (as it's also CODE_REG on that architecture), so we
  // both use it as a scratch to hold the current type to inspect and also
  // clobber it for the return value.
  const Register kCurrentTypeReg = TypeTestABI::kSubtypeTestCacheReg;
  // We reuse the first scratch register as the output register because we're
  // always guaranteed to have a type in it (starting with the contents of
  // kDstTypeReg), and all non-Smi ObjectPtrs are non-zero values.
  const Register kOutputReg = kCurrentTypeReg;
#if defined(TARGET_ARCH_IA32)
  // The remaining scratch registers are preserved and restored before exit on
  // IA32. Because  we have few registers to choose from (which are all used in
  // TypeTestABI), use specific TestTypeABI registers.
  const Register kScratchReg = TypeTestABI::kFunctionTypeArgumentsReg;
  // Preserve non-output scratch registers.
  __ PushRegister(kScratchReg);
#else
  const Register kScratchReg = TypeTestABI::kScratchReg;
#endif
  static_assert(kCurrentTypeReg != kScratchReg,
                "code assumes distinct scratch registers");

  compiler::Label is_assignable, done;
  // Initialize the first scratch register (and thus the output register) with
  // the destination type. We do this before the check to ensure the output
  // register has a non-zero value if kInstanceReg is not null.
  __ MoveRegister(kCurrentTypeReg, TypeTestABI::kDstTypeReg);
  __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());

  compiler::Label check_null_assignable;
  // Skip checking the type if not null.
  __ BranchIf(NOT_EQUAL, &done);
  __ Bind(&check_null_assignable);
  // scratch1_reg: Current type to check.
  EnsureIsSomeKindOfType(assembler, kCurrentTypeReg, kScratchReg);
  compiler::Label is_not_type;
  __ CompareClassId(kCurrentTypeReg, kTypeCid, kScratchReg);
  __ BranchIf(NOT_EQUAL, &is_not_type, compiler::Assembler::kNearJump);
  __ CompareAbstractTypeNullabilityWith(
      kCurrentTypeReg, static_cast<int8_t>(Nullability::kNonNullable),
      kScratchReg);
  __ BranchIf(NOT_EQUAL, &is_assignable);
  // FutureOr is a special case because it may have the non-nullable bit set,
  // but FutureOr<T> functions as the union of T and Future<T>, so it must be
  // unwrapped to see if T is nullable.
  __ LoadTypeClassId(kScratchReg, kCurrentTypeReg);
  __ CompareImmediate(kScratchReg, kFutureOrCid);
  __ BranchIf(NOT_EQUAL, &done);
  __ LoadCompressedField(
      kScratchReg,
      compiler::FieldAddress(kCurrentTypeReg,
                             compiler::target::Type::arguments_offset()));
  __ CompareObject(kScratchReg, Object::null_object());
  // If the arguments are null, then unwrapping gives the dynamic type,
  // which can take null.
  __ BranchIf(EQUAL, &is_assignable);
  __ LoadCompressedField(
      kCurrentTypeReg,
      compiler::FieldAddress(
          kScratchReg, compiler::target::TypeArguments::type_at_offset(0)));
  __ Jump(&check_null_assignable, compiler::Assembler::kNearJump);
  __ Bind(&is_not_type);
  // Null is assignable to a type parameter only if it is nullable or if the
  // instantiation is nullable.
  __ CompareAbstractTypeNullabilityWith(
      kCurrentTypeReg, static_cast<int8_t>(Nullability::kNonNullable),
      kScratchReg);
  __ BranchIf(NOT_EQUAL, &is_assignable);

  // Don't set kScratchReg in here as on IA32, that's the function TAV reg.
  auto handle_case = [&](Register tav) {
    // We can reuse kCurrentTypeReg to hold the index because we no longer
    // need the type parameter afterwards.
    auto const kIndexReg = kCurrentTypeReg;
    // If the TAV is null, resolving gives the (nullable) dynamic type.
    __ CompareObject(tav, NullObject());
    __ BranchIf(EQUAL, &is_assignable, Assembler::kNearJump);
    // Resolve the type parameter to its instantiated type and loop.
    __ LoadFieldFromOffset(kIndexReg, kCurrentTypeReg,
                           target::TypeParameter::index_offset(),
                           kUnsignedTwoBytes);
    __ LoadIndexedCompressed(kCurrentTypeReg, tav,
                             target::TypeArguments::types_offset(), kIndexReg);
    __ Jump(&check_null_assignable);
  };

  Label function_type_param;
  __ LoadFromSlot(kScratchReg, TypeTestABI::kDstTypeReg,
                  Slot::AbstractType_flags());
  __ BranchIfBit(kScratchReg,
                 target::UntaggedTypeParameter::kIsFunctionTypeParameterBit,
                 NOT_ZERO, &function_type_param, Assembler::kNearJump);
  handle_case(TypeTestABI::kInstantiatorTypeArgumentsReg);
  __ Bind(&function_type_param);
#if defined(TARGET_ARCH_IA32)
  // Function TAV is on top of stack because we're using that register as
  // kScratchReg.
  __ LoadFromStack(TypeTestABI::kFunctionTypeArgumentsReg, 0);
#endif
  handle_case(TypeTestABI::kFunctionTypeArgumentsReg);

  __ Bind(&is_assignable);
  __ LoadImmediate(kOutputReg, 0);
  __ Bind(&done);
#if defined(TARGET_ARCH_IA32)
  // Restore preserved scratch registers.
  __ PopRegister(kScratchReg);
#endif
  __ Ret();
}

#if !defined(TARGET_ARCH_IA32)
// The <X>TypeTestStubs are used to test whether a given value is of a given
// type. All variants have the same calling convention:
//
// Inputs (from TypeTestABI struct):
//   - kSubtypeTestCacheReg: RawSubtypeTestCache
//   - kInstanceReg: instance to test against.
//   - kInstantiatorTypeArgumentsReg : instantiator type arguments (if needed).
//   - kFunctionTypeArgumentsReg : function type arguments (if needed).
//
// See GenerateSubtypeNTestCacheStub for registers that may need saving by the
// caller.
//
// Output (from TypeTestABI struct):
//   - kResultReg: checked instance.
//
// Throws if the check is unsuccessful.
//
// Note of warning: The caller will not populate CODE_REG and we have therefore
// no access to the pool.
void StubCodeCompiler::GenerateDefaultTypeTestStub() {
  __ LoadFromOffset(CODE_REG, THR,
                    target::Thread::slow_type_test_stub_offset());
  __ Jump(FieldAddress(CODE_REG, target::Code::entry_point_offset()));
}

// Used instead of DefaultTypeTestStub when null is assignable.
void StubCodeCompiler::GenerateDefaultNullableTypeTestStub() {
  Label done;

  // Fast case for 'null'.
  __ CompareObject(TypeTestABI::kInstanceReg, NullObject());
  __ BranchIf(EQUAL, &done);

  __ LoadFromOffset(CODE_REG, THR,
                    target::Thread::slow_type_test_stub_offset());
  __ Jump(FieldAddress(CODE_REG, target::Code::entry_point_offset()));

  __ Bind(&done);
  __ Ret();
}

void StubCodeCompiler::GenerateTopTypeTypeTestStub() {
  __ Ret();
}

void StubCodeCompiler::GenerateUnreachableTypeTestStub() {
  __ Breakpoint();
}

static void BuildTypeParameterTypeTestStub(Assembler* assembler,
                                           bool allow_null) {
  Label done;

  if (allow_null) {
    __ CompareObject(TypeTestABI::kInstanceReg, NullObject());
    __ BranchIf(EQUAL, &done, Assembler::kNearJump);
  }

  auto handle_case = [&](Register tav) {
    // If the TAV is null, then resolving the type parameter gives the dynamic
    // type, which is a top type.
    __ CompareObject(tav, NullObject());
    __ BranchIf(EQUAL, &done, Assembler::kNearJump);
    // Resolve the type parameter to its instantiated type and tail call the
    // instantiated type's TTS.
    __ LoadFieldFromOffset(TypeTestABI::kScratchReg, TypeTestABI::kDstTypeReg,
                           target::TypeParameter::index_offset(),
                           kUnsignedTwoBytes);
    __ LoadIndexedCompressed(TypeTestABI::kScratchReg, tav,
                             target::TypeArguments::types_offset(),
                             TypeTestABI::kScratchReg);
    __ Jump(FieldAddress(
        TypeTestABI::kScratchReg,
        target::AbstractType::type_test_stub_entry_point_offset()));
  };

  Label function_type_param;
  __ LoadFromSlot(TypeTestABI::kScratchReg, TypeTestABI::kDstTypeReg,
                  Slot::AbstractType_flags());
  __ BranchIfBit(TypeTestABI::kScratchReg,
                 target::UntaggedTypeParameter::kIsFunctionTypeParameterBit,
                 NOT_ZERO, &function_type_param, Assembler::kNearJump);
  handle_case(TypeTestABI::kInstantiatorTypeArgumentsReg);
  __ Bind(&function_type_param);
  handle_case(TypeTestABI::kFunctionTypeArgumentsReg);
  __ Bind(&done);
  __ Ret();
}

void StubCodeCompiler::GenerateNullableTypeParameterTypeTestStub() {
  BuildTypeParameterTypeTestStub(assembler, /*allow_null=*/true);
}

void StubCodeCompiler::GenerateTypeParameterTypeTestStub() {
  BuildTypeParameterTypeTestStub(assembler, /*allow_null=*/false);
}

static void InvokeTypeCheckFromTypeTestStub(Assembler* assembler,
                                            TypeCheckMode mode) {
  __ PushObject(NullObject());  // Make room for result.
  __ PushRegistersInOrder({TypeTestABI::kInstanceReg, TypeTestABI::kDstTypeReg,
                           TypeTestABI::kInstantiatorTypeArgumentsReg,
                           TypeTestABI::kFunctionTypeArgumentsReg});
  __ PushObject(NullObject());
  __ PushRegister(TypeTestABI::kSubtypeTestCacheReg);
  __ PushImmediate(target::ToRawSmi(mode));
  __ CallRuntime(kTypeCheckRuntimeEntry, 7);
  __ Drop(1);  // mode
  __ PopRegister(TypeTestABI::kSubtypeTestCacheReg);
  __ Drop(1);  // dst_name
  __ PopRegister(TypeTestABI::kFunctionTypeArgumentsReg);
  __ PopRegister(TypeTestABI::kInstantiatorTypeArgumentsReg);
  __ PopRegister(TypeTestABI::kDstTypeReg);
  __ PopRegister(TypeTestABI::kInstanceReg);
  __ Drop(1);  // Discard return value.
}

void StubCodeCompiler::GenerateLazySpecializeTypeTestStub() {
  __ LoadFromOffset(CODE_REG, THR,
                    target::Thread::lazy_specialize_type_test_stub_offset());
  __ EnterStubFrame();
  InvokeTypeCheckFromTypeTestStub(assembler, kTypeCheckFromLazySpecializeStub);
  __ LeaveStubFrame();
  __ Ret();
}

// Used instead of LazySpecializeTypeTestStub when null is assignable.
void StubCodeCompiler::GenerateLazySpecializeNullableTypeTestStub() {
  Label done;

  __ CompareObject(TypeTestABI::kInstanceReg, NullObject());
  __ BranchIf(EQUAL, &done);

  __ LoadFromOffset(CODE_REG, THR,
                    target::Thread::lazy_specialize_type_test_stub_offset());
  __ EnterStubFrame();
  InvokeTypeCheckFromTypeTestStub(assembler, kTypeCheckFromLazySpecializeStub);
  __ LeaveStubFrame();

  __ Bind(&done);
  __ Ret();
}

void StubCodeCompiler::GenerateSlowTypeTestStub() {
  Label done, call_runtime;

  if (!FLAG_precompiled_mode) {
    __ LoadFromOffset(CODE_REG, THR,
                      target::Thread::slow_type_test_stub_offset());
  }
  __ EnterStubFrame();

  // If the subtype-cache is null, it needs to be lazily-created by the runtime.
  __ CompareObject(TypeTestABI::kSubtypeTestCacheReg, NullObject());
  __ BranchIf(EQUAL, &call_runtime);

  // Use the number of inputs used by the STC to determine which stub to call.
  Label call_2, call_3, call_4, call_6;
  __ Comment("Check number of STC inputs");
  __ LoadFromSlot(TypeTestABI::kScratchReg, TypeTestABI::kSubtypeTestCacheReg,
                  Slot::SubtypeTestCache_num_inputs());
  __ CompareImmediate(TypeTestABI::kScratchReg, 2);
  __ BranchIf(EQUAL, &call_2, Assembler::kNearJump);
  __ CompareImmediate(TypeTestABI::kScratchReg, 3);
  __ BranchIf(EQUAL, &call_3, Assembler::kNearJump);
  __ CompareImmediate(TypeTestABI::kScratchReg, 4);
  __ BranchIf(EQUAL, &call_4, Assembler::kNearJump);
  __ CompareImmediate(TypeTestABI::kScratchReg, 6);
  __ BranchIf(EQUAL, &call_6, Assembler::kNearJump);
  // Fall through to the all inputs case.

  {
    __ Comment("Call 7 input STC check");
    __ Call(StubCodeSubtype7TestCache());
    __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg,
                     CastHandle<Object>(TrueObject()));
    __ BranchIf(EQUAL, &done);  // Cache said: yes.
    __ Jump(&call_runtime, Assembler::kNearJump);
  }

  __ Bind(&call_6);
  {
    __ Comment("Call 6 input STC check");
    __ Call(StubCodeSubtype6TestCache());
    __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg,
                     CastHandle<Object>(TrueObject()));
    __ BranchIf(EQUAL, &done);  // Cache said: yes.
    __ Jump(&call_runtime, Assembler::kNearJump);
  }

  __ Bind(&call_4);
  {
    __ Comment("Call 4 input STC check");
    __ Call(StubCodeSubtype4TestCache());
    __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg,
                     CastHandle<Object>(TrueObject()));
    __ BranchIf(EQUAL, &done);  // Cache said: yes.
    __ Jump(&call_runtime, Assembler::kNearJump);
  }

  __ Bind(&call_3);
  {
    __ Comment("Call 3 input STC check");
    __ Call(StubCodeSubtype3TestCache());
    __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg,
                     CastHandle<Object>(TrueObject()));
    __ BranchIf(EQUAL, &done);  // Cache said: yes.
    __ Jump(&call_runtime, Assembler::kNearJump);
  }

  __ Bind(&call_2);
  {
    __ Comment("Call 2 input STC check");
    __ Call(StubCodeSubtype2TestCache());
    __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg,
                     CastHandle<Object>(TrueObject()));
    __ BranchIf(EQUAL, &done);  // Cache said: yes.
    // Fall through to runtime_call
  }

  __ Bind(&call_runtime);
  __ Comment("Call runtime");

  InvokeTypeCheckFromTypeTestStub(assembler, kTypeCheckFromSlowStub);

  __ Bind(&done);
  __ Comment("Done");
  __ LeaveStubFrame();
  __ Ret();
}
#else
// Type testing stubs are not implemented on IA32.
#define GENERATE_BREAKPOINT_STUB(Name)                                         \
  void StubCodeCompiler::Generate##Name##Stub() {                              \
    __ Breakpoint();                                                           \
  }

VM_TYPE_TESTING_STUB_CODE_LIST(GENERATE_BREAKPOINT_STUB)

#undef GENERATE_BREAKPOINT_STUB
#endif  // !defined(TARGET_ARCH_IA32)

// Called for inline allocation of closure.
// Input (preserved):
//   AllocateClosureABI::kFunctionReg: closure function.
//   AllocateClosureABI::kLengthAndFlagsReg: encoded length_and_flags.
//   AllocateClosureABI::kContextReg: closure context.
// Output:
//   AllocateClosureABI::kResultReg: new allocated Closure object.
// Clobbered:
//   AllocateClosureABI::kScratchReg
void StubCodeCompiler::GenerateAllocateClosureStub(intptr_t num_elements) {
  const intptr_t instance_size = target::RoundedAllocationSize(
      target::Closure::InstanceSize(num_elements));
  const Register result_reg = AllocateClosureABI::kResultReg;
  const Register scratch_reg = AllocateClosureABI::kScratchReg;

  __ EnsureHasClassIdInDEBUG(kFunctionCid, AllocateClosureABI::kFunctionReg,
                             scratch_reg);

  if (!FLAG_use_slow_path && FLAG_inline_alloc) {
    Label slow_case;
    __ Comment("Inline allocation of uninitialized closure");
#if defined(DEBUG)
    // Need to account for the debug checks added by StoreToSlotNoBarrier.
    const auto distance = Assembler::kFarJump;
#else
    const auto distance = Assembler::kNearJump;
#endif
    __ TryAllocateObject(kClosureCid, instance_size, &slow_case, distance,
                         result_reg, scratch_reg);

    __ Comment("Inline initialization of allocated closure");
    // Put null in the scratch register for initializing most boxed fields.
    // We initialize the fields in offset order below.
    // Since the TryAllocateObject above did not go to the slow path, we're
    // guaranteed an object in new space here, and thus no barriers are needed.
    __ LoadObject(scratch_reg, NullObject());
    __ StoreToSlotNoBarrier(AllocateClosureABI::kFunctionReg, result_reg,
                            Slot::Closure_function());
    __ StoreToSlotNoBarrier(AllocateClosureABI::kLengthAndFlagsReg, result_reg,
                            Slot::Closure_length_and_flags());
    for (intptr_t i = 0; i < num_elements - 1; ++i) {
      __ StoreCompressedIntoObjectNoBarrier(
          result_reg,
          FieldAddress(result_reg, target::Closure::element_offset(i)),
          scratch_reg);
    }
    __ StoreCompressedIntoObjectNoBarrier(
        result_reg,
        FieldAddress(result_reg,
                     target::Closure::element_offset(num_elements - 1)),
        AllocateClosureABI::kContextReg);
    if (num_elements >= 2) {
      Label initialized;
      __ BranchIfBit(
          AllocateClosureABI::kLengthAndFlagsReg,
          UntaggedClosure::kHasDelayedTypeArgumentsBit + kSmiTagShift, ZERO,
          &initialized);
      __ LoadObject(scratch_reg, EmptyTypeArguments());
      __ StoreCompressedIntoObjectNoBarrier(
          result_reg,
          FieldAddress(result_reg,
                       target::Closure::element_offset(
                           UntaggedClosure::kDelayedTypeArgumentsIndex)),
          scratch_reg);
      __ Bind(&initialized);
    }
    __ LoadImmediate(scratch_reg, target::ToRawSmi(0));
    __ StoreToSlotNoBarrier(scratch_reg, result_reg, Slot::Closure_hash());
#if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_IA32)
    if (FLAG_precompiled_mode) {
      // Set the closure entry point in precompiled mode, either to the function
      // entry point in bare instructions mode or to 0 otherwise (to catch
      // misuse).
      __ LoadFromSlot(scratch_reg, AllocateClosureABI::kFunctionReg,
                      Slot::Function_entry_point());
      __ StoreToSlotNoBarrier(scratch_reg, result_reg,
                              Slot::Closure_entry_point());
    }
#endif

    // result_reg: new object.
    __ Ret();

    __ Bind(&slow_case);
  }

  __ Comment("Closure allocation via runtime");
  __ EnterStubFrame();
  __ PushObject(NullObject());  // Space on the stack for the return value.
  __ PushRegistersInOrder({AllocateClosureABI::kFunctionReg,
                           AllocateClosureABI::kLengthAndFlagsReg,
                           AllocateClosureABI::kContextReg});
  __ CallRuntime(kAllocateClosureRuntimeEntry, 3);
  __ PopRegister(AllocateClosureABI::kContextReg);
  __ PopRegister(AllocateClosureABI::kLengthAndFlagsReg);
  __ PopRegister(AllocateClosureABI::kFunctionReg);
  __ PopRegister(AllocateClosureABI::kResultReg);
  ASSERT(target::WillAllocateNewOrRememberedObject(instance_size));
  EnsureIsNewOrRemembered();
  __ LeaveStubFrame();

  // AllocateClosureABI::kResultReg: new object
  __ Ret();

  if (FLAG_use_slow_path || !FLAG_inline_alloc) {
    // Make sure AllocateClosureN stubs have different code as
    // precompiler chokes on distinct stub Code objects with the same
    // (de-duplicated) instructions.
    __ LoadImmediate(scratch_reg, num_elements);
  }
}

void StubCodeCompiler::GenerateAllocateClosure1Stub() {
  GenerateAllocateClosureStub(1);
}
void StubCodeCompiler::GenerateAllocateClosure2Stub() {
  GenerateAllocateClosureStub(2);
}
void StubCodeCompiler::GenerateAllocateClosure3Stub() {
  GenerateAllocateClosureStub(3);
}
void StubCodeCompiler::GenerateAllocateClosure4Stub() {
  GenerateAllocateClosureStub(4);
}

// Generates allocation stub for _GrowableList class.
// This stub exists solely for performance reasons: default allocation
// stub is slower as it doesn't use specialized inline allocation.
void StubCodeCompiler::GenerateAllocateGrowableArrayStub() {
#if defined(TARGET_ARCH_IA32)
  // This stub is not used on IA32 because IA32 version of
  // StubCodeCompiler::GenerateAllocationStubForClass uses inline
  // allocation. Also, AllocateObjectSlow stub is not generated on IA32.
  __ Breakpoint();
#else
  const intptr_t instance_size = target::RoundedAllocationSize(
      target::GrowableObjectArray::InstanceSize());

  if (!FLAG_use_slow_path && FLAG_inline_alloc) {
    Label slow_case;
    __ Comment("Inline allocation of GrowableList");
    __ TryAllocateObject(kGrowableObjectArrayCid, instance_size, &slow_case,
                         Assembler::kNearJump, AllocateObjectABI::kResultReg,
                         /*temp=*/AllocateObjectABI::kTagsReg);
    __ StoreIntoObjectNoBarrier(
        AllocateObjectABI::kResultReg,
        FieldAddress(AllocateObjectABI::kResultReg,
                     target::GrowableObjectArray::type_arguments_offset()),
        AllocateObjectABI::kTypeArgumentsReg);

    __ Ret();
    __ Bind(&slow_case);
  }

  const uword tags = target::MakeTagWordForNewSpaceObject(
      kGrowableObjectArrayCid, instance_size);
  __ LoadImmediate(AllocateObjectABI::kTagsReg, tags);
  __ Jump(
      Address(THR, target::Thread::allocate_object_slow_entry_point_offset()));
#endif  // defined(TARGET_ARCH_IA32)
}

void StubCodeCompiler::GenerateAllocateRecordStub() {
  const Register result_reg = AllocateRecordABI::kResultReg;
  const Register shape_reg = AllocateRecordABI::kShapeReg;
  const Register temp_reg = AllocateRecordABI::kTemp1Reg;
  const Register new_top_reg = AllocateRecordABI::kTemp2Reg;

  if (!FLAG_use_slow_path && FLAG_inline_alloc) {
    Label slow_case;

    // Check for allocation tracing.
    NOT_IN_PRODUCT(__ MaybeTraceAllocation(kRecordCid, &slow_case, temp_reg));

    // Extract number of fields from the shape.
    __ AndImmediate(
        temp_reg, shape_reg,
        compiler::target::RecordShape::kNumFieldsMask << kSmiTagShift);

    // Compute the rounded instance size.
    const intptr_t fixed_size_plus_alignment_padding =
        (target::Record::field_offset(0) +
         target::ObjectAlignment::kObjectAlignment - 1);
    __ AddScaled(temp_reg, kNoRegister, temp_reg,
                 TIMES_COMPRESSED_HALF_WORD_SIZE,
                 fixed_size_plus_alignment_padding);
    __ AndImmediate(temp_reg, -target::ObjectAlignment::kObjectAlignment);

    // Now allocate the object.
    __ LoadFromOffset(result_reg, THR, target::Thread::top_offset());
    __ MoveRegister(new_top_reg, temp_reg);
    __ AddRegisters(new_top_reg, result_reg);
    // Check if the allocation fits into the remaining space.
    __ CompareWithMemoryValue(new_top_reg,
                              Address(THR, target::Thread::end_offset()));
    __ BranchIf(UNSIGNED_GREATER_EQUAL, &slow_case);
    __ CheckAllocationCanary(result_reg);

    // Successfully allocated the object, now update top to point to
    // next object start and initialize the object.
    __ StoreToOffset(new_top_reg, THR, target::Thread::top_offset());
    __ AddImmediate(result_reg, kHeapObjectTag);

    // Calculate the size tag.
    {
      Label size_tag_overflow, done;
      __ CompareImmediate(temp_reg, target::UntaggedObject::kSizeTagMaxSizeTag);
      __ BranchIf(UNSIGNED_GREATER, &size_tag_overflow, Assembler::kNearJump);
      __ LslImmediate(temp_reg,
                      target::UntaggedObject::kSizeTagPos -
                          target::ObjectAlignment::kObjectAlignmentLog2);
      __ Jump(&done, Assembler::kNearJump);

      __ Bind(&size_tag_overflow);
      // Set overflow size tag value.
      __ LoadImmediate(temp_reg, 0);

      __ Bind(&done);
      uword tags = target::MakeTagWordForNewSpaceObject(kRecordCid, 0);
      __ OrImmediate(temp_reg, tags);
      __ InitializeHeader(temp_reg, result_reg);
    }

    __ StoreCompressedIntoObjectNoBarrier(
        result_reg, FieldAddress(result_reg, target::Record::shape_offset()),
        shape_reg);

    // Initialize the remaining words of the object.
    {
      const Register field_reg = shape_reg;
#if defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_RISCV32) ||              \
    defined(TARGET_ARCH_RISCV64)
      const Register null_reg = NULL_REG;
#else
      const Register null_reg = temp_reg;
      __ LoadObject(null_reg, NullObject());
#endif

      Label loop, done;
      __ AddImmediate(field_reg, result_reg, target::Record::field_offset(0));
      __ CompareRegisters(field_reg, new_top_reg);
      __ BranchIf(UNSIGNED_GREATER_EQUAL, &done, Assembler::kNearJump);

      __ Bind(&loop);
      for (intptr_t offset = 0; offset < target::kObjectAlignment;
           offset += target::kCompressedWordSize) {
        __ StoreCompressedIntoObjectNoBarrier(
            result_reg, FieldAddress(field_reg, offset), null_reg);
      }
      // Safe to only check every kObjectAlignment bytes instead of each word.
      ASSERT(kAllocationRedZoneSize >= target::kObjectAlignment);
      __ AddImmediate(field_reg, target::kObjectAlignment);
      __ CompareRegisters(field_reg, new_top_reg);
      __ BranchIf(UNSIGNED_LESS, &loop, Assembler::kNearJump);
      __ Bind(&done);
    }

    __ WriteAllocationCanary(new_top_reg);  // Fix overshoot.
    __ Ret();

    __ Bind(&slow_case);
  }

  __ EnterStubFrame();
  __ PushObject(NullObject());  // Space on the stack for the return value.
  __ PushRegister(shape_reg);
  __ CallRuntime(kAllocateRecordRuntimeEntry, 1);
  __ Drop(1);
  __ PopRegister(AllocateRecordABI::kResultReg);

  EnsureIsNewOrRemembered();
  __ LeaveStubFrame();
  __ Ret();
}

void StubCodeCompiler::GenerateAllocateSmallRecordStub(intptr_t num_fields,
                                                       bool has_named_fields) {
  ASSERT(num_fields == 2 || num_fields == 3);
  const Register result_reg = AllocateSmallRecordABI::kResultReg;
  const Register shape_reg = AllocateSmallRecordABI::kShapeReg;
  const Register value0_reg = AllocateSmallRecordABI::kValue0Reg;
  const Register value1_reg = AllocateSmallRecordABI::kValue1Reg;
  const Register value2_reg = AllocateSmallRecordABI::kValue2Reg;
  const Register temp_reg = AllocateSmallRecordABI::kTempReg;
  Label slow_case;

  if ((num_fields > 2) && (value2_reg == kNoRegister)) {
    // Not implemented.
    __ Breakpoint();
    return;
  }

#if defined(DEBUG)
  // Need to account for the debug checks added by
  // StoreCompressedIntoObjectNoBarrier.
  const auto distance = Assembler::kFarJump;
#else
  const auto distance = Assembler::kNearJump;
#endif
  __ TryAllocateObject(kRecordCid, target::Record::InstanceSize(num_fields),
                       &slow_case, distance, result_reg, temp_reg);

  if (!has_named_fields) {
    __ LoadImmediate(
        shape_reg, Smi::RawValue(RecordShape::ForUnnamed(num_fields).AsInt()));
  }
  __ StoreCompressedIntoObjectNoBarrier(
      result_reg, FieldAddress(result_reg, target::Record::shape_offset()),
      shape_reg);

  __ StoreCompressedIntoObjectNoBarrier(
      result_reg, FieldAddress(result_reg, target::Record::field_offset(0)),
      value0_reg);

  __ StoreCompressedIntoObjectNoBarrier(
      result_reg, FieldAddress(result_reg, target::Record::field_offset(1)),
      value1_reg);

  if (num_fields > 2) {
    __ StoreCompressedIntoObjectNoBarrier(
        result_reg, FieldAddress(result_reg, target::Record::field_offset(2)),
        value2_reg);
  }

  __ Ret();

  __ Bind(&slow_case);

  __ EnterStubFrame();
  __ PushObject(NullObject());  // Space on the stack for the return value.
  if (has_named_fields) {
    __ PushRegister(shape_reg);
  } else {
    __ PushImmediate(
        Smi::RawValue(RecordShape::ForUnnamed(num_fields).AsInt()));
  }
  __ PushRegistersInOrder({value0_reg, value1_reg});
  if (num_fields > 2) {
    __ PushRegister(value2_reg);
  } else {
    __ PushObject(NullObject());
  }
  __ CallRuntime(kAllocateSmallRecordRuntimeEntry, 4);
  __ Drop(4);
  __ PopRegister(result_reg);

  EnsureIsNewOrRemembered();
  __ LeaveStubFrame();
  __ Ret();
}

void StubCodeCompiler::GenerateAllocateRecord2Stub() {
  GenerateAllocateSmallRecordStub(2, /*has_named_fields=*/false);
}

void StubCodeCompiler::GenerateAllocateRecord2NamedStub() {
  GenerateAllocateSmallRecordStub(2, /*has_named_fields=*/true);
}

void StubCodeCompiler::GenerateAllocateRecord3Stub() {
  GenerateAllocateSmallRecordStub(3, /*has_named_fields=*/false);
}

void StubCodeCompiler::GenerateAllocateRecord3NamedStub() {
  GenerateAllocateSmallRecordStub(3, /*has_named_fields=*/true);
}

// The UnhandledException class lives in the VM isolate, so it cannot cache
// an allocation stub for itself. Instead, we cache it in the stub code list.
void StubCodeCompiler::GenerateAllocateUnhandledExceptionStub() {
  Thread* thread = Thread::Current();
  auto class_table = thread->isolate_group()->class_table();
  ASSERT(class_table->HasValidClassAt(kUnhandledExceptionCid));
  const auto& cls = Class::ZoneHandle(thread->zone(),
                                      class_table->At(kUnhandledExceptionCid));
  ASSERT(!cls.IsNull());

  GenerateAllocationStubForClass(nullptr, cls, Code::Handle(Code::null()),
                                 Code::Handle(Code::null()));
}

#define TYPED_DATA_ALLOCATION_STUB(clazz)                                      \
  void StubCodeCompiler::GenerateAllocate##clazz##Stub() {                     \
    GenerateAllocateTypedDataArrayStub(kTypedData##clazz##Cid);                \
  }
CLASS_LIST_TYPED_DATA(TYPED_DATA_ALLOCATION_STUB)
#undef TYPED_DATA_ALLOCATION_STUB

void StubCodeCompiler::GenerateLateInitializationError(bool with_fpu_regs) {
  auto perform_runtime_call = [&]() {
    __ PushRegister(LateInitializationErrorABI::kFieldReg);
    __ CallRuntime(kLateFieldNotInitializedErrorRuntimeEntry,
                   /*argument_count=*/1);
  };
  GenerateSharedStubGeneric(
      /*save_fpu_registers=*/with_fpu_regs,
      with_fpu_regs
          ? target::Thread::
                late_initialization_error_shared_with_fpu_regs_stub_offset()
          : target::Thread::
                late_initialization_error_shared_without_fpu_regs_stub_offset(),
      /*allow_return=*/false, perform_runtime_call);
}

void StubCodeCompiler::
    GenerateLateInitializationErrorSharedWithoutFPURegsStub() {
  GenerateLateInitializationError(/*with_fpu_regs=*/false);
}

void StubCodeCompiler::GenerateLateInitializationErrorSharedWithFPURegsStub() {
  GenerateLateInitializationError(/*with_fpu_regs=*/true);
}

void StubCodeCompiler::GenerateNullErrorSharedWithoutFPURegsStub() {
  GenerateSharedStub(
      /*save_fpu_registers=*/false, &kNullErrorRuntimeEntry,
      target::Thread::null_error_shared_without_fpu_regs_stub_offset(),
      /*allow_return=*/false);
}

void StubCodeCompiler::GenerateNullErrorSharedWithFPURegsStub() {
  GenerateSharedStub(
      /*save_fpu_registers=*/true, &kNullErrorRuntimeEntry,
      target::Thread::null_error_shared_with_fpu_regs_stub_offset(),
      /*allow_return=*/false);
}

void StubCodeCompiler::GenerateNullArgErrorSharedWithoutFPURegsStub() {
  GenerateSharedStub(
      /*save_fpu_registers=*/false, &kArgumentNullErrorRuntimeEntry,
      target::Thread::null_arg_error_shared_without_fpu_regs_stub_offset(),
      /*allow_return=*/false);
}

void StubCodeCompiler::GenerateNullArgErrorSharedWithFPURegsStub() {
  GenerateSharedStub(
      /*save_fpu_registers=*/true, &kArgumentNullErrorRuntimeEntry,
      target::Thread::null_arg_error_shared_with_fpu_regs_stub_offset(),
      /*allow_return=*/false);
}

void StubCodeCompiler::GenerateNullCastErrorSharedWithoutFPURegsStub() {
  GenerateSharedStub(
      /*save_fpu_registers=*/false, &kNullCastErrorRuntimeEntry,
      target::Thread::null_cast_error_shared_without_fpu_regs_stub_offset(),
      /*allow_return=*/false);
}

void StubCodeCompiler::GenerateNullCastErrorSharedWithFPURegsStub() {
  GenerateSharedStub(
      /*save_fpu_registers=*/true, &kNullCastErrorRuntimeEntry,
      target::Thread::null_cast_error_shared_with_fpu_regs_stub_offset(),
      /*allow_return=*/false);
}

void StubCodeCompiler::GenerateStackOverflowSharedWithoutFPURegsStub() {
  GenerateSharedStub(
      /*save_fpu_registers=*/false, &kInterruptOrStackOverflowRuntimeEntry,
      target::Thread::stack_overflow_shared_without_fpu_regs_stub_offset(),
      /*allow_return=*/true);
}

void StubCodeCompiler::GenerateStackOverflowSharedWithFPURegsStub() {
  GenerateSharedStub(
      /*save_fpu_registers=*/true, &kInterruptOrStackOverflowRuntimeEntry,
      target::Thread::stack_overflow_shared_with_fpu_regs_stub_offset(),
      /*allow_return=*/true);
}

void StubCodeCompiler::GenerateRangeErrorSharedWithoutFPURegsStub() {
  GenerateRangeError(/*with_fpu_regs=*/false);
}

void StubCodeCompiler::GenerateRangeErrorSharedWithFPURegsStub() {
  GenerateRangeError(/*with_fpu_regs=*/true);
}

void StubCodeCompiler::GenerateWriteErrorSharedWithoutFPURegsStub() {
  GenerateWriteError(/*with_fpu_regs=*/false);
}

void StubCodeCompiler::GenerateWriteErrorSharedWithFPURegsStub() {
  GenerateWriteError(/*with_fpu_regs=*/true);
}

void StubCodeCompiler::GenerateFieldAccessError(bool with_fpu_regs) {
  auto perform_runtime_call = [&]() {
    __ PushRegister(FieldAccessErrorABI::kFieldReg);
    __ CallRuntime(kStaticFieldAccessedWithoutIsolateErrorRuntimeEntry,
                   /*argument_count=*/1);
  };
  GenerateSharedStubGeneric(
      /*save_fpu_registers=*/with_fpu_regs,
      with_fpu_regs
          ? target::Thread::
                field_access_error_shared_with_fpu_regs_stub_offset()
          : target::Thread::
                field_access_error_shared_without_fpu_regs_stub_offset(),
      /*allow_return=*/false, perform_runtime_call);
}

void StubCodeCompiler::GenerateFieldAccessErrorSharedWithoutFPURegsStub() {
  GenerateFieldAccessError(/*with_fpu_regs=*/false);
}

void StubCodeCompiler::GenerateFieldAccessErrorSharedWithFPURegsStub() {
  GenerateFieldAccessError(/*with_fpu_regs=*/true);
}

void StubCodeCompiler::GenerateFrameAwaitingMaterializationStub() {
  __ Breakpoint();  // Marker stub.
}

void StubCodeCompiler::GenerateAsynchronousGapMarkerStub() {
  __ Breakpoint();  // Marker stub.
}

void StubCodeCompiler::GenerateUnknownDartCodeStub() {
  // Enter frame to include caller into the backtrace.
  __ EnterStubFrame();
  __ Breakpoint();  // Marker stub.
}

void StubCodeCompiler::GenerateNotLoadedStub() {
  __ EnterStubFrame();
  __ CallRuntime(kNotLoadedRuntimeEntry, 0);
  __ Breakpoint();
}

#define EMIT_BOX_ALLOCATION(Name)                                              \
  void StubCodeCompiler::GenerateAllocate##Name##Stub() {                      \
    Label call_runtime;                                                        \
    if (!FLAG_use_slow_path && FLAG_inline_alloc) {                            \
      __ TryAllocate(compiler::Name##Class(), &call_runtime,                   \
                     Assembler::kNearJump, AllocateBoxABI::kResultReg,         \
                     AllocateBoxABI::kTempReg);                                \
      __ Ret();                                                                \
    }                                                                          \
    __ Bind(&call_runtime);                                                    \
    __ EnterStubFrame();                                                       \
    __ PushObject(NullObject()); /* Make room for result. */                   \
    __ CallRuntime(kAllocate##Name##RuntimeEntry, 0);                          \
    __ PopRegister(AllocateBoxABI::kResultReg);                                \
    __ LeaveStubFrame();                                                       \
    __ Ret();                                                                  \
  }

EMIT_BOX_ALLOCATION(Mint)
EMIT_BOX_ALLOCATION(Double)
EMIT_BOX_ALLOCATION(Float32x4)
EMIT_BOX_ALLOCATION(Float64x2)
EMIT_BOX_ALLOCATION(Int32x4)

#undef EMIT_BOX_ALLOCATION

static void GenerateBoxFpuValueStub(Assembler* assembler,
                                    const dart::Class& cls,
                                    const RuntimeEntry& runtime_entry,
                                    void (Assembler::*store_value)(FpuRegister,
                                                                   Register,
                                                                   int32_t)) {
  Label call_runtime;
  if (!FLAG_use_slow_path && FLAG_inline_alloc) {
    __ TryAllocate(cls, &call_runtime, compiler::Assembler::kFarJump,
                   BoxDoubleStubABI::kResultReg, BoxDoubleStubABI::kTempReg);
    (assembler->*store_value)(
        BoxDoubleStubABI::kValueReg, BoxDoubleStubABI::kResultReg,
        compiler::target::Double::value_offset() - kHeapObjectTag);
    __ Ret();
  }
  __ Bind(&call_runtime);
  __ EnterStubFrame();
  __ PushObject(NullObject()); /* Make room for result. */
  (assembler->*store_value)(BoxDoubleStubABI::kValueReg, THR,
                            target::Thread::unboxed_runtime_arg_offset());
  __ CallRuntime(runtime_entry, 0);
  __ PopRegister(BoxDoubleStubABI::kResultReg);
  __ LeaveStubFrame();
  __ Ret();
}

void StubCodeCompiler::GenerateBoxDoubleStub() {
  GenerateBoxFpuValueStub(assembler, compiler::DoubleClass(),
                          kBoxDoubleRuntimeEntry,
                          &Assembler::StoreUnboxedDouble);
}

void StubCodeCompiler::GenerateBoxFloat32x4Stub() {
#if !defined(TARGET_ARCH_RISCV32) && !defined(TARGET_ARCH_RISCV64)
  GenerateBoxFpuValueStub(assembler, compiler::Float32x4Class(),
                          kBoxFloat32x4RuntimeEntry,
                          &Assembler::StoreUnboxedSimd128);
#else
  __ Stop("Not supported on RISC-V.");
#endif
}

void StubCodeCompiler::GenerateBoxFloat64x2Stub() {
#if !defined(TARGET_ARCH_RISCV32) && !defined(TARGET_ARCH_RISCV64)
  GenerateBoxFpuValueStub(assembler, compiler::Float64x2Class(),
                          kBoxFloat64x2RuntimeEntry,
                          &Assembler::StoreUnboxedSimd128);
#else
  __ Stop("Not supported on RISC-V.");
#endif
}

void StubCodeCompiler::GenerateDoubleToIntegerStub() {
  __ EnterStubFrame();
  __ StoreUnboxedDouble(DoubleToIntegerStubABI::kInputReg, THR,
                        target::Thread::unboxed_runtime_arg_offset());
  __ PushObject(NullObject()); /* Make room for result. */
  __ PushRegister(DoubleToIntegerStubABI::kRecognizedKindReg);
  __ CallRuntime(kDoubleToIntegerRuntimeEntry, 1);
  __ Drop(1);
  __ PopRegister(DoubleToIntegerStubABI::kResultReg);
  __ LeaveStubFrame();
  __ Ret();
}

void StubCodeCompiler::GenerateCheckedStoreIntoSharedStub() {
  __ EnterStubFrame();
  __ PushObject(NullObject());  // Make room for result.
  __ PushRegistersInOrder({CheckedStoreIntoSharedStubABI::kFieldReg,
                           CheckedStoreIntoSharedStubABI::kValueReg});
  __ CallRuntime(kCheckedStoreIntoSharedRuntimeEntry, /*argument_count=*/2);
  __ Drop(2);
  __ PopRegister(CheckedStoreIntoSharedStubABI::kResultReg);
  __ LeaveStubFrame();
  __ Ret();
}

void StubCodeCompiler::GenerateEnsureDeeplyImmutableStub() {
  __ EnterStubFrame();
  __ PushRegister(EnsureDeeplyImmutableStubABI::kValueReg);
  __ CallRuntime(kEnsureDeeplyImmutableRuntimeEntry, /*argument_count=*/1);
  __ Drop(1);
  __ LeaveStubFrame();
  __ Ret();
}

static intptr_t SuspendStateFpOffset() {
  return compiler::target::frame_layout.FrameSlotForVariableIndex(
             SuspendState::kSuspendStateVarIndex) *
         compiler::target::kWordSize;
}

static void CallDartCoreLibraryFunction(
    Assembler* assembler,
    intptr_t entry_point_offset_in_thread,
    intptr_t function_offset_in_object_store,
    bool uses_args_desc = false) {
  if (FLAG_target_thread_sanitizer) {
    __ TsanFuncEntry();
  }
  if (FLAG_precompiled_mode) {
    __ Call(Address(THR, entry_point_offset_in_thread));
  } else {
    __ LoadIsolateGroup(FUNCTION_REG);
    __ LoadFromOffset(FUNCTION_REG, FUNCTION_REG,
                      target::IsolateGroup::object_store_offset());
    __ LoadFromOffset(FUNCTION_REG, FUNCTION_REG,
                      function_offset_in_object_store);
    __ LoadCompressedFieldFromOffset(CODE_REG, FUNCTION_REG,
                                     target::Function::code_offset());
    if (!uses_args_desc) {
      // Load a GC-safe value for the arguments descriptor (unused but tagged).
      __ LoadImmediate(ARGS_DESC_REG, 0);
    }
    __ Call(FieldAddress(FUNCTION_REG, target::Function::entry_point_offset()));
  }
  if (FLAG_target_thread_sanitizer) {
    __ TsanFuncExit();
  }
}

// Helper to generate allocation of _SuspendState instance.
// Initializes tags, frame_capacity and frame_size.
// Other fields are not initialized.
//
// Input:
//   frame_size_reg: size of the frame payload in bytes.
// Output:
//   result_reg: allocated instance.
// Clobbers:
//   result_reg, temp_reg.
static void GenerateAllocateSuspendState(Assembler* assembler,
                                         Label* slow_case,
                                         Register result_reg,
                                         Register frame_size_reg,
                                         Register temp_reg) {
  if (FLAG_use_slow_path || !FLAG_inline_alloc) {
    __ Jump(slow_case);
    return;
  }

  // Check for allocation tracing.
  NOT_IN_PRODUCT(
      __ MaybeTraceAllocation(kSuspendStateCid, slow_case, temp_reg));

  // Compute the rounded instance size.
  const intptr_t fixed_size_plus_alignment_padding =
      (target::SuspendState::HeaderSize() +
       target::SuspendState::FrameSizeGrowthGap() * target::kWordSize +
       target::ObjectAlignment::kObjectAlignment - 1);
  __ AddImmediate(temp_reg, frame_size_reg, fixed_size_plus_alignment_padding);
  __ AndImmediate(temp_reg, -target::ObjectAlignment::kObjectAlignment);

  // Now allocate the object.
  __ LoadFromOffset(result_reg, THR, target::Thread::top_offset());
  __ AddRegisters(temp_reg, result_reg);
  // Check if the allocation fits into the remaining space.
  __ CompareWithMemoryValue(temp_reg,
                            Address(THR, target::Thread::end_offset()));
  __ BranchIf(UNSIGNED_GREATER_EQUAL, slow_case);
  __ CheckAllocationCanary(result_reg);

  // Successfully allocated the object, now update top to point to
  // next object start and initialize the object.
  __ StoreToOffset(temp_reg, THR, target::Thread::top_offset());
  __ SubRegisters(temp_reg, result_reg);
  __ AddImmediate(result_reg, kHeapObjectTag);

  if (!FLAG_precompiled_mode) {
    // Use rounded object size to calculate and save frame capacity.
    __ AddImmediate(temp_reg, temp_reg,
                    -target::SuspendState::payload_offset());
    __ StoreFieldToOffset(temp_reg, result_reg,
                          target::SuspendState::frame_capacity_offset());
    // Restore rounded object size.
    __ AddImmediate(temp_reg, temp_reg, target::SuspendState::payload_offset());
  }

  // Calculate the size tag.
  {
    Label size_tag_overflow, done;
    __ CompareImmediate(temp_reg, target::UntaggedObject::kSizeTagMaxSizeTag);
    __ BranchIf(UNSIGNED_GREATER, &size_tag_overflow, Assembler::kNearJump);
    __ LslImmediate(temp_reg,
                    target::UntaggedObject::kSizeTagPos -
                        target::ObjectAlignment::kObjectAlignmentLog2);
    __ Jump(&done, Assembler::kNearJump);

    __ Bind(&size_tag_overflow);
    // Set overflow size tag value.
    __ LoadImmediate(temp_reg, 0);

    __ Bind(&done);
    uword tags = target::MakeTagWordForNewSpaceObject(kSuspendStateCid, 0);
    __ OrImmediate(temp_reg, tags);
    __ InitializeHeader(temp_reg, result_reg);
  }

  __ StoreFieldToOffset(frame_size_reg, result_reg,
                        target::SuspendState::frame_size_offset());
}

void StubCodeCompiler::GenerateSuspendStub(
    bool call_suspend_function,
    bool pass_type_arguments,
    intptr_t suspend_entry_point_offset_in_thread,
    intptr_t suspend_function_offset_in_object_store) {
  const Register kArgument = SuspendStubABI::kArgumentReg;
  const Register kTypeArgs = SuspendStubABI::kTypeArgsReg;
  const Register kTemp = SuspendStubABI::kTempReg;
  const Register kFrameSize = SuspendStubABI::kFrameSizeReg;
  const Register kSuspendState = SuspendStubABI::kSuspendStateReg;
  const Register kFunctionData = SuspendStubABI::kFunctionDataReg;
  const Register kSrcFrame = SuspendStubABI::kSrcFrameReg;
  const Register kDstFrame = SuspendStubABI::kDstFrameReg;
  Label alloc_slow_case, alloc_done, init_done, resize_suspend_state,
      remember_object, call_dart;

#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
  SPILLS_LR_TO_FRAME({});  // Simulate entering the caller (Dart) frame.
#endif

  __ LoadFromOffset(kSuspendState, FPREG, SuspendStateFpOffset());

  __ AddImmediate(
      kFrameSize, FPREG,
      -target::frame_layout.last_param_from_entry_sp * target::kWordSize);
  __ SubRegisters(kFrameSize, SPREG);

  __ EnterStubFrame();

  if (pass_type_arguments) {
    __ PushRegister(kTypeArgs);
  }

  __ CompareClassId(kSuspendState, kSuspendStateCid, kTemp);

  if (FLAG_precompiled_mode) {
    __ BranchIf(EQUAL, &init_done);
  } else {
    Label alloc_suspend_state;
    __ BranchIf(NOT_EQUAL, &alloc_suspend_state);

    __ CompareWithMemoryValue(
        kFrameSize,
        FieldAddress(kSuspendState,
                     target::SuspendState::frame_capacity_offset()));
    __ BranchIf(UNSIGNED_GREATER, &resize_suspend_state);

    __ StoreFieldToOffset(kFrameSize, kSuspendState,
                          target::SuspendState::frame_size_offset());
    __ Jump(&init_done);

    __ Bind(&alloc_suspend_state);
  }

  __ Comment("Allocate SuspendState");
  __ MoveRegister(kFunctionData, kSuspendState);

  GenerateAllocateSuspendState(assembler, &alloc_slow_case, kSuspendState,
                               kFrameSize, kTemp);

  __ StoreCompressedIntoObjectNoBarrier(
      kSuspendState,
      FieldAddress(kSuspendState, target::SuspendState::function_data_offset()),
      kFunctionData);

  {
#if defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_RISCV32) ||              \
    defined(TARGET_ARCH_RISCV64)
    const Register kNullReg = NULL_REG;
#else
    const Register kNullReg = kTemp;
    __ LoadObject(kNullReg, NullObject());
#endif
    __ StoreCompressedIntoObjectNoBarrier(
        kSuspendState,
        FieldAddress(kSuspendState,
                     target::SuspendState::then_callback_offset()),
        kNullReg);
    __ StoreCompressedIntoObjectNoBarrier(
        kSuspendState,
        FieldAddress(kSuspendState,
                     target::SuspendState::error_callback_offset()),
        kNullReg);
  }

  __ Bind(&alloc_done);

  __ Comment("Save SuspendState to frame");
  __ LoadFromOffset(kTemp, FPREG, kSavedCallerFpSlotFromFp * target::kWordSize);
  __ StoreToOffset(kSuspendState, kTemp, SuspendStateFpOffset());

  __ Bind(&init_done);
  __ Comment("Copy frame to SuspendState");

#ifdef DEBUG
  {
    // Verify that SuspendState.frame_size == kFrameSize.
    Label okay;
    __ LoadFieldFromOffset(kTemp, kSuspendState,
                           target::SuspendState::frame_size_offset());
    __ CompareRegisters(kTemp, kFrameSize);
    __ BranchIf(EQUAL, &okay);
    __ Breakpoint();
    __ Bind(&okay);
  }
#endif

  if (kSrcFrame == THR) {
    __ PushRegister(THR);
  }
  __ AddImmediate(kSrcFrame, FPREG, kCallerSpSlotFromFp * target::kWordSize);
  __ AddImmediate(kDstFrame, kSuspendState,
                  target::SuspendState::payload_offset() - kHeapObjectTag);
  __ CopyMemoryWords(kSrcFrame, kDstFrame, kFrameSize, kTemp);
  if (kSrcFrame == THR) {
    __ PopRegister(THR);
  }

  __ LoadFromOffset(kTemp, FPREG, kSavedCallerPcSlotFromFp * target::kWordSize);
  __ StoreFieldToOffset(kTemp, kSuspendState,
                        target::SuspendState::pc_offset());

#ifdef DEBUG
  {
    // Verify that kSuspendState matches :suspend_state in the copied stack
    // frame.
    Label okay;
    __ LoadFieldFromOffset(kTemp, kSuspendState,
                           target::SuspendState::frame_size_offset());
    __ AddRegisters(kTemp, kSuspendState);
    __ LoadFieldFromOffset(
        kTemp, kTemp,
        target::SuspendState::payload_offset() + SuspendStateFpOffset());
    __ CompareRegisters(kTemp, kSuspendState);
    __ BranchIf(EQUAL, &okay);
    __ Breakpoint();
    __ Bind(&okay);
  }
#endif

  if (call_suspend_function) {
    // Push arguments for suspend Dart function early to preserve them
    // across write barrier.
    __ PushRegistersInOrder({kSuspendState, kArgument});
  }

  // Write barrier.
  __ AndImmediate(kTemp, kSuspendState, target::Page::kPageMask);
  __ LoadFromOffset(kTemp, kTemp, target::Page::original_top_offset());
  __ CompareRegisters(kSuspendState, kTemp);
  __ BranchIf(UNSIGNED_LESS, &remember_object);
  // Assumption: SuspendStates are always on non-image pages.
  // TODO(rmacnak): Also check original_end if we bound TLABs to smaller than a
  // heap page.

  __ Bind(&call_dart);
  if (call_suspend_function) {
    __ Comment("Call suspend Dart function");
    if (pass_type_arguments) {
      __ LoadObject(ARGS_DESC_REG,
                    ArgumentsDescriptorBoxed(/*type_args_len=*/1,
                                             /*num_arguments=*/2));
    }
    CallDartCoreLibraryFunction(assembler, suspend_entry_point_offset_in_thread,
                                suspend_function_offset_in_object_store,
                                /*uses_args_desc=*/pass_type_arguments);
  } else {
    // SuspendStub returns either the result of Dart callback,
    // or SuspendStub argument (if Dart callback is not used).
    // The latter is used by yield/yield* in sync* functions
    // to indicate that iteration should be continued.
    __ MoveRegister(CallingConventions::kReturnReg, kArgument);
  }

  __ LeaveStubFrame();

  if (FLAG_target_thread_sanitizer) {
    __ TsanFuncExit();
  }
#if !defined(TARGET_ARCH_X64) && !defined(TARGET_ARCH_IA32)
  // Drop caller frame on all architectures except x86 (X64/IA32) which
  // needs to maintain call/return balance to avoid performance regressions.
  __ LeaveDartFrame();
#elif defined(TARGET_ARCH_X64)
  // Restore PP in JIT mode on x64 as epilogue following SuspendStub call
  // will only unwind frame and return.
  if (!FLAG_precompiled_mode) {
    __ LoadFromOffset(
        PP, FPREG,
        target::frame_layout.saved_caller_pp_from_fp * target::kWordSize);
  }
#endif
  __ Ret();

#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
  // Slow path is executed with Dart and stub frames still on the stack.
  SPILLS_LR_TO_FRAME({});
  SPILLS_LR_TO_FRAME({});
#endif
  __ Bind(&alloc_slow_case);
  __ Comment("SuspendState Allocation slow case");
  // Save argument and frame size.
  __ PushRegistersInOrder({kArgument, kFrameSize});
  __ PushObject(NullObject());  // Make space on stack for the return value.
  __ SmiTag(kFrameSize);
  // Pass frame size and function data to runtime entry.
  __ PushRegistersInOrder({kFrameSize, kFunctionData});
  __ CallRuntime(kAllocateSuspendStateRuntimeEntry, 2);
  __ Drop(2);                     // Drop arguments
  __ PopRegister(kSuspendState);  // Get result.
  __ PopRegister(kFrameSize);     // Restore frame size.
  __ PopRegister(kArgument);      // Restore argument.
  __ Jump(&alloc_done);

  __ Bind(&resize_suspend_state);
  __ Comment("Resize SuspendState");
  // Save argument and frame size.
  __ PushRegistersInOrder({kArgument, kFrameSize});
  __ PushObject(NullObject());  // Make space on stack for the return value.
  __ SmiTag(kFrameSize);
  // Pass frame size and old suspend state to runtime entry.
  __ PushRegistersInOrder({kFrameSize, kSuspendState});
  // It's okay to call runtime for resizing SuspendState objects
  // as it can only happen in the unoptimized code if expression
  // stack grows between suspends, or once after OSR transition.
  __ CallRuntime(kAllocateSuspendStateRuntimeEntry, 2);
  __ Drop(2);                     // Drop arguments
  __ PopRegister(kSuspendState);  // Get result.
  __ PopRegister(kFrameSize);     // Restore frame size.
  __ PopRegister(kArgument);      // Restore argument.
  __ Jump(&alloc_done);

  __ Bind(&remember_object);
  __ Comment("Old gen SuspendState slow case");
  if (!call_suspend_function) {
    // Save kArgument which contains the return value
    // if suspend function is not called.
    __ PushRegister(kArgument);
  }
  {
#if defined(TARGET_ARCH_IA32)
    LeafRuntimeScope rt(assembler, /*frame_size=*/2 * target::kWordSize,
                        /*preserve_registers=*/false);
    __ movl(Address(ESP, 1 * target::kWordSize), THR);
    __ movl(Address(ESP, 0 * target::kWordSize), kSuspendState);
#else
    LeafRuntimeScope rt(assembler, /*frame_size=*/0,
                        /*preserve_registers=*/false);
    __ MoveRegister(CallingConventions::ArgumentRegisters[0], kSuspendState);
    __ MoveRegister(CallingConventions::ArgumentRegisters[1], THR);
#endif
    rt.Call(kEnsureRememberedAndMarkingDeferredRuntimeEntry, 2);
  }
  if (!call_suspend_function) {
    __ PopRegister(kArgument);
  }
  __ Jump(&call_dart);
}

void StubCodeCompiler::GenerateAwaitStub() {
  GenerateSuspendStub(
      /*call_suspend_function=*/true,
      /*pass_type_arguments=*/false,
      target::Thread::suspend_state_await_entry_point_offset(),
      target::ObjectStore::suspend_state_await_offset());
}

void StubCodeCompiler::GenerateAwaitWithTypeCheckStub() {
  GenerateSuspendStub(

      /*call_suspend_function=*/true,
      /*pass_type_arguments=*/true,
      target::Thread::suspend_state_await_with_type_check_entry_point_offset(),
      target::ObjectStore::suspend_state_await_with_type_check_offset());
}

void StubCodeCompiler::GenerateYieldAsyncStarStub() {
  GenerateSuspendStub(

      /*call_suspend_function=*/true,
      /*pass_type_arguments=*/false,
      target::Thread::suspend_state_yield_async_star_entry_point_offset(),
      target::ObjectStore::suspend_state_yield_async_star_offset());
}

void StubCodeCompiler::GenerateSuspendSyncStarAtStartStub() {
  GenerateSuspendStub(

      /*call_suspend_function=*/true,
      /*pass_type_arguments=*/false,
      target::Thread::
          suspend_state_suspend_sync_star_at_start_entry_point_offset(),
      target::ObjectStore::suspend_state_suspend_sync_star_at_start_offset());
}

void StubCodeCompiler::GenerateSuspendSyncStarAtYieldStub() {
  GenerateSuspendStub(
      /*call_suspend_function=*/false,
      /*pass_type_arguments=*/false, -1, -1);
}

void StubCodeCompiler::GenerateInitSuspendableFunctionStub(
    intptr_t init_entry_point_offset_in_thread,
    intptr_t init_function_offset_in_object_store) {
  const Register kTypeArgs = InitSuspendableFunctionStubABI::kTypeArgsReg;

  __ EnterStubFrame();
  __ LoadObject(ARGS_DESC_REG, ArgumentsDescriptorBoxed(/*type_args_len=*/1,
                                                        /*num_arguments=*/0));
  __ PushRegister(kTypeArgs);
  CallDartCoreLibraryFunction(assembler, init_entry_point_offset_in_thread,
                              init_function_offset_in_object_store,
                              /*uses_args_desc=*/true);
  __ LeaveStubFrame();

  // Set :suspend_state in the caller frame.
  __ StoreToOffset(CallingConventions::kReturnReg, FPREG,
                   SuspendStateFpOffset());
  __ Ret();
}

void StubCodeCompiler::GenerateInitAsyncStub() {
  GenerateInitSuspendableFunctionStub(
      target::Thread::suspend_state_init_async_entry_point_offset(),
      target::ObjectStore::suspend_state_init_async_offset());
}

void StubCodeCompiler::GenerateInitAsyncStarStub() {
  GenerateInitSuspendableFunctionStub(
      target::Thread::suspend_state_init_async_star_entry_point_offset(),
      target::ObjectStore::suspend_state_init_async_star_offset());
}

void StubCodeCompiler::GenerateInitSyncStarStub() {
  GenerateInitSuspendableFunctionStub(
      target::Thread::suspend_state_init_sync_star_entry_point_offset(),
      target::ObjectStore::suspend_state_init_sync_star_offset());
}

void StubCodeCompiler::GenerateResumeStub() {
  const Register kSuspendState = ResumeStubABI::kSuspendStateReg;
  const Register kTemp = ResumeStubABI::kTempReg;
  const Register kFrameSize = ResumeStubABI::kFrameSizeReg;
  const Register kSrcFrame = ResumeStubABI::kSrcFrameReg;
  const Register kDstFrame = ResumeStubABI::kDstFrameReg;
  const Register kResumePc = ResumeStubABI::kResumePcReg;
  const Register kException = ResumeStubABI::kExceptionReg;
  const Register kStackTrace = ResumeStubABI::kStackTraceReg;
  Label call_runtime;

  // Top of the stack on entry:
  // ... [SuspendState] [value] [exception] [stackTrace] [ReturnAddress]

  __ EnterDartFrame(0);
  if (FLAG_target_thread_sanitizer) {
    __ TsanFuncEntry();
  }

  const intptr_t param_offset =
      target::frame_layout.param_end_from_fp * target::kWordSize;
  __ LoadFromOffset(kSuspendState, FPREG, param_offset + 4 * target::kWordSize);
#ifdef DEBUG
  {
    Label okay;
    __ CompareClassId(kSuspendState, kSuspendStateCid, kTemp);
    __ BranchIf(EQUAL, &okay);
    __ Breakpoint();
    __ Bind(&okay);
  }
  {
    Label okay;
    __ LoadFieldFromOffset(kTemp, kSuspendState,
                           target::SuspendState::pc_offset());
    __ CompareImmediate(kTemp, 0);
    __ BranchIf(NOT_EQUAL, &okay);
    __ Breakpoint();
    __ Bind(&okay);
  }
#endif

  __ LoadFieldFromOffset(kFrameSize, kSuspendState,
                         target::SuspendState::frame_size_offset());
#ifdef DEBUG
  {
    Label okay;
    __ MoveRegister(kTemp, kFrameSize);
    __ AddRegisters(kTemp, kSuspendState);
    __ LoadFieldFromOffset(
        kTemp, kTemp,
        target::SuspendState::payload_offset() + SuspendStateFpOffset());
    __ CompareRegisters(kTemp, kSuspendState);
    __ BranchIf(EQUAL, &okay);
    __ Breakpoint();
    __ Bind(&okay);
  }
#endif
  if (!FLAG_precompiled_mode) {
    // Copy Code object (part of the fixed frame which is not copied below)
    // and restore pool pointer.
    __ MoveRegister(kTemp, kSuspendState);
    __ AddRegisters(kTemp, kFrameSize);
    __ LoadFromOffset(
        CODE_REG, kTemp,
        target::SuspendState::payload_offset() - kHeapObjectTag +
            target::frame_layout.code_from_fp * target::kWordSize);
    __ StoreToOffset(CODE_REG, FPREG,
                     target::frame_layout.code_from_fp * target::kWordSize);
#if !defined(TARGET_ARCH_IA32)
    __ LoadPoolPointer(PP);
#endif
  }
  // Do not copy fixed frame between the first local and FP.
  __ AddImmediate(kFrameSize, (target::frame_layout.first_local_from_fp + 1) *
                                  target::kWordSize);
  __ SubRegisters(SPREG, kFrameSize);

  __ Comment("Copy frame from SuspendState");
  intptr_t num_saved_regs = 0;
  if (kSrcFrame == THR) {
    __ PushRegister(THR);
    ++num_saved_regs;
  }
  if (kDstFrame == CODE_REG) {
    __ PushRegister(CODE_REG);
    ++num_saved_regs;
  }
  __ AddImmediate(kSrcFrame, kSuspendState,
                  target::SuspendState::payload_offset() - kHeapObjectTag);
  __ AddImmediate(kDstFrame, SPREG, num_saved_regs * target::kWordSize);
  __ CopyMemoryWords(kSrcFrame, kDstFrame, kFrameSize, kTemp);
  if (kDstFrame == CODE_REG) {
    __ PopRegister(CODE_REG);
  }
  if (kSrcFrame == THR) {
    __ PopRegister(THR);
  }

  __ Comment("Transfer control");

  __ LoadFieldFromOffset(kResumePc, kSuspendState,
                         target::SuspendState::pc_offset());
  __ StoreZero(FieldAddress(kSuspendState, target::SuspendState::pc_offset()),
               kTemp);

#if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_IA32)
  // Adjust resume PC to skip extra epilogue generated on x86
  // right after the call to suspend stub in order to maintain
  // call/return balance.
  __ AddImmediate(kResumePc, SuspendStubABI::kResumePcDistance);
#endif

  static_assert((kException != CODE_REG) && (kException != PP),
                "should not interfere");
  __ LoadFromOffset(kException, FPREG, param_offset + 2 * target::kWordSize);
  __ CompareObject(kException, NullObject());
  __ BranchIf(NOT_EQUAL, &call_runtime);

  if (!FLAG_precompiled_mode) {
    // Check if Code is disabled.
    __ LoadFieldFromOffset(kTemp, CODE_REG,
                           target::Code::instructions_offset());
    __ CompareWithMemoryValue(
        kTemp,
        FieldAddress(CODE_REG, target::Code::active_instructions_offset()));
    __ BranchIf(NOT_EQUAL, &call_runtime);

#if !defined(PRODUCT)
    // Check if there is a breakpoint at resumption.
    __ LoadIsolate(kTemp);
    Label skip_breakpoints_check;
    // Skip check if no isolate is available(running isolategroup-bound code)
    __ BranchIfZero(kTemp, &skip_breakpoints_check);
    __ LoadFromOffset(kTemp, kTemp,
                      target::Isolate::has_resumption_breakpoints_offset(),
                      kUnsignedByte);
    __ CompareImmediate(kTemp, 0);
    __ BranchIf(NOT_EQUAL, &call_runtime);
    __ Bind(&skip_breakpoints_check);
#endif
  }

  __ LoadFromOffset(CallingConventions::kReturnReg, FPREG,
                    param_offset + 3 * target::kWordSize);

  __ Jump(kResumePc);

  __ Comment("Call runtime to throw exception or deopt");
  __ Bind(&call_runtime);

  __ LoadFromOffset(kStackTrace, FPREG, param_offset + 1 * target::kWordSize);
  static_assert((kStackTrace != CODE_REG) && (kStackTrace != PP),
                "should not interfere");

#if defined(DART_DYNAMIC_MODULES)
  Label resume_interpreter;
  __ CompareWithMemoryValue(
      kResumePc,
      compiler::Address(THR,
                        compiler::target::Thread::
                            resume_interpreter_adjusted_entry_point_offset()));
  __ BranchIf(EQUAL, &resume_interpreter);
#endif  // defined(DART_DYNAMIC_MODULES)

  // Set return address as if suspended Dart function called
  // stub with kResumePc as a return address.
  __ SetReturnAddress(kResumePc);

  if (!FLAG_precompiled_mode) {
    __ LoadFromOffset(CODE_REG, THR, target::Thread::resume_stub_offset());
  }
#if !defined(TARGET_ARCH_IA32)
  __ set_constant_pool_allowed(false);
#endif
  __ EnterStubFrame();
  __ PushObject(NullObject());  // Make room for (unused) result.
  __ PushRegistersInOrder({kException, kStackTrace});
  __ CallRuntime(kResumeFrameRuntimeEntry, /*argument_count=*/2);

  if (FLAG_precompiled_mode) {
    __ Breakpoint();
  } else {
    __ LeaveStubFrame();
    __ LoadFromOffset(CallingConventions::kReturnReg, FPREG,
                      param_offset + 3 * target::kWordSize);
    // Lazy deoptimize.
    __ Ret();
  }

#if defined(DART_DYNAMIC_MODULES)
#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
  // This case is used when Dart frame is still on the stack.
  if (FLAG_precompiled_mode) {
    RESTORES_LR_FROM_FRAME({});  // Undo EnterStubFrame() from the case above.
  }
  SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER({});  // Undo SetReturnAddress().
#endif
  __ Comment("Resume interpreter");
  __ Bind(&resume_interpreter);
  __ PushObject(NullObject());  // Make room for result.
  // Load the value to pass to the resumed bytecode.
  __ LoadFromOffset(kTemp, FPREG, param_offset + 3 * target::kWordSize);
  __ PushRegistersInOrder({kTemp, kException, kStackTrace});
  __ CallRuntime(kResumeInterpreterRuntimeEntry, /*argument_count=*/3);
  __ Drop(3);                                      // Drop arguments.
  __ PopRegister(CallingConventions::kReturnReg);  // Get result.
  __ LeaveDartFrame();
  __ Ret();
#endif  // defined(DART_DYNAMIC_MODULES)
}

void StubCodeCompiler::GenerateReturnStub(
    intptr_t return_entry_point_offset_in_thread,
    intptr_t return_function_offset_in_object_store,
    intptr_t return_stub_offset_in_thread) {
  const Register kSuspendState = ReturnStubABI::kSuspendStateReg;

#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
  SPILLS_LR_TO_FRAME({});  // Simulate entering the caller (Dart) frame.
#endif

  __ LoadFromOffset(kSuspendState, FPREG, SuspendStateFpOffset());
#ifdef DEBUG
  {
    Label okay;
    __ CompareObject(kSuspendState, NullObject());
    __ BranchIf(NOT_EQUAL, &okay);
    __ Breakpoint();
    __ Bind(&okay);
  }
#endif
  if (FLAG_target_thread_sanitizer) {
    __ TsanFuncExit();
  }
  __ LeaveDartFrame();
  if (!FLAG_precompiled_mode) {
    __ LoadFromOffset(CODE_REG, THR, return_stub_offset_in_thread);
  }
  __ EnterStubFrame();
  __ PushRegistersInOrder({kSuspendState, CallingConventions::kReturnReg});
  CallDartCoreLibraryFunction(assembler, return_entry_point_offset_in_thread,
                              return_function_offset_in_object_store);
  __ LeaveStubFrame();
  __ Ret();
}

void StubCodeCompiler::GenerateReturnAsyncStub() {
  GenerateReturnStub(
      target::Thread::suspend_state_return_async_entry_point_offset(),
      target::ObjectStore::suspend_state_return_async_offset(),
      target::Thread::return_async_stub_offset());
}

void StubCodeCompiler::GenerateReturnAsyncNotFutureStub() {
  GenerateReturnStub(
      target::Thread::
          suspend_state_return_async_not_future_entry_point_offset(),
      target::ObjectStore::suspend_state_return_async_not_future_offset(),
      target::Thread::return_async_not_future_stub_offset());
}

void StubCodeCompiler::GenerateReturnAsyncStarStub() {
  GenerateReturnStub(
      target::Thread::suspend_state_return_async_star_entry_point_offset(),
      target::ObjectStore::suspend_state_return_async_star_offset(),
      target::Thread::return_async_star_stub_offset());
}

void StubCodeCompiler::GenerateAsyncExceptionHandlerStub() {
  const Register kSuspendState = AsyncExceptionHandlerStubABI::kSuspendStateReg;
  ASSERT(kSuspendState != kExceptionObjectReg);
  ASSERT(kSuspendState != kStackTraceObjectReg);
  Label rethrow_exception;

#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
  SPILLS_LR_TO_FRAME({});  // Simulate entering the caller (Dart) frame.
#endif

  __ LoadFromOffset(kSuspendState, FPREG, SuspendStateFpOffset());

  // Check if suspend_state is initialized. Otherwise
  // exception was thrown from the prologue code and
  // should be synchronously propagated.
  __ CompareObject(kSuspendState, NullObject());
  __ BranchIf(EQUAL, &rethrow_exception);

  if (FLAG_target_thread_sanitizer) {
    __ TsanFuncExit();
  }
  __ LeaveDartFrame();
  if (!FLAG_precompiled_mode) {
    __ LoadFromOffset(CODE_REG, THR,
                      target::Thread::async_exception_handler_stub_offset());
  }
  __ EnterStubFrame();
  __ PushRegistersInOrder(
      {kSuspendState, kExceptionObjectReg, kStackTraceObjectReg});
  CallDartCoreLibraryFunction(
      assembler,
      target::Thread::suspend_state_handle_exception_entry_point_offset(),
      target::ObjectStore::suspend_state_handle_exception_offset());
  __ LeaveStubFrame();
  __ Ret();

#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
  // Rethrow case is used when Dart frame is still on the stack.
  SPILLS_LR_TO_FRAME({});
#endif
  __ Comment("Rethrow exception");
  __ Bind(&rethrow_exception);
  if (FLAG_target_thread_sanitizer) {
    __ TsanFuncExit();
  }
  __ LeaveDartFrame();
  if (!FLAG_precompiled_mode) {
    __ LoadFromOffset(CODE_REG, THR,
                      target::Thread::async_exception_handler_stub_offset());
  }
  __ EnterStubFrame();
  __ PushObject(NullObject());  // Make room for (unused) result.
  __ PushRegistersInOrder({kExceptionObjectReg, kStackTraceObjectReg});
  __ PushImmediate(Smi::RawValue(0));  // Do not bypass debugger.
  __ CallRuntime(kReThrowRuntimeEntry, /*argument_count=*/3);
  __ Breakpoint();
}

void StubCodeCompiler::GenerateCloneSuspendStateStub() {
  const Register kSource = CloneSuspendStateStubABI::kSourceReg;
  const Register kDestination = CloneSuspendStateStubABI::kDestinationReg;
  const Register kTemp = CloneSuspendStateStubABI::kTempReg;
  const Register kFrameSize = CloneSuspendStateStubABI::kFrameSizeReg;
  const Register kSrcFrame = CloneSuspendStateStubABI::kSrcFrameReg;
  const Register kDstFrame = CloneSuspendStateStubABI::kDstFrameReg;
  Label alloc_slow_case;

#ifdef DEBUG
  {
    // Can only clone _SuspendState objects with copied frames.
    Label okay;
    __ LoadFieldFromOffset(kTemp, kSource, target::SuspendState::pc_offset());
    __ CompareImmediate(kTemp, 0);
    __ BranchIf(NOT_EQUAL, &okay);
    __ Breakpoint();
    __ Bind(&okay);
  }
#endif

  __ LoadFieldFromOffset(kFrameSize, kSource,
                         target::SuspendState::frame_size_offset());

  GenerateAllocateSuspendState(assembler, &alloc_slow_case, kDestination,
                               kFrameSize, kTemp);

  // Copy pc.
  __ LoadFieldFromOffset(kTemp, kSource, target::SuspendState::pc_offset());
  __ StoreFieldToOffset(kTemp, kDestination, target::SuspendState::pc_offset());

  // Copy function_data.
  __ LoadCompressedFieldFromOffset(
      kTemp, kSource, target::SuspendState::function_data_offset());
  __ StoreCompressedIntoObjectNoBarrier(
      kDestination,
      FieldAddress(kDestination, target::SuspendState::function_data_offset()),
      kTemp);

  // Copy then_callback.
  __ LoadCompressedFieldFromOffset(
      kTemp, kSource, target::SuspendState::then_callback_offset());
  __ StoreCompressedIntoObjectNoBarrier(
      kDestination,
      FieldAddress(kDestination, target::SuspendState::then_callback_offset()),
      kTemp);

  // Copy error_callback.
  __ LoadCompressedFieldFromOffset(
      kTemp, kSource, target::SuspendState::error_callback_offset());
  __ StoreCompressedIntoObjectNoBarrier(
      kDestination,
      FieldAddress(kDestination, target::SuspendState::error_callback_offset()),
      kTemp);

  // Copy payload frame.
  if (kSrcFrame == THR) {
    __ PushRegister(THR);
  }
  const uword offset = target::SuspendState::payload_offset() - kHeapObjectTag;
  __ AddImmediate(kSrcFrame, kSource, offset);
  __ AddImmediate(kDstFrame, kDestination, offset);
  __ CopyMemoryWords(kSrcFrame, kDstFrame, kFrameSize, kTemp);
  if (kSrcFrame == THR) {
    __ PopRegister(THR);
  }

  // Update value of :suspend_state variable in the copied frame
  // for the new SuspendState.
  __ LoadFieldFromOffset(kTemp, kDestination,
                         target::SuspendState::frame_size_offset());
  __ AddRegisters(kTemp, kDestination);
  __ StoreFieldToOffset(
      kDestination, kTemp,
      target::SuspendState::payload_offset() + SuspendStateFpOffset());

  __ MoveRegister(CallingConventions::kReturnReg, kDestination);
  EnsureIsNewOrRemembered();
  __ Ret();

  __ Bind(&alloc_slow_case);
  __ Comment("CloneSuspendState slow case");
  __ EnterStubFrame();
  __ PushObject(NullObject());  // Make space on stack for the return value.
  __ PushRegister(kSource);
  __ CallRuntime(kCloneSuspendStateRuntimeEntry, 1);
  __ Drop(1);                                      // Drop argument
  __ PopRegister(CallingConventions::kReturnReg);  // Get result.
  __ LeaveStubFrame();
  __ Ret();
}

void StubCodeCompiler::GenerateResumeInterpreterStub() {
#if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_IA32)
  // On X64/IA32 execution is resumed at PC + kResumePcDistance.
  const intptr_t start = __ CodeSize();
  for (intptr_t i = 0; i < SuspendStubABI::kResumePcDistance; ++i) {
    __ nop();
  }
  RELEASE_ASSERT(__ CodeSize() - start == SuspendStubABI::kResumePcDistance);
#endif

#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
  SPILLS_LR_TO_FRAME({});  // Simulate entering the caller (Dart) frame.
#endif

  __ PushObject(NullObject());                      // Make room for result.
  __ PushRegister(CallingConventions::kReturnReg);  // Return value.
  __ PushObject(NullObject());                      // Exception.
  __ PushObject(NullObject());                      // Stack trace.
  __ CallRuntime(kResumeInterpreterRuntimeEntry, /*argument_count=*/3);
  __ Drop(3);                                      // Drop arguments.
  __ PopRegister(CallingConventions::kReturnReg);  // Get result.
  __ LeaveDartFrame();
  __ Ret();
}

void StubCodeCompiler::GenerateFfiAsyncCallbackSendStub() {
  __ EnterStubFrame();
  __ PushObject(NullObject());  // Make space on stack for the return value.
  __ PushRegister(FfiAsyncCallbackSendStubABI::kArgsReg);
  __ CallRuntime(kFfiAsyncCallbackSendRuntimeEntry, 1);
  __ Drop(1);                                      // Drop argument.
  __ PopRegister(CallingConventions::kReturnReg);  // Get result.
  __ LeaveStubFrame();
  __ Ret();
}

#if !defined(TARGET_ARCH_IA32)
static void GenerateSubtypeTestCacheLoopBody(Assembler* assembler,
                                             int n,
                                             Register null_reg,
                                             Register cache_entry_reg,
                                             Register instance_cid_or_sig_reg,
                                             Register instance_type_args_reg,
                                             Register parent_fun_type_args_reg,
                                             Register delayed_type_args_reg,
                                             Label* found,
                                             Label* not_found,
                                             Label* next_iteration) {
  __ Comment("Loop");
  // LoadAcquireCompressed assumes the loaded value is a heap object and
  // extends it with the heap bits if compressed. However, the entry may be
  // a Smi.
  //
  // Instead, just use LoadAcquire to load the lower bits when compressed and
  // only compare the low bits of the loaded value using CompareObjectRegisters.
  __ LoadAcquireFromOffset(
      TypeTestABI::kScratchReg, cache_entry_reg,
      target::kCompressedWordSize *
          target::SubtypeTestCache::kInstanceCidOrSignature,
      kObjectBytes);
  __ CompareObjectRegisters(TypeTestABI::kScratchReg, null_reg);
  __ BranchIf(EQUAL, not_found, Assembler::kNearJump);
  __ CompareObjectRegisters(TypeTestABI::kScratchReg, instance_cid_or_sig_reg);
  if (n == 1) {
    __ BranchIf(EQUAL, found, Assembler::kNearJump);
    return;
  }

  __ BranchIf(NOT_EQUAL, next_iteration, Assembler::kNearJump);
  __ CompareWithMemoryValue(
      instance_type_args_reg,
      Address(cache_entry_reg,
              target::kCompressedWordSize *
                  target::SubtypeTestCache::kInstanceTypeArguments),
      kObjectBytes);
  if (n == 2) {
    __ BranchIf(EQUAL, found, Assembler::kNearJump);
    return;
  }

  __ BranchIf(NOT_EQUAL, next_iteration, Assembler::kNearJump);
  __ CompareWithMemoryValue(
      TypeTestABI::kInstantiatorTypeArgumentsReg,
      Address(cache_entry_reg,
              target::kCompressedWordSize *
                  target::SubtypeTestCache::kInstantiatorTypeArguments),
      kObjectBytes);
  if (n == 3) {
    __ BranchIf(EQUAL, found, Assembler::kNearJump);
    return;
  }

  __ BranchIf(NOT_EQUAL, next_iteration, Assembler::kNearJump);
  __ CompareWithMemoryValue(
      TypeTestABI::kFunctionTypeArgumentsReg,
      Address(cache_entry_reg,
              target::kCompressedWordSize *
                  target::SubtypeTestCache::kFunctionTypeArguments),
      kObjectBytes);
  if (n == 4) {
    __ BranchIf(EQUAL, found, Assembler::kNearJump);
    return;
  }

  __ BranchIf(NOT_EQUAL, next_iteration, Assembler::kNearJump);
  __ CompareWithMemoryValue(
      parent_fun_type_args_reg,
      Address(
          cache_entry_reg,
          target::kCompressedWordSize *
              target::SubtypeTestCache::kInstanceParentFunctionTypeArguments),
      kObjectBytes);
  if (n == 5) {
    __ BranchIf(EQUAL, found, Assembler::kNearJump);
    return;
  }

  __ BranchIf(NOT_EQUAL, next_iteration, Assembler::kNearJump);
  __ CompareWithMemoryValue(
      delayed_type_args_reg,
      Address(
          cache_entry_reg,
          target::kCompressedWordSize *
              target::SubtypeTestCache::kInstanceDelayedFunctionTypeArguments),
      kObjectBytes);
  if (n == 6) {
    __ BranchIf(EQUAL, found, Assembler::kNearJump);
    return;
  }

  __ BranchIf(NOT_EQUAL, next_iteration, Assembler::kNearJump);
  __ CompareWithMemoryValue(
      TypeTestABI::kDstTypeReg,
      Address(cache_entry_reg, target::kCompressedWordSize *
                                   target::SubtypeTestCache::kDestinationType),
      kObjectBytes);
  __ BranchIf(EQUAL, found, Assembler::kNearJump);
}

// An object that uses RAII to load from and store to the stack when
// appropriate, allowing the code within that scope to act as if the given
// register is always provided. Either the Register value stored at [reg] must
// be a valid register (not kNoRegister) or [depth] must be a valid stack depth
// (not StackRegisterScope::kNoDepth).
//
// When the Register value stored at [reg] is a valid register, this scope
// generates no assembly and does not change the value stored at [reg].
//
// When [depth] is a valid stack depth, this scope object performs the
// following actions:
//
// On construction:
// * Generates assembly to load the value on the stack at [depth] into [alt].
// * Sets the Register value pointed to by [reg] to [alt].
//
// On destruction:
// * Generates assembly to store the value of [alt] into the stack at [depth].
// * Resets the Register value pointed to by [reg] to kNoRegister.
class StackRegisterScope : ValueObject {
 public:
  StackRegisterScope(Assembler* assembler,
                     Register* reg,
                     intptr_t depth,
                     Register alt = TMP)
      : assembler(assembler), reg_(reg), depth_(depth), alt_(alt) {
    if (depth_ != kNoDepth) {
      ASSERT(depth_ >= 0);
      ASSERT(*reg_ == kNoRegister);
      ASSERT(alt_ != kNoRegister);
      __ LoadFromStack(alt_, depth_);
      *reg_ = alt_;
    } else {
      ASSERT(*reg_ != kNoRegister);
    }
  }

  ~StackRegisterScope() {
    if (depth_ != kNoDepth) {
      __ StoreToStack(alt_, depth_);
      *reg_ = kNoRegister;
    }
  }

  static constexpr intptr_t kNoDepth = kIntptrMin;

 private:
  Assembler* const assembler;
  Register* const reg_;
  const intptr_t depth_;
  const Register alt_;
};

// Same inputs as StubCodeCompiler::GenerateSubtypeTestCacheSearch with
// the following additional requirements:
// - catch_entry_reg: the address of the backing array for the cache.
// - TypeTestABI::kScratchReg: the Smi value of the length field for the
//   backing array in cache_entry_reg
//
// Also expects that all the STC entry input registers have been filled.
static void GenerateSubtypeTestCacheHashSearch(
    Assembler* assembler,
    int n,
    Register null_reg,
    Register cache_entry_reg,
    Register instance_cid_or_sig_reg,
    Register instance_type_args_reg,
    Register parent_fun_type_args_reg,
    Register delayed_type_args_reg,
    Register cache_entry_end_reg,
    Register cache_contents_size_reg,
    Register probe_distance_reg,
    const StubCodeCompiler::STCSearchExitGenerator& gen_found,
    const StubCodeCompiler::STCSearchExitGenerator& gen_not_found) {
  // Since the test entry size is a power of 2, we can use shr to divide.
  const intptr_t kTestEntryLengthLog2 =
      Utils::ShiftForPowerOfTwo(target::SubtypeTestCache::kTestEntryLength);

  // Before we finish calculating the initial probe entry, we'll need the
  // starting cache entry and the number of entries. We'll store these in
  // [cache_contents_size_reg] and [probe_distance_reg] (or their equivalent
  // stack slots), respectively.
  __ Comment("Hash cache traversal");
  __ Comment("Calculating number of entries");
  // The array length is a Smi so it needs to be untagged.
  __ SmiUntag(TypeTestABI::kScratchReg);
  __ LsrImmediate(TypeTestABI::kScratchReg, kTestEntryLengthLog2);
  if (probe_distance_reg != kNoRegister) {
    __ MoveRegister(probe_distance_reg, TypeTestABI::kScratchReg);
  } else {
    __ PushRegister(TypeTestABI::kScratchReg);
  }

  __ Comment("Calculating starting entry address");
  __ AddImmediate(cache_entry_reg,
                  target::Array::data_offset() - kHeapObjectTag);
  if (cache_contents_size_reg != kNoRegister) {
    __ MoveRegister(cache_contents_size_reg, cache_entry_reg);
  } else {
    __ PushRegister(cache_entry_reg);
  }

  __ Comment("Calculating end of entries address");
  __ LslImmediate(TypeTestABI::kScratchReg,
                  kTestEntryLengthLog2 + target::kCompressedWordSizeLog2);
  __ AddRegisters(TypeTestABI::kScratchReg, cache_entry_reg);
  if (cache_entry_end_reg != kNoRegister) {
    __ MoveRegister(cache_entry_end_reg, TypeTestABI::kScratchReg);
  } else {
    __ PushRegister(TypeTestABI::kScratchReg);
  }

  // At this point, the stack is in the following order, if the corresponding
  // value doesn't have a register assignment:
  // <number of total entries in cache array>
  // <cache array entries start>
  // <cache array entries end>
  // --------- top of stack
  //
  // and after calculating the initial entry, we'll replace them as follows:
  // <probe distance>
  // <-cache array contents size> (note this is _negative_)
  // <cache array entries end>
  // ---------- top of stack
  //
  // So name them according to their later use.
  intptr_t kProbeDistanceDepth = StackRegisterScope::kNoDepth;
  intptr_t kHashStackElements = 0;
  if (probe_distance_reg == kNoRegister) {
    kProbeDistanceDepth = 0;
    kHashStackElements++;
  }
  intptr_t kCacheContentsSizeDepth = StackRegisterScope::kNoDepth;
  if (cache_contents_size_reg == kNoRegister) {
    kProbeDistanceDepth++;
    kHashStackElements++;
    kCacheContentsSizeDepth = 0;
  }
  intptr_t kCacheArrayEndDepth = StackRegisterScope::kNoDepth;
  if (cache_entry_end_reg == kNoRegister) {
    kProbeDistanceDepth++;
    kCacheContentsSizeDepth++;
    kHashStackElements++;
    kCacheArrayEndDepth = 0;
  }

  // After this point, any exits should go through one of these two labels,
  // which will pop the extra stack elements pushed above.
  Label found, not_found;

  // When retrieving hashes from objects below, note that a hash of 0 means
  // the hash hasn't been computed yet and we need to go to runtime.
  auto get_abstract_type_hash = [&](Register dst, Register src,
                                    const char* name) {
    ASSERT(dst != kNoRegister);
    ASSERT(src != kNoRegister);
    __ Comment("Loading %s type hash", name);
    __ LoadFromSlot(dst, src, Slot::AbstractType_hash());
    __ SmiUntag(dst);
    __ CompareImmediate(dst, 0);
    __ BranchIf(EQUAL, &not_found);
  };
  auto get_type_arguments_hash = [&](Register dst, Register src,
                                     const char* name) {
    ASSERT(dst != kNoRegister);
    ASSERT(src != kNoRegister);
    Label done;
    __ Comment("Loading %s type arguments hash", name);
    // Preload the hash value for TypeArguments::null() so control can jump
    // to done if null.
    __ LoadImmediate(dst, TypeArguments::kAllDynamicHash);
    __ CompareRegisters(src, null_reg);
    __ BranchIf(EQUAL, &done, Assembler::kNearJump);
    __ LoadFromSlot(dst, src, Slot::TypeArguments_hash());
    __ SmiUntag(dst);
    __ CompareImmediate(dst, 0);
    __ BranchIf(EQUAL, &not_found);
    __ Bind(&done);
  };

  __ Comment("Hash the entry inputs");
  {
    Label done;
    // Assume a Smi tagged instance cid to avoid a branch in the common case.
    __ MoveRegister(cache_entry_reg, instance_cid_or_sig_reg);
    __ SmiUntag(cache_entry_reg);
    __ BranchIfSmi(instance_cid_or_sig_reg, &done, Assembler::kNearJump);
    get_abstract_type_hash(cache_entry_reg, instance_cid_or_sig_reg,
                           "closure signature");
    __ Bind(&done);
  }
  if (n >= 7) {
    get_abstract_type_hash(TypeTestABI::kScratchReg, TypeTestABI::kDstTypeReg,
                           "destination");
    __ CombineHashes(cache_entry_reg, TypeTestABI::kScratchReg);
  }
  if (n >= 6) {
    get_type_arguments_hash(TypeTestABI::kScratchReg, delayed_type_args_reg,
                            "delayed");
    __ CombineHashes(cache_entry_reg, TypeTestABI::kScratchReg);
  }
  if (n >= 5) {
    get_type_arguments_hash(TypeTestABI::kScratchReg, parent_fun_type_args_reg,
                            "parent function");
    __ CombineHashes(cache_entry_reg, TypeTestABI::kScratchReg);
  }
  if (n >= 4) {
    get_type_arguments_hash(TypeTestABI::kScratchReg,
                            TypeTestABI::kFunctionTypeArgumentsReg, "function");
    __ CombineHashes(cache_entry_reg, TypeTestABI::kScratchReg);
  }
  if (n >= 3) {
    get_type_arguments_hash(TypeTestABI::kScratchReg,
                            TypeTestABI::kInstantiatorTypeArgumentsReg,
                            "instantiator");
    __ CombineHashes(cache_entry_reg, TypeTestABI::kScratchReg);
  }
  if (n >= 2) {
    get_type_arguments_hash(TypeTestABI::kScratchReg, instance_type_args_reg,
                            "instance");
    __ CombineHashes(cache_entry_reg, TypeTestABI::kScratchReg);
  }
  __ FinalizeHash(cache_entry_reg);

  // This requires the number of entries in a hash cache to be a power of 2.
  __ Comment("Converting hash to probe entry index");
  {
    StackRegisterScope scope(assembler, &probe_distance_reg,
                             kProbeDistanceDepth, TypeTestABI::kScratchReg);
    // The entry count is not needed after this point; create the mask in place.
    __ AddImmediate(probe_distance_reg, -1);
    __ AndRegisters(cache_entry_reg, probe_distance_reg);
    // Now set the register to the initial probe distance in words.
    __ Comment("Set initial probe distance");
    __ LoadImmediate(probe_distance_reg,
                     target::kCompressedWordSize *
                         target::SubtypeTestCache::kTestEntryLength);
  }

  // Now cache_entry_reg is the starting probe entry index.
  __ Comment("Converting probe entry index to probe entry address");
  {
    StackRegisterScope scope(assembler, &cache_contents_size_reg,
                             kCacheContentsSizeDepth, TypeTestABI::kScratchReg);
    __ LslImmediate(cache_entry_reg,
                    kTestEntryLengthLog2 + target::kCompressedWordSizeLog2);
    __ AddRegisters(cache_entry_reg, cache_contents_size_reg);
    // Now set the register to the negated size of the cache contents in words.
    __ Comment("Set negated cache contents size");
    if (cache_entry_end_reg != kNoRegister) {
      __ SubRegisters(cache_contents_size_reg, cache_entry_end_reg);
    } else {
      __ LoadFromStack(TMP, kCacheArrayEndDepth);
      __ SubRegisters(cache_contents_size_reg, TMP);
    }
  }

  Label loop, next_iteration;
  __ Bind(&loop);
  GenerateSubtypeTestCacheLoopBody(
      assembler, n, null_reg, cache_entry_reg, instance_cid_or_sig_reg,
      instance_type_args_reg, parent_fun_type_args_reg, delayed_type_args_reg,
      &found, &not_found, &next_iteration);
  __ Bind(&next_iteration);
  __ Comment("Move to next entry");
  {
    StackRegisterScope scope(assembler, &probe_distance_reg,
                             kProbeDistanceDepth, TypeTestABI::kScratchReg);
    __ AddRegisters(cache_entry_reg, probe_distance_reg);
    __ Comment("Adjust probe distance");
    __ AddImmediate(probe_distance_reg,
                    target::kCompressedWordSize *
                        target::SubtypeTestCache::kTestEntryLength);
  }
  __ Comment("Check for leaving array");
  // Make sure we haven't run off the array.
  if (cache_entry_end_reg != kNoRegister) {
    __ CompareRegisters(cache_entry_reg, cache_entry_end_reg);
  } else {
    __ CompareToStack(cache_entry_reg, kCacheArrayEndDepth);
  }
  __ BranchIf(LESS, &loop, Assembler::kNearJump);
  __ Comment("Wrap around to start of entries");
  // Add the negated size of the cache contents.
  if (cache_contents_size_reg != kNoRegister) {
    __ AddRegisters(cache_entry_reg, cache_contents_size_reg);
  } else {
    __ LoadFromStack(TypeTestABI::kScratchReg, kCacheContentsSizeDepth);
    __ AddRegisters(cache_entry_reg, TypeTestABI::kScratchReg);
  }
  __ Jump(&loop, Assembler::kNearJump);

  __ Bind(&found);
  __ Comment("Hash found");
  __ Drop(kHashStackElements);
  gen_found(assembler, n);
  __ Bind(&not_found);
  __ Comment("Hash not found");
  __ Drop(kHashStackElements);
  gen_not_found(assembler, n);
}

// Same inputs as StubCodeCompiler::GenerateSubtypeTestCacheSearch with
// the following additional requirement:
// - catch_entry_reg: the address of the backing array for the cache.
//
// Also expects that all the STC entry input registers have been filled.
static void GenerateSubtypeTestCacheLinearSearch(
    Assembler* assembler,
    int n,
    Register null_reg,
    Register cache_entry_reg,
    Register instance_cid_or_sig_reg,
    Register instance_type_args_reg,
    Register parent_fun_type_args_reg,
    Register delayed_type_args_reg,
    const StubCodeCompiler::STCSearchExitGenerator& gen_found,
    const StubCodeCompiler::STCSearchExitGenerator& gen_not_found) {
  __ Comment("Linear cache traversal");
  __ AddImmediate(cache_entry_reg,
                  target::Array::data_offset() - kHeapObjectTag);

  Label found, not_found, loop, next_iteration;
  __ Bind(&loop);
  GenerateSubtypeTestCacheLoopBody(
      assembler, n, null_reg, cache_entry_reg, instance_cid_or_sig_reg,
      instance_type_args_reg, parent_fun_type_args_reg, delayed_type_args_reg,
      &found, &not_found, &next_iteration);
  __ Bind(&next_iteration);
  __ Comment("Next iteration");
  __ AddImmediate(
      cache_entry_reg,
      target::kCompressedWordSize * target::SubtypeTestCache::kTestEntryLength);
  __ Jump(&loop, Assembler::kNearJump);

  __ Bind(&found);
  __ Comment("Linear found");
  gen_found(assembler, n);
  __ Bind(&not_found);
  __ Comment("Linear not found");
  gen_not_found(assembler, n);
}

void StubCodeCompiler::GenerateSubtypeTestCacheSearch(
    Assembler* assembler,
    int n,
    Register null_reg,
    Register cache_entry_reg,
    Register instance_cid_or_sig_reg,
    Register instance_type_args_reg,
    Register parent_fun_type_args_reg,
    Register delayed_type_args_reg,
    Register cache_entry_end_reg,
    Register cache_contents_size_reg,
    Register probe_distance_reg,
    const StubCodeCompiler::STCSearchExitGenerator& gen_found,
    const StubCodeCompiler::STCSearchExitGenerator& gen_not_found) {
#if defined(DEBUG)
  RegisterSet input_regs;
  ASSERT(null_reg != kNoRegister);
  input_regs.AddRegister(null_reg);
  ASSERT(cache_entry_reg != kNoRegister);
  ASSERT(!input_regs.ContainsRegister(cache_entry_reg));
  input_regs.AddRegister(cache_entry_reg);
  ASSERT(instance_cid_or_sig_reg != kNoRegister);
  ASSERT(!input_regs.ContainsRegister(instance_cid_or_sig_reg));
  input_regs.AddRegister(instance_cid_or_sig_reg);
  if (n >= 2) {
    ASSERT(instance_type_args_reg != kNoRegister);
    ASSERT(!input_regs.ContainsRegister(instance_type_args_reg));
    input_regs.AddRegister(instance_type_args_reg);
  }
  if (n >= 5) {
    ASSERT(parent_fun_type_args_reg != kNoRegister);
    ASSERT(!input_regs.ContainsRegister(parent_fun_type_args_reg));
    input_regs.AddRegister(parent_fun_type_args_reg);
  }
  ASSERT(!input_regs.ContainsRegister(TypeTestABI::kInstanceReg));
  if (n >= 6) {
    ASSERT(delayed_type_args_reg != kNoRegister);
    ASSERT(!input_regs.ContainsRegister(delayed_type_args_reg));
    input_regs.AddRegister(delayed_type_args_reg);
  }
  if (cache_entry_end_reg != kNoRegister) {
    ASSERT(!input_regs.ContainsRegister(cache_entry_end_reg));
    input_regs.AddRegister(cache_entry_end_reg);
  }
  if (cache_contents_size_reg != kNoRegister) {
    ASSERT(!input_regs.ContainsRegister(cache_contents_size_reg));
    input_regs.AddRegister(cache_contents_size_reg);
  }
  if (probe_distance_reg != kNoRegister) {
    ASSERT(!input_regs.ContainsRegister(probe_distance_reg));
    input_regs.AddRegister(probe_distance_reg);
  }
  // We can allow the use of the registers below only if we're not expecting
  // them as an inspected input.
  if (n >= 3) {
    ASSERT(!input_regs.ContainsRegister(
        TypeTestABI::kInstantiatorTypeArgumentsReg));
  }
  if (n >= 4) {
    ASSERT(
        !input_regs.ContainsRegister(TypeTestABI::kFunctionTypeArgumentsReg));
  }
  if (n >= 7) {
    ASSERT(!input_regs.ContainsRegister(TypeTestABI::kDstTypeReg));
  }
  // We use this as a scratch, so it has to be distinct from the others.
  ASSERT(!input_regs.ContainsRegister(TypeTestABI::kScratchReg));

  // Verify the STC we received has exactly as many inputs as this stub expects.
  Label search_stc;
  __ LoadFromSlot(TypeTestABI::kScratchReg, TypeTestABI::kSubtypeTestCacheReg,
                  Slot::SubtypeTestCache_num_inputs());
  __ CompareImmediate(TypeTestABI::kScratchReg, n);
  __ BranchIf(EQUAL, &search_stc, Assembler::kNearJump);
  __ Breakpoint();
  __ Bind(&search_stc);
#endif

  __ LoadAcquireCompressedFromOffset(
      cache_entry_reg, TypeTestABI::kSubtypeTestCacheReg,
      target::SubtypeTestCache::cache_offset() - kHeapObjectTag);

  // Fill in all the STC input registers.
  Label initialized, not_closure;
  if (n >= 3) {
    __ LoadClassIdMayBeSmi(instance_cid_or_sig_reg, TypeTestABI::kInstanceReg);
  } else {
    // If the type is fully instantiated, then it can be determined at compile
    // time whether Smi is a subtype of the type or not. Thus, this code should
    // never be called with a Smi instance.
    __ LoadClassId(instance_cid_or_sig_reg, TypeTestABI::kInstanceReg);
  }
  __ CompareImmediate(instance_cid_or_sig_reg, kClosureCid);
  __ BranchIf(NOT_EQUAL, &not_closure, Assembler::kNearJump);

  // Closure handling.
  {
    __ Comment("Closure");
    __ LoadCompressed(instance_cid_or_sig_reg,
                      FieldAddress(TypeTestABI::kInstanceReg,
                                   target::Closure::function_offset()));
    __ LoadCompressed(instance_cid_or_sig_reg,
                      FieldAddress(instance_cid_or_sig_reg,
                                   target::Function::signature_offset()));
    if (n >= 2) {
      __ LoadCompressedSmi(
          TypeTestABI::kScratchReg,
          FieldAddress(TypeTestABI::kInstanceReg,
                       target::Closure::length_and_flags_offset()));

      Label load_function_type_arguments, load_delayed_type_arguments;
      ASSERT(instance_type_args_reg != TypeTestABI::kInstanceReg);
      ASSERT(instance_type_args_reg != TypeTestABI::kScratchReg);
      __ MoveRegister(instance_type_args_reg, null_reg);
      __ BranchIfBit(
          TypeTestABI::kScratchReg,
          UntaggedClosure::kHasInstantiatorTypeArgumentsBit + kSmiTagShift,
          ZERO, (n >= 5) ? &load_function_type_arguments : &initialized);
      __ ExtractBitField(
          instance_type_args_reg, TypeTestABI::kScratchReg,
          UntaggedClosure::InstantiatorTypeArgumentsIndexBits::shift() +
              kSmiTagShift,
          UntaggedClosure::InstantiatorTypeArgumentsIndexBits::bitsize());
      __ LoadIndexedCompressed(
          instance_type_args_reg, TypeTestABI::kInstanceReg,
          target::Closure::element_offset(0), instance_type_args_reg);
      if (n >= 5) {
        __ Bind(&load_function_type_arguments);

        ASSERT(parent_fun_type_args_reg != TypeTestABI::kInstanceReg);
        ASSERT(parent_fun_type_args_reg != TypeTestABI::kScratchReg);
        __ MoveRegister(parent_fun_type_args_reg, null_reg);
        __ BranchIfBit(
            TypeTestABI::kScratchReg,
            UntaggedClosure::kHasFunctionTypeArgumentsBit + kSmiTagShift, ZERO,
            (n >= 6) ? &load_delayed_type_arguments : &initialized);
        __ ExtractBitField(
            parent_fun_type_args_reg, TypeTestABI::kScratchReg,
            UntaggedClosure::FunctionTypeArgumentsIndexBits::shift() +
                kSmiTagShift,
            UntaggedClosure::FunctionTypeArgumentsIndexBits::bitsize());
        __ LoadIndexedCompressed(
            parent_fun_type_args_reg, TypeTestABI::kInstanceReg,
            target::Closure::element_offset(0), parent_fun_type_args_reg);
      }

      if (n >= 6) {
        __ Bind(&load_delayed_type_arguments);

        // On 32-bit ARM [delayed_type_args_reg] can be the same as
        // [TypeTestABI::kInstanceReg]. In such a case [delayed_type_args_reg]
        // should not be overwritten before the last use of [kInstanceReg].
        if (delayed_type_args_reg == TypeTestABI::kInstanceReg) {
          Label no_delayed_type_arguments;
          __ BranchIfBit(
              TypeTestABI::kScratchReg,
              UntaggedClosure::kHasDelayedTypeArgumentsBit + kSmiTagShift, ZERO,
              &no_delayed_type_arguments);
          __ LoadCompressed(
              delayed_type_args_reg,
              FieldAddress(TypeTestABI::kInstanceReg,
                           target::Closure::element_offset(
                               UntaggedClosure::kDelayedTypeArgumentsIndex)));
          __ Jump(&initialized, Assembler::kNearJump);

          __ Bind(&no_delayed_type_arguments);
          __ MoveRegister(delayed_type_args_reg, null_reg);
        } else {
          ASSERT(delayed_type_args_reg != TypeTestABI::kInstanceReg);
          ASSERT(delayed_type_args_reg != TypeTestABI::kScratchReg);
          __ MoveRegister(delayed_type_args_reg, null_reg);
          __ BranchIfBit(
              TypeTestABI::kScratchReg,
              UntaggedClosure::kHasDelayedTypeArgumentsBit + kSmiTagShift, ZERO,
              &initialized);
          __ LoadCompressed(
              delayed_type_args_reg,
              FieldAddress(TypeTestABI::kInstanceReg,
                           target::Closure::element_offset(
                               UntaggedClosure::kDelayedTypeArgumentsIndex)));
        }
      }
    }

    __ Jump(&initialized, Assembler::kNearJump);
  }

  // Non-Closure handling.
  {
    __ Comment("Non-Closure");
    __ Bind(&not_closure);
    if (n >= 2) {
      Label has_no_type_arguments;
      __ LoadClassById(TypeTestABI::kScratchReg, instance_cid_or_sig_reg);
      __ MoveRegister(instance_type_args_reg, null_reg);
      __ LoadFieldFromOffset(
          TypeTestABI::kScratchReg, TypeTestABI::kScratchReg,
          target::Class::host_type_arguments_field_offset_in_words_offset(),
          kFourBytes);
      __ CompareImmediate(TypeTestABI::kScratchReg,
                          target::Class::kNoTypeArguments, kFourBytes);
      __ BranchIf(EQUAL, &has_no_type_arguments, Assembler::kNearJump);
      __ LoadIndexedCompressed(instance_type_args_reg,
                               TypeTestABI::kInstanceReg, 0,
                               TypeTestABI::kScratchReg);
      __ Bind(&has_no_type_arguments);
      __ Comment("No type arguments");
    }
    __ SmiTag(instance_cid_or_sig_reg);
    if (n >= 5) {
      __ MoveRegister(parent_fun_type_args_reg, null_reg);
    }
    if (n >= 6) {
      __ MoveRegister(delayed_type_args_reg, null_reg);
    }
  }

  __ Bind(&initialized);
  // There is a maximum size for linear caches that is smaller than the size
  // of any hash-based cache, so we check the size of the backing array to
  // determine if this is a linear or hash-based cache.
  //
  // We load it into TypeTestABI::kScratchReg as the hash search code expects
  // it there.
  Label is_hash;
  __ LoadFromSlot(TypeTestABI::kScratchReg, cache_entry_reg,
                  Slot::Array_length());
  __ CompareImmediate(TypeTestABI::kScratchReg,
                      target::ToRawSmi(SubtypeTestCache::kMaxLinearCacheSize));
  __ BranchIf(GREATER, &is_hash);

  GenerateSubtypeTestCacheLinearSearch(
      assembler, n, null_reg, cache_entry_reg, instance_cid_or_sig_reg,
      instance_type_args_reg, parent_fun_type_args_reg, delayed_type_args_reg,
      gen_found, gen_not_found);

  __ Bind(&is_hash);
  GenerateSubtypeTestCacheHashSearch(
      assembler, n, null_reg, cache_entry_reg, instance_cid_or_sig_reg,
      instance_type_args_reg, parent_fun_type_args_reg, delayed_type_args_reg,
      cache_entry_end_reg, cache_contents_size_reg, probe_distance_reg,
      gen_found, gen_not_found);
}
#endif

// See comment on [GenerateSubtypeNTestCacheStub].
void StubCodeCompiler::GenerateSubtype1TestCacheStub() {
  GenerateSubtypeNTestCacheStub(assembler, 1);
}

// See comment on [GenerateSubtypeNTestCacheStub].
void StubCodeCompiler::GenerateSubtype2TestCacheStub() {
  GenerateSubtypeNTestCacheStub(assembler, 2);
}

// See comment on [GenerateSubtypeNTestCacheStub].
void StubCodeCompiler::GenerateSubtype3TestCacheStub() {
  GenerateSubtypeNTestCacheStub(assembler, 3);
}

// See comment on [GenerateSubtypeNTestCacheStub].
void StubCodeCompiler::GenerateSubtype4TestCacheStub() {
  GenerateSubtypeNTestCacheStub(assembler, 4);
}

// See comment on [GenerateSubtypeNTestCacheStub].
void StubCodeCompiler::GenerateSubtype6TestCacheStub() {
  GenerateSubtypeNTestCacheStub(assembler, 6);
}

// See comment on [GenerateSubtypeNTestCacheStub].
void StubCodeCompiler::GenerateSubtype7TestCacheStub() {
  GenerateSubtypeNTestCacheStub(assembler, 7);
}

#ifndef DART_TARGET_SUPPORTS_PROBE_POINTS
void StubCodeCompiler::GenerateAllocationProbePointStub() {
  __ Stop("allocation probes are not supported on this platform");
}
#endif

}  // namespace compiler

}  // namespace dart
