// Copyright (c) 2012, 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/object.h"

#include <memory>

#include "include/dart_api.h"
#include "lib/integers.h"
#include "lib/stacktrace.h"
#include "platform/assert.h"
#include "platform/text_buffer.h"
#include "platform/unaligned.h"
#include "platform/unicode.h"
#include "vm/bit_vector.h"
#include "vm/bootstrap.h"
#include "vm/bytecode_reader.h"
#include "vm/canonical_tables.h"
#include "vm/class_finalizer.h"
#include "vm/class_id.h"
#include "vm/closure_functions_cache.h"
#include "vm/code_comments.h"
#include "vm/code_descriptors.h"
#include "vm/code_observers.h"
#include "vm/compiler/assembler/disassembler.h"
#include "vm/compiler/assembler/disassembler_kbc.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/compiler/method_recognizer.h"
#include "vm/compiler/runtime_api.h"
#include "vm/cpu.h"
#include "vm/dart.h"
#include "vm/dart_api_state.h"
#include "vm/dart_entry.h"
#include "vm/datastream.h"
#include "vm/debugger.h"
#include "vm/deopt_instructions.h"
#include "vm/double_conversion.h"
#include "vm/elf.h"
#include "vm/exceptions.h"
#include "vm/growable_array.h"
#include "vm/hash.h"
#include "vm/hash_table.h"
#include "vm/heap/become.h"
#include "vm/heap/heap.h"
#include "vm/heap/sampler.h"
#include "vm/heap/weak_code.h"
#include "vm/image_snapshot.h"
#include "vm/isolate_reload.h"
#include "vm/kernel.h"
#include "vm/kernel_binary.h"
#include "vm/kernel_isolate.h"
#include "vm/kernel_loader.h"
#include "vm/line_starts_reader.h"
#include "vm/log.h"
#include "vm/native_symbol.h"
#include "vm/object_graph.h"
#include "vm/object_store.h"
#include "vm/os.h"
#include "vm/parser.h"
#include "vm/profiler.h"
#include "vm/resolver.h"
#include "vm/reusable_handles.h"
#include "vm/reverse_pc_lookup_cache.h"
#include "vm/runtime_entry.h"
#include "vm/scopes.h"
#include "vm/stack_frame.h"
#include "vm/stub_code.h"
#include "vm/symbols.h"
#include "vm/tags.h"
#include "vm/thread_registry.h"
#include "vm/timeline.h"
#include "vm/tsan_symbolize.h"
#include "vm/type_testing_stubs.h"
#include "vm/zone_text_buffer.h"

#if !defined(DART_PRECOMPILED_RUNTIME)
#include "vm/compiler/aot/precompiler.h"
#include "vm/compiler/assembler/assembler.h"
#include "vm/compiler/backend/code_statistics.h"
#include "vm/compiler/compiler_state.h"
#include "vm/compiler/frontend/kernel_fingerprints.h"
#include "vm/compiler/frontend/kernel_translation_helper.h"
#include "vm/compiler/intrinsifier.h"
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

namespace dart {

DEFINE_FLAG(uint64_t,
            huge_method_cutoff_in_code_size,
            200000,
            "Huge method cutoff in unoptimized code size (in bytes).");
DEFINE_FLAG(
    bool,
    show_internal_names,
    false,
    "Show names of internal classes (e.g. \"OneByteString\") in error messages "
    "instead of showing the corresponding interface names (e.g. \"String\"). "
    "Also show legacy nullability in type names.");

DEFINE_FLAG(bool,
            remove_script_timestamps_for_test,
            false,
            "Remove script timestamps to allow for deterministic testing.");

#if !defined(DART_PRECOMPILED_RUNTIME)
DEFINE_FLAG(bool, use_register_cc, true, "Use register calling conventions");
#endif

DECLARE_FLAG(bool, intrinsify);
DECLARE_FLAG(bool, trace_deoptimization);
DECLARE_FLAG(bool, trace_deoptimization_verbose);
DECLARE_FLAG(bool, trace_reload);
DECLARE_FLAG(bool, write_protect_code);
DECLARE_FLAG(bool, precompiled_mode);
DECLARE_FLAG(int, max_polymorphic_checks);

static const char* const kGetterPrefix = "get:";
static const intptr_t kGetterPrefixLength = strlen(kGetterPrefix);
static const char* const kSetterPrefix = "set:";
static const intptr_t kSetterPrefixLength = strlen(kSetterPrefix);
static const char* const kInitPrefix = "init:";
static const intptr_t kInitPrefixLength = strlen(kInitPrefix);

cpp_vtable Object::builtin_vtables_[kNumPredefinedCids] = {};

#define CHECK_ERROR(error)                                                     \
  {                                                                            \
    ErrorPtr err = (error);                                                    \
    if (err != Error::null()) {                                                \
      return err;                                                              \
    }                                                                          \
  }

static void AppendSubString(BaseTextBuffer* buffer,
                            const char* name,
                            intptr_t start_pos,
                            intptr_t len) {
  buffer->Printf("%.*s", static_cast<int>(len), &name[start_pos]);
}

// Used to define setters and getters for untagged object fields that are
// defined with the WSR_COMPRESSED_POINTER_FIELD macro. See
// PRECOMPILER_WSR_FIELD_DECLARATION in object.h for more information.
#if defined(DART_PRECOMPILER)
#define PRECOMPILER_WSR_FIELD_DEFINITION(Class, Type, Name)                    \
  Type##Ptr Class::Name() const {                                              \
    return Type::RawCast(WeakSerializationReference::Unwrap(untag()->Name())); \
  }
#else
#define PRECOMPILER_WSR_FIELD_DEFINITION(Class, Type, Name)                    \
  void Class::set_##Name(const Type& value) const {                            \
    untag()->set_##Name(value.ptr());                                          \
  }
#endif

PRECOMPILER_WSR_FIELD_DEFINITION(ClosureData, Function, parent_function)
PRECOMPILER_WSR_FIELD_DEFINITION(Function, FunctionType, signature)

#undef PRECOMPILER_WSR_FIELD_DEFINITION

// Suppress lint for the following definition because the whitespace/parens
// check doesn't like __VA_OPT__(, ), which is what the formatter produces here.
// NOLINTBEGIN
#define TRACE_TYPE_CHECKS_VERBOSE(format, ...)                                 \
  if (FLAG_trace_type_checks_verbose) {                                        \
    OS::PrintErr(format __VA_OPT__(, ) __VA_ARGS__);                           \
  }
// NOLINTEND

// Takes a vm internal name and makes it suitable for external user.
//
// Examples:
//
// Internal getter and setter prefixes are changed:
//
//   get:foo -> foo
//   set:foo -> foo=
//
// Private name mangling is removed, possibly multiple times:
//
//   _ReceivePortImpl@709387912 -> _ReceivePortImpl
//   _ReceivePortImpl@709387912._internal@709387912 ->
//      _ReceivePortImpl._internal
//   _C@6328321&_E@6328321&_F@6328321 -> _C&_E&_F
//
// The trailing . on the default constructor name is dropped:
//
//   List. -> List
//
// And so forth:
//
//   get:foo@6328321 -> foo
//   _MyClass@6328321. -> _MyClass
//   _MyClass@6328321.named -> _MyClass.named
//
// For extension methods the following demangling is done
//   ext|func -> ext.func (instance extension method)
//   ext|get#prop -> ext.prop (instance extension getter)
//   ext|set#prop -> ext.prop= (instance extension setter)
//   ext|sfunc -> ext.sfunc (static extension method)
//   get:ext|sprop -> ext.sprop (static extension getter)
//   set:ext|sprop -> ext.sprop= (static extension setter)
//
const char* String::ScrubName(const String& name, bool is_extension) {
  Thread* thread = Thread::Current();
  NoSafepointScope no_safepoint(thread);
  Zone* zone = thread->zone();
  ZoneTextBuffer printer(zone);

#if !defined(DART_PRECOMPILED_RUNTIME)
  if (name.Equals(Symbols::TopLevel())) {
    // Name of invisible top-level class.
    return "";
  }
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

  const char* cname = name.ToCString();
  ASSERT(strlen(cname) == static_cast<size_t>(name.Length()));
  const intptr_t name_len = name.Length();
  // First remove all private name mangling and if 'is_extension' is true
  // substitute the first '|' character with '.'.
  intptr_t start_pos = 0;
  intptr_t sum_segment_len = 0;
  for (intptr_t i = 0; i < name_len; i++) {
    if ((cname[i] == '@') && ((i + 1) < name_len) && (cname[i + 1] >= '0') &&
        (cname[i + 1] <= '9')) {
      // Append the current segment to the unmangled name.
      const intptr_t segment_len = i - start_pos;
      sum_segment_len += segment_len;
      AppendSubString(&printer, cname, start_pos, segment_len);
      // Advance until past the name mangling. The private keys are only
      // numbers so we skip until the first non-number.
      i++;  // Skip the '@'.
      while ((i < name.Length()) && (name.CharAt(i) >= '0') &&
             (name.CharAt(i) <= '9')) {
        i++;
      }
      start_pos = i;
      i--;  // Account for for-loop increment.
    } else if (is_extension && cname[i] == '|') {
      // Append the current segment to the unmangled name.
      const intptr_t segment_len = i - start_pos;
      AppendSubString(&printer, cname, start_pos, segment_len);
      // Append the '.' character (replaces '|' with '.').
      AppendSubString(&printer, ".", 0, 1);
      start_pos = i + 1;
      // Account for length of segments added so far.
      sum_segment_len += (segment_len + 1);
    }
  }

  const char* unmangled_name = nullptr;
  if (start_pos == 0) {
    // No name unmangling needed, reuse the name that was passed in.
    unmangled_name = cname;
    sum_segment_len = name_len;
  } else if (name.Length() != start_pos) {
    // Append the last segment.
    const intptr_t segment_len = name.Length() - start_pos;
    sum_segment_len += segment_len;
    AppendSubString(&printer, cname, start_pos, segment_len);
  }
  if (unmangled_name == nullptr) {
    // Merge unmangled_segments.
    unmangled_name = printer.buffer();
  }

  printer.Clear();
  intptr_t start = 0;
  intptr_t len = sum_segment_len;
  bool is_setter = false;
  if (is_extension) {
    // First scan till we see the '.' character.
    for (intptr_t i = 0; i < len; i++) {
      if (unmangled_name[i] == '.') {
        intptr_t slen = i + 1;
        intptr_t plen = slen - start;
        AppendSubString(&printer, unmangled_name, start, plen);
        unmangled_name += slen;
        len -= slen;
        break;
      } else if (unmangled_name[i] == ':') {
        if (start != 0) {
          // Reset and break.
          start = 0;
          is_setter = false;
          break;
        }
        if (unmangled_name[0] == 's') {
          is_setter = true;
        }
        start = i + 1;
      }
    }
  }
  intptr_t dot_pos = -1;  // Position of '.' in the name, if any.
  start = 0;
  for (intptr_t i = start; i < len; i++) {
    if (unmangled_name[i] == ':' ||
        (is_extension && unmangled_name[i] == '#')) {
      if (start != 0) {
        // Reset and break.
        start = 0;
        dot_pos = -1;
        break;
      }
      ASSERT(start == 0);  // Only one : is possible in getters or setters.
      if (unmangled_name[0] == 's') {
        ASSERT(!is_setter);
        is_setter = true;
      }
      start = i + 1;
    } else if (unmangled_name[i] == '.') {
      if (dot_pos != -1) {
        // Reset and break.
        start = 0;
        dot_pos = -1;
        break;
      }
      ASSERT(dot_pos == -1);  // Only one dot is supported.
      dot_pos = i;
    }
  }

  if (!is_extension && (start == 0) && (dot_pos == -1)) {
    // This unmangled_name is fine as it is.
    return unmangled_name;
  }

  // Drop the trailing dot if needed.
  intptr_t end = ((dot_pos + 1) == len) ? dot_pos : len;

  intptr_t substr_len = end - start;
  AppendSubString(&printer, unmangled_name, start, substr_len);
  if (is_setter) {
    const char* equals = Symbols::Equals().ToCString();
    const intptr_t equals_len = strlen(equals);
    AppendSubString(&printer, equals, 0, equals_len);
  }

  return printer.buffer();
}

StringPtr String::ScrubNameRetainPrivate(const String& name,
                                         bool is_extension) {
#if !defined(DART_PRECOMPILED_RUNTIME)
  intptr_t len = name.Length();
  intptr_t start = 0;
  intptr_t at_pos = -1;  // Position of '@' in the name, if any.
  bool is_setter = false;

  String& result = String::Handle();

  // If extension strip out the leading prefix e.g" ext|func would strip out
  // 'ext|'.
  if (is_extension) {
    // First scan till we see the '|' character.
    for (intptr_t i = 0; i < len; i++) {
      if (name.CharAt(i) == '|') {
        result = String::SubString(name, start, (i - start));
        result = String::Concat(result, Symbols::Dot());
        start = i + 1;
        break;
      } else if (name.CharAt(i) == ':') {
        if (start != 0) {
          // Reset and break.
          start = 0;
          is_setter = false;
          break;
        }
        if (name.CharAt(0) == 's') {
          is_setter = true;
        }
        start = i + 1;
      }
    }
  }

  for (intptr_t i = start; i < len; i++) {
    if (name.CharAt(i) == ':' || (is_extension && name.CharAt(i) == '#')) {
      // Only one : is possible in getters or setters.
      ASSERT(is_extension || start == 0);
      if (name.CharAt(start) == 's') {
        is_setter = true;
      }
      start = i + 1;
    } else if (name.CharAt(i) == '@') {
      // Setters should have only one @ so we know where to put the =.
      ASSERT(!is_setter || (at_pos == -1));
      at_pos = i;
    }
  }

  if (start == 0) {
    // This unmangled_name is fine as it is.
    return name.ptr();
  }

  if (is_extension) {
    const String& fname =
        String::Handle(String::SubString(name, start, (len - start)));
    result = String::Concat(result, fname);
  } else {
    result = String::SubString(name, start, (len - start));
  }

  if (is_setter) {
    // Setters need to end with '='.
    if (at_pos == -1) {
      return String::Concat(result, Symbols::Equals());
    } else {
      const String& pre_at =
          String::Handle(String::SubString(result, 0, at_pos - 4));
      const String& post_at =
          String::Handle(String::SubString(name, at_pos, len - at_pos));
      result = String::Concat(pre_at, Symbols::Equals());
      result = String::Concat(result, post_at);
    }
  }

  return result.ptr();
#endif                // !defined(DART_PRECOMPILED_RUNTIME)
  return name.ptr();  // In AOT, return argument unchanged.
}

template <typename type>
static bool IsSpecialCharacter(type value) {
  return ((value == '"') || (value == '\n') || (value == '\f') ||
          (value == '\b') || (value == '\t') || (value == '\v') ||
          (value == '\r') || (value == '\\') || (value == '$'));
}

static inline bool IsAsciiNonprintable(int32_t c) {
  return ((0 <= c) && (c < 32)) || (c == 127);
}

static int32_t EscapeOverhead(int32_t c) {
  if (IsSpecialCharacter(c)) {
    return 1;  // 1 additional byte for the backslash.
  } else if (IsAsciiNonprintable(c)) {
    return 3;  // 3 additional bytes to encode c as \x00.
  }
  return 0;
}

template <typename type>
static type SpecialCharacter(type value) {
  if (value == '"') {
    return '"';
  } else if (value == '\n') {
    return 'n';
  } else if (value == '\f') {
    return 'f';
  } else if (value == '\b') {
    return 'b';
  } else if (value == '\t') {
    return 't';
  } else if (value == '\v') {
    return 'v';
  } else if (value == '\r') {
    return 'r';
  } else if (value == '\\') {
    return '\\';
  } else if (value == '$') {
    return '$';
  }
  UNREACHABLE();
  return '\0';
}

#if defined(DART_DYNAMIC_MODULES)
static BytecodePtr CreateVMInternalBytecode(KernelBytecode::Opcode opcode) {
  const KBCInstr* instructions = nullptr;
  intptr_t instructions_size = 0;

  KernelBytecode::GetVMInternalBytecodeInstructions(opcode, &instructions,
                                                    &instructions_size);

  const auto& bytecode = Bytecode::Handle(
      Bytecode::New(reinterpret_cast<uword>(instructions), instructions_size,
                    -1, TypedDataBase::Handle(), Object::empty_object_pool()));
  bytecode.set_pc_descriptors(Object::empty_descriptors());
  bytecode.set_exception_handlers(Object::empty_exception_handlers());
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
  bytecode.set_var_descriptors(Object::empty_var_descriptors());
#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
  return bytecode.ptr();
}
#endif  // defined(DART_DYNAMIC_MODULES)

void Object::InitNullAndBool(IsolateGroup* isolate_group) {
  Thread* thread = Thread::Current();
  auto heap = isolate_group->heap();

  // TODO(iposva): NoSafepointScope needs to be added here.

  // Allocate and initialize the null instance.
  // 'null_' must be the first object allocated as it is used in allocation to
  // clear the pointer fields of objects.
  {
    uword address =
        heap->Allocate(thread, Instance::InstanceSize(), Heap::kOld);
    Roots::set_null_obj(static_cast<InstancePtr>(address + kHeapObjectTag));
    InitializeObjectVariant<Instance>(address, kNullCid);
    Roots::null_obj()->untag()->SetCanonical();
#if defined(HASH_IN_OBJECT_HEADER)
    Object::SetCachedHashIfNotSet(Roots::null_obj(), kNullIdentityHash);
#endif
  }

  // Allocate and initialize the bool instances.
  // These must be allocated such that at kBoolValueBitPosition, the address
  // of true is 0 and the address of false is 1, and their addresses are
  // otherwise identical.
  {
    // Allocate a dummy bool object to give true the desired alignment.
    uword address = heap->Allocate(thread, Bool::InstanceSize(), Heap::kOld);
    InitializeObject<Bool>(address);
    static_cast<BoolPtr>(address + kHeapObjectTag)->untag()->value_ = false;
  }
  {
    // Allocate true.
    uword address = heap->Allocate(thread, Bool::InstanceSize(), Heap::kOld);
    Roots::set_true_obj(static_cast<BoolPtr>(address + kHeapObjectTag));
    InitializeObject<Bool>(address);
    Roots::true_obj()->untag()->value_ = true;
    Roots::true_obj()->untag()->SetCanonical();
#if defined(HASH_IN_OBJECT_HEADER)
    Object::SetCachedHashIfNotSet(Roots::true_obj(), kTrueIdentityHash);
#endif
  }
  {
    // Allocate false.
    uword address = heap->Allocate(thread, Bool::InstanceSize(), Heap::kOld);
    Roots::set_false_obj(static_cast<BoolPtr>(address + kHeapObjectTag));
    InitializeObject<Bool>(address);
    Roots::false_obj()->untag()->value_ = false;
    Roots::false_obj()->untag()->SetCanonical();
#if defined(HASH_IN_OBJECT_HEADER)
    Object::SetCachedHashIfNotSet(Roots::false_obj(), kFalseIdentityHash);
#endif
  }

  {
    uword address =
        heap->Allocate(thread, Sentinel::InstanceSize(), Heap::kOld);
    Roots::set_sentinel_obj(static_cast<SentinelPtr>(address + kHeapObjectTag));
    InitializeObject<Sentinel>(address);
  }
  {
    uword address =
        heap->Allocate(thread, Sentinel::InstanceSize(), Heap::kOld);
    Roots::set_unknown_constant_obj(
        static_cast<SentinelPtr>(address + kHeapObjectTag));
    InitializeObject<Sentinel>(address);
  }
  {
    uword address =
        heap->Allocate(thread, Sentinel::InstanceSize(), Heap::kOld);
    Roots::set_non_constant_obj(
        static_cast<SentinelPtr>(address + kHeapObjectTag));
    InitializeObject<Sentinel>(address);
  }
  {
    uword address =
        heap->Allocate(thread, Sentinel::InstanceSize(), Heap::kOld);
    Roots::set_optimized_out_obj(
        static_cast<SentinelPtr>(address + kHeapObjectTag));
    InitializeObject<Sentinel>(address);
  }

  // Check that the objects have been allocated at appropriate addresses.
  uword null_ = static_cast<uword>(Roots::null_obj());
  uword true_ = static_cast<uword>(Roots::true_obj());
  uword false_ = static_cast<uword>(Roots::false_obj());
  ASSERT(true_ == null_ + kTrueOffsetFromNull);
  ASSERT(false_ == null_ + kFalseOffsetFromNull);
  ASSERT((true_ & kBoolValueMask) == 0);
  ASSERT((false_ & kBoolValueMask) != 0);
  ASSERT(false_ == (true_ | kBoolValueMask));
  ASSERT((null_ & kBoolVsNullMask) == 0);
  ASSERT((true_ & kBoolVsNullMask) != 0);
  ASSERT((false_ & kBoolVsNullMask) != 0);
  // And prevent the compactor from disrupting these patterns.
  Page::Of(Roots::null_obj())->set_never_evacuate(true);

  Roots::null_object().initRO(Object::null());
  Roots::null_class().initRO(Class::null());
  Roots::null_array().initRO(Array::null());
  Roots::null_string().initRO(String::null());
  Roots::null_instance().initRO(Instance::null());
  Roots::null_function().initRO(Function::null());
  Roots::null_function_type().initRO(FunctionType::null());
  Roots::null_record_type().initRO(RecordType::null());
  Roots::null_type_arguments().initRO(TypeArguments::null());
  Roots::null_closure().initRO(Closure::null());
  Roots::empty_type_arguments().initRO(TypeArguments::null());
  Roots::null_abstract_type().initRO(AbstractType::null());
  Roots::null_compressed_stackmaps().initRO(CompressedStackMaps::null());
  Roots::bool_true().initRO(Roots::true_obj());
  Roots::bool_false().initRO(Roots::false_obj());
  Roots::sentinel().initRO(Roots::sentinel_obj());
  Roots::unknown_constant().initRO(Roots::unknown_constant_obj());
  Roots::non_constant().initRO(Roots::non_constant_obj());
  Roots::optimized_out().initRO(Roots::optimized_out_obj());
}

void Object::InitVtables() {
  {
    Object fake_handle;
    builtin_vtables_[kObjectCid] = fake_handle.vtable();
  }

#define INIT_VTABLE(clazz)                                                     \
  {                                                                            \
    clazz fake_handle;                                                         \
    builtin_vtables_[k##clazz##Cid] = fake_handle.vtable();                    \
  }
  CLASS_LIST_NO_OBJECT_NOR_STRING_NOR_ARRAY_NOR_MAP(INIT_VTABLE)
  INIT_VTABLE(GrowableObjectArray)
#undef INIT_VTABLE

#define INIT_VTABLE(clazz)                                                     \
  {                                                                            \
    Map fake_handle;                                                           \
    builtin_vtables_[k##clazz##Cid] = fake_handle.vtable();                    \
  }
  CLASS_LIST_MAPS(INIT_VTABLE)
#undef INIT_VTABLE

#define INIT_VTABLE(clazz)                                                     \
  {                                                                            \
    Set fake_handle;                                                           \
    builtin_vtables_[k##clazz##Cid] = fake_handle.vtable();                    \
  }
  CLASS_LIST_SETS(INIT_VTABLE)
#undef INIT_VTABLE

#define INIT_VTABLE(clazz)                                                     \
  {                                                                            \
    Array fake_handle;                                                         \
    builtin_vtables_[k##clazz##Cid] = fake_handle.vtable();                    \
  }
  CLASS_LIST_FIXED_LENGTH_ARRAYS(INIT_VTABLE)
#undef INIT_VTABLE

#define INIT_VTABLE(clazz)                                                     \
  {                                                                            \
    String fake_handle;                                                        \
    builtin_vtables_[k##clazz##Cid] = fake_handle.vtable();                    \
  }
  CLASS_LIST_STRINGS(INIT_VTABLE)
#undef INIT_VTABLE

  {
    Instance fake_handle;
    builtin_vtables_[kFfiNativeTypeCid] = fake_handle.vtable();
  }

#define INIT_VTABLE(clazz)                                                     \
  {                                                                            \
    Instance fake_handle;                                                      \
    builtin_vtables_[kFfi##clazz##Cid] = fake_handle.vtable();                 \
  }
  CLASS_LIST_FFI_TYPE_MARKER(INIT_VTABLE)
#undef INIT_VTABLE

  {
    Instance fake_handle;
    builtin_vtables_[kFfiNativeFunctionCid] = fake_handle.vtable();
  }

  {
    Pointer fake_handle;
    builtin_vtables_[kPointerCid] = fake_handle.vtable();
  }

  {
    DynamicLibrary fake_handle;
    builtin_vtables_[kDynamicLibraryCid] = fake_handle.vtable();
  }

#define INIT_VTABLE(clazz)                                                     \
  {                                                                            \
    TypedData fake_internal_handle;                                            \
    builtin_vtables_[kTypedData##clazz##Cid] = fake_internal_handle.vtable();  \
    TypedDataView fake_view_handle;                                            \
    builtin_vtables_[kTypedData##clazz##ViewCid] = fake_view_handle.vtable();  \
    builtin_vtables_[kUnmodifiableTypedData##clazz##ViewCid] =                 \
        fake_view_handle.vtable();                                             \
    ExternalTypedData fake_external_handle;                                    \
    builtin_vtables_[kExternalTypedData##clazz##Cid] =                         \
        fake_external_handle.vtable();                                         \
  }
  CLASS_LIST_TYPED_DATA(INIT_VTABLE)
#undef INIT_VTABLE

  {
    TypedDataView fake_handle;
    builtin_vtables_[kByteDataViewCid] = fake_handle.vtable();
    builtin_vtables_[kUnmodifiableByteDataViewCid] = fake_handle.vtable();
  }

  {
    LinkedHashBase fake_handle;
    builtin_vtables_[kLinkedHashBaseCid] = fake_handle.vtable();
  }

  {
    Instance fake_handle;
    builtin_vtables_[kByteBufferCid] = fake_handle.vtable();
    builtin_vtables_[kNullCid] = fake_handle.vtable();
    builtin_vtables_[kDynamicCid] = fake_handle.vtable();
    builtin_vtables_[kVoidCid] = fake_handle.vtable();
    builtin_vtables_[kNeverCid] = fake_handle.vtable();
  }
}

void Object::Init(IsolateGroup* isolate_group) {
  Heap* heap = isolate_group->heap();
  Thread* thread = Thread::Current();
  ASSERT(thread != nullptr);
  // Ensure lock checks in setters are happy.
  SafepointWriteRwLocker ml(thread, isolate_group->program_lock());

  // Initialize the empty array and empty instantiations cache array handles to
  // null_ in order to be able to check if the empty and zero arrays were
  // allocated (RAW_NULL is not available).
  Roots::empty_array().initRO(Array::null());
  Roots::empty_instantiations_cache_array().initRO(Array::null());
  Roots::empty_subtype_test_cache_array().initRO(Array::null());
  Roots::mutable_empty_array().initRO(Array::null());

  Class& cls = Class::Handle();

  // Allocate and initialize the class class.
  {
    intptr_t size = Class::InstanceSize();
    uword address = heap->Allocate(thread, size, Heap::kOld);
    InitializeObject<Class>(address);

    Class fake;
    // Initialization from Class::New<Class>.
    // Directly set ptr_ to break a circular dependency: SetRaw will attempt
    // to lookup class class in the class table where it is not registered yet.
    cls.ptr_ = static_cast<ClassPtr>(address + kHeapObjectTag);
    ASSERT(builtin_vtables_[kClassCid] == fake.vtable());
    cls.set_instance_size(
        Class::InstanceSize(),
        compiler::target::RoundedAllocationSize(RTN::Class::InstanceSize()));
    const intptr_t host_next_field_offset = Class::NextFieldOffset();
    const intptr_t target_next_field_offset = RTN::Class::NextFieldOffset();
    cls.set_next_field_offset(host_next_field_offset, target_next_field_offset);
    cls.set_id(Class::kClassId);
    cls.set_state_bits(0);
    cls.set_is_allocate_finalized();
    cls.set_is_declaration_loaded();
    cls.set_is_type_finalized();
    cls.set_type_arguments_field_offset_in_words(Class::kNoTypeArguments,
                                                 RTN::Class::kNoTypeArguments);
    cls.set_num_type_arguments_unsafe(0);
    cls.set_num_native_fields(0);
    cls.InitEmptyFields();
    isolate_group->class_table()->Register(cls);
  }

  // Allocate and initialize the null class.
  cls = Class::New<Instance, RTN::Instance>(kNullCid, isolate_group);
  cls.set_num_type_arguments_unsafe(0);
  isolate_group->object_store()->set_null_class(cls);

  // Allocate and initialize Never class.
  cls = Class::New<Instance, RTN::Instance>(kNeverCid, isolate_group);
  cls.set_num_type_arguments_unsafe(0);
  cls.set_is_allocate_finalized();
  cls.set_is_declaration_loaded();
  cls.set_is_type_finalized();
  isolate_group->object_store()->set_never_class(cls);

  // Allocate and initialize the free list element class.
  cls = Class::New<FreeListElement::FakeInstance,
                   RTN::FreeListElement::FakeInstance>(kFreeListElement,
                                                       isolate_group);
  cls.set_num_type_arguments_unsafe(0);
  cls.set_is_allocate_finalized();
  cls.set_is_declaration_loaded();
  cls.set_is_type_finalized();

  // Allocate and initialize the forwarding corpse class.
  cls = Class::New<ForwardingCorpse::FakeInstance,
                   RTN::ForwardingCorpse::FakeInstance>(kForwardingCorpse,
                                                        isolate_group);
  cls.set_num_type_arguments_unsafe(0);
  cls.set_is_allocate_finalized();
  cls.set_is_declaration_loaded();
  cls.set_is_type_finalized();

  // Allocate the remaining VM internal classes.
  cls = Class::New<Sentinel, RTN::Sentinel>(isolate_group);
  cls = Class::New<TypeParameters, RTN::TypeParameters>(isolate_group);
  cls = Class::New<TypeArguments, RTN::TypeArguments>(isolate_group);
  cls = Class::New<PatchClass, RTN::PatchClass>(isolate_group);
  cls = Class::New<Function, RTN::Function>(isolate_group);
  cls = Class::New<ClosureData, RTN::ClosureData>(isolate_group);
  cls = Class::New<FfiTrampolineData, RTN::FfiTrampolineData>(isolate_group);
  cls = Class::New<Field, RTN::Field>(isolate_group);
  cls = Class::New<Script, RTN::Script>(isolate_group);
  cls = Class::New<Library, RTN::Library>(isolate_group);
  cls = Class::New<Namespace, RTN::Namespace>(isolate_group);
  cls = Class::New<KernelProgramInfo, RTN::KernelProgramInfo>(isolate_group);
  cls = Class::New<Code, RTN::Code>(isolate_group);
  cls = Class::New<Instructions, RTN::Instructions>(isolate_group);
  cls =
      Class::New<InstructionsSection, RTN::InstructionsSection>(isolate_group);
  cls = Class::New<InstructionsTable, RTN::InstructionsTable>(isolate_group);
  cls = Class::New<ObjectPool, RTN::ObjectPool>(isolate_group);
  cls = Class::New<PcDescriptors, RTN::PcDescriptors>(isolate_group);
  cls = Class::New<CodeSourceMap, RTN::CodeSourceMap>(isolate_group);
  cls =
      Class::New<CompressedStackMaps, RTN::CompressedStackMaps>(isolate_group);
  cls =
      Class::New<LocalVarDescriptors, RTN::LocalVarDescriptors>(isolate_group);
  cls = Class::New<ExceptionHandlers, RTN::ExceptionHandlers>(isolate_group);
  cls = Class::New<Context, RTN::Context>(isolate_group);
  cls = Class::New<ContextScope, RTN::ContextScope>(isolate_group);
  cls = Class::New<Bytecode, RTN::Bytecode>(isolate_group);
  cls = Class::New<SingleTargetCache, RTN::SingleTargetCache>(isolate_group);
  cls = Class::New<UnlinkedCall, RTN::UnlinkedCall>(isolate_group);
  cls = Class::New<MonomorphicSmiableCall, RTN::MonomorphicSmiableCall>(
      isolate_group);
  cls = Class::New<ICData, RTN::ICData>(isolate_group);
  cls = Class::New<MegamorphicCache, RTN::MegamorphicCache>(isolate_group);
  cls = Class::New<SubtypeTestCache, RTN::SubtypeTestCache>(isolate_group);
  cls = Class::New<LoadingUnit, RTN::LoadingUnit>(isolate_group);
  cls = Class::New<ApiError, RTN::ApiError>(isolate_group);
  cls = Class::New<LanguageError, RTN::LanguageError>(isolate_group);
  cls = Class::New<UnhandledException, RTN::UnhandledException>(isolate_group);
  cls = Class::New<UnwindError, RTN::UnwindError>(isolate_group);
  cls = Class::New<WeakSerializationReference, RTN::WeakSerializationReference>(
      isolate_group);
  cls = Class::New<WeakArray, RTN::WeakArray>(isolate_group);

  ASSERT(isolate_group->class_table()->At(kClassCid) != Roots::null_obj());

  // Pre-allocate classes in the vm isolate so that we can for example create a
  // symbol table and populate it with some frequently used strings as symbols.
  cls = Class::New<Array, RTN::Array>(isolate_group);
  isolate_group->object_store()->set_array_class(cls);
  cls.set_type_arguments_field_offset(Array::type_arguments_offset(),
                                      RTN::Array::type_arguments_offset());
  cls.set_num_type_arguments_unsafe(1);
  cls = Class::New<Array, RTN::Array>(kImmutableArrayCid, isolate_group);
  isolate_group->object_store()->set_immutable_array_class(cls);
  cls.set_type_arguments_field_offset(Array::type_arguments_offset(),
                                      RTN::Array::type_arguments_offset());
  cls.set_num_type_arguments_unsafe(1);
  // In order to be able to canonicalize arguments descriptors early.
  cls.set_is_prefinalized();
  cls =
      Class::New<GrowableObjectArray, RTN::GrowableObjectArray>(isolate_group);
  isolate_group->object_store()->set_growable_object_array_class(cls);
  cls.set_type_arguments_field_offset(
      GrowableObjectArray::type_arguments_offset(),
      RTN::GrowableObjectArray::type_arguments_offset());
  cls.set_num_type_arguments_unsafe(1);
  cls = Class::NewStringClass(kOneByteStringCid, isolate_group);
  isolate_group->object_store()->set_one_byte_string_class(cls);
  cls = Class::NewStringClass(kTwoByteStringCid, isolate_group);
  isolate_group->object_store()->set_two_byte_string_class(cls);
  cls = Class::New<Mint, RTN::Mint>(isolate_group);
  isolate_group->object_store()->set_mint_class(cls);
  cls = Class::New<Double, RTN::Double>(isolate_group);
  isolate_group->object_store()->set_double_class(cls);
  cls = Class::New<Float32x4, RTN::Float32x4>(isolate_group);
  isolate_group->object_store()->set_float32x4_class(cls);
  cls = Class::New<Float64x2, RTN::Float64x2>(isolate_group);
  isolate_group->object_store()->set_float64x2_class(cls);
  cls = Class::New<Int32x4, RTN::Int32x4>(isolate_group);
  isolate_group->object_store()->set_int32x4_class(cls);

  // Ensure that class kExternalTypedDataUint8ArrayCid is registered as we
  // need it when reading in the token stream of bootstrap classes in the VM
  // isolate.
  Class::NewExternalTypedDataClass(kExternalTypedDataUint8ArrayCid,
                                   isolate_group);

  // Needed for object pools of VM isolate stubs.
  Class::NewTypedDataClass(kTypedDataInt8ArrayCid, isolate_group);

  // Allocate and initialize the empty_array instance.
  {
    uword address = heap->Allocate(thread, Array::InstanceSize(0), Heap::kOld);
    InitializeObjectVariant<Array>(address, kImmutableArrayCid, 0);
    Roots::empty_array().initRO(
        static_cast<ArrayPtr>(address + kHeapObjectTag));
    Roots::empty_array().untag()->set_length(Smi::New(0));
    Roots::empty_array().SetCanonical();
    Roots::empty_array().SetDeeplyImmutable();
  }
  {
    uword address = heap->Allocate(thread, Array::InstanceSize(0), Heap::kOld);
    InitializeObjectVariant<Array>(address, kArrayCid, 0);
    Roots::mutable_empty_array().initRO(
        static_cast<ArrayPtr>(address + kHeapObjectTag));
    Roots::mutable_empty_array().untag()->set_length(Smi::New(0));
    Roots::mutable_empty_array().SetCanonical();
    Roots::mutable_empty_array().SetDeeplyImmutable();
  }

  Smi& smi = Smi::Handle();
  // Allocate and initialize the empty instantiations cache array instance,
  // which contains metadata as the first element and a sentinel value
  // at the start of the first entry.
  {
    const intptr_t array_size =
        static_cast<intptr_t>(TypeArguments::Cache::kHeaderSize) +
        static_cast<intptr_t>(TypeArguments::Cache::kEntrySize);
    uword address =
        heap->Allocate(thread, Array::InstanceSize(array_size), Heap::kOld);
    InitializeObjectVariant<Array>(address, kImmutableArrayCid, array_size);
    Roots::empty_instantiations_cache_array().initRO(
        static_cast<ArrayPtr>(address + kHeapObjectTag));
    Roots::empty_instantiations_cache_array().untag()->set_length(
        Smi::New(array_size));
    // The empty cache has no occupied entries and is not a hash-based cache.
    smi = Smi::New(0);
    Roots::empty_instantiations_cache_array().SetAt(
        TypeArguments::Cache::kMetadataIndex, smi);
    // Make the first (and only) entry unoccupied by setting its first element
    // to the sentinel value.
    smi = TypeArguments::Cache::Sentinel();
    InstantiationsCacheTable table(Roots::empty_instantiations_cache_array());
    table.At(0).Set<TypeArguments::Cache::kSentinelIndex>(smi);
    // The other contents of the array are immaterial.
  }

  // Allocate and initialize the empty subtype test cache array instance,
  // which contains a single unoccupied entry.
  {
    const intptr_t array_size = SubtypeTestCache::kTestEntryLength;
    uword address =
        heap->Allocate(thread, Array::InstanceSize(array_size), Heap::kOld);
    InitializeObjectVariant<Array>(address, kImmutableArrayCid, array_size);
    Roots::empty_subtype_test_cache_array().initRO(
        static_cast<ArrayPtr>(address + kHeapObjectTag));
    Roots::empty_subtype_test_cache_array().untag()->set_length(
        Smi::New(array_size));
    // Make the first (and only) entry unoccupied by setting its first element
    // to the null value.
    Roots::empty_subtype_test_cache_array().SetAt(
        SubtypeTestCache::kInstanceCidOrSignature, Object::null_object());
    smi = TypeArguments::Cache::Sentinel();
    SubtypeTestCacheTable table(Roots::empty_subtype_test_cache_array());
    table.At(0).Set<SubtypeTestCache::kInstanceCidOrSignature>(
        Object::null_object());
    // The other contents of the array are immaterial.
  }

  // Allocate and initialize the canonical empty context scope object.
  {
    uword address =
        heap->Allocate(thread, ContextScope::InstanceSize(0), Heap::kOld);
    InitializeObject<ContextScope>(address, 0);
    Roots::empty_context_scope().initRO(
        static_cast<ContextScopePtr>(address + kHeapObjectTag));
    Roots::empty_context_scope().StoreNonPointer(
        &Roots::empty_context_scope().untag()->num_variables_, 0);
    Roots::empty_context_scope().StoreNonPointer(
        &Roots::empty_context_scope().untag()->is_implicit_, true);
  }

  // Allocate and initialize the canonical empty object pool object.
  {
    uword address =
        heap->Allocate(thread, ObjectPool::InstanceSize(0), Heap::kOld);
    InitializeObject<ObjectPool>(address, 0);
    Roots::empty_object_pool().initRO(
        static_cast<ObjectPoolPtr>(address + kHeapObjectTag));
    Roots::empty_object_pool().StoreNonPointer(
        &Roots::empty_object_pool().untag()->length_, 0);
  }

  // Allocate and initialize the empty_compressed_stackmaps instance.
  {
    const intptr_t instance_size = CompressedStackMaps::InstanceSize(0);
    uword address = heap->Allocate(thread, instance_size, Heap::kOld);
    InitializeObject<CompressedStackMaps>(address, 0);
    Roots::empty_compressed_stackmaps().initRO(
        static_cast<CompressedStackMapsPtr>(address + kHeapObjectTag));
    Roots::empty_compressed_stackmaps().untag()->payload()->set_flags_and_size(
        0);
  }

  // Allocate and initialize the empty_descriptors instance.
  {
    uword address =
        heap->Allocate(thread, PcDescriptors::InstanceSize(0), Heap::kOld);
    InitializeObject<PcDescriptors>(address, 0);
    Roots::empty_descriptors().initRO(
        static_cast<PcDescriptorsPtr>(address + kHeapObjectTag));
    Roots::empty_descriptors().StoreNonPointer(
        &Roots::empty_descriptors().untag()->length_, 0);
  }

  // Allocate and initialize the canonical empty variable descriptor object.
  {
    uword address = heap->Allocate(thread, LocalVarDescriptors::InstanceSize(0),
                                   Heap::kOld);
    InitializeObject<LocalVarDescriptors>(address, 0);
    Roots::empty_var_descriptors().initRO(
        static_cast<LocalVarDescriptorsPtr>(address + kHeapObjectTag));
    Roots::empty_var_descriptors().StoreNonPointer(
        &Roots::empty_var_descriptors().untag()->num_entries_, 0);
  }

  // Allocate and initialize the canonical empty exception handler info object.
  // The vast majority of all functions do not contain an exception handler
  // and can share this canonical descriptor.
  {
    uword address =
        heap->Allocate(thread, ExceptionHandlers::InstanceSize(0), Heap::kOld);
    InitializeObject<ExceptionHandlers>(address, 0);
    Roots::empty_exception_handlers().initRO(
        static_cast<ExceptionHandlersPtr>(address + kHeapObjectTag));
    Roots::empty_exception_handlers().StoreNonPointer(
        &Roots::empty_exception_handlers().untag()->packed_fields_, 0);
  }

  // Empty exception handlers for async/async* functions.
  {
    uword address =
        heap->Allocate(thread, ExceptionHandlers::InstanceSize(0), Heap::kOld);
    InitializeObject<ExceptionHandlers>(address, 0);
    Roots::empty_async_exception_handlers().initRO(
        static_cast<ExceptionHandlersPtr>(address + kHeapObjectTag));
    Roots::empty_async_exception_handlers().StoreNonPointer(
        &Roots::empty_async_exception_handlers().untag()->packed_fields_,
        UntaggedExceptionHandlers::AsyncHandlerBit::update(true, 0));
  }

  // Allocate and initialize the canonical empty type arguments object.
  {
    uword address =
        heap->Allocate(thread, TypeArguments::InstanceSize(0), Heap::kOld);
    InitializeObject<TypeArguments>(address, 0);
    Roots::empty_type_arguments().initRO(
        static_cast<TypeArgumentsPtr>(address + kHeapObjectTag));
    Roots::empty_type_arguments().untag()->set_length(Smi::New(0));
    Roots::empty_type_arguments().untag()->set_hash(Smi::New(0));
    Roots::empty_type_arguments().untag()->set_nullability(Smi::New(0));
    Roots::empty_type_arguments().ComputeHash();
    Roots::empty_type_arguments().SetCanonical();
  }

  cls = Class::New<Instance, RTN::Instance>(kDynamicCid, isolate_group);
  cls.set_is_abstract();
  cls.set_num_type_arguments_unsafe(0);
  cls.set_is_allocate_finalized();
  cls.set_is_declaration_loaded();
  cls.set_is_type_finalized();

  cls = Class::New<Instance, RTN::Instance>(kVoidCid, isolate_group);
  cls.set_num_type_arguments_unsafe(0);
  cls.set_is_allocate_finalized();
  cls.set_is_declaration_loaded();
  cls.set_is_type_finalized();

  cls = Class::New<Type, RTN::Type>(isolate_group);
  cls.set_is_allocate_finalized();
  cls.set_is_declaration_loaded();
  cls.set_is_type_finalized();

  cls = Class::New<FunctionType, RTN::FunctionType>(isolate_group);
  cls.set_is_allocate_finalized();
  cls.set_is_declaration_loaded();
  cls.set_is_type_finalized();

  cls = Class::New<RecordType, RTN::RecordType>(isolate_group);
  cls.set_is_allocate_finalized();
  cls.set_is_declaration_loaded();
  cls.set_is_type_finalized();

  cls = isolate_group->class_table()->At(kDynamicCid);
  Roots::dynamic_type().initRO(
      Type::New(cls, Object::null_type_arguments(), Nullability::kNullable));
  Roots::dynamic_type().SetIsFinalized();
  Roots::dynamic_type().ComputeHash();
  Roots::dynamic_type().SetCanonical();

  cls = isolate_group->class_table()->At(kVoidCid);
  Roots::void_type().initRO(
      Type::New(cls, Object::null_type_arguments(), Nullability::kNullable));
  Roots::void_type().SetIsFinalized();
  Roots::void_type().ComputeHash();
  Roots::void_type().SetCanonical();

  // Since TypeArguments objects are passed as function arguments, make them
  // behave as Dart instances, although they are just VM objects.
  // Note that we cannot set the super type to ObjectType, which does not live
  // in the vm isolate. See special handling in Class::SuperClass().
  cls = isolate_group->class_table()->At(kTypeArgumentsCid);
  cls.set_interfaces(Object::empty_array());
  cls.SetFields(Object::empty_array());
  cls.SetFunctions(Object::empty_array());

  cls = Class::New<Bool, RTN::Bool>(isolate_group);
  isolate_group->object_store()->set_bool_class(cls);

  Roots::smi_illegal_cid().initRO(Smi::New(kIllegalCid));
  Roots::smi_zero().initRO(Smi::New(0));

  String& error_str = String::Handle();
  error_str = String::New(
      "Callbacks into the Dart VM are currently prohibited. Either there are "
      "outstanding pointers from Dart_TypedDataAcquireData that have not been "
      "released with Dart_TypedDataReleaseData, or a finalizer is running.",
      Heap::kOld);
  Roots::no_callbacks_error().initRO(ApiError::New(error_str, Heap::kOld));
  error_str = String::New("isolate is exiting", Heap::kOld);
  Roots::unwind_error().initRO(UnwindError::New(error_str, Heap::kOld));
  error_str = String::New(
      "No api calls are allowed while unwind is in progress", Heap::kOld);
  Roots::unwind_in_progress_error().initRO(
      UnwindError::New(error_str, Heap::kOld));
  error_str = String::New("SnapshotWriter Error", Heap::kOld);
  Roots::snapshot_writer_error().initRO(
      LanguageError::New(error_str, Report::kError, Heap::kOld));
  error_str = String::New("Branch offset overflow", Heap::kOld);
  Roots::branch_offset_error().initRO(
      LanguageError::New(error_str, Report::kBailout, Heap::kOld));
  error_str = String::New("Background Compilation Failed", Heap::kOld);
  Roots::background_compilation_error().initRO(
      LanguageError::New(error_str, Report::kBailout, Heap::kOld));
  error_str = String::New("No debuggable code where breakpoint was requested",
                          Heap::kOld);
  Roots::no_debuggable_code_error().initRO(
      LanguageError::New(error_str, Report::kError, Heap::kOld));
  error_str = String::New("Out of memory", Heap::kOld);
  Roots::out_of_memory_error().initRO(
      LanguageError::New(error_str, Report::kError, Heap::kOld));
  Roots::unhandled_oom_exception().initRO(
      UnhandledException::New(error_str, StackTrace::Handle(), Heap::kOld));

  // Allocate the parameter types and names for synthetic getters.
  Roots::synthetic_getter_parameter_types().initRO(Array::New(1, Heap::kOld));
  Roots::synthetic_getter_parameter_types().SetAt(0, Object::dynamic_type());
  Roots::synthetic_getter_parameter_names().initRO(Array::New(1, Heap::kOld));
  // Fill in synthetic_getter_parameter_names_ later, after symbols are
  // initialized (in Object::FinalizeVMIsolate).
  // synthetic_getter_parameter_names_ object needs to be created earlier as
  // VM isolate snapshot reader references it before Object::FinalizeVMIsolate.

#if defined(DART_DYNAMIC_MODULES)
  Roots::implicit_getter_bytecode().initRO(
      CreateVMInternalBytecode(KernelBytecode::kVMInternal_ImplicitGetter));
  Roots::implicit_setter_bytecode().initRO(
      CreateVMInternalBytecode(KernelBytecode::kVMInternal_ImplicitSetter));
  Roots::implicit_static_getter_bytecode().initRO(CreateVMInternalBytecode(
      KernelBytecode::kVMInternal_ImplicitStaticGetter));
  Roots::implicit_shared_static_getter_bytecode().initRO(
      CreateVMInternalBytecode(
          KernelBytecode::kVMInternal_ImplicitSharedStaticGetter));
  Roots::implicit_static_setter_bytecode().initRO(CreateVMInternalBytecode(
      KernelBytecode::kVMInternal_ImplicitStaticSetter));
  Roots::implicit_shared_static_setter_bytecode().initRO(
      CreateVMInternalBytecode(
          KernelBytecode::kVMInternal_ImplicitSharedStaticSetter));
  Roots::method_extractor_with_ita_bytecode().initRO(CreateVMInternalBytecode(
      KernelBytecode::kVMInternal_MethodExtractorWithITA));
  Roots::method_extractor_without_ita_bytecode().initRO(
      CreateVMInternalBytecode(
          KernelBytecode::kVMInternal_MethodExtractorWithoutITA));
  Roots::invoke_closure_bytecode().initRO(
      CreateVMInternalBytecode(KernelBytecode::kVMInternal_InvokeClosure));
  Roots::invoke_field_bytecode().initRO(
      CreateVMInternalBytecode(KernelBytecode::kVMInternal_InvokeField));
  Roots::nsm_dispatcher_bytecode().initRO(CreateVMInternalBytecode(
      KernelBytecode::kVMInternal_NoSuchMethodDispatcher));
  Roots::dynamic_invocation_forwarder_bytecode().initRO(
      CreateVMInternalBytecode(
          KernelBytecode::kVMInternal_ForwardDynamicInvocation));
  Roots::implicit_static_closure_bytecode().initRO(CreateVMInternalBytecode(
      KernelBytecode::kVMInternal_ImplicitStaticClosure));
  Roots::implicit_instance_closure_bytecode().initRO(CreateVMInternalBytecode(
      KernelBytecode::kVMInternal_ImplicitInstanceClosure));
  Roots::implicit_constructor_closure_bytecode().initRO(
      CreateVMInternalBytecode(
          KernelBytecode::kVMInternal_ImplicitConstructorClosure));
#else
  Roots::implicit_getter_bytecode().initRO(Bytecode::null());
  Roots::implicit_setter_bytecode().initRO(Bytecode::null());
  Roots::implicit_static_getter_bytecode().initRO(Bytecode::null());
  Roots::implicit_shared_static_getter_bytecode().initRO(Bytecode::null());
  Roots::implicit_static_setter_bytecode().initRO(Bytecode::null());
  Roots::implicit_shared_static_setter_bytecode().initRO(Bytecode::null());
  Roots::method_extractor_with_ita_bytecode().initRO(Bytecode::null());
  Roots::method_extractor_without_ita_bytecode().initRO(Bytecode::null());
  Roots::invoke_closure_bytecode().initRO(Bytecode::null());
  Roots::invoke_field_bytecode().initRO(Bytecode::null());
  Roots::nsm_dispatcher_bytecode().initRO(Bytecode::null());
  Roots::dynamic_invocation_forwarder_bytecode().initRO(Bytecode::null());
  Roots::implicit_static_closure_bytecode().initRO(Bytecode::null());
  Roots::implicit_instance_closure_bytecode().initRO(Bytecode::null());
  Roots::implicit_constructor_closure_bytecode().initRO(Bytecode::null());
#endif  // defined(DART_DYNAMIC_MODULES)

  Roots::uninitialized_index().initRO(
      TypedData::New(kTypedDataUint32ArrayCid,
                     LinkedHashBase::kUninitializedIndexSize, Heap::kOld));
  Roots::uninitialized_data().initRO(Array::New(0, Heap::kOld));
  Roots::empty_coverage_array().initRO(
      TypedData::New(kTypedDataUint32ArrayCid, 0, Heap::kOld));

  // Some thread fields need to be reinitialized as null constants have not been
  // initialized until now.
  thread->ClearStickyError();

  ASSERT(!Roots::null_object().IsSmi());
  ASSERT(!Roots::null_class().IsSmi());
  ASSERT(Roots::null_class().IsClass());
  ASSERT(!Roots::null_array().IsSmi());
  ASSERT(Roots::null_array().IsArray());
  ASSERT(!Roots::null_string().IsSmi());
  ASSERT(Roots::null_string().IsString());
  ASSERT(!Roots::null_instance().IsSmi());
  ASSERT(Roots::null_instance().IsInstance());
  ASSERT(!Roots::null_function().IsSmi());
  ASSERT(Roots::null_function().IsFunction());
  ASSERT(!Roots::null_function_type().IsSmi());
  ASSERT(Roots::null_function_type().IsFunctionType());
  ASSERT(!Roots::null_record_type().IsSmi());
  ASSERT(Roots::null_record_type().IsRecordType());
  ASSERT(!Roots::null_type_arguments().IsSmi());
  ASSERT(Roots::null_type_arguments().IsTypeArguments());
  ASSERT(!Roots::null_compressed_stackmaps().IsSmi());
  ASSERT(Roots::null_compressed_stackmaps().IsCompressedStackMaps());
  ASSERT(!Roots::empty_array().IsSmi());
  ASSERT(Roots::empty_array().IsArray());
  ASSERT(!Roots::empty_instantiations_cache_array().IsSmi());
  ASSERT(Roots::empty_instantiations_cache_array().IsArray());
  ASSERT(!Roots::empty_subtype_test_cache_array().IsSmi());
  ASSERT(Roots::empty_subtype_test_cache_array().IsArray());
  ASSERT(!Roots::empty_type_arguments().IsSmi());
  ASSERT(Roots::empty_type_arguments().IsTypeArguments());
  ASSERT(!Roots::empty_context_scope().IsSmi());
  ASSERT(Roots::empty_context_scope().IsContextScope());
  ASSERT(!Roots::empty_compressed_stackmaps().IsSmi());
  ASSERT(Roots::empty_compressed_stackmaps().IsCompressedStackMaps());
  ASSERT(!Roots::empty_descriptors().IsSmi());
  ASSERT(Roots::empty_descriptors().IsPcDescriptors());
  ASSERT(!Roots::empty_var_descriptors().IsSmi());
  ASSERT(Roots::empty_var_descriptors().IsLocalVarDescriptors());
  ASSERT(!Roots::empty_exception_handlers().IsSmi());
  ASSERT(Roots::empty_exception_handlers().IsExceptionHandlers());
  ASSERT(!Roots::empty_async_exception_handlers().IsSmi());
  ASSERT(Roots::empty_async_exception_handlers().IsExceptionHandlers());
  ASSERT(!Roots::sentinel().IsSmi());
  ASSERT(Roots::sentinel().IsSentinel());
  ASSERT(!Roots::unknown_constant().IsSmi());
  ASSERT(Roots::unknown_constant().IsSentinel());
  ASSERT(!Roots::non_constant().IsSmi());
  ASSERT(Roots::non_constant().IsSentinel());
  ASSERT(!Roots::optimized_out().IsSmi());
  ASSERT(Roots::optimized_out().IsSentinel());
  ASSERT(!Roots::bool_true().IsSmi());
  ASSERT(Roots::bool_true().IsBool());
  ASSERT(!Roots::bool_false().IsSmi());
  ASSERT(Roots::bool_false().IsBool());
  ASSERT(Roots::smi_illegal_cid().IsSmi());
  ASSERT(Roots::smi_zero().IsSmi());
  ASSERT(!Roots::no_callbacks_error().IsSmi());
  ASSERT(Roots::no_callbacks_error().IsApiError());
  ASSERT(!Roots::unwind_error().IsSmi());
  ASSERT(Roots::unwind_error().IsUnwindError());
  ASSERT(!Roots::unwind_in_progress_error().IsSmi());
  ASSERT(Roots::unwind_in_progress_error().IsUnwindError());
  ASSERT(!Roots::snapshot_writer_error().IsSmi());
  ASSERT(Roots::snapshot_writer_error().IsLanguageError());
  ASSERT(!Roots::branch_offset_error().IsSmi());
  ASSERT(Roots::branch_offset_error().IsLanguageError());
  ASSERT(!Roots::background_compilation_error().IsSmi());
  ASSERT(Roots::background_compilation_error().IsLanguageError());
  ASSERT(!Roots::out_of_memory_error().IsSmi());
  ASSERT(Roots::out_of_memory_error().IsLanguageError());
  ASSERT(!Roots::unhandled_oom_exception().IsSmi());
  ASSERT(Roots::unhandled_oom_exception().IsUnhandledException());
  ASSERT(!Roots::synthetic_getter_parameter_types().IsSmi());
  ASSERT(Roots::synthetic_getter_parameter_types().IsArray());
  ASSERT(!Roots::synthetic_getter_parameter_names().IsSmi());
  ASSERT(Roots::synthetic_getter_parameter_names().IsArray());
  ASSERT(!Roots::implicit_getter_bytecode().IsSmi());
  ASSERT(Roots::implicit_getter_bytecode().IsBytecode());
  ASSERT(!Roots::implicit_setter_bytecode().IsSmi());
  ASSERT(Roots::implicit_setter_bytecode().IsBytecode());
  ASSERT(!Roots::implicit_static_getter_bytecode().IsSmi());
  ASSERT(Roots::implicit_static_getter_bytecode().IsBytecode());
  ASSERT(!Roots::implicit_static_setter_bytecode().IsSmi());
  ASSERT(Roots::implicit_static_setter_bytecode().IsBytecode());
  ASSERT(!Roots::method_extractor_with_ita_bytecode().IsSmi());
  ASSERT(Roots::method_extractor_with_ita_bytecode().IsBytecode());
  ASSERT(!Roots::method_extractor_without_ita_bytecode().IsSmi());
  ASSERT(Roots::method_extractor_without_ita_bytecode().IsBytecode());
  ASSERT(!Roots::invoke_closure_bytecode().IsSmi());
  ASSERT(Roots::invoke_closure_bytecode().IsBytecode());
  ASSERT(!Roots::invoke_field_bytecode().IsSmi());
  ASSERT(Roots::invoke_field_bytecode().IsBytecode());
  ASSERT(!Roots::nsm_dispatcher_bytecode().IsSmi());
  ASSERT(Roots::nsm_dispatcher_bytecode().IsBytecode());
  ASSERT(!Roots::dynamic_invocation_forwarder_bytecode().IsSmi());
  ASSERT(Roots::dynamic_invocation_forwarder_bytecode().IsBytecode());
  ASSERT(!Roots::implicit_static_closure_bytecode().IsSmi());
  ASSERT(Roots::implicit_static_closure_bytecode().IsBytecode());
  ASSERT(!Roots::implicit_instance_closure_bytecode().IsSmi());
  ASSERT(Roots::implicit_instance_closure_bytecode().IsBytecode());
  ASSERT(!Roots::implicit_constructor_closure_bytecode().IsSmi());
  ASSERT(Roots::implicit_constructor_closure_bytecode().IsBytecode());
  ASSERT(!Roots::uninitialized_index().IsSmi());
  ASSERT(Roots::uninitialized_index().IsTypedData());
  ASSERT(!Roots::uninitialized_data().IsSmi());
  ASSERT(Roots::uninitialized_data().IsArray());
  ASSERT(!Roots::empty_coverage_array().IsSmi());
  ASSERT(Roots::empty_coverage_array().IsTypedData());
}

void Object::FinishInit(IsolateGroup* isolate_group) {
  // Finish initialization of synthetic_getter_parameter_names_ which was
  // Started in Object::InitOnce()
  Roots::synthetic_getter_parameter_names().SetAt(0, Symbols::This());

  // Set up names for all VM singleton classes.
  Class& cls = Class::Handle();
  ClassTable* table = isolate_group->class_table();

#define SET_CLASS_NAME(class_name, name)                                       \
  cls = table->At(class_name);                                                 \
  cls.set_name(Symbols::name());

  SET_CLASS_NAME(kClassCid, Class);
  SET_CLASS_NAME(kDynamicCid, Dynamic);
  SET_CLASS_NAME(kVoidCid, Void);
  SET_CLASS_NAME(kTypeParametersCid, TypeParameters);
  SET_CLASS_NAME(kTypeArgumentsCid, TypeArguments);
  SET_CLASS_NAME(kPatchClassCid, PatchClass);
  SET_CLASS_NAME(kFunctionCid, Function);
  SET_CLASS_NAME(kClosureDataCid, ClosureData);
  SET_CLASS_NAME(kFfiTrampolineDataCid, FfiTrampolineData);
  SET_CLASS_NAME(kFieldCid, Field);
  SET_CLASS_NAME(kScriptCid, Script);
  SET_CLASS_NAME(kLibraryCid, LibraryClass);
  SET_CLASS_NAME(kNamespaceCid, Namespace);
  SET_CLASS_NAME(kKernelProgramInfoCid, KernelProgramInfo);
  SET_CLASS_NAME(kWeakSerializationReferenceCid, WeakSerializationReference);
  SET_CLASS_NAME(kWeakArrayCid, WeakArray);
  SET_CLASS_NAME(kCodeCid, Code);
  SET_CLASS_NAME(kInstructionsCid, Instructions);
  SET_CLASS_NAME(kInstructionsSectionCid, InstructionsSection);
  SET_CLASS_NAME(kInstructionsTableCid, InstructionsTable);
  SET_CLASS_NAME(kObjectPoolCid, ObjectPool);
  SET_CLASS_NAME(kCodeSourceMapCid, CodeSourceMap);
  SET_CLASS_NAME(kPcDescriptorsCid, PcDescriptors);
  SET_CLASS_NAME(kCompressedStackMapsCid, CompressedStackMaps);
  SET_CLASS_NAME(kLocalVarDescriptorsCid, LocalVarDescriptors);
  SET_CLASS_NAME(kExceptionHandlersCid, ExceptionHandlers);
  SET_CLASS_NAME(kContextCid, Context);
  SET_CLASS_NAME(kContextScopeCid, ContextScope);
  SET_CLASS_NAME(kBytecodeCid, Bytecode);
  SET_CLASS_NAME(kSentinelCid, Sentinel);
  SET_CLASS_NAME(kSingleTargetCacheCid, SingleTargetCache);
  SET_CLASS_NAME(kUnlinkedCallCid, UnlinkedCall);
  SET_CLASS_NAME(kMonomorphicSmiableCallCid, MonomorphicSmiableCall);
  SET_CLASS_NAME(kICDataCid, ICData);
  SET_CLASS_NAME(kMegamorphicCacheCid, MegamorphicCache);
  SET_CLASS_NAME(kSubtypeTestCacheCid, SubtypeTestCache);
  SET_CLASS_NAME(kLoadingUnitCid, LoadingUnit);
  SET_CLASS_NAME(kApiErrorCid, ApiError);
  SET_CLASS_NAME(kLanguageErrorCid, LanguageError);
  SET_CLASS_NAME(kUnhandledExceptionCid, UnhandledException);
  SET_CLASS_NAME(kUnwindErrorCid, UnwindError);

  // Set up names for classes which are also pre-allocated in the vm isolate.
  SET_CLASS_NAME(kArrayCid, _List);
  SET_CLASS_NAME(kOneByteStringCid, OneByteString);
  SET_CLASS_NAME(kNeverCid, Never);

  // Set up names for the pseudo-classes for free list elements and forwarding
  // corpses. Mainly this makes VM debugging easier.
  SET_CLASS_NAME(kFreeListElement, FreeListElement);
  SET_CLASS_NAME(kForwardingCorpse, ForwardingCorpse);

#undef SET_CLASS_NAME

#if defined(DART_PRECOMPILER)
  const auto& function =
      Function::Handle(StubCode::UnknownDartCode().function());
  function.set_name(Symbols::OptimizedOut());
#endif  // defined(DART_PRECOMPILER)

  // The type testing stubs we initialize in AbstractType objects for the
  // canonical type of kDynamicCid/kVoidCid need to be set in this
  // method, which is called after StubCode::InitOnce().
  Code& code = Code::Handle();

  code = TypeTestingStubGenerator::DefaultCodeForType(Roots::dynamic_type());
  Roots::dynamic_type().InitializeTypeTestingStubNonAtomic(code);

  code = TypeTestingStubGenerator::DefaultCodeForType(Roots::void_type());
  Roots::void_type().InitializeTypeTestingStubNonAtomic(code);
}

void Object::FinalizeReadOnlyObject(ObjectPtr object) {
  NoSafepointScope no_safepoint;
  intptr_t cid = object->GetClassIdOfHeapObject();
  if (cid == kOneByteStringCid) {
    OneByteStringPtr str = static_cast<OneByteStringPtr>(object);
    if (String::GetCachedHash(str) == 0) {
      intptr_t hash = String::Hash(str);
      String::SetCachedHashIfNotSet(str, hash);
    }
    intptr_t size = OneByteString::UnroundedSize(str);
    ASSERT(size <= str->untag()->HeapSize());
    memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(str) + size), 0,
           str->untag()->HeapSize() - size);
  } else if (cid == kTwoByteStringCid) {
    TwoByteStringPtr str = static_cast<TwoByteStringPtr>(object);
    if (String::GetCachedHash(str) == 0) {
      intptr_t hash = String::Hash(str);
      String::SetCachedHashIfNotSet(str, hash);
    }
    ASSERT(String::GetCachedHash(str) != 0);
    intptr_t size = TwoByteString::UnroundedSize(str);
    ASSERT(size <= str->untag()->HeapSize());
    memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(str) + size), 0,
           str->untag()->HeapSize() - size);
  } else if (cid == kCodeSourceMapCid) {
    CodeSourceMapPtr map = CodeSourceMap::RawCast(object);
    intptr_t size = CodeSourceMap::UnroundedSize(map);
    ASSERT(size <= map->untag()->HeapSize());
    memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(map) + size), 0,
           map->untag()->HeapSize() - size);
  } else if (cid == kCompressedStackMapsCid) {
    CompressedStackMapsPtr maps = CompressedStackMaps::RawCast(object);
    intptr_t size = CompressedStackMaps::UnroundedSize(maps);
    ASSERT(size <= maps->untag()->HeapSize());
    memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(maps) + size), 0,
           maps->untag()->HeapSize() - size);
  } else if (cid == kPcDescriptorsCid) {
    PcDescriptorsPtr desc = PcDescriptors::RawCast(object);
    intptr_t size = PcDescriptors::UnroundedSize(desc);
    ASSERT(size <= desc->untag()->HeapSize());
    memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(desc) + size), 0,
           desc->untag()->HeapSize() - size);
  }
}

// Make unused space in an object whose type has been transformed safe
// for traversing during GC.
// The unused part of the transformed object is marked as a FreeListElement
// object that is not inserted into to the freelist.
void Object::MakeUnusedSpaceTraversable(const Object& obj,
                                        intptr_t original_size,
                                        intptr_t used_size) {
  ASSERT(Thread::Current()->no_safepoint_scope_depth() > 0);
  ASSERT(!obj.IsNull());
  ASSERT(original_size >= used_size);
  if (original_size > used_size) {
    intptr_t leftover_size = original_size - used_size;
    uword addr = UntaggedObject::ToAddr(obj.ptr()) + used_size;
    if (obj.ptr()->IsNewObject()) {
      FreeListElement::AsElementNew(addr, leftover_size);
    } else {
      FreeListElement::AsElement(addr, leftover_size);
    }
    // On architectures with a relaxed memory model, the concurrent marker may
    // observe the write of the filler object's header before observing the
    // new array length, and so treat it as a pointer. Ensure it is a Smi so
    // the marker won't dereference it.
    ASSERT((*reinterpret_cast<uword*>(addr) & kSmiTagMask) == kSmiTag);
    ASSERT((*reinterpret_cast<uword*>(addr + kWordSize) & kSmiTagMask) ==
           kSmiTag);
  }
}

void Object::VerifyBuiltinVtables() {
#if defined(DEBUG)
  ASSERT(builtin_vtables_[kIllegalCid] == 0);
  ASSERT(builtin_vtables_[kFreeListElement] == 0);
  ASSERT(builtin_vtables_[kForwardingCorpse] == 0);
  ClassTable* table = IsolateGroup::Current()->class_table();
  for (intptr_t cid = kObjectCid; cid < kNumPredefinedCids; cid++) {
    if (table->HasValidClassAt(cid)) {
      ASSERT(builtin_vtables_[cid] != 0);
    }
  }
#endif
}

void Object::RegisterClass(const Class& cls,
                           const String& name,
                           const Library& lib) {
  ASSERT(name.Length() > 0);
  ASSERT(name.CharAt(0) != '_');
  cls.set_name(name);
  lib.AddClass(cls);
}

void Object::RegisterPrivateClass(const Class& cls,
                                  const String& public_class_name,
                                  const Library& lib) {
  ASSERT(public_class_name.Length() > 0);
  ASSERT(public_class_name.CharAt(0) == '_');
  String& str = String::Handle();
  str = lib.PrivateName(public_class_name);
  cls.set_name(str);
  lib.AddClass(cls);
}

class VerificationWorkSet : public StackResource {
 public:
  explicit VerificationWorkSet(Thread* thread, Zone* zone)
      : StackResource(thread),
        thread_(thread),
        list_(GrowableObjectArray::Handle(zone)),
        stack_(GrowableObjectArray::Handle(zone)) {
    ASSERT(thread->forward_table_new() == nullptr);
    set_ = new WeakTable();
    list_ = GrowableObjectArray::New(16);
    stack_ = GrowableObjectArray::New(16);
    thread->set_forward_table_new(set_);
  }

  ~VerificationWorkSet() {
    thread_->set_forward_table_new(nullptr);
    set_ = nullptr;
  }

  void Add(const Object& object) {
    if (!IsMarked(object.ptr())) {
      Mark(object.ptr());
      list_.Add(object);
    }
  }

  bool TakeAndPush(Object* object) {
    if (list_.Length() == 0) {
      return false;
    }
    *object = list_.RemoveLast();
    stack_.Add(*object);  // Push
    return true;
  }

  void PopAndProcessCompletedClosuresAndContexts(Object* object) {
    if (stack_.Length() > 0) {
      *object = stack_.RemoveLast();  // Pop
      // Are we done processing nested context or closures?
      while (stack_.Length() > 0) {
        *object = stack_.At(stack_.Length() - 1);
        if (object->IsContext()) {
          stack_.RemoveLast();
        } else if (object->IsClosure()) {
          object->SetDeeplyImmutable();
          stack_.RemoveLast();
        }
      }
    }
  }

  intptr_t StackLength() { return stack_.Length(); }

 private:
  bool IsMarked(ObjectPtr object) {
    return set_->GetValueExclusive(object) != 0;
  }
  void Mark(ObjectPtr object) { set_->SetValueExclusive(object, 1); }

  Thread* thread_;
  WeakTable* set_;
  GrowableObjectArray& list_;
  // Stack of objects that are currently being processed
  GrowableObjectArray& stack_;
};

void Object::EnsureDeeplyImmutable(Zone* zone) const {
  auto thread = Thread::Current();
  VerificationWorkSet workset(thread, zone);
  workset.Add(*this);

  Object& current = Object::Handle(zone);
  Function& function = Function::Handle(zone);
  Object& obj = Object::Handle(zone);

  ObjectStore* object_store = thread->isolate_group()->object_store();
  const intptr_t bigint_cid =
      Class::Handle(zone, object_store->bigint_impl_class()).id();

  while (workset.TakeAndPush(&current)) {
    if (current.IsSmi() || current.IsNull() || current.IsCanonical() ||
        current.IsDeeplyImmutable() ||
        IsTypedDataBaseClassId(current.GetClassId()) ||
        current.GetClassId() == bigint_cid) {
      workset.PopAndProcessCompletedClosuresAndContexts(&obj);
      continue;
    }

    if (current.IsClosure()) {
      const Closure& closure = Closure::Cast(current);
      function = closure.function();
      if (!function.IsImplicitClosureFunction() &&
          !function.captures_only_final_not_late_vars()) {
        Exceptions::ThrowArgumentError(String::Handle(String::New(
            "Only final not-late variables can be captured here.")));
        UNREACHABLE();
      }
      obj = closure.RawContext();
      workset.Add(obj);
      continue;
    }

    if (current.IsContext()) {
      const Context& context = Context::Cast(current);
      // Iterate through all elements of the context.
      for (intptr_t i = 0; i < context.num_variables(); i++) {
        obj = context.At(i);
        workset.Add(obj);
      }
      continue;
    }

    if (current.GetClassId() == ConstMap::kClassId) {
      continue;
    }

    Exceptions::ThrowArgumentError(String::Handle(
        String::NewFormatted("Only trivially-immutable values are allowed: %s.",
                             current.ToCString())));
    UNREACHABLE();
  }

  RELEASE_ASSERT(workset.StackLength() == 0);
}

// Initialize a new isolate from source or from a snapshot.
//
// There are three possibilities:
//   1. Running a Kernel binary.  This function will bootstrap from the KERNEL
//      file.
//   2. There is no vm snapshot.  This function will bootstrap from source.
//   3. There is a vm snapshot.  The caller should initialize from the snapshot.
//
// A non-null kernel argument indicates (1).
// A nullptr kernel indicates (2) or (3).
ErrorPtr Object::Init(IsolateGroup* isolate_group,
                      const uint8_t* kernel_buffer,
                      intptr_t kernel_buffer_size) {
  Thread* thread = Thread::Current();
  ASSERT(isolate_group == thread->isolate_group());
  TIMELINE_DURATION(thread, Isolate, "Object::Init");

#if defined(DART_PRECOMPILED_RUNTIME)
  const bool bootstrapping = false;
#else
  Zone* zone = thread->zone();
  const bool is_kernel = (kernel_buffer != nullptr);
  const bool bootstrapping = is_kernel;
#endif  // defined(DART_PRECOMPILED_RUNTIME).

  if (bootstrapping) {
#if !defined(DART_PRECOMPILED_RUNTIME)
    // Object::Init version when we are bootstrapping from source or from a
    // Kernel binary.
    // This will initialize isolate group object_store, shared by all isolates
    // running in the isolate group.
    ObjectStore* object_store = isolate_group->object_store();
    SafepointWriteRwLocker ml(thread, isolate_group->program_lock());

    Class& cls = Class::Handle(zone);
    Type& type = Type::Handle(zone);
    Array& array = Array::Handle(zone);
    WeakArray& weak_array = WeakArray::Handle(zone);
    Library& lib = Library::Handle(zone);
    TypeArguments& type_args = TypeArguments::Handle(zone);

    // All RawArray fields will be initialized to an empty array, therefore
    // initialize array class first.
    cls = Class::New<Array, RTN::Array>(isolate_group);
    object_store->set_array_class(cls);

    // VM classes that are parameterized (Array, ImmutableArray,
    // GrowableObjectArray, Map, ConstMap,
    // Set, ConstSet) are also pre-finalized, so
    // CalculateFieldOffsets() is not called, so we need to set the offset
    // of their type_arguments_ field, which is explicitly
    // declared in their respective Raw* classes.
    cls.set_type_arguments_field_offset(Array::type_arguments_offset(),
                                        RTN::Array::type_arguments_offset());
    cls.set_num_type_arguments_unsafe(1);

    // Set up the growable object array class (Has to be done after the array
    // class is setup as one of its field is an array object).
    cls = Class::New<GrowableObjectArray, RTN::GrowableObjectArray>(
        isolate_group);
    object_store->set_growable_object_array_class(cls);
    cls.set_type_arguments_field_offset(
        GrowableObjectArray::type_arguments_offset(),
        RTN::GrowableObjectArray::type_arguments_offset());
    cls.set_num_type_arguments_unsafe(1);

    // Initialize hash set for regexp_table_.
    const intptr_t kInitialCanonicalRegExpSize = 4;
    weak_array = HashTables::New<CanonicalRegExpSet>(
        kInitialCanonicalRegExpSize, Heap::kOld);
    object_store->set_regexp_table(weak_array);

    // Initialize hash set for canonical types.
    const intptr_t kInitialCanonicalTypeSize = 16;
    array = HashTables::New<CanonicalTypeSet>(kInitialCanonicalTypeSize,
                                              Heap::kOld);
    object_store->set_canonical_types(array);

    // Initialize hash set for canonical function types.
    const intptr_t kInitialCanonicalFunctionTypeSize = 16;
    array = HashTables::New<CanonicalFunctionTypeSet>(
        kInitialCanonicalFunctionTypeSize, Heap::kOld);
    object_store->set_canonical_function_types(array);

    // Initialize hash set for canonical record types.
    const intptr_t kInitialCanonicalRecordTypeSize = 16;
    array = HashTables::New<CanonicalRecordTypeSet>(
        kInitialCanonicalRecordTypeSize, Heap::kOld);
    object_store->set_canonical_record_types(array);

    // Initialize hash set for canonical type parameters.
    const intptr_t kInitialCanonicalTypeParameterSize = 4;
    array = HashTables::New<CanonicalTypeParameterSet>(
        kInitialCanonicalTypeParameterSize, Heap::kOld);
    object_store->set_canonical_type_parameters(array);

    // Initialize hash set for canonical_type_arguments_.
    const intptr_t kInitialCanonicalTypeArgumentsSize = 4;
    array = HashTables::New<CanonicalTypeArgumentsSet>(
        kInitialCanonicalTypeArgumentsSize, Heap::kOld);
    object_store->set_canonical_type_arguments(array);

    // Setup type class early in the process.
    const Class& type_cls =
        Class::Handle(zone, Class::New<Type, RTN::Type>(isolate_group));
    const Class& function_type_cls = Class::Handle(
        zone, Class::New<FunctionType, RTN::FunctionType>(isolate_group));
    const Class& record_type_cls = Class::Handle(
        zone, Class::New<RecordType, RTN::RecordType>(isolate_group));
    const Class& type_parameter_cls = Class::Handle(
        zone, Class::New<TypeParameter, RTN::TypeParameter>(isolate_group));
    const Class& library_prefix_cls = Class::Handle(
        zone, Class::New<LibraryPrefix, RTN::LibraryPrefix>(isolate_group));

    // Pre-allocate the OneByteString class needed by the symbol table.
    cls = Class::NewStringClass(kOneByteStringCid, isolate_group);
    object_store->set_one_byte_string_class(cls);

    // Pre-allocate the TwoByteString class needed by the symbol table.
    cls = Class::NewStringClass(kTwoByteStringCid, isolate_group);
    object_store->set_two_byte_string_class(cls);

    // Set up the libraries array before initializing the core library.
    const GrowableObjectArray& libraries =
        GrowableObjectArray::Handle(zone, GrowableObjectArray::New(Heap::kOld));
    object_store->set_libraries(libraries);

    // Pre-register the core library.
    Library::InitCoreLibrary(isolate_group);

    // Basic infrastructure has been setup, initialize the class dictionary.
    const Library& core_lib = Library::Handle(zone, Library::CoreLibrary());
    ASSERT(!core_lib.IsNull());

    const GrowableObjectArray& pending_classes =
        GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
    object_store->set_pending_classes(pending_classes);

    // Now that the symbol table is initialized and that the core dictionary as
    // well as the core implementation dictionary have been setup, preallocate
    // remaining classes and register them by name in the dictionaries.
    String& name = String::Handle(zone);
    cls = object_store->array_class();  // Was allocated above.
    RegisterPrivateClass(cls, Symbols::_List(), core_lib);
    pending_classes.Add(cls);
    // We cannot use NewNonParameterizedType(), because Array is
    // parameterized.  Warning: class _List has not been patched yet. Its
    // declared number of type parameters is still 0. It will become 1 after
    // patching. The array type allocated below represents the raw type _List
    // and not _List<E> as we could expect. Use with caution.
    type = Type::New(Class::Handle(zone, cls.ptr()),
                     Object::null_type_arguments(), Nullability::kNonNullable);
    type.SetIsFinalized();
    type ^= type.Canonicalize(thread);
    object_store->set_array_type(type);

    cls = object_store->growable_object_array_class();  // Was allocated above.
    RegisterPrivateClass(cls, Symbols::_GrowableList(), core_lib);
    pending_classes.Add(cls);

    cls = Class::New<Array, RTN::Array>(kImmutableArrayCid, isolate_group);
    object_store->set_immutable_array_class(cls);
    cls.set_type_arguments_field_offset(Array::type_arguments_offset(),
                                        RTN::Array::type_arguments_offset());
    cls.set_num_type_arguments_unsafe(1);
    ASSERT(object_store->immutable_array_class() !=
           object_store->array_class());
    cls.set_is_prefinalized();
    RegisterPrivateClass(cls, Symbols::_ImmutableList(), core_lib);
    pending_classes.Add(cls);

    cls = object_store->one_byte_string_class();  // Was allocated above.
    RegisterPrivateClass(cls, Symbols::OneByteString(), core_lib);
    pending_classes.Add(cls);

    cls = object_store->two_byte_string_class();  // Was allocated above.
    RegisterPrivateClass(cls, Symbols::TwoByteString(), core_lib);
    pending_classes.Add(cls);

    // Pre-register the isolate library so the native class implementations can
    // be hooked up before compiling it.
    Library& isolate_lib = Library::Handle(
        zone, Library::LookupLibrary(thread, Symbols::DartIsolate()));
    if (isolate_lib.IsNull()) {
      isolate_lib = Library::NewLibraryHelper(Symbols::DartIsolate(), true);
      isolate_lib.SetLoadRequested();
      isolate_lib.Register(thread);
    }
    object_store->set_bootstrap_library(ObjectStore::kIsolate, isolate_lib);
    ASSERT(!isolate_lib.IsNull());
    ASSERT(isolate_lib.ptr() == Library::IsolateLibrary());

    cls = Class::New<Capability, RTN::Capability>(isolate_group);
    RegisterPrivateClass(cls, Symbols::_Capability(), isolate_lib);
    pending_classes.Add(cls);

    cls = Class::New<ReceivePort, RTN::ReceivePort>(isolate_group);
    RegisterPrivateClass(cls, Symbols::_RawReceivePort(), isolate_lib);
    pending_classes.Add(cls);

    cls = Class::New<SendPort, RTN::SendPort>(isolate_group);
    RegisterPrivateClass(cls, Symbols::_SendPort(), isolate_lib);
    pending_classes.Add(cls);

    cls = Class::New<TransferableTypedData, RTN::TransferableTypedData>(
        isolate_group);
    RegisterPrivateClass(cls, Symbols::_TransferableTypedDataImpl(),
                         isolate_lib);
    pending_classes.Add(cls);

    const Class& stacktrace_cls = Class::Handle(
        zone, Class::New<StackTrace, RTN::StackTrace>(isolate_group));
    RegisterPrivateClass(stacktrace_cls, Symbols::_StackTrace(), core_lib);
    pending_classes.Add(stacktrace_cls);
    // Super type set below, after Object is allocated.

    cls = Class::New<RegExp, RTN::RegExp>(isolate_group);
    RegisterPrivateClass(cls, Symbols::_RegExp(), core_lib);
    pending_classes.Add(cls);

    // Initialize the base interfaces used by the core VM classes.

    // Allocate and initialize the pre-allocated classes in the core library.
    // The script and token index of these pre-allocated classes is set up when
    // the corelib script is compiled.
    cls = Class::New<Instance, RTN::Instance>(kInstanceCid, isolate_group);
    object_store->set_object_class(cls);
    cls.set_name(Symbols::Object());
    cls.set_num_type_arguments_unsafe(0);
    cls.set_is_prefinalized();
    cls.set_is_const();
    core_lib.AddClass(cls);
    pending_classes.Add(cls);
    type = Type::NewNonParameterizedType(cls);
    ASSERT(type.IsCanonical());
    object_store->set_object_type(type);
    type = type.ToNullability(Nullability::kNonNullable, Heap::kOld);
    ASSERT(type.IsCanonical());
    object_store->set_non_nullable_object_type(type);
    type = type.ToNullability(Nullability::kNullable, Heap::kOld);
    ASSERT(type.IsCanonical());
    object_store->set_nullable_object_type(type);

    cls = Class::New<Bool, RTN::Bool>(isolate_group);
    object_store->set_bool_class(cls);
    RegisterClass(cls, Symbols::Bool(), core_lib);
    pending_classes.Add(cls);

    cls = Class::New<Instance, RTN::Instance>(kNullCid, isolate_group);
    object_store->set_null_class(cls);
    cls.set_num_type_arguments_unsafe(0);
    cls.set_is_prefinalized();
    RegisterClass(cls, Symbols::Null(), core_lib);
    pending_classes.Add(cls);

    cls = Class::New<Instance, RTN::Instance>(kNeverCid, isolate_group);
    cls.set_num_type_arguments_unsafe(0);
    cls.set_is_allocate_finalized();
    cls.set_is_declaration_loaded();
    cls.set_is_type_finalized();
    cls.set_name(Symbols::Never());
    object_store->set_never_class(cls);

    ASSERT(!library_prefix_cls.IsNull());
    RegisterPrivateClass(library_prefix_cls, Symbols::_LibraryPrefix(),
                         core_lib);
    pending_classes.Add(library_prefix_cls);

    RegisterPrivateClass(type_cls, Symbols::_Type(), core_lib);
    pending_classes.Add(type_cls);

    RegisterPrivateClass(function_type_cls, Symbols::_FunctionType(), core_lib);
    pending_classes.Add(function_type_cls);

    RegisterPrivateClass(record_type_cls, Symbols::_RecordType(), core_lib);
    pending_classes.Add(record_type_cls);

    RegisterPrivateClass(type_parameter_cls, Symbols::_TypeParameter(),
                         core_lib);
    pending_classes.Add(type_parameter_cls);

    cls = Class::New<Integer, RTN::Integer>(isolate_group);
    object_store->set_integer_implementation_class(cls);
    RegisterPrivateClass(cls, Symbols::_IntegerImplementation(), core_lib);
    pending_classes.Add(cls);

    cls = Class::New<Smi, RTN::Smi>(isolate_group);
    object_store->set_smi_class(cls);
    RegisterPrivateClass(cls, Symbols::_Smi(), core_lib);
    pending_classes.Add(cls);

    cls = Class::New<Mint, RTN::Mint>(isolate_group);
    object_store->set_mint_class(cls);
    RegisterPrivateClass(cls, Symbols::_Mint(), core_lib);
    pending_classes.Add(cls);

    cls = Class::New<Double, RTN::Double>(isolate_group);
    object_store->set_double_class(cls);
    RegisterPrivateClass(cls, Symbols::_Double(), core_lib);
    pending_classes.Add(cls);

    // Class that represents the Dart class _Closure and C++ class Closure.
    cls = Class::New<Closure, RTN::Closure>(isolate_group);
    object_store->set_closure_class(cls);
    RegisterPrivateClass(cls, Symbols::_Closure(), core_lib);
    pending_classes.Add(cls);

    cls = Class::New<Record, RTN::Record>(isolate_group);
    RegisterPrivateClass(cls, Symbols::_Record(), core_lib);
    pending_classes.Add(cls);

    cls = Class::New<WeakProperty, RTN::WeakProperty>(isolate_group);
    object_store->set_weak_property_class(cls);
    RegisterPrivateClass(cls, Symbols::_WeakProperty(), core_lib);

    cls = Class::New<WeakReference, RTN::WeakReference>(isolate_group);
    cls.set_type_arguments_field_offset(
        WeakReference::type_arguments_offset(),
        RTN::WeakReference::type_arguments_offset());
    cls.set_num_type_arguments_unsafe(1);
    object_store->set_weak_reference_class(cls);
    RegisterPrivateClass(cls, Symbols::_WeakReference(), core_lib);

    // Pre-register the mirrors library so we can place the vm class
    // MirrorReference there rather than the core library.
    lib = Library::LookupLibrary(thread, Symbols::DartMirrors());
    if (lib.IsNull()) {
      lib = Library::NewLibraryHelper(Symbols::DartMirrors(), true);
      lib.SetLoadRequested();
      lib.Register(thread);
    }
    object_store->set_bootstrap_library(ObjectStore::kMirrors, lib);
    ASSERT(!lib.IsNull());
    ASSERT(lib.ptr() == Library::MirrorsLibrary());

    cls = Class::New<MirrorReference, RTN::MirrorReference>(isolate_group);
    RegisterPrivateClass(cls, Symbols::_MirrorReference(), lib);

    // Pre-register dart:_compact_hash library so that we could place
    // collection classes (_Map, _ConstMap, _Set, _ConstSet) here.
    lib = Library::LookupLibrary(thread, Symbols::DartCompactHash());
    if (lib.IsNull()) {
      lib = Library::NewLibraryHelper(Symbols::DartCompactHash(), true);
      lib.SetLoadRequested();
      lib.Register(thread);
    }
    object_store->set_bootstrap_library(ObjectStore::kCompactHash, lib);

    ASSERT(!lib.IsNull());
    ASSERT(lib.ptr() == Library::CompactHashLibrary());

    cls = Class::New<LinkedHashBase, RTN::LinkedHashBase>(
        kLinkedHashBaseCid, isolate_group, /*register_class=*/true,
        /*is_abstract=*/true);
    cls.set_type_arguments_field_offset(
        LinkedHashBase::type_arguments_offset(),
        RTN::LinkedHashBase::type_arguments_offset());
    cls.set_is_prefinalized();
    isolate_group->class_table()->Register(cls);
    RegisterPrivateClass(cls, Symbols::_LinkedHashBase(), lib);
    pending_classes.Add(cls);

    cls = Class::New<Map, RTN::Map>(isolate_group);
    object_store->set_map_impl_class(cls);
    cls.set_type_arguments_field_offset(Map::type_arguments_offset(),
                                        RTN::Map::type_arguments_offset());
    cls.set_num_type_arguments_unsafe(2);
    RegisterPrivateClass(cls, Symbols::_Map(), lib);
    pending_classes.Add(cls);

    cls = Class::New<Map, RTN::Map>(kConstMapCid, isolate_group);
    object_store->set_const_map_impl_class(cls);
    cls.set_type_arguments_field_offset(Map::type_arguments_offset(),
                                        RTN::Map::type_arguments_offset());
    cls.set_num_type_arguments_unsafe(2);
    cls.set_is_prefinalized();
    RegisterPrivateClass(cls, Symbols::_ConstMap(), lib);
    pending_classes.Add(cls);

    cls = Class::New<Set, RTN::Set>(isolate_group);
    object_store->set_set_impl_class(cls);
    cls.set_type_arguments_field_offset(Set::type_arguments_offset(),
                                        RTN::Set::type_arguments_offset());
    cls.set_num_type_arguments_unsafe(1);
    RegisterPrivateClass(cls, Symbols::_Set(), lib);
    pending_classes.Add(cls);

    cls = Class::New<Set, RTN::Set>(kConstSetCid, isolate_group);
    object_store->set_const_set_impl_class(cls);
    cls.set_type_arguments_field_offset(Set::type_arguments_offset(),
                                        RTN::Set::type_arguments_offset());
    cls.set_num_type_arguments_unsafe(1);
    cls.set_is_prefinalized();
    RegisterPrivateClass(cls, Symbols::_ConstSet(), lib);
    pending_classes.Add(cls);

    // Pre-register the collection library.
    lib = Library::LookupLibrary(thread, Symbols::DartCollection());
    if (lib.IsNull()) {
      lib = Library::NewLibraryHelper(Symbols::DartCollection(), true);
      lib.SetLoadRequested();
      lib.Register(thread);
    }
    object_store->set_bootstrap_library(ObjectStore::kCollection, lib);

    // Pre-register the async library so we can place the vm class
    // FutureOr there rather than the core library.
    lib = Library::LookupLibrary(thread, Symbols::DartAsync());
    if (lib.IsNull()) {
      lib = Library::NewLibraryHelper(Symbols::DartAsync(), true);
      lib.SetLoadRequested();
      lib.Register(thread);
    }
    object_store->set_bootstrap_library(ObjectStore::kAsync, lib);
    ASSERT(!lib.IsNull());
    ASSERT(lib.ptr() == Library::AsyncLibrary());
    cls = Class::New<FutureOr, RTN::FutureOr>(isolate_group);
    cls.set_type_arguments_field_offset(FutureOr::type_arguments_offset(),
                                        RTN::FutureOr::type_arguments_offset());
    cls.set_num_type_arguments_unsafe(1);
    RegisterClass(cls, Symbols::FutureOr(), lib);
    pending_classes.Add(cls);
    object_store->set_future_or_class(cls);

    cls = Class::New<SuspendState, RTN::SuspendState>(isolate_group);
    RegisterPrivateClass(cls, Symbols::_SuspendState(), lib);
    pending_classes.Add(cls);

    // Pre-register the developer library so we can place the vm class
    // UserTag there rather than the core library.
    lib = Library::LookupLibrary(thread, Symbols::DartDeveloper());
    if (lib.IsNull()) {
      lib = Library::NewLibraryHelper(Symbols::DartDeveloper(), true);
      lib.SetLoadRequested();
      lib.Register(thread);
    }
    object_store->set_bootstrap_library(ObjectStore::kDeveloper, lib);
    ASSERT(!lib.IsNull());
    ASSERT(lib.ptr() == Library::DeveloperLibrary());
    cls = Class::New<UserTag, RTN::UserTag>(isolate_group);
    RegisterPrivateClass(cls, Symbols::_UserTag(), lib);
    pending_classes.Add(cls);

    // Setup some default native field classes which can be extended for
    // specifying native fields in dart classes.
    Library::InitNativeWrappersLibrary(isolate_group, is_kernel);
    ASSERT(object_store->native_wrappers_library() != Library::null());

    // Pre-register the typed_data library so the native class implementations
    // can be hooked up before compiling it.
    lib = Library::LookupLibrary(thread, Symbols::DartTypedData());
    if (lib.IsNull()) {
      lib = Library::NewLibraryHelper(Symbols::DartTypedData(), true);
      lib.SetLoadRequested();
      lib.Register(thread);
    }
    object_store->set_bootstrap_library(ObjectStore::kTypedData, lib);
    ASSERT(!lib.IsNull());
    ASSERT(lib.ptr() == Library::TypedDataLibrary());
#define REGISTER_TYPED_DATA_CLASS(clazz)                                       \
  cls = Class::NewTypedDataClass(kTypedData##clazz##ArrayCid, isolate_group);  \
  RegisterPrivateClass(cls, Symbols::_##clazz##List(), lib);

    DART_CLASS_LIST_TYPED_DATA(REGISTER_TYPED_DATA_CLASS);
#undef REGISTER_TYPED_DATA_CLASS
#define REGISTER_TYPED_DATA_VIEW_CLASS(clazz)                                  \
  cls =                                                                        \
      Class::NewTypedDataViewClass(kTypedData##clazz##ViewCid, isolate_group); \
  RegisterPrivateClass(cls, Symbols::_##clazz##View(), lib);                   \
  pending_classes.Add(cls);                                                    \
  cls = Class::NewUnmodifiableTypedDataViewClass(                              \
      kUnmodifiableTypedData##clazz##ViewCid, isolate_group);                  \
  RegisterPrivateClass(cls, Symbols::_Unmodifiable##clazz##View(), lib);       \
  pending_classes.Add(cls);

    CLASS_LIST_TYPED_DATA(REGISTER_TYPED_DATA_VIEW_CLASS);

    cls = Class::NewTypedDataViewClass(kByteDataViewCid, isolate_group);
    RegisterPrivateClass(cls, Symbols::_ByteDataView(), lib);
    pending_classes.Add(cls);
    cls = Class::NewUnmodifiableTypedDataViewClass(kUnmodifiableByteDataViewCid,
                                                   isolate_group);
    RegisterPrivateClass(cls, Symbols::_UnmodifiableByteDataView(), lib);
    pending_classes.Add(cls);

#undef REGISTER_TYPED_DATA_VIEW_CLASS
#define REGISTER_EXT_TYPED_DATA_CLASS(clazz)                                   \
  cls = Class::NewExternalTypedDataClass(kExternalTypedData##clazz##Cid,       \
                                         isolate_group);                       \
  RegisterPrivateClass(cls, Symbols::_External##clazz(), lib);

    cls = Class::New<Instance, RTN::Instance>(kByteBufferCid, isolate_group,
                                              /*register_class=*/false);
    cls.set_instance_size(0, 0);
    cls.set_next_field_offset(-kWordSize, -compiler::target::kWordSize);
    isolate_group->class_table()->Register(cls);
    RegisterPrivateClass(cls, Symbols::_ByteBuffer(), lib);
    pending_classes.Add(cls);

    CLASS_LIST_TYPED_DATA(REGISTER_EXT_TYPED_DATA_CLASS);
#undef REGISTER_EXT_TYPED_DATA_CLASS
    // Register Float32x4, Int32x4, and Float64x2 in the object store.
    cls = Class::New<Float32x4, RTN::Float32x4>(isolate_group);
    RegisterPrivateClass(cls, Symbols::_Float32x4(), lib);
    pending_classes.Add(cls);
    object_store->set_float32x4_class(cls);

    cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
                                              /*register_class=*/true,
                                              /*is_abstract=*/true);
    RegisterClass(cls, Symbols::Float32x4(), lib);
    cls.set_num_type_arguments_unsafe(0);
    cls.set_is_prefinalized();
    type = Type::NewNonParameterizedType(cls);
    object_store->set_float32x4_type(type);

    cls = Class::New<Int32x4, RTN::Int32x4>(isolate_group);
    RegisterPrivateClass(cls, Symbols::_Int32x4(), lib);
    pending_classes.Add(cls);
    object_store->set_int32x4_class(cls);

    cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
                                              /*register_class=*/true,
                                              /*is_abstract=*/true);
    RegisterClass(cls, Symbols::Int32x4(), lib);
    cls.set_num_type_arguments_unsafe(0);
    cls.set_is_prefinalized();
    type = Type::NewNonParameterizedType(cls);
    object_store->set_int32x4_type(type);

    cls = Class::New<Float64x2, RTN::Float64x2>(isolate_group);
    RegisterPrivateClass(cls, Symbols::_Float64x2(), lib);
    pending_classes.Add(cls);
    object_store->set_float64x2_class(cls);

    cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
                                              /*register_class=*/true,
                                              /*is_abstract=*/true);
    RegisterClass(cls, Symbols::Float64x2(), lib);
    cls.set_num_type_arguments_unsafe(0);
    cls.set_is_prefinalized();
    type = Type::NewNonParameterizedType(cls);
    object_store->set_float64x2_type(type);

    // Set the super type of class StackTrace to Object type so that the
    // 'toString' method is implemented.
    type = object_store->object_type();
    stacktrace_cls.set_super_type(type);

    // Abstract class that represents the Dart class Type.
    // Note that this class is implemented by Dart class _AbstractType.
    cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
                                              /*register_class=*/true,
                                              /*is_abstract=*/true);
    cls.set_num_type_arguments_unsafe(0);
    cls.set_is_prefinalized();
    RegisterClass(cls, Symbols::Type(), core_lib);
    pending_classes.Add(cls);
    type = Type::NewNonParameterizedType(cls);
    object_store->set_type_type(type);

    // Abstract class that represents the Dart class Function.
    cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
                                              /*register_class=*/true,
                                              /*is_abstract=*/true);
    cls.set_num_type_arguments_unsafe(0);
    cls.set_is_prefinalized();
    RegisterClass(cls, Symbols::Function(), core_lib);
    pending_classes.Add(cls);
    type = Type::NewNonParameterizedType(cls);
    object_store->set_function_type(type);

    // Abstract class that represents the Dart class Record.
    cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
                                              /*register_class=*/true,
                                              /*is_abstract=*/true);
    RegisterClass(cls, Symbols::Record(), core_lib);
    pending_classes.Add(cls);
    object_store->set_record_class(cls);

    cls = Class::New<Number, RTN::Number>(isolate_group);
    RegisterClass(cls, Symbols::Number(), core_lib);
    pending_classes.Add(cls);
    type = Type::NewNonParameterizedType(cls);
    object_store->set_number_type(type);
    type = type.ToNullability(Nullability::kNullable, Heap::kOld);
    object_store->set_nullable_number_type(type);

    cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
                                              /*register_class=*/true,
                                              /*is_abstract=*/true);
    RegisterClass(cls, Symbols::Int(), core_lib);
    cls.set_num_type_arguments_unsafe(0);
    cls.set_is_prefinalized();
    pending_classes.Add(cls);
    type = Type::NewNonParameterizedType(cls);
    object_store->set_int_type(type);
    type = type.ToNullability(Nullability::kNonNullable, Heap::kOld);
    object_store->set_non_nullable_int_type(type);
    type = type.ToNullability(Nullability::kNullable, Heap::kOld);
    object_store->set_nullable_int_type(type);

    cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
                                              /*register_class=*/true,
                                              /*is_abstract=*/true);
    RegisterClass(cls, Symbols::Double(), core_lib);
    cls.set_num_type_arguments_unsafe(0);
    cls.set_is_prefinalized();
    pending_classes.Add(cls);
    type = Type::NewNonParameterizedType(cls);
    object_store->set_double_type(type);
    type = type.ToNullability(Nullability::kNullable, Heap::kOld);
    object_store->set_nullable_double_type(type);

    name = Symbols::_String().ptr();
    cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
                                              /*register_class=*/true,
                                              /*is_abstract=*/true);
    RegisterClass(cls, name, core_lib);
    cls.set_num_type_arguments_unsafe(0);
    cls.set_is_prefinalized();
    pending_classes.Add(cls);
    type = Type::NewNonParameterizedType(cls);
    object_store->set_string_type(type);

    cls = object_store->bool_class();
    type = Type::NewNonParameterizedType(cls);
    object_store->set_bool_type(type);

    cls = object_store->smi_class();
    type = Type::NewNonParameterizedType(cls);
    object_store->set_smi_type(type);

    cls = object_store->mint_class();
    type = Type::NewNonParameterizedType(cls);
    object_store->set_mint_type(type);

    // The classes 'void' and 'dynamic' are phony classes to make type checking
    // more regular; they live in the VM isolate. The class 'void' is not
    // registered in the class dictionary because its name is a reserved word.
    // The class 'dynamic' is registered in the class dictionary because its
    // name is a built-in identifier (this is wrong).  The corresponding types
    // are stored in the object store.
    cls = object_store->null_class();
    type =
        Type::New(cls, Object::null_type_arguments(), Nullability::kNullable);
    type.SetIsFinalized();
    type ^= type.Canonicalize(thread);
    object_store->set_null_type(type);
    cls.set_declaration_type(type);
    ASSERT(type.IsNullable());

    // Consider removing when/if Null becomes an ordinary class.
    type = object_store->object_type();
    cls.set_super_type(type);

    cls = object_store->never_class();
    type = Type::New(cls, Object::null_type_arguments(),
                     Nullability::kNonNullable);
    type.SetIsFinalized();
    type ^= type.Canonicalize(thread);
    object_store->set_never_type(type);
    type_args = TypeArguments::New(1);
    type_args.SetTypeAt(0, type);
    type_args = type_args.Canonicalize(thread);
    object_store->set_type_argument_never(type_args);

    // Create and cache commonly used type arguments <int>, <double>,
    // <String>, <String, dynamic> and <String, String>.
    type_args = TypeArguments::New(1);
    type = object_store->int_type();
    type_args.SetTypeAt(0, type);
    type_args = type_args.Canonicalize(thread);
    object_store->set_type_argument_int(type_args);

    type_args = TypeArguments::New(1);
    type = object_store->double_type();
    type_args.SetTypeAt(0, type);
    type_args = type_args.Canonicalize(thread);
    object_store->set_type_argument_double(type_args);

    type_args = TypeArguments::New(1);
    type = object_store->string_type();
    type_args.SetTypeAt(0, type);
    type_args = type_args.Canonicalize(thread);
    object_store->set_type_argument_string(type_args);

    type_args = TypeArguments::New(2);
    type = object_store->string_type();
    type_args.SetTypeAt(0, type);
    type_args.SetTypeAt(1, Object::dynamic_type());
    type_args = type_args.Canonicalize(thread);
    object_store->set_type_argument_string_dynamic(type_args);

    type_args = TypeArguments::New(2);
    type = object_store->string_type();
    type_args.SetTypeAt(0, type);
    type_args.SetTypeAt(1, type);
    type_args = type_args.Canonicalize(thread);
    object_store->set_type_argument_string_string(type_args);

    lib = Library::LookupLibrary(thread, Symbols::DartFfi());
    if (lib.IsNull()) {
      lib = Library::NewLibraryHelper(Symbols::DartFfi(), true);
      lib.SetLoadRequested();
      lib.Register(thread);
    }
    object_store->set_bootstrap_library(ObjectStore::kFfi, lib);

    cls = Class::New<Instance, RTN::Instance>(kFfiNativeTypeCid, isolate_group);
    cls.set_num_type_arguments_unsafe(0);
    cls.set_is_prefinalized();
    pending_classes.Add(cls);
    object_store->set_ffi_native_type_class(cls);
    RegisterClass(cls, Symbols::FfiNativeType(), lib);

#define REGISTER_FFI_TYPE_MARKER(clazz)                                        \
  cls = Class::New<Instance, RTN::Instance>(kFfi##clazz##Cid, isolate_group);  \
  cls.set_num_type_arguments_unsafe(0);                                        \
  cls.set_is_prefinalized();                                                   \
  pending_classes.Add(cls);                                                    \
  RegisterClass(cls, Symbols::Ffi##clazz(), lib);
    CLASS_LIST_FFI_TYPE_MARKER(REGISTER_FFI_TYPE_MARKER);
#undef REGISTER_FFI_TYPE_MARKER

    cls = Class::New<Instance, RTN::Instance>(kFfiNativeFunctionCid,
                                              isolate_group);
    cls.set_type_arguments_field_offset(Instance::NextFieldOffset(),
                                        RTN::Instance::NextFieldOffset());
    cls.set_num_type_arguments_unsafe(1);
    cls.set_is_prefinalized();
    pending_classes.Add(cls);
    RegisterClass(cls, Symbols::FfiNativeFunction(), lib);

    cls = Class::NewPointerClass(kPointerCid, isolate_group);
    object_store->set_ffi_pointer_class(cls);
    pending_classes.Add(cls);
    RegisterClass(cls, Symbols::FfiPointer(), lib);

    cls = Class::New<DynamicLibrary, RTN::DynamicLibrary>(kDynamicLibraryCid,
                                                          isolate_group);
    cls.set_instance_size(DynamicLibrary::InstanceSize(),
                          compiler::target::RoundedAllocationSize(
                              RTN::DynamicLibrary::InstanceSize()));
    cls.set_is_prefinalized();
    pending_classes.Add(cls);
    RegisterClass(cls, Symbols::FfiDynamicLibrary(), lib);

    cls = Class::New<NativeFinalizer, RTN::NativeFinalizer>(isolate_group);
    object_store->set_native_finalizer_class(cls);
    RegisterPrivateClass(cls, Symbols::_NativeFinalizer(), lib);

    cls = Class::New<Finalizer, RTN::Finalizer>(isolate_group);
    cls.set_type_arguments_field_offset(
        Finalizer::type_arguments_offset(),
        RTN::Finalizer::type_arguments_offset());
    cls.set_num_type_arguments_unsafe(1);
    object_store->set_finalizer_class(cls);
    pending_classes.Add(cls);
    RegisterPrivateClass(cls, Symbols::_FinalizerImpl(), core_lib);

    // Pre-register the internal library so we can place the vm class
    // FinalizerEntry there rather than the core library.
    lib = Library::LookupLibrary(thread, Symbols::DartInternal());
    if (lib.IsNull()) {
      lib = Library::NewLibraryHelper(Symbols::DartInternal(), true);
      lib.SetLoadRequested();
      lib.Register(thread);
    }
    object_store->set_bootstrap_library(ObjectStore::kInternal, lib);
    ASSERT(!lib.IsNull());
    ASSERT(lib.ptr() == Library::InternalLibrary());

    cls = Class::New<FinalizerEntry, RTN::FinalizerEntry>(isolate_group);
    object_store->set_finalizer_entry_class(cls);
    pending_classes.Add(cls);
    RegisterClass(cls, Symbols::FinalizerEntry(), lib);

    lib = Library::LookupLibrary(thread, Symbols::DartVM());
    if (lib.IsNull()) {
      lib = Library::NewLibraryHelper(Symbols::DartVM(), true);
      lib.SetLoadRequested();
      lib.Register(thread);
    }
    object_store->set_bootstrap_library(ObjectStore::kVM, lib);

    // Finish the initialization by compiling the bootstrap scripts containing
    // the base interfaces and the implementation of the internal classes.
    const Error& error = Error::Handle(
        zone, Bootstrap::DoBootstrapping(kernel_buffer, kernel_buffer_size));
    if (!error.IsNull()) {
      return error.ptr();
    }

    isolate_group->class_table()->CopySizesFromClassObjects();

    ClassFinalizer::VerifyBootstrapClasses();

    // Adds static const fields (class ids) to the class 'ClassID');
    lib = Library::LookupLibrary(thread, Symbols::DartInternal());
    ASSERT(!lib.IsNull());
    cls = lib.LookupClassAllowPrivate(Symbols::ClassID());
    ASSERT(!cls.IsNull());
    const bool injected = cls.InjectCIDFields();
    ASSERT(injected);

    // Set up recognized state of all functions (core, math and typed data).
    MethodRecognizer::InitializeState();
#endif  // !defined(DART_PRECOMPILED_RUNTIME)
  }
  return Error::null();
}

void Object::Print() const {
  THR_Print("%s\n", ToCString());
}

StringPtr Object::DictionaryName() const {
  return String::null();
}

bool Object::ShouldHaveShallowImmutabilityBitSet(classid_t class_id) {
  if (class_id < kNumPredefinedCids) {
    return IsShallowlyImmutableCid(class_id);
  }
  return false;
}

bool Object::ShouldHaveDeeplyImmutabilityBitSet(classid_t class_id) {
  if (class_id < kNumPredefinedCids) {
    return IsDeeplyImmutableCid(class_id);
  } else {
    return Class::IsDeeplyImmutable(
        IsolateGroup::Current()->class_table()->At(class_id));
  }
}

void Object::InitializeObject(uword address,
                              intptr_t class_id,
                              intptr_t size,
                              bool compressed,
                              uword ptr_field_start_offset,
                              uword ptr_field_end_offset) {
  // Note: we skip the header word here to avoid a racy read in the concurrent
  // marker from observing the null object when it reads into a heap page
  // allocated after marking started.
  uword cur = address + sizeof(UntaggedObject);
  uword ptr_field_start = address + ptr_field_start_offset;
  uword ptr_field_end = address + ptr_field_end_offset;
  uword end = address + size;
  // The start of pointer fields should always be past the object header, even
  // if there are no pointer fields (ptr_field_end < ptr_field_start).
  ASSERT(cur <= ptr_field_start);
  // The start of pointer fields can be at the end for empty payload objects.
  ASSERT(ptr_field_start <= end);
  // The end of pointer fields should always be before the end, as the end of
  // pointer fields is inclusive (the address of the last field to initialize).
  ASSERT(ptr_field_end < end);
  bool needs_init = true;
  if (IsTypedDataBaseClassId(class_id) || class_id == kArrayCid) {
    // If the size is greater than both Heap::kNewAllocatableSize and
    // kAllocatablePageSize, the object must have been allocated to a new
    // large page, which must already have been zero initialized by the OS.
    // Note that zero is a GC-safe value.
    //
    // For arrays, the caller will then initialize the fields to null with
    // safepoint checks to avoid blocking for the full duration of
    // initializing this array.
    needs_init =
        Heap::IsAllocatableInNewSpace(size) || IsAllocatableViaFreeLists(size);
  }
  if (needs_init) {
    // Initialize the memory prior to any pointer fields with 0. (This loop
    // and the next will be a no-op if the object has no pointer fields.)
    uword initial_value = 0;
    while (cur < ptr_field_start) {
      *reinterpret_cast<uword*>(cur) = initial_value;
      cur += kWordSize;
    }
    // Initialize any pointer fields with Object::null().
    initial_value = static_cast<uword>(Roots::null_obj());
#if defined(DART_COMPRESSED_POINTERS)
    if (compressed) {
      initial_value &= 0xFFFFFFFF;
      initial_value |= initial_value << 32;
    }
    const bool has_pointer_fields = ptr_field_start <= ptr_field_end;
    // If there are compressed pointer fields and the first compressed pointer
    // field is not at a word start, then initialize it to Object::null().
    if (compressed && has_pointer_fields &&
        (ptr_field_start % kWordSize != 0)) {
      *reinterpret_cast<compressed_uword*>(ptr_field_start) = initial_value;
    }
#endif
    while (cur <= ptr_field_end) {
      *reinterpret_cast<uword*>(cur) = initial_value;
      cur += kWordSize;
    }
    // Initialize the memory after any pointer fields with 0, unless this is
    // an instructions object in which case we use the break instruction.
    initial_value = class_id == kInstructionsCid ? kBreakInstructionFiller : 0;
#if defined(DART_COMPRESSED_POINTERS)
    // If there are compressed pointer fields and the last compressed pointer
    // field is the start of a word, then initialize the other part of the word
    // to the new initial value.
    //
    // (We're guaranteed there's always space in the object after the last
    // pointer field in this case since objects are allocated in multiples of
    // the word size.)
    if (compressed && has_pointer_fields && (ptr_field_end % kWordSize == 0)) {
      *reinterpret_cast<compressed_uword*>(ptr_field_end +
                                           kCompressedWordSize) = initial_value;
    }
#endif
    while (cur < end) {
      *reinterpret_cast<uword*>(cur) = initial_value;
      cur += kWordSize;
    }
  } else {
    // Check that MemorySanitizer understands this is initialized.
    MSAN_CHECK_INITIALIZED(reinterpret_cast<void*>(address), size);
#if defined(DEBUG)
    const uword initial_value = 0;
    while (cur < end) {
      ASSERT_EQUAL(*reinterpret_cast<uword*>(cur), initial_value);
      cur += kWordSize;
    }
#endif
  }
  InitializeHeader(address, class_id, size);
#if defined(HOST_HAS_FAST_WRITE_WRITE_FENCE)
  StoreStoreFence();
#endif
}

void Object::CheckHandle() const {
#if defined(DEBUG)
  if (ptr_ != Object::null()) {
    intptr_t cid = ptr_->GetClassId();
    if (cid >= kNumPredefinedCids) {
      cid = kInstanceCid;
    }
    ASSERT(vtable() == builtin_vtables_[cid]);
  }
#endif
}

ObjectPtr Object::Allocate(intptr_t cls_id,
                           intptr_t size,
                           Heap::Space space,
                           bool compressed,
                           uword ptr_field_start_offset,
                           uword ptr_field_end_offset) {
  ASSERT(Utils::IsAligned(size, kObjectAlignment));
  Thread* thread = Thread::Current();
  ASSERT(thread->execution_state() == Thread::kThreadInVM);
  ASSERT(thread->no_safepoint_scope_depth() == 0);
  ASSERT(thread->no_callback_scope_depth() == 0);
  Heap* heap = thread->heap();

  uword address = heap->Allocate(thread, size, space);
  if (UNLIKELY(address == 0)) {
    // SuspendLongJumpScope during Dart entry ensures that if a longjmp base is
    // available, it is the innermost error handler, so check for a longjmp base
    // before checking for an exit frame.
    if (thread->long_jump_base() != nullptr) {
      Report::LongJump(Object::out_of_memory_error());
      UNREACHABLE();
    } else if (thread->top_exit_frame_info() != 0) {
      Exceptions::ThrowOOM();
      UNREACHABLE();
    } else {
      // Nowhere to propagate an exception to.
      OUT_OF_MEMORY();
    }
  }

  ObjectPtr raw_obj;
  NoSafepointScope no_safepoint(thread);
  InitializeObject(address, cls_id, size, compressed, ptr_field_start_offset,
                   ptr_field_end_offset);
  raw_obj = static_cast<ObjectPtr>(address + kHeapObjectTag);
  ASSERT(cls_id == UntaggedObject::ClassIdTag::decode(raw_obj->untag()->tags_));
  if (raw_obj->IsOldObject() && UNLIKELY(thread->is_marking())) {
    // Black allocation. Prevents a data race between the mutator and
    // concurrent marker on ARM and ARM64 (the marker may observe a
    // publishing store of this object before the stores that initialize its
    // slots), and helps the collection to finish sooner.
    // release: Setting the mark bit must not be ordered after a publishing
    // store of this object. Compare Scavenger::ScavengePointer.
    raw_obj->untag()->SetMarkBitRelease();
    heap->old_space()->AllocateBlack(size);
  }

#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
  HeapProfileSampler& heap_sampler = thread->heap_sampler();
  if (heap_sampler.HasOutstandingSample()) {
    thread->IncrementNoCallbackScopeDepth();
    void* data = heap_sampler.InvokeCallbackForLastSample(cls_id);
    heap->SetHeapSamplingData(raw_obj, data);
    thread->DecrementNoCallbackScopeDepth();
  }
#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)

#if !defined(PRODUCT)
  auto class_table = thread->isolate_group()->class_table();
  if (class_table->ShouldTraceAllocationFor(cls_id)) {
    uint32_t hash =
        HeapSnapshotWriter::GetHeapSnapshotIdentityHash(thread, raw_obj);
    Profiler::SampleAllocation(thread, cls_id, hash);
  }
#endif  // !defined(PRODUCT)
  return raw_obj;
}

class WriteBarrierUpdateVisitor : public ObjectPointerVisitor {
 public:
  explicit WriteBarrierUpdateVisitor(Thread* thread, ObjectPtr obj)
      : ObjectPointerVisitor(thread->isolate_group()),
        thread_(thread),
        old_obj_(obj) {
    ASSERT(old_obj_->IsOldObject());
  }

  void VisitPointers(ObjectPtr* from, ObjectPtr* to) override {
    if (old_obj_->IsArray()) {
      for (ObjectPtr* slot = from; slot <= to; ++slot) {
        ObjectPtr value = *slot;
        if (value->IsHeapObject()) {
          old_obj_->untag()->CheckArrayPointerStore(slot, value, thread_);
        }
      }
    } else {
      for (ObjectPtr* slot = from; slot <= to; ++slot) {
        ObjectPtr value = *slot;
        if (value->IsHeapObject()) {
          old_obj_->untag()->CheckHeapPointerStore(value, thread_);
        }
      }
    }
  }

#if defined(DART_COMPRESSED_POINTERS)
  void VisitCompressedPointers(uword heap_base,
                               CompressedObjectPtr* from,
                               CompressedObjectPtr* to) override {
    if (old_obj_->IsArray()) {
      for (CompressedObjectPtr* slot = from; slot <= to; ++slot) {
        ObjectPtr value = slot->Decompress(heap_base);
        if (value->IsHeapObject()) {
          old_obj_->untag()->CheckArrayPointerStore(slot, value, thread_);
        }
      }
    } else {
      for (CompressedObjectPtr* slot = from; slot <= to; ++slot) {
        ObjectPtr value = slot->Decompress(heap_base);
        if (value->IsHeapObject()) {
          old_obj_->untag()->CheckHeapPointerStore(value, thread_);
        }
      }
    }
  }
#endif

 private:
  Thread* thread_;
  ObjectPtr old_obj_;

  DISALLOW_COPY_AND_ASSIGN(WriteBarrierUpdateVisitor);
};

#if defined(DEBUG)
bool Object::IsZoneHandle() const {
  return VMHandles::IsZoneHandle(reinterpret_cast<uword>(this));
}

bool Object::IsReadOnlyHandle() const {
  return Roots::IsReadOnlyHandle(reinterpret_cast<uword>(this));
}

bool Object::IsNotTemporaryScopedHandle() const {
  return (IsZoneHandle() || IsReadOnlyHandle());
}
#endif

ObjectPtr Object::Clone(const Object& orig,
                        Heap::Space space,
                        bool load_with_relaxed_atomics) {
  ASSERT(orig.ptr()->IsHeapObject());
  // Generic function types should be cloned with FunctionType::Clone.
  ASSERT(!orig.IsFunctionType() || !FunctionType::Cast(orig).IsGeneric());
  const Class& cls = Class::Handle(orig.clazz());
  intptr_t size = orig.ptr()->untag()->HeapSize();
  // All fields (including non-SmiPtr fields) will be initialized with Smi 0,
  // but the contents of the original object are copied over before the thread
  // is allowed to reach a safepoint.
  ObjectPtr raw_clone =
      Object::Allocate(cls.id(), size, space, cls.HasCompressedPointers(),
                       from_offset<Object>(), to_offset<Object>());
  NoSafepointScope no_safepoint;
  // Copy the body of the original into the clone.
  uword orig_addr = UntaggedObject::ToAddr(orig.ptr());
  uword clone_addr = UntaggedObject::ToAddr(raw_clone);
  const intptr_t kHeaderSizeInBytes = sizeof(UntaggedObject);
  if (load_with_relaxed_atomics) {
    auto orig_atomics_ptr = reinterpret_cast<std::atomic<uword>*>(orig_addr);
    auto clone_ptr = reinterpret_cast<uword*>(clone_addr);
    for (intptr_t i = kHeaderSizeInBytes / kWordSize; i < size / kWordSize;
         i++) {
      *(clone_ptr + i) =
          (orig_atomics_ptr + i)->load(std::memory_order_relaxed);
    }
  } else {
    memmove(reinterpret_cast<uint8_t*>(clone_addr + kHeaderSizeInBytes),
            reinterpret_cast<uint8_t*>(orig_addr + kHeaderSizeInBytes),
            size - kHeaderSizeInBytes);
  }

  if (IsTypedDataClassId(raw_clone->GetClassIdOfHeapObject())) {
    auto raw_typed_data = TypedData::RawCast(raw_clone);
    raw_typed_data.untag()->RecomputeDataField();
  }

  // Add clone to store buffer, if needed.
  if (!raw_clone->IsOldObject()) {
    // No need to remember an object in new space.
    return raw_clone;
  }
  WriteBarrierUpdateVisitor visitor(Thread::Current(), raw_clone);
  raw_clone->untag()->VisitPointers(&visitor);
  return raw_clone;
}

bool Class::HasCompressedPointers() const {
  const intptr_t cid = id();
  switch (cid) {
    case kByteBufferCid:
      return ByteBuffer::ContainsCompressedPointers();
#define HANDLE_CASE(clazz)                                                     \
  case k##clazz##Cid:                                                          \
    return dart::clazz::ContainsCompressedPointers();
      CLASS_LIST(HANDLE_CASE)
#undef HANDLE_CASE
#define HANDLE_CASE(clazz)                                                     \
  case kTypedData##clazz##Cid:                                                 \
    return dart::TypedData::ContainsCompressedPointers();                      \
  case kTypedData##clazz##ViewCid:                                             \
  case kUnmodifiableTypedData##clazz##ViewCid:                                 \
    return dart::TypedDataView::ContainsCompressedPointers();                  \
  case kExternalTypedData##clazz##Cid:                                         \
    return dart::ExternalTypedData::ContainsCompressedPointers();
      CLASS_LIST_TYPED_DATA(HANDLE_CASE)
#undef HANDLE_CASE
    default:
      if (cid >= kNumPredefinedCids) {
        return dart::Instance::ContainsCompressedPointers();
      }
  }
  FATAL("Unsupported class for compressed pointers translation: %s (id=%" Pd
        ", kNumPredefinedCids=%" Pd ")\n",
        ToCString(), cid, kNumPredefinedCids);
  return false;
}

StringPtr Class::Name() const {
  return untag()->name();
}

StringPtr Class::ScrubbedName() const {
  return Symbols::New(Thread::Current(), ScrubbedNameCString());
}

const char* Class::ScrubbedNameCString() const {
  return String::ScrubName(String::Handle(Name()));
}

StringPtr Class::UserVisibleName() const {
#if !defined(PRODUCT)
  ASSERT(untag()->user_name() != String::null());
  return untag()->user_name();
#endif  // !defined(PRODUCT)
  // No caching in PRODUCT, regenerate.
  return Symbols::New(Thread::Current(), GenerateUserVisibleName());
}

const char* Class::UserVisibleNameCString() const {
#if !defined(PRODUCT)
  ASSERT(untag()->user_name() != String::null());
  return String::Handle(untag()->user_name()).ToCString();
#endif                               // !defined(PRODUCT)
  return GenerateUserVisibleName();  // No caching in PRODUCT, regenerate.
}

const char* Class::NameCString(NameVisibility name_visibility) const {
  switch (name_visibility) {
    case Object::kInternalName:
      return String::Handle(Name()).ToCString();
    case Object::kScrubbedName:
      return ScrubbedNameCString();
    case Object::kUserVisibleName:
      return UserVisibleNameCString();
    default:
      UNREACHABLE();
      return nullptr;
  }
}

ClassPtr Class::Mixin() const {
  if (is_transformed_mixin_application()) {
    const Array& interfaces = Array::Handle(this->interfaces());
    ASSERT(interfaces.Length() > 0);
    const Type& mixin_type =
        Type::Handle(Type::RawCast(interfaces.At(interfaces.Length() - 1)));
    return mixin_type.type_class();
  }
  return ptr();
}

TypePtr Class::RareType() const {
  if (!IsGeneric()) {
    return DeclarationType();
  }
  ASSERT(is_declaration_loaded());
  Thread* const thread = Thread::Current();
  Zone* const zone = thread->zone();
  const auto& inst_to_bounds =
      TypeArguments::Handle(zone, DefaultTypeArguments(zone));
  ASSERT(inst_to_bounds.ptr() != Object::empty_type_arguments().ptr());
  auto& type = Type::Handle(
      zone, Type::New(*this, inst_to_bounds, Nullability::kNonNullable));
  type ^= ClassFinalizer::FinalizeType(type);
  return type.ptr();
}

template <class FakeObject, class TargetFakeObject>
ClassPtr Class::New(IsolateGroup* isolate_group, bool register_class) {
  ASSERT(isolate_group->class_table()->At(kClassCid) != Class::null());
  const auto& result = Class::Handle(Object::Allocate<Class>(Heap::kOld));
  Object::VerifyBuiltinVtable<FakeObject>(FakeObject::kClassId);
  NOT_IN_PRECOMPILED(result.set_token_pos(TokenPosition::kNoSource));
  NOT_IN_PRECOMPILED(result.set_end_token_pos(TokenPosition::kNoSource));
  result.set_instance_size(FakeObject::InstanceSize(),
                           compiler::target::RoundedAllocationSize(
                               TargetFakeObject::InstanceSize()));
  result.set_type_arguments_field_offset_in_words(kNoTypeArguments,
                                                  RTN::Class::kNoTypeArguments);
  const intptr_t host_next_field_offset = FakeObject::NextFieldOffset();
  const intptr_t target_next_field_offset = TargetFakeObject::NextFieldOffset();
  result.set_next_field_offset(host_next_field_offset,
                               target_next_field_offset);
  COMPILE_ASSERT((FakeObject::kClassId != kInstanceCid));
  result.set_id(FakeObject::kClassId);
  NOT_IN_PRECOMPILED(result.set_implementor_cid(kIllegalCid));
  result.set_num_type_arguments_unsafe(0);
  result.set_num_native_fields(0);
  result.set_state_bits(0);
  if (IsInternalOnlyClassId(FakeObject::kClassId) ||
      (FakeObject::kClassId == kTypeArgumentsCid)) {
    // VM internal classes are done. There is no finalization needed or
    // possible in this case.
    result.set_is_declaration_loaded();
    result.set_is_type_finalized();
    result.set_is_allocate_finalized();
  } else if (FakeObject::kClassId != kClosureCid) {
    // VM backed classes are almost ready: run checks and resolve class
    // references, but do not recompute size.
    result.set_is_prefinalized();
  }
  if (FakeObject::kClassId < kNumPredefinedCids &&
      IsDeeplyImmutableCid(FakeObject::kClassId)) {
    result.set_is_deeply_immutable(true);
  }
  NOT_IN_PRECOMPILED(result.set_kernel_offset(0));
  result.InitEmptyFields();
  if (register_class) {
    isolate_group->class_table()->Register(result);
  }
  return result.ptr();
}

#if !defined(DART_PRECOMPILED_RUNTIME) || defined(DART_DYNAMIC_MODULES)
static void ReportTooManyTypeArguments(const Class& cls) {
  Report::MessageF(Report::kError, Script::Handle(cls.script()),
                   cls.token_pos(), Report::AtLocation,
                   "too many type parameters declared in class '%s' or in its "
                   "super classes",
                   String::Handle(cls.Name()).ToCString());
  UNREACHABLE();
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME) || defined(DART_DYNAMIC_MODULES)

void Class::set_num_type_arguments(intptr_t value) const {
#if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
  UNREACHABLE();
#else
  if (!Utils::IsInt(16, value)) {
    ReportTooManyTypeArguments(*this);
  }
  // We allow concurrent calculation of the number of type arguments. If two
  // threads perform this operation it doesn't matter which one wins.
  DEBUG_ONLY(intptr_t old_value = num_type_arguments());
  DEBUG_ASSERT(old_value == kUnknownNumTypeArguments || old_value == value);
  StoreNonPointer<int16_t, int16_t, std::memory_order_relaxed>(
      &untag()->num_type_arguments_, value);
#endif  // defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
}

void Class::set_num_type_arguments_unsafe(intptr_t value) const {
  StoreNonPointer(&untag()->num_type_arguments_, value);
}

void Class::set_has_pragma(bool value) const {
  set_state_bits(HasPragmaBit::update(value, state_bits()));
}

void Class::set_is_isolate_unsendable(bool value) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(IsIsolateUnsendableBit::update(value, state_bits()));
}

void Class::set_is_isolate_unsendable_due_to_pragma(bool value) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(
      IsIsolateUnsendableDueToPragmaBit::update(value, state_bits()));
}

void Class::set_is_deeply_immutable(bool value) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(IsDeeplyImmutableBit::update(value, state_bits()));
}

void Class::set_is_future_subtype(bool value) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(IsFutureSubtypeBit::update(value, state_bits()));
}

void Class::set_can_be_future(bool value) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(CanBeFutureBit::update(value, state_bits()));
}

void Class::set_has_dynamically_extendable_subtypes(bool value) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(
      HasDynamicallyExtendableSubtypesBit::update(value, state_bits()));
}

// Initialize class fields of type Array with empty array.
void Class::InitEmptyFields() const {
  if (Object::empty_array().ptr() == Array::null()) {
    // The empty array has not been initialized yet.
    return;
  }
  untag()->set_interfaces(Object::empty_array().ptr());
  untag()->set_constants(Object::null_array().ptr());
  set_functions(Object::empty_array());
  set_fields(Object::empty_array());
  set_invocation_dispatcher_cache(Object::empty_array());
}

ArrayPtr Class::OffsetToFieldMap(
    ClassTable* class_table /* = nullptr */) const {
  ASSERT(is_finalized());
  if (untag()->offset_in_words_to_field<std::memory_order_acquire>() ==
      Array::null()) {
    // Even if multiple threads are calling this concurrently, all of them would
    // compute the same array, so we intentionally don't acquire any locks here.
    const intptr_t length = untag()->host_instance_size_in_words_;
    const Array& array = Array::Handle(Array::New(length, Heap::kOld));
    Class& cls = Class::Handle(this->ptr());
    Array& fields = Array::Handle();
    Field& f = Field::Handle();
    while (!cls.IsNull()) {
      fields = cls.fields();
      for (intptr_t i = 0; i < fields.Length(); ++i) {
        f ^= fields.At(i);
        if (f.is_instance()) {
          array.SetAt(f.HostOffset() >> kCompressedWordSizeLog2, f);
        }
      }
      cls = cls.SuperClass(class_table);
    }
    untag()->set_offset_in_words_to_field<std::memory_order_release>(
        array.ptr());
  }
  return untag()->offset_in_words_to_field<std::memory_order_acquire>();
}

bool Class::HasInstanceFields() const {
  const Array& field_array = Array::Handle(fields());
  Field& field = Field::Handle();
  for (intptr_t i = 0; i < field_array.Length(); ++i) {
    field ^= field_array.At(i);
    if (!field.is_static()) {
      return true;
    }
  }
  return false;
}

class FunctionName {
 public:
  FunctionName(const String& name, String* tmp_string)
      : name_(name), tmp_string_(tmp_string) {}
  bool Matches(const Function& function) const {
    if (name_.IsSymbol()) {
      return name_.ptr() == function.name();
    } else {
      *tmp_string_ = function.name();
      return name_.Equals(*tmp_string_);
    }
  }
  intptr_t Hash() const { return name_.Hash(); }

 private:
  const String& name_;
  String* tmp_string_;
};

// Traits for looking up Functions by name.
class ClassFunctionsTraits {
 public:
  static const char* Name() { return "ClassFunctionsTraits"; }
  static bool ReportStats() { return false; }

  // Called when growing the table.
  static bool IsMatch(const Object& a, const Object& b) {
    ASSERT(a.IsFunction() && b.IsFunction());
    // Function objects are always canonical.
    return a.ptr() == b.ptr();
  }
  static bool IsMatch(const FunctionName& name, const Object& obj) {
    return name.Matches(Function::Cast(obj));
  }
  static uword Hash(const Object& key) {
    return String::HashRawSymbol(Function::Cast(key).name());
  }
  static uword Hash(const FunctionName& name) { return name.Hash(); }
};
typedef UnorderedHashSet<ClassFunctionsTraits> ClassFunctionsSet;

void Class::SetFunctions(const Array& value) const {
  ASSERT(!value.IsNull());
  const intptr_t len = value.Length();
#if defined(DEBUG)
  Thread* thread = Thread::Current();
  ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
  if (is_finalized()) {
    Function& function = Function::Handle();
    FunctionType& signature = FunctionType::Handle();
    for (intptr_t i = 0; i < len; ++i) {
      function ^= value.At(i);
      signature = function.signature();
      ASSERT(signature.IsFinalized());
    }
  }
#endif
  set_functions(value);
  if (len >= kFunctionLookupHashThreshold) {
    ClassFunctionsSet set(HashTables::New<ClassFunctionsSet>(len, Heap::kOld));
    Function& func = Function::Handle();
    for (intptr_t i = 0; i < len; ++i) {
      func ^= value.At(i);
      // Verify that all the functions in the array have this class as owner.
      ASSERT(func.Owner() == ptr());
      set.Insert(func);
    }
    untag()->set_functions_hash_table(set.Release().ptr());
  } else {
    untag()->set_functions_hash_table(Array::null());
  }
}

void Class::AddFunction(const Function& function) const {
#if defined(DEBUG)
  Thread* thread = Thread::Current();
  ASSERT(thread->IsDartMutatorThread());
  ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
  ASSERT(!is_finalized() ||
         FunctionType::Handle(function.signature()).IsFinalized());
#endif
  const Array& arr = Array::Handle(functions());
  const Array& new_array =
      Array::Handle(Array::Grow(arr, arr.Length() + 1, Heap::kOld));
  new_array.SetAt(arr.Length(), function);
  set_functions(new_array);
  // Add to hash table, if any.
  const intptr_t new_len = new_array.Length();
  if (new_len == kFunctionLookupHashThreshold) {
    // Transition to using hash table.
    SetFunctions(new_array);
  } else if (new_len > kFunctionLookupHashThreshold) {
    ClassFunctionsSet set(untag()->functions_hash_table());
    set.Insert(function);
    untag()->set_functions_hash_table(set.Release().ptr());
  }
}

intptr_t Class::FindFunctionIndex(const Function& needle) const {
  Thread* thread = Thread::Current();
  if (EnsureIsFinalized(thread) != Error::null()) {
    return -1;
  }
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_FUNCTION_HANDLESCOPE(thread);
  Array& funcs = thread->ArrayHandle();
  Function& function = thread->FunctionHandle();
  funcs = current_functions();
  ASSERT(!funcs.IsNull());
  const intptr_t len = funcs.Length();
  for (intptr_t i = 0; i < len; i++) {
    function ^= funcs.At(i);
    if (needle.ptr() == function.ptr()) {
      return i;
    }
  }
  // No function found.
  return -1;
}

FunctionPtr Class::FunctionFromIndex(intptr_t idx) const {
  const Array& funcs = Array::Handle(current_functions());
  if ((idx < 0) || (idx >= funcs.Length())) {
    return Function::null();
  }
  Function& func = Function::Handle();
  func ^= funcs.At(idx);
  ASSERT(!func.IsNull());
  return func.ptr();
}

FunctionPtr Class::ImplicitClosureFunctionFromIndex(intptr_t idx) const {
  Function& func = Function::Handle(FunctionFromIndex(idx));
  if (func.IsNull() || !func.HasImplicitClosureFunction()) {
    return Function::null();
  }
  func = func.ImplicitClosureFunction();
  ASSERT(!func.IsNull());
  return func.ptr();
}

intptr_t Class::FindImplicitClosureFunctionIndex(const Function& needle) const {
  Thread* thread = Thread::Current();
  if (EnsureIsFinalized(thread) != Error::null()) {
    return -1;
  }
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_FUNCTION_HANDLESCOPE(thread);
  Array& funcs = thread->ArrayHandle();
  Function& function = thread->FunctionHandle();
  funcs = current_functions();
  ASSERT(!funcs.IsNull());
  Function& implicit_closure = Function::Handle(thread->zone());
  const intptr_t len = funcs.Length();
  for (intptr_t i = 0; i < len; i++) {
    function ^= funcs.At(i);
    implicit_closure = function.implicit_closure_function();
    if (implicit_closure.IsNull()) {
      // Skip non-implicit closure functions.
      continue;
    }
    if (needle.ptr() == implicit_closure.ptr()) {
      return i;
    }
  }
  // No function found.
  return -1;
}

intptr_t Class::FindInvocationDispatcherFunctionIndex(
    const Function& needle) const {
  Thread* thread = Thread::Current();
  if (EnsureIsFinalized(thread) != Error::null()) {
    return -1;
  }
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_OBJECT_HANDLESCOPE(thread);
  Array& funcs = thread->ArrayHandle();
  Object& object = thread->ObjectHandle();
  funcs = invocation_dispatcher_cache();
  ASSERT(!funcs.IsNull());
  const intptr_t len = funcs.Length();
  for (intptr_t i = 0; i < len; i++) {
    object = funcs.At(i);
    // The invocation_dispatcher_cache is a table with some entries that
    // are functions.
    if (object.IsFunction()) {
      if (Function::Cast(object).ptr() == needle.ptr()) {
        return i;
      }
    }
  }
  // No function found.
  return -1;
}

FunctionPtr Class::InvocationDispatcherFunctionFromIndex(intptr_t idx) const {
  Thread* thread = Thread::Current();
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_OBJECT_HANDLESCOPE(thread);
  Array& dispatcher_cache = thread->ArrayHandle();
  Object& object = thread->ObjectHandle();
  dispatcher_cache = invocation_dispatcher_cache();
  object = dispatcher_cache.At(idx);
  if (!object.IsFunction()) {
    return Function::null();
  }
  return Function::Cast(object).ptr();
}

void Class::set_state_bits(intptr_t bits) const {
  StoreNonPointer<uint32_t, uint32_t, std::memory_order_release>(
      &untag()->state_bits_, static_cast<uint32_t>(bits));
}

void Class::set_library(const Library& value) const {
  untag()->set_library(value.ptr());
}

void Class::set_type_parameters(const TypeParameters& value) const {
  ASSERT((num_type_arguments() == kUnknownNumTypeArguments) ||
         is_declared_in_bytecode() || is_prefinalized());
  untag()->set_type_parameters(value.ptr());
}

void Class::set_functions(const Array& value) const {
  // Ensure all writes to the [Function]s are visible by the time the array
  // is visible.
  untag()->set_functions<std::memory_order_release>(value.ptr());
}

void Class::set_fields(const Array& value) const {
  // Ensure all writes to the [Field]s are visible by the time the array
  // is visible.
  untag()->set_fields<std::memory_order_release>(value.ptr());
}

void Class::set_invocation_dispatcher_cache(const Array& cache) const {
  // Ensure all writes to the cache are visible by the time the array
  // is visible.
  untag()->set_invocation_dispatcher_cache<std::memory_order_release>(
      cache.ptr());
}

void Class::set_declaration_instance_type_arguments(
    const TypeArguments& value) const {
  ASSERT(value.IsNull() || (value.IsCanonical() && value.IsOld()));
  ASSERT((declaration_instance_type_arguments() == TypeArguments::null()) ||
         (declaration_instance_type_arguments() == value.ptr()));
  untag()->set_declaration_instance_type_arguments<std::memory_order_release>(
      value.ptr());
}

TypeArgumentsPtr Class::GetDeclarationInstanceTypeArguments() const {
  const intptr_t num_type_arguments = NumTypeArguments();
  if (num_type_arguments == 0) {
    return TypeArguments::null();
  }
  if (declaration_instance_type_arguments() != TypeArguments::null()) {
    return declaration_instance_type_arguments();
  }
  Thread* thread = Thread::Current();
  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
  if (declaration_instance_type_arguments() != TypeArguments::null()) {
    return declaration_instance_type_arguments();
  }
  Zone* zone = thread->zone();
  auto& args = TypeArguments::Handle(zone);
  auto& type = AbstractType::Handle(zone);
  const intptr_t num_type_parameters = NumTypeParameters(thread);
  if (num_type_arguments == num_type_parameters) {
    type = DeclarationType();
    args = Type::Cast(type).arguments();
  } else {
    type = super_type();
    const auto& super_args = TypeArguments::Handle(
        zone, Type::Cast(type).GetInstanceTypeArguments(thread));
    if ((num_type_parameters == 0) ||
        (!super_args.IsNull() && (super_args.Length() == num_type_arguments))) {
      args = super_args.ptr();
    } else {
      args = TypeArguments::New(num_type_arguments);
      const intptr_t offset = num_type_arguments - num_type_parameters;
      for (intptr_t i = 0; i < offset; ++i) {
        type = super_args.TypeAtNullSafe(i);
        args.SetTypeAt(i, type);
      }
      type = DeclarationType();
      const auto& decl_args =
          TypeArguments::Handle(zone, Type::Cast(type).arguments());
      for (intptr_t i = 0; i < num_type_parameters; ++i) {
        type = decl_args.TypeAt(i);
        args.SetTypeAt(offset + i, type);
      }
    }
  }
  args = args.Canonicalize(thread);
  set_declaration_instance_type_arguments(args);
  return args.ptr();
}

TypeArgumentsPtr Class::GetInstanceTypeArguments(
    Thread* thread,
    const TypeArguments& type_arguments,
    bool canonicalize) const {
  const intptr_t num_type_arguments = NumTypeArguments();
  if (num_type_arguments == 0) {
    return TypeArguments::null();
  }
  Zone* zone = thread->zone();
  auto& args = TypeArguments::Handle(zone);
  const intptr_t num_type_parameters = NumTypeParameters(thread);
  ASSERT(type_arguments.IsNull() ||
         type_arguments.Length() == num_type_parameters);
  if (num_type_arguments == num_type_parameters) {
    args = type_arguments.ptr();
  } else {
    args = GetDeclarationInstanceTypeArguments();
    if (num_type_parameters == 0) {
      return args.ptr();
    }
    args = args.InstantiateFrom(
        TypeArguments::Handle(
            zone, type_arguments.ToInstantiatorTypeArguments(thread, *this)),
        Object::null_type_arguments(), kAllFree, Heap::kOld);
  }
  if (canonicalize) {
    args = args.Canonicalize(thread);
  }
  return args.ptr();
}

intptr_t Class::NumTypeParameters(Thread* thread) const {
  if (!is_declaration_loaded()) {
    ASSERT(is_prefinalized());
    const intptr_t cid = id();
    if ((cid == kArrayCid) || (cid == kImmutableArrayCid) ||
        (cid == kGrowableObjectArrayCid)) {
      return 1;  // List's type parameter may not have been parsed yet.
    }
    return 0;
  }
  if (type_parameters() == TypeParameters::null()) {
    return 0;
  }
  REUSABLE_TYPE_PARAMETERS_HANDLESCOPE(thread);
  TypeParameters& type_params = thread->TypeParametersHandle();
  type_params = type_parameters();
  return type_params.Length();
}

intptr_t Class::ComputeNumTypeArguments() const {
  ASSERT(is_declaration_loaded());
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  auto isolate_group = thread->isolate_group();
  const intptr_t num_type_params = NumTypeParameters();

  if ((super_type() == AbstractType::null()) ||
      (super_type() == isolate_group->object_store()->object_type())) {
    return num_type_params;
  }

  const auto& sup_type = Type::Handle(zone, super_type());
  const auto& sup_class = Class::Handle(zone, sup_type.type_class());
  const intptr_t sup_class_num_type_args = sup_class.NumTypeArguments();
  if (num_type_params == 0) {
    return sup_class_num_type_args;
  }

  const auto& sup_type_args = TypeArguments::Handle(zone, sup_type.arguments());
  if (sup_type_args.IsNull()) {
    // The super type is raw or the super class is non generic.
    // In either case, overlapping is not possible.
    return sup_class_num_type_args + num_type_params;
  }

  const intptr_t sup_type_args_length = sup_type_args.Length();
  // Determine the maximum overlap of a prefix of the vector consisting of the
  // type parameters of this class with a suffix of the vector consisting of the
  // type arguments of the super type of this class.
  // The number of own type arguments of this class is the number of its type
  // parameters minus the number of type arguments in the overlap.
  // Attempt to overlap the whole vector of type parameters; reduce the size
  // of the vector (keeping the first type parameter) until it fits or until
  // its size is zero.
  auto& sup_type_arg = AbstractType::Handle(zone);
  for (intptr_t num_overlapping_type_args =
           (num_type_params < sup_type_args_length) ? num_type_params
                                                    : sup_type_args_length;
       num_overlapping_type_args > 0; num_overlapping_type_args--) {
    intptr_t i = 0;
    for (; i < num_overlapping_type_args; i++) {
      sup_type_arg = sup_type_args.TypeAt(sup_type_args_length -
                                          num_overlapping_type_args + i);
      ASSERT(!sup_type_arg.IsNull());
      if (!sup_type_arg.IsTypeParameter()) break;
      // The only type parameters appearing in the type arguments of the super
      // type are those declared by this class. Their finalized indices depend
      // on the number of type arguments being computed here. Therefore, they
      // cannot possibly be finalized yet.
      ASSERT(!TypeParameter::Cast(sup_type_arg).IsFinalized());
      if (TypeParameter::Cast(sup_type_arg).index() != i ||
          TypeParameter::Cast(sup_type_arg).IsNullable()) {
        break;
      }
    }
    if (i == num_overlapping_type_args) {
      // Overlap found.
      return sup_class_num_type_args + num_type_params -
             num_overlapping_type_args;
    }
  }
  // No overlap found.
  return sup_class_num_type_args + num_type_params;
}

intptr_t Class::NumTypeArguments() const {
  // Return cached value if already calculated.
  intptr_t num_type_args = num_type_arguments();
  if (num_type_args != kUnknownNumTypeArguments) {
    return num_type_args;
  }

#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
  return 0;
#else
  num_type_args = ComputeNumTypeArguments();
  ASSERT(num_type_args != kUnknownNumTypeArguments);
  set_num_type_arguments(num_type_args);
  return num_type_args;
#endif  // defined(DART_PRECOMPILED_RUNTIME)
}

TypeArgumentsPtr Class::DefaultTypeArguments(Zone* zone) const {
  if (type_parameters() == TypeParameters::null()) {
    return Object::empty_type_arguments().ptr();
  }
  return TypeParameters::Handle(zone, type_parameters()).defaults();
}

ClassPtr Class::SuperClass(ClassTable* class_table /* = nullptr */) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  if (class_table == nullptr) {
    class_table = thread->isolate_group()->class_table();
  }

  if (super_type() == AbstractType::null()) {
    if (id() == kTypeArgumentsCid) {
      // Pretend TypeArguments objects are Dart instances.
      return class_table->At(kInstanceCid);
    }
    return Class::null();
  }
  const AbstractType& sup_type = AbstractType::Handle(zone, super_type());
  const intptr_t type_class_id = sup_type.type_class_id();
  return class_table->At(type_class_id);
}

void Class::set_super_type(const Type& value) const {
  ASSERT(value.IsNull() || !value.IsDynamicType());
  untag()->set_super_type(value.ptr());
}

TypeParameterPtr Class::TypeParameterAt(intptr_t index,
                                        Nullability nullability) const {
  ASSERT(index >= 0 && index < NumTypeParameters());
  TypeParameter& type_param =
      TypeParameter::Handle(TypeParameter::New(*this, 0, index, nullability));
  // Finalize type parameter only if its declaring class is
  // finalized and available in the current class table.
  if (is_type_finalized() && (type_param.parameterized_class() == ptr())) {
    type_param ^= ClassFinalizer::FinalizeType(type_param);
  }
  return type_param.ptr();
}

intptr_t Class::UnboxedFieldSizeInBytesByCid(intptr_t cid) {
  switch (cid) {
    case kDoubleCid:
      return sizeof(UntaggedDouble::value_);
    case kFloat32x4Cid:
      return sizeof(UntaggedFloat32x4::value_);
    case kFloat64x2Cid:
      return sizeof(UntaggedFloat64x2::value_);
    default:
      return sizeof(UntaggedMint::value_);
  }
}

UnboxedFieldBitmap Class::CalculateFieldOffsets() const {
  Array& flds = Array::Handle(fields());
  const Class& super = Class::Handle(SuperClass());
  intptr_t host_offset = 0;
  UnboxedFieldBitmap host_bitmap{};
  // Target offsets might differ if the word size are different
  intptr_t target_offset = 0;
  intptr_t host_type_args_field_offset = kNoTypeArguments;
  intptr_t target_type_args_field_offset = RTN::Class::kNoTypeArguments;
  if (super.IsNull()) {
    host_offset = Instance::NextFieldOffset();
    target_offset = RTN::Instance::NextFieldOffset();
    ASSERT(host_offset > 0);
    ASSERT(target_offset > 0);
  } else {
    ASSERT(super.is_finalized() || super.is_prefinalized());
    host_type_args_field_offset = super.host_type_arguments_field_offset();
    target_type_args_field_offset = super.target_type_arguments_field_offset();
    host_offset = super.host_next_field_offset();
    ASSERT(host_offset > 0);
    target_offset = super.target_next_field_offset();
    ASSERT(target_offset > 0);
    // We should never call CalculateFieldOffsets for native wrapper
    // classes, assert this.
    ASSERT(num_native_fields() == 0);
    const intptr_t num_native_fields = super.num_native_fields();
    set_num_native_fields(num_native_fields);
    if (num_native_fields > 0 || is_isolate_unsendable_due_to_pragma()) {
      set_is_isolate_unsendable(true);
    }

    host_bitmap = IsolateGroup::Current()->class_table()->GetUnboxedFieldsMapAt(
        super.id());
  }
  // If the super class is parameterized, use the same type_arguments field,
  // otherwise, if this class is the first in the super chain to be
  // parameterized, introduce a new type_arguments field.
  if (host_type_args_field_offset == kNoTypeArguments) {
    ASSERT(target_type_args_field_offset == RTN::Class::kNoTypeArguments);
    if (IsGeneric()) {
      // The instance needs a type_arguments field.
      host_type_args_field_offset = host_offset;
      target_type_args_field_offset = target_offset;
      host_offset += kCompressedWordSize;
      target_offset += compiler::target::kCompressedWordSize;
    }
  } else {
    ASSERT(target_type_args_field_offset != RTN::Class::kNoTypeArguments);
  }

  set_type_arguments_field_offset(host_type_args_field_offset,
                                  target_type_args_field_offset);
  ASSERT(host_offset > 0);
  ASSERT(target_offset > 0);
  Field& field = Field::Handle();
  const intptr_t len = flds.Length();
  for (intptr_t i = 0; i < len; i++) {
    field ^= flds.At(i);
    // Offset is computed only for instance fields.
    if (!field.is_static()) {
      ASSERT(field.HostOffset() == 0);
      ASSERT(field.TargetOffset() == 0);
      field.SetOffset(host_offset, target_offset);

      if (field.is_unboxed()) {
        const intptr_t field_size =
            UnboxedFieldSizeInBytesByCid(field.guarded_cid());

        const intptr_t host_num_words = field_size / kCompressedWordSize;
        const intptr_t host_next_offset = host_offset + field_size;
        const intptr_t host_next_position =
            host_next_offset / kCompressedWordSize;

        const intptr_t target_next_offset = target_offset + field_size;
        const intptr_t target_next_position =
            target_next_offset / compiler::target::kCompressedWordSize;

        // The bitmap has fixed length. Checks if the offset position is smaller
        // than its length. If it is not, than the field should be boxed
        if (host_next_position <= UnboxedFieldBitmap::Length() &&
            target_next_position <= UnboxedFieldBitmap::Length()) {
          for (intptr_t j = 0; j < host_num_words; j++) {
            // Activate the respective bit in the bitmap, indicating that the
            // content is not a pointer
            host_bitmap.Set(host_offset / kCompressedWordSize);
            host_offset += kCompressedWordSize;
          }

          ASSERT(host_offset == host_next_offset);
          target_offset = target_next_offset;
        } else {
          // Make the field boxed
          field.set_is_unboxed(false);
          host_offset += kCompressedWordSize;
          target_offset += compiler::target::kCompressedWordSize;
        }
      } else {
        host_offset += kCompressedWordSize;
        target_offset += compiler::target::kCompressedWordSize;
      }
    }
  }

  const intptr_t host_instance_size = RoundedAllocationSize(host_offset);
  const intptr_t target_instance_size =
      compiler::target::RoundedAllocationSize(target_offset);
  if (!Utils::IsInt(32, target_instance_size)) {
    // Many parts of the compiler assume offsets can be represented with
    // int32_t.
    FATAL("Too many fields in %s\n", UserVisibleNameCString());
  }
  set_instance_size(host_instance_size, target_instance_size);
  set_next_field_offset(host_offset, target_offset);
  return host_bitmap;
}

void Class::AddInvocationDispatcher(const String& target_name,
                                    const Array& args_desc,
                                    const Function& dispatcher) const {
  auto thread = Thread::Current();
  ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());

  ASSERT(target_name.ptr() == dispatcher.name());

  DispatcherSet dispatchers(invocation_dispatcher_cache() ==
                                    Array::empty_array().ptr()
                                ? HashTables::New<DispatcherSet>(4, Heap::kOld)
                                : invocation_dispatcher_cache());
  dispatchers.Insert(dispatcher);
  set_invocation_dispatcher_cache(dispatchers.Release());
}

FunctionPtr Class::GetInvocationDispatcher(const String& target_name,
                                           const Array& args_desc,
                                           UntaggedFunction::Kind kind,
                                           bool create_if_absent,
                                           bool is_dynamically_callable) const {
  ASSERT(kind == UntaggedFunction::kNoSuchMethodDispatcher ||
         kind == UntaggedFunction::kInvokeFieldDispatcher ||
         kind == UntaggedFunction::kDynamicInvocationForwarder);
  auto thread = Thread::Current();
  auto Z = thread->zone();
  auto& function = Function::Handle(Z);

  // First we'll try to find it without using locks.
  DispatcherKey key(target_name, args_desc, kind);
  if (invocation_dispatcher_cache() != Array::empty_array().ptr()) {
    DispatcherSet dispatchers(Z, invocation_dispatcher_cache());
    function ^= dispatchers.GetOrNull(key);
    dispatchers.Release();
  }
  if (!function.IsNull() || !create_if_absent) {
    return function.ptr();
  }

  // If we failed to find it and possibly need to create it, use a write lock.
  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());

  // Try to find it again & return if it was added in the meantime.
  if (invocation_dispatcher_cache() != Array::empty_array().ptr()) {
    DispatcherSet dispatchers(Z, invocation_dispatcher_cache());
    function ^= dispatchers.GetOrNull(key);
    dispatchers.Release();
  }
  if (!function.IsNull()) return function.ptr();

  // Otherwise create it & add it.
  function = CreateInvocationDispatcher(target_name, args_desc, kind,
                                        is_dynamically_callable);
  AddInvocationDispatcher(target_name, args_desc, function);
  return function.ptr();
}

FunctionPtr Class::CreateInvocationDispatcher(
    const String& target_name,
    const Array& args_desc,
    UntaggedFunction::Kind kind,
    bool is_dynamically_callable) const {
  ASSERT(target_name.ptr() != Symbols::DynamicImplicitCall().ptr());
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  FunctionType& signature = FunctionType::Handle(zone, FunctionType::New());
  Function& invocation = Function::Handle(
      zone, Function::New(
                signature,
                String::Handle(zone, Symbols::New(thread, target_name)), kind,
                false,  // Not static.
                false,  // Not const.
                false,  // Not abstract.
                false,  // Not external.
                false,  // Not native.
                *this, TokenPosition::kMinSource));
  ArgumentsDescriptor desc(args_desc);
  const intptr_t type_args_len = desc.TypeArgsLen();
  if (type_args_len > 0) {
    // Make dispatcher function generic, since type arguments are passed.
    const auto& type_parameters =
        TypeParameters::Handle(zone, TypeParameters::New(type_args_len));
    // Allow any type, as any type checking is compiled into the dispatcher.
    auto& bound = Type::Handle(
        zone, IsolateGroup::Current()->object_store()->nullable_object_type());
    for (intptr_t i = 0; i < type_args_len; i++) {
      // The name of the type parameter does not matter, as a type error using
      // it should never be thrown.
      type_parameters.SetNameAt(i, Symbols::OptimizedOut());
      type_parameters.SetBoundAt(i, bound);
      // Type arguments will always be provided, so the default is not used.
      type_parameters.SetDefaultAt(i, Object::dynamic_type());
    }
    signature.SetTypeParameters(type_parameters);
  }

  signature.set_num_fixed_parameters(desc.PositionalCount());
  signature.SetNumOptionalParameters(desc.NamedCount(),
                                     false);  // Not positional.
  signature.set_parameter_types(
      Array::Handle(zone, Array::New(desc.Count(), Heap::kOld)));
  invocation.CreateNameArray();
  signature.CreateNameArrayIncludingFlags();
  // Receiver.
  signature.SetParameterTypeAt(0, Object::dynamic_type());
  invocation.SetParameterNameAt(0, Symbols::This());
  // Remaining positional parameters.
  for (intptr_t i = 1; i < desc.PositionalCount(); i++) {
    signature.SetParameterTypeAt(i, Object::dynamic_type());
    char name[64];
    Utils::SNPrint(name, 64, ":p%" Pd, i);
    invocation.SetParameterNameAt(
        i, String::Handle(zone, Symbols::New(thread, name)));
  }

  // Named parameters.
  for (intptr_t i = 0; i < desc.NamedCount(); i++) {
    const intptr_t param_index = desc.PositionAt(i);
    const auto& param_name = String::Handle(zone, desc.NameAt(i));
    signature.SetParameterTypeAt(param_index, Object::dynamic_type());
    signature.SetParameterNameAt(param_index, param_name);
  }
  signature.FinalizeNameArray();
  signature.set_result_type(Object::dynamic_type());
  invocation.set_is_debuggable(false);
  invocation.set_is_visible(false);
  invocation.set_is_reflectable(false);
  invocation.set_saved_args_desc(args_desc);
  invocation.set_is_dynamically_callable(is_dynamically_callable);

  signature ^= ClassFinalizer::FinalizeType(signature);
  invocation.SetSignature(signature);

#if defined(DART_DYNAMIC_MODULES)
#if defined(DART_PRECOMPILED_RUNTIME)
  const bool attach_bytecode = true;
#else
  const bool attach_bytecode = is_declared_in_bytecode();
#endif
  if (attach_bytecode) {
    switch (kind) {
      case UntaggedFunction::kNoSuchMethodDispatcher:
        invocation.AttachBytecode(Object::nsm_dispatcher_bytecode());
        break;
      case UntaggedFunction::kInvokeFieldDispatcher:
        invocation.AttachBytecode(Object::invoke_field_bytecode());
        break;
      default:
        UNREACHABLE();
    }
  }
#endif  // defined(DART_DYNAMIC_MODULES)

  return invocation.ptr();
}

// Method extractors are used to create implicit closures from methods.
// When an expression obj.M is evaluated for the first time and receiver obj
// does not have a getter called M but has a method called M then an extractor
// is created and injected as a getter (under the name get:M) into the class
// owning method M.
FunctionPtr Function::CreateMethodExtractor(const String& getter_name) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  ASSERT(Field::IsGetterName(getter_name));
  const Function& closure_function =
      Function::Handle(zone, ImplicitClosureFunction());

  const Class& owner = Class::Handle(zone, closure_function.Owner());
  FunctionType& signature = FunctionType::Handle(zone, FunctionType::New());
  const Function& extractor = Function::Handle(
      zone,
      Function::New(signature,
                    String::Handle(zone, Symbols::New(thread, getter_name)),
                    UntaggedFunction::kMethodExtractor,
                    false,  // Not static.
                    false,  // Not const.
                    is_abstract(),
                    false,  // Not external.
                    false,  // Not native.
                    owner, TokenPosition::kMethodExtractor));

  // Initialize signature: receiver is a single fixed parameter.
  const intptr_t kNumParameters = 1;
  signature.set_num_fixed_parameters(kNumParameters);
  signature.SetNumOptionalParameters(0, false);
  signature.set_parameter_types(Object::synthetic_getter_parameter_types());
#if !defined(DART_PRECOMPILED_RUNTIME)
  extractor.set_positional_parameter_names(
      Object::synthetic_getter_parameter_names());
#endif
  signature.set_result_type(Object::dynamic_type());

  extractor.InheritKernelOffsetFrom(*this);

  extractor.set_extracted_method_closure(closure_function);
  extractor.set_is_debuggable(false);
  extractor.set_is_visible(false);
#if !defined(DART_PRECOMPILED_RUNTIME)
  extractor.SetIsDynamicallyOverridden(IsDynamicallyOverridden());
#endif

  signature ^= ClassFinalizer::FinalizeType(signature);
  extractor.SetSignature(signature);

#if defined(DART_DYNAMIC_MODULES)
#if defined(DART_PRECOMPILED_RUNTIME)
  const bool attach_bytecode = true;
#else
  const bool attach_bytecode = is_declared_in_bytecode();
#endif
  if (attach_bytecode) {
    // Bytecode method extractor may be used to create a closure instance
    // with a compiled closure function, so layout of the closure object
    // has to be compatible between them.
    // 'Closure::HasInstantiatorTypeArgumentsField' involves a heavy signature
    // type inspection which is not feasible to perform in the method
    // extractor. So it is performed here to select a variant of the bytecode
    // method extractor.
    if (Closure::HasInstantiatorTypeArgumentsField(closure_function)) {
      extractor.AttachBytecode(Object::method_extractor_with_ita_bytecode());
    } else {
      extractor.AttachBytecode(Object::method_extractor_without_ita_bytecode());
    }
  }
#endif  // defined(DART_DYNAMIC_MODULES)

  owner.AddFunction(extractor);

  return extractor.ptr();
}

FunctionPtr Function::GetMethodExtractor(const String& getter_name) const {
  ASSERT(Field::IsGetterName(getter_name));
  const Function& closure_function =
      Function::Handle(ImplicitClosureFunction());
  const Class& owner = Class::Handle(closure_function.Owner());
  Thread* thread = Thread::Current();
  if (owner.EnsureIsFinalized(thread) != Error::null()) {
    return Function::null();
  }
  IsolateGroup* group = thread->isolate_group();
  Function& result = Function::Handle(
      Resolver::ResolveDynamicFunction(thread->zone(), owner, getter_name));
  if (result.IsNull()) {
    SafepointWriteRwLocker ml(thread, group->program_lock());
    result = owner.LookupDynamicFunctionUnsafe(getter_name);
    if (result.IsNull()) {
      result = CreateMethodExtractor(getter_name);
    }
  }
  ASSERT(result.kind() == UntaggedFunction::kMethodExtractor);
  return result.ptr();
}

// Record field getters are used to access fields of arbitrary
// record instances dynamically.
FunctionPtr Class::CreateRecordFieldGetter(const String& getter_name) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  ASSERT(IsRecordClass());
  ASSERT(Field::IsGetterName(getter_name));
  FunctionType& signature = FunctionType::Handle(zone, FunctionType::New());
  const Function& getter = Function::Handle(
      zone,
      Function::New(signature,
                    String::Handle(zone, Symbols::New(thread, getter_name)),
                    UntaggedFunction::kRecordFieldGetter,
                    false,  // Not static.
                    false,  // Not const.
                    false,  // Not abstract.
                    false,  // Not external.
                    false,  // Not native.
                    *this, TokenPosition::kMinSource));

  // Initialize signature: receiver is a single fixed parameter.
  const intptr_t kNumParameters = 1;
  signature.set_num_fixed_parameters(kNumParameters);
  signature.SetNumOptionalParameters(0, false);
  signature.set_parameter_types(Object::synthetic_getter_parameter_types());
#if !defined(DART_PRECOMPILED_RUNTIME)
  getter.set_positional_parameter_names(
      Object::synthetic_getter_parameter_names());
#endif
  signature.set_result_type(Object::dynamic_type());

  getter.set_is_debuggable(false);
  getter.set_is_visible(false);

  signature ^= ClassFinalizer::FinalizeType(signature);
  getter.SetSignature(signature);

  AddFunction(getter);

  return getter.ptr();
}

FunctionPtr Class::GetRecordFieldGetter(const String& getter_name) const {
  ASSERT(IsRecordClass());
  ASSERT(Field::IsGetterName(getter_name));
  Thread* thread = Thread::Current();
  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
  Function& result = Function::Handle(thread->zone(),
                                      LookupDynamicFunctionUnsafe(getter_name));
  if (result.IsNull()) {
    result = CreateRecordFieldGetter(getter_name);
  }
  ASSERT(result.kind() == UntaggedFunction::kRecordFieldGetter);
  return result.ptr();
}

bool FindPragmaInMetadata(Thread* T,
                          const Object& metadata_obj,
                          const String& pragma_name,
                          bool multiple,
                          Object* options) {
  auto IG = T->isolate_group();
  auto Z = T->zone();

  // If there is a compile-time error while evaluating the metadata, we will
  // simply claim there was no @pragma annotation.
  if (metadata_obj.IsNull() || metadata_obj.IsLanguageError()) {
    return false;
  }
  ASSERT(metadata_obj.IsArray());

  auto& metadata = Array::Cast(metadata_obj);
  auto& pragma_class = Class::Handle(Z, IG->object_store()->pragma_class());
  if (pragma_class.IsNull()) {
    // Precompiler may drop pragma class.
    return false;
  }
  auto& pragma_name_field =
      Field::Handle(Z, pragma_class.LookupField(Symbols::name()));
  auto& pragma_options_field =
      Field::Handle(Z, pragma_class.LookupField(Symbols::options()));

  auto& pragma = Object::Handle(Z);
  bool found = false;
  auto& options_value = Object::Handle(Z);
  auto& results = GrowableObjectArray::Handle(Z);
  if (multiple) {
    ASSERT(options != nullptr);
    results ^= GrowableObjectArray::New(1);
  }
  for (intptr_t i = 0; i < metadata.Length(); ++i) {
    pragma = metadata.At(i);
    if (pragma.clazz() != pragma_class.ptr() ||
        Instance::Cast(pragma).GetField(pragma_name_field) !=
            pragma_name.ptr()) {
      continue;
    }
    options_value = Instance::Cast(pragma).GetField(pragma_options_field);
    found = true;
    if (multiple) {
      results.Add(options_value);
      continue;
    }
    if (options != nullptr) {
      *options = options_value.ptr();
    }
    return true;
  }

  if (found && options != nullptr) {
    *options = results.ptr();
  }
  return false;
}

bool Library::FindPragma(Thread* T,
                         bool only_core,
                         const Object& obj,
                         const String& pragma_name,
                         bool multiple,
                         Object* options) {
  auto Z = T->zone();
  auto& lib = Library::Handle(Z);

  if (obj.IsLibrary()) {
    lib = Library::Cast(obj).ptr();
  } else if (obj.IsClass()) {
    auto& klass = Class::Cast(obj);
    if (!klass.has_pragma()) return false;
    lib = klass.library();
  } else if (obj.IsFunction()) {
    auto& function = Function::Cast(obj);
    if (!function.has_pragma()) return false;
    lib = Class::Handle(Z, function.Owner()).library();
  } else if (obj.IsField()) {
    auto& field = Field::Cast(obj);
    if (!field.has_pragma()) return false;
    lib = Class::Handle(Z, field.Owner()).library();
  } else {
    UNREACHABLE();
  }

  if (only_core && !lib.IsAnyCoreLibrary()) {
    return false;
  }

  Object& metadata_obj = Object::Handle(Z, lib.GetMetadata(obj));
  if (metadata_obj.IsUnwindError()) {
    Report::LongJump(UnwindError::Cast(metadata_obj));
  }

  return FindPragmaInMetadata(T, metadata_obj, pragma_name, multiple, options);
}

bool Function::IsDynamicInvocationForwarderName(const String& name) {
  return IsDynamicInvocationForwarderName(name.ptr());
}

bool Function::IsDynamicInvocationForwarderName(StringPtr name) {
  return String::StartsWith(name, Symbols::DynamicPrefix().ptr());
}

StringPtr Function::DemangleDynamicInvocationForwarderName(const String& name) {
  if (name.ptr() == Symbols::DynamicImplicitCall().ptr()) {
    return Symbols::call().ptr();
  }
  const intptr_t kDynamicPrefixLength = 4;  // "dyn:"
  ASSERT(Symbols::DynamicPrefix().Length() == kDynamicPrefixLength);
  return Symbols::New(Thread::Current(), name, kDynamicPrefixLength,
                      name.Length() - kDynamicPrefixLength);
}

const String& Function::DropImplicitCallPrefix(const String& name) {
  if (name.ptr() == Symbols::DynamicImplicitCall().ptr()) {
    return Symbols::DynamicCall();
  }
  return name;
}

StringPtr Function::CreateDynamicInvocationForwarderName(const String& name) {
  return Symbols::FromConcat(Thread::Current(), Symbols::DynamicPrefix(), name);
}

#if !defined(DART_PRECOMPILED_RUNTIME) || defined(DART_DYNAMIC_MODULES)
FunctionPtr Function::CreateDynamicInvocationForwarder(
    const String& mangled_name) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();

  Function& forwarder = Function::Handle(zone);
  // Load with relaxed atomics to prevent data race with updating original's
  // properties that are overridden below for the copy anyway.
  forwarder ^= Object::Clone(*this, Heap::kOld,
                             /*load_with_relaxed_atomics=*/true);

  forwarder.reset_unboxed_parameters_and_return();

  forwarder.set_name(mangled_name);
  forwarder.set_is_native(false);
  // TODO(dartbug.com/37737): Currently, we intentionally keep the recognized
  // kind when creating the dynamic invocation forwarder.
  forwarder.set_kind(UntaggedFunction::kDynamicInvocationForwarder);
  forwarder.set_modifier(UntaggedFunction::kNoModifier);
  forwarder.set_is_debuggable(false);

  // TODO(vegorov) for error reporting reasons it is better to make this
  // function visible and instead use a TailCall to invoke the target.
  // Our TailCall instruction is not ready for such usage though it
  // blocks inlining and can't take Function-s only Code objects.
  forwarder.set_is_visible(false);

#if defined(DART_DYNAMIC_MODULES)
  if (HasBytecode()) {
    forwarder.ClearBytecode();
  }
#endif
  forwarder.ClearICDataArray();
  forwarder.ClearCode();
#if !defined(DART_PRECOMPILED_RUNTIME)
  forwarder.set_usage_counter(0);
  forwarder.set_deoptimization_counter(0);
  forwarder.set_optimized_instruction_count(0);
  forwarder.set_inlining_depth(0);
  forwarder.set_optimized_call_site_count(0);
#endif

  forwarder.InheritKernelOffsetFrom(*this);
  forwarder.SetForwardingTarget(*this);

#if defined(DART_DYNAMIC_MODULES)
#if defined(DART_PRECOMPILED_RUNTIME)
  // Allow the creation of a lazily created interpreted dynamic invocation
  // forwarders for compiled code that does not already have one created,
  // for example, when the only dynamic callers are in interpreted code.
  const bool attach_bytecode = true;
#else
  const bool attach_bytecode = is_declared_in_bytecode();
#endif
  if (attach_bytecode) {
    forwarder.AttachBytecode(Object::dynamic_invocation_forwarder_bytecode());
  }
#endif

  return forwarder.ptr();
}

FunctionPtr Function::GetDynamicInvocationForwarder(
    const String& mangled_name) const {
  ASSERT(IsDynamicInvocationForwarderName(mangled_name));
  ASSERT(mangled_name.ptr() != Symbols::DynamicImplicitCall().ptr());
  auto thread = Thread::Current();
  auto zone = thread->zone();
  const Class& owner = Class::Handle(zone, Owner());
  Function& result = Function::Handle(zone);

  // First we'll try to find it without using locks.
  result = owner.GetInvocationDispatcher(
      mangled_name, Array::null_array(),
      UntaggedFunction::kDynamicInvocationForwarder,
      /*create_if_absent=*/false);
  if (!result.IsNull()) return result.ptr();

  const bool needs_dyn_forwarder = NeedsDynamicInvocationForwarder();
  if (!needs_dyn_forwarder) {
    return ptr();
  }

  // If we failed to find it and possibly need to create it, use a write lock.
  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());

  // Try to find it again & return if it was added in the mean time.
  result = owner.GetInvocationDispatcher(
      mangled_name, Array::null_array(),
      UntaggedFunction::kDynamicInvocationForwarder,
      /*create_if_absent=*/false);
  if (!result.IsNull()) return result.ptr();

  // Otherwise create it & add it.
  result = CreateDynamicInvocationForwarder(mangled_name);
  owner.AddInvocationDispatcher(mangled_name, Array::null_array(), result);
  return result.ptr();
}

bool Function::NeedsDynamicInvocationForwarder() const {
  Zone* zone = Thread::Current()->zone();

  // Right now closures do not need a dyn:* forwarder.
  // See https://github.com/dart-lang/sdk/issues/40813
  if (IsClosureFunction()) return false;

  // Method extractors have no parameters to check and return value is a closure
  // and therefore not an unboxed primitive type.
  if (IsMethodExtractor()) {
    return false;
  }

  // Record field getters have no parameters to check and 'dynamic' return type.
  if (IsRecordFieldGetter()) {
    return false;
  }

  // Invoke field dispatchers are dynamically generated, will invoke a getter to
  // obtain the field value and then invoke ".call()" on the result.
  // Those dynamically generated dispathers don't have proper kernel metadata
  // associated with them - we can therefore not query if there are dynamic
  // calls to them or not and are therefore conservative.
  if (IsInvokeFieldDispatcher()) {
    return true;
  }

#if !defined(DART_PRECOMPILED_RUNTIME)
  // The dyn:* forwarders perform unboxing of parameters before calling the
  // actual target (which accepts unboxed parameters) and boxes return values
  // of the return value.
  if (HasUnboxedParameters() || HasUnboxedReturnValue()) {
    return true;
  }

  if (MaxNumberOfParametersInRegisters(zone) > 0) {
    return true;
  }
#endif

  // There are no parameters to type check for getters and if the return value
  // is boxed, then the dyn:* forwarder is not needed.
  if (IsImplicitGetterFunction()) {
    return false;
  }

  // Covariant parameters (both explicitly covariant and generic-covariant-impl)
  // are checked in the body of a function and therefore don't need checks in a
  // dynamic invocation forwarder. So dynamic invocation forwarder is only
  // needed if there are non-covariant parameters of non-top type.
  if (IsImplicitSetterFunction()) {
    const auto& field = Field::Handle(zone, accessor_field());
    return !(field.is_covariant() || field.is_generic_covariant_impl());
  }

  const auto& type_params = TypeParameters::Handle(zone, type_parameters());
  if (!type_params.IsNull()) {
    auto& bound = AbstractType::Handle(zone);
    for (intptr_t i = 0, n = type_params.Length(); i < n; ++i) {
      bound = type_params.BoundAt(i);
      if (!bound.IsTopTypeForSubtyping() &&
          !type_params.IsGenericCovariantImplAt(i)) {
        return true;
      }
    }
  }

  const intptr_t num_params = NumParameters();
  BitVector is_covariant(zone, num_params);
  BitVector is_generic_covariant_impl(zone, num_params);
  ReadParameterCovariance(&is_covariant, &is_generic_covariant_impl);

  auto& type = AbstractType::Handle(zone);
  for (intptr_t i = NumImplicitParameters(); i < num_params; ++i) {
    type = ParameterTypeAt(i);
    if (!type.IsTopTypeForSubtyping() &&
        !is_generic_covariant_impl.Contains(i) && !is_covariant.Contains(i)) {
      return true;
    }
  }

  return false;
}

void Function::ReadParameterCovariance(
    BitVector* is_covariant,
    BitVector* is_generic_covariant_impl) const {
#if defined(DART_DYNAMIC_MODULES)
  if (is_declared_in_bytecode()) {
    bytecode::BytecodeReader::ReadParameterCovariance(
        *this, is_covariant, is_generic_covariant_impl);
    return;
  }
#endif
#if !defined(DART_PRECOMPILED_RUNTIME)
  kernel::ReadParameterCovariance(*this, is_covariant,
                                  is_generic_covariant_impl);
#endif
}

#endif

bool AbstractType::InstantiateAndTestSubtype(
    AbstractType* subtype,
    AbstractType* supertype,
    const TypeArguments& instantiator_type_args,
    const TypeArguments& function_type_args) {
  if (!subtype->IsInstantiated()) {
    *subtype = subtype->InstantiateFrom(
        instantiator_type_args, function_type_args, kAllFree, Heap::kOld);
  }
  if (!supertype->IsInstantiated()) {
    *supertype = supertype->InstantiateFrom(
        instantiator_type_args, function_type_args, kAllFree, Heap::kOld);
  }
  return subtype->IsSubtypeOf(*supertype, Heap::kOld);
}

ArrayPtr Class::invocation_dispatcher_cache() const {
  return untag()->invocation_dispatcher_cache<std::memory_order_acquire>();
}

void Class::Finalize() const {
  auto thread = Thread::Current();
  auto isolate_group = thread->isolate_group();
  ASSERT(!thread->isolate_group()->all_classes_finalized());
  ASSERT(!is_finalized());
  // Prefinalized classes have a VM internal representation and no Dart fields.
  // Their instance size  is precomputed and field offsets are known.
  if (!is_prefinalized()) {
    // Compute offsets of instance fields, instance size and bitmap for unboxed
    // fields.
    const auto host_bitmap = CalculateFieldOffsets();
    if (ptr() == isolate_group->class_table()->At(id())) {
      if (!ClassTable::IsTopLevelCid(id())) {
        // Unless class is top-level, which don't get instantiated,
        // sets the new size in the class table.
        isolate_group->class_table()->UpdateClassSize(id(), ptr());
        isolate_group->class_table()->SetUnboxedFieldsMapAt(id(), host_bitmap);
      }
    }
  }

#if defined(DEBUG)
  if (is_const()) {
    // Double-check that all fields are final (CFE should guarantee that if it
    // marks the class as having a constant constructor).
    auto Z = thread->zone();
    const auto& super_class = Class::Handle(Z, SuperClass());
    ASSERT(super_class.IsNull() || super_class.is_const());
    const auto& fields = Array::Handle(Z, this->fields());
    auto& field = Field::Handle(Z);
    for (intptr_t i = 0; i < fields.Length(); ++i) {
      field ^= fields.At(i);
      ASSERT(field.is_static() || field.is_final());
    }
  }
#endif

  set_is_finalized();
}

#if defined(DEBUG)
static bool IsMutatorOrAtDeoptSafepoint() {
  Thread* thread = Thread::Current();
  return thread->IsDartMutatorThread() || thread->OwnsDeoptSafepoint();
}
#endif

#if !defined(DART_PRECOMPILED_RUNTIME)

class CHACodeArray : public WeakCodeReferences {
 public:
  explicit CHACodeArray(const Class& cls)
      : WeakCodeReferences(WeakArray::Handle(cls.dependent_code())),
        cls_(cls) {}

  virtual void UpdateArrayTo(const WeakArray& value) {
    cls_.set_dependent_code(value);
  }

  virtual void ReportDeoptimization(const Code& code) {
    if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) {
      Function& function = Function::Handle(code.function());
      THR_Print("Deoptimizing %s because CHA optimized (%s).\n",
                function.ToFullyQualifiedCString(), cls_.ToCString());
    }
  }

  virtual void ReportSwitchingCode(const Code& code) {
    if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) {
      Function& function = Function::Handle(code.function());
      THR_Print(
          "Switching %s to unoptimized code because CHA invalid"
          " (%s)\n",
          function.ToFullyQualifiedCString(), cls_.ToCString());
    }
  }

 private:
  const Class& cls_;
  DISALLOW_COPY_AND_ASSIGN(CHACodeArray);
};

void Class::RegisterCHACode(const Code& code) {
  if (FLAG_trace_cha) {
    THR_Print("RegisterCHACode '%s' depends on class '%s'\n",
              Function::Handle(code.function()).ToQualifiedCString(),
              ToCString());
  }
  DEBUG_ASSERT(IsMutatorOrAtDeoptSafepoint());
  ASSERT(code.is_optimized());
  CHACodeArray a(*this);
  a.Register(code);
}

void Class::DisableCHAOptimizedCode(const Class& subclass) {
  DEBUG_ASSERT(
      IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  CHACodeArray a(*this);
  if (FLAG_trace_deoptimization && a.HasCodes()) {
    if (subclass.IsNull()) {
      THR_Print("Deopt for CHA (all)\n");
    } else {
      THR_Print("Deopt for CHA (new subclass %s)\n", subclass.ToCString());
    }
  }
  a.DisableCode(/*are_mutators_stopped=*/false);
}

void Class::DisableAllCHAOptimizedCode() {
  DisableCHAOptimizedCode(Class::Handle());
}

WeakArrayPtr Class::dependent_code() const {
  DEBUG_ASSERT(
      IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
  return untag()->dependent_code();
}

void Class::set_dependent_code(const WeakArray& array) const {
  DEBUG_ASSERT(
      IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  untag()->set_dependent_code(array.ptr());
}

#endif  // !defined(DART_PRECOMPILED_RUNTIME)

bool Class::TraceAllocation(IsolateGroup* isolate_group) const {
#ifndef PRODUCT
  auto class_table = isolate_group->class_table();
  return class_table->ShouldTraceAllocationFor(id());
#else
  return false;
#endif
}

void Class::SetTraceAllocation(bool trace_allocation) const {
#ifndef PRODUCT
  auto isolate_group = IsolateGroup::Current();
  const bool changed = trace_allocation != this->TraceAllocation(isolate_group);
  if (changed) {
    auto class_table = isolate_group->class_table();
    class_table->SetTraceAllocationFor(id(), trace_allocation);
#ifdef TARGET_ARCH_IA32
    DisableAllocationStub();
#endif
  }
#else
  UNREACHABLE();
#endif
}

// Conventions:
// * For throwing a NSM in a library or top-level class (i.e., level is
//   kTopLevel), if a method was found but was incompatible, we pass the
//   signature of the found method as a string, otherwise the null instance.
// * Otherwise, for throwing a NSM in a class klass we use its runtime type as
//   receiver, i.e., klass.RareType().
static ObjectPtr ThrowNoSuchMethod(const Instance& receiver,
                                   const String& function_name,
                                   const Array& arguments,
                                   const Array& argument_names,
                                   const InvocationMirror::Level level,
                                   const InvocationMirror::Kind kind) {
  const Smi& invocation_type =
      Smi::Handle(Smi::New(InvocationMirror::EncodeType(level, kind)));

  ASSERT(!receiver.IsNull() || level == InvocationMirror::Level::kTopLevel);
  ASSERT(level != InvocationMirror::Level::kTopLevel || receiver.IsString());
  const Array& args = Array::Handle(Array::New(7));
  args.SetAt(0, receiver);
  args.SetAt(1, function_name);
  args.SetAt(2, invocation_type);
  args.SetAt(3, Object::smi_zero());  // Type arguments length.
  args.SetAt(4, Object::null_type_arguments());
  args.SetAt(5, arguments);
  args.SetAt(6, argument_names);

  const Library& libcore = Library::Handle(Library::CoreLibrary());
  const Class& cls =
      Class::Handle(libcore.LookupClass(Symbols::NoSuchMethodError()));
  ASSERT(!cls.IsNull());
  const auto& error = cls.EnsureIsFinalized(Thread::Current());
  ASSERT(error == Error::null());
  const Function& throwNew =
      Function::Handle(cls.LookupFunctionAllowPrivate(Symbols::ThrowNew()));
  return DartEntry::InvokeFunction(throwNew, args);
}

static ObjectPtr ThrowTypeError(const TokenPosition token_pos,
                                const Instance& src_value,
                                const AbstractType& dst_type,
                                const String& dst_name) {
  const Array& args = Array::Handle(Array::New(4));
  const Smi& pos = Smi::Handle(Smi::New(token_pos.Serialize()));
  args.SetAt(0, pos);
  args.SetAt(1, src_value);
  args.SetAt(2, dst_type);
  args.SetAt(3, dst_name);

  const Library& libcore = Library::Handle(Library::CoreLibrary());
  const Class& cls =
      Class::Handle(libcore.LookupClassAllowPrivate(Symbols::TypeError()));
  const auto& error = cls.EnsureIsFinalized(Thread::Current());
  ASSERT(error == Error::null());
  const Function& throwNew =
      Function::Handle(cls.LookupFunctionAllowPrivate(Symbols::ThrowNew()));
  return DartEntry::InvokeFunction(throwNew, args);
}

static bool WriteQualifiedMemberName(Zone* zone,
                                     BaseTextBuffer* buffer,
                                     const Object& member) {
  if (member.IsFunction()) {
    const auto& fun = Function::Cast(member);
    const auto& cls = Class::Handle(zone, fun.Owner());
    if (WriteQualifiedMemberName(zone, buffer, cls)) {
      buffer->AddString(".");
    }
    buffer->AddString(fun.UserVisibleNameCString());
    if (!fun.IsRegularFunction()) {
      buffer->Printf(" (kind %s)", Function::KindToCString(fun.kind()));
    }
  } else if (member.IsField()) {
    const auto& field = Field::Cast(member);
    const auto& cls = Class::Handle(field.Owner());
    if (WriteQualifiedMemberName(zone, buffer, cls)) {
      buffer->AddString(".");
    }
    const auto& name = String::Handle(field.name());
    buffer->AddString(name.ToCString());
  } else if (member.IsClass()) {
    const Class& cls = Class::Cast(member);
    const Library& lib = Library::Handle(cls.library());
    if (!lib.IsNull()) {
      const String& name = String::Handle(lib.url());
      buffer->Printf("%s::", name.ToCString());
    }
    if (!cls.IsTopLevel()) {
      buffer->AddString(cls.UserVisibleNameCString());
    }
    return !cls.IsTopLevel();
  }
  return false;
}

#if defined(DART_PRECOMPILED_RUNTIME)
DART_WARN_UNUSED_RESULT
static bool VerifyEntryPointHelper(const Object& object,
                                   EntryPointPragma expected) {
  // Annotations are discarded in the AOT snapshot, so we can't determine
  // precisely if this member was marked as an entry-point. Instead, we use
  // "has_pragma()" as a proxy, since that bit is usually retained.

  if (object.IsClass()) {
    return Class::Cast(object).has_pragma() &&
           expected == EntryPointPragma::kAlways;
  }
  if (object.IsField()) {
    return Field::Cast(object).has_pragma() &&
           expected != EntryPointPragma::kCallOnly;
  }
  if (!object.IsFunction()) {
    FATAL("Unexpected annotated node %s", object.ToCString());
  }

  const auto& f = Function::Cast(object);
  if (!f.has_pragma()) return false;

  // For non-closurization uses, if the function does not have code
  // attached, that means it was not properly annotated to allow the use.

  if (f.IsGetterFunction()) {
    return EntryPointPragmaUtils::AllowsGet(expected) && f.HasCode();
  }
  if (f.IsSetterFunction()) {
    return EntryPointPragmaUtils::AllowsSet(expected) && f.HasCode();
  }
  if (EntryPointPragmaUtils::AllowsCall(expected)) {
    return f.HasCode();
  }
  if (f.IsConstructor()) {
    // We're not checking for a call, which is the only allowed access.
    return false;
  }
  if (EntryPointPragmaUtils::AllowsGet(expected)) {
    // For non-getter functions, a 'get' entry point expectation denotes
    // closurization. The precompiler saves the implicit closure
    // function information if the function is properly annotated.
    return f.HasImplicitClosureFunction();
  }
  return false;
}
#endif

DART_WARN_UNUSED_RESULT
static ErrorPtr VerifyEntryPoint(const Library& lib,
                                 const Object& member,
                                 const Object& annotated,
                                 EntryPointPragma expected) {
  ASSERT(expected != EntryPointPragma::kNever);
  auto* const thread = Thread::Current();
  auto* const zone = thread->zone();
  // Special cases for certain types of functions that should delegate to
  // different members within the same class.
  if (member.IsFunction()) {
    const auto& fun = Function::Cast(member);
    if (fun.IsMethodExtractor()) {
      ASSERT(expected == EntryPointPragma::kGetterOnly);
      // To be able to call a method extractor, the original method needs to
      // be annotated for closure retrieval.
      const auto& closure =
          Function::Handle(zone, fun.extracted_method_closure());
      return VerifyEntryPoint(
          lib, closure, annotated.IsNull() ? annotated : closure, expected);
    } else if (fun.IsImplicitClosureFunction()) {
      // Check the annotations on the parent function instead.
      const auto& parent = Function::Handle(zone, fun.parent_function());
      return VerifyEntryPoint(
          lib, parent, annotated.IsNull() ? annotated : parent, expected);
    } else if (fun.IsImplicitGetterOrSetter()) {
      ASSERT(fun.IsImplicitSetterFunction() ||
             expected == EntryPointPragma::kGetterOnly);
      ASSERT(!fun.IsImplicitSetterFunction() ||
             expected == EntryPointPragma::kSetterOnly);
      // For implicit getters or setters, the field must be properly annotated.
      const auto& field = Field::Handle(zone, fun.accessor_field());
#if defined(DART_PRECOMPILED_RUNTIME)
      if (!fun.HasCode()) {
        return VerifyEntryPoint(lib, field, Object::null_object(), expected);
      }
#endif
      return VerifyEntryPoint(lib, field,
                              annotated.IsNull() ? annotated : field, expected);
    }
  }

  // For method and fields, the expected pragma should always be more specific.
  ASSERT_EQUAL(member.IsClass(), expected == EntryPointPragma::kAlways);

  // A null annotated object is used to signal an error should always be thrown.
  if (!annotated.IsNull()) {
    bool is_marked_entrypoint = false;
#if defined(DART_PRECOMPILED_RUNTIME)
    is_marked_entrypoint = VerifyEntryPointHelper(annotated, expected);
#else
    const auto& metadata = Object::Handle(zone, lib.GetMetadata(annotated));
    if (metadata.IsError()) {
      return Error::RawCast(metadata.ptr());
    }
    ASSERT(!metadata.IsNull() && metadata.IsArray());
    const EntryPointPragma pragma =
        FindEntryPointPragma(thread->isolate_group(), Array::Cast(metadata),
                             &Field::Handle(zone), &Object::Handle(zone));
    ASSERT(pragma != EntryPointPragma::kCallOnly || annotated.IsFunction());
    is_marked_entrypoint =
        pragma == EntryPointPragma::kAlways || pragma == expected;
#endif
    if (is_marked_entrypoint) {
      return Error::null();
    }
  }

  ZoneTextBuffer buffer(zone);
  switch (expected) {
    case EntryPointPragma::kAlways:
      buffer.AddString("ERROR: To access '");
      WriteQualifiedMemberName(zone, &buffer, member);
      buffer.AddString("' from native code, it must be annotated.\n");
      break;
    case EntryPointPragma::kCallOnly:
      ASSERT(member.IsFunction());
      buffer.AddString("ERROR: To invoke '");
      WriteQualifiedMemberName(zone, &buffer, member);
      buffer.AddString("' from native code, it must be annotated.\n");
      break;
    case EntryPointPragma::kGetterOnly:
      if (member.IsField() ||
          (member.IsFunction() && Function::Cast(member).IsGetterFunction())) {
        buffer.AddString("ERROR: To retrieve the value of '");
        WriteQualifiedMemberName(zone, &buffer, member);
        buffer.AddString("' from native code, it must be annotated.\n");
      } else {
        const auto& function = Function::Cast(member);
        // Other types of functions cannot be closurized.
        if (!function.IsRegularFunction()) {
          // Other types of functions reaching here are internal errors.
          buffer.AddString("Cannot get closure value for method '");
          WriteQualifiedMemberName(zone, &buffer, function);
          buffer.Printf("' of kind %s",
                        Function::KindToCString(function.kind()));
          FATAL("%s", buffer.buffer());
        }
        buffer.AddString("ERROR: To closurize '");
        WriteQualifiedMemberName(zone, &buffer, function);
        buffer.AddString("' from native code, it must be annotated.\n");
      }
      break;
    case EntryPointPragma::kSetterOnly:
      if (member.IsField() ||
          (member.IsFunction() && Function::Cast(member).IsSetterFunction())) {
        buffer.AddString("ERROR: To set the value of '");
        WriteQualifiedMemberName(zone, &buffer, member);
        buffer.AddString("' from native code, it must be annotated.\n");
      } else {
        // Other types of functions reaching here are internal errors.
        buffer.AddString("Cannot set value of '");
        WriteQualifiedMemberName(zone, &buffer, member);
        buffer.AddString("'");
        FATAL("%s", buffer.buffer());
      }
      break;
    default:
      FATAL("Unexpected EntryPointPragma value %" Pd,
            static_cast<intptr_t>(expected));
  }
  buffer.AddString(
      "ERROR: See https://github.com/dart-lang/sdk/blob/master/runtime/"
      "docs/compiler/aot/entry_point_pragma.md\n");
  OS::PrintErr("%s", buffer.buffer());
  return ApiError::New(String::Handle(String::New(buffer.buffer())));
}

#if defined(DART_PRECOMPILED_RUNTIME)
DART_WARN_UNUSED_RESULT
static ErrorPtr EntryPointFunctionInvocationError(const Function& function) {
  // Ensures a failure by passing null for the annotated object.
  return VerifyEntryPoint(Library::Handle(Library::null()), function,
                          Object::null_object(), EntryPointPragma::kCallOnly);
}
#endif

ObjectPtr Class::InvokeGetter(const String& getter_name,
                              bool check_is_entrypoint,
                              bool respect_reflectable,
                              bool for_invocation) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();

  CHECK_ERROR(EnsureIsFinalized(thread));

  // Note static fields do not have implicit getters.
  const Field& field = Field::Handle(zone, LookupStaticField(getter_name));

  if (field.IsNull() || field.IsUninitialized()) {
    const String& internal_getter_name =
        String::Handle(zone, Field::GetterName(getter_name));
    Function& getter =
        Function::Handle(zone, LookupStaticFunction(internal_getter_name));

    if (getter.IsNull() || (respect_reflectable && !getter.is_reflectable())) {
      if (for_invocation) {
        // LookupStaticFunction(getter_name) has already failed in Invoke().
        // Instead of throwing an NSM, indicate that no getter was found
        // by returning a value that cannot be returned by a getter (here,
        // the sentinel value).
        return Object::sentinel().ptr();
      } else if (getter.IsNull()) {
        getter = LookupStaticFunction(getter_name);
        if (!getter.IsNull()) {
          if (getter.SafeToClosurize()) {
            if (check_is_entrypoint) {
              CHECK_ERROR(
                  getter.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
            }
            // Looking for a getter but found a regular method: closurize it.
            const Function& closure_function =
                Function::Handle(zone, getter.ImplicitClosureFunction());
            return closure_function.ImplicitStaticClosure();
          }
        }
      }

      return ThrowNoSuchMethod(AbstractType::Handle(zone, RareType()),
                               getter_name, Object::null_array(),
                               Object::null_array(), InvocationMirror::kStatic,
                               InvocationMirror::kGetter);
    }

    if (check_is_entrypoint) {
      CHECK_ERROR(getter.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
    }

    // Invoke the getter and return the result.
    return DartEntry::InvokeFunction(getter, Object::empty_array());
  }

  if (check_is_entrypoint) {
    CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
  }

  return field.StaticValue();
}

ObjectPtr Class::InvokeSetter(const String& setter_name,
                              const Instance& value,
                              bool check_is_entrypoint,
                              bool respect_reflectable) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();

  CHECK_ERROR(EnsureIsFinalized(thread));

  // Check for real fields and user-defined setters.
  const Field& field = Field::Handle(zone, LookupStaticField(setter_name));
  const String& internal_setter_name =
      String::Handle(zone, Field::SetterName(setter_name));

  if (field.IsNull()) {
    const Function& setter =
        Function::Handle(zone, LookupStaticFunction(internal_setter_name));
    const int kNumArgs = 1;
    const Array& args = Array::Handle(zone, Array::New(kNumArgs));
    args.SetAt(0, value);
    if (setter.IsNull() || (respect_reflectable && !setter.is_reflectable())) {
      return ThrowNoSuchMethod(AbstractType::Handle(zone, RareType()),
                               internal_setter_name, args, Object::null_array(),
                               InvocationMirror::kStatic,
                               InvocationMirror::kSetter);
    }
    const auto& type = AbstractType::Handle(zone, setter.ParameterTypeAt(0));
    if (!value.RuntimeTypeIsSubtypeOf(type, Object::null_type_arguments(),
                                      Object::null_type_arguments())) {
      const String& argument_name =
          String::Handle(zone, setter.ParameterNameAt(0));
      return ThrowTypeError(setter.token_pos(), value, type, argument_name);
    }
    if (check_is_entrypoint) {
      CHECK_ERROR(setter.VerifyEntryPoint(EntryPointPragma::kSetterOnly));
    }
    // Invoke the setter and return the result.
    return DartEntry::InvokeFunction(setter, args);
  }

  if (field.is_final() || (respect_reflectable && !field.is_reflectable())) {
    const int kNumArgs = 1;
    const Array& args = Array::Handle(zone, Array::New(kNumArgs));
    args.SetAt(0, value);
    return ThrowNoSuchMethod(AbstractType::Handle(zone, RareType()),
                             internal_setter_name, args, Object::null_array(),
                             InvocationMirror::kStatic,
                             InvocationMirror::kSetter);
  }

  const auto& type = AbstractType::Handle(zone, field.type());
  if (!value.RuntimeTypeIsSubtypeOf(type, Object::null_type_arguments(),
                                    Object::null_type_arguments())) {
    const String& argument_name = String::Handle(zone, field.name());
    return ThrowTypeError(field.token_pos(), value, type, argument_name);
  }
  if (check_is_entrypoint) {
    CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kSetterOnly));
  }
  field.SetStaticValue(value);
  return value.ptr();
}

// Creates a new array of boxed arguments suitable for invoking the callable
// from the original boxed arguments for a static call. Also sets the contents
// of the handle pointed to by [callable_args_desc_array_out] to an appropriate
// arguments descriptor array for the new arguments.
//
// Assumes [arg_names] are consistent with [static_args_descriptor].
static ArrayPtr CreateCallableArgumentsFromStatic(
    Zone* zone,
    const Instance& receiver,
    const Array& static_args,
    const Array& arg_names,
    const ArgumentsDescriptor& static_args_descriptor) {
  const intptr_t num_static_type_args = static_args_descriptor.TypeArgsLen();
  const intptr_t num_static_args = static_args_descriptor.Count();
  // Double check that the static args descriptor expects boxed arguments
  // and the static args descriptor is consistent with the static arguments.
  ASSERT_EQUAL(static_args_descriptor.Size(), num_static_args);
  ASSERT_EQUAL(static_args.Length(),
               num_static_args + (num_static_type_args > 0 ? 1 : 0));
  // Add an additional slot to store the callable as the receiver.
  const auto& callable_args =
      Array::Handle(zone, Array::New(static_args.Length() + 1));
  const intptr_t first_arg_index = static_args_descriptor.FirstArgIndex();
  auto& temp = Object::Handle(zone);
  // Copy the static args into the corresponding slots of the callable args.
  if (num_static_type_args > 0) {
    temp = static_args.At(0);
    callable_args.SetAt(0, temp);
  }
  for (intptr_t i = first_arg_index; i < static_args.Length(); i++) {
    temp = static_args.At(i);
    callable_args.SetAt(i + 1, temp);
  }
  // Set the receiver slot in the callable args.
  callable_args.SetAt(first_arg_index, receiver);
  return callable_args.ptr();
}

ObjectPtr Class::Invoke(const String& function_name,
                        const Array& args,
                        const Array& arg_names,
                        bool check_is_entrypoint,
                        bool respect_reflectable) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  CHECK_ERROR(EnsureIsFinalized(thread));

  // We don't pass any explicit type arguments, which will be understood as
  // using dynamic for any function type arguments by lower layers.
  const int kTypeArgsLen = 0;
  const Array& args_descriptor_array = Array::Handle(
      zone, ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(),
                                          arg_names, Heap::kNew));
  ArgumentsDescriptor args_descriptor(args_descriptor_array);

  Function& function =
      Function::Handle(zone, LookupStaticFunction(function_name));

  if (function.IsNull()) {
    // Didn't find a method: try to find a getter and invoke call on its result.
    const Object& getter_result =
        Object::Handle(zone, InvokeGetter(function_name, check_is_entrypoint,
                                          respect_reflectable,
                                          /*for_invocation=*/true));
    if (getter_result.IsError()) {
      return getter_result.ptr();
    } else if (getter_result.ptr() != Object::sentinel().ptr()) {
      const auto& call_args_descriptor_array = Array::Handle(
          zone, ArgumentsDescriptor::NewBoxed(args_descriptor.TypeArgsLen(),
                                              args_descriptor.Count() + 1,
                                              arg_names, Heap::kNew));
      const auto& call_args = Array::Handle(
          zone,
          CreateCallableArgumentsFromStatic(zone, Instance::Cast(getter_result),
                                            args, arg_names, args_descriptor));
      return DartEntry::InvokeClosure(thread, call_args,
                                      call_args_descriptor_array);
    }
  }

  if (function.IsNull() ||
      !function.AreValidArguments(args_descriptor, nullptr) ||
      (respect_reflectable && !function.is_reflectable())) {
    return ThrowNoSuchMethod(
        AbstractType::Handle(zone, RareType()), function_name, args, arg_names,
        InvocationMirror::kStatic, InvocationMirror::kMethod);
  }
  // This is a static function, so we pass an empty instantiator tav.
  ASSERT(function.is_static());
  ObjectPtr type_error = function.DoArgumentTypesMatch(
      args, args_descriptor, Object::empty_type_arguments());
  if (type_error != Error::null()) {
    return type_error;
  }
  if (check_is_entrypoint) {
    CHECK_ERROR(function.VerifyEntryPoint(EntryPointPragma::kCallOnly));
  }
  return DartEntry::InvokeFunction(function, args, args_descriptor_array);
}

#if !defined(DART_PRECOMPILED_RUNTIME)

static ObjectPtr LoadExpressionEvaluationFunction(
    Thread* thread,
    const ExternalTypedData& kernel_buffer,
    const Class& klass) {
  Zone* zone = thread->zone();
#if defined(DART_DYNAMIC_MODULES)
  if (Dart_IsBytecode(
          reinterpret_cast<const uint8_t*>(kernel_buffer.DataAddr(0)),
          kernel_buffer.LengthInBytes())) {
    SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
    bytecode::BytecodeLoader loader(thread, kernel_buffer);
    loader.SetExpressionEvaluationRealClass(klass);
    loader.LoadBytecode();
    return loader.GetExpressionEvaluationFunction();
  }
#endif  // defined(DART_DYNAMIC_MODULES)

  std::unique_ptr<kernel::Program> kernel_pgm =
      kernel::Program::ReadFromTypedData(kernel_buffer);

  if (kernel_pgm == nullptr) {
    return ApiError::New(String::Handle(
        zone, String::New("Kernel isolate returned ill-formed kernel.")));
  }

  auto& result = Object::Handle(zone);
  {
    kernel::KernelLoader loader(kernel_pgm.get(),
                                /*uri_to_source_table=*/nullptr);
    result = loader.LoadExpressionEvaluationFunction(klass);
    kernel_pgm.reset();
  }
  if (result.IsError()) return result.ptr();
  return Function::Cast(result).ptr();
}

static bool EvaluationFunctionNeedsReceiver(Thread* thread,
                                            Zone* zone,
                                            const Function& eval_function) {
  auto parsed_function = new ParsedFunction(
      thread, Function::ZoneHandle(zone, eval_function.ptr()));
  parsed_function->EnsureKernelScopes();
  return parsed_function->is_receiver_used();
}

static ObjectPtr EvaluateCompiledExpressionHelper(
    Zone* zone,
    const Function& eval_function,
    const Array& type_definitions,
    const Array& arguments,
    const TypeArguments& type_arguments) {
  // type_arguments is null if all type arguments are dynamic.
  if (type_definitions.Length() == 0 || type_arguments.IsNull()) {
    return DartEntry::InvokeFunction(eval_function, arguments);
  }

  intptr_t num_type_args = type_arguments.Length();
  const auto& real_arguments =
      Array::Handle(zone, Array::New(arguments.Length() + 1));
  real_arguments.SetAt(0, type_arguments);
  Object& arg = Object::Handle(zone);
  for (intptr_t i = 0; i < arguments.Length(); ++i) {
    arg = arguments.At(i);
    real_arguments.SetAt(i + 1, arg);
  }

  const Array& args_desc =
      Array::Handle(zone, ArgumentsDescriptor::NewBoxed(
                              num_type_args, arguments.Length(), Heap::kNew));
  return DartEntry::InvokeFunction(eval_function, real_arguments, args_desc);
}

#endif  // !defined(DART_PRECOMPILED_RUNTIME)

ObjectPtr Library::EvaluateCompiledExpression(
    const ExternalTypedData& kernel_buffer,
    const Array& type_definitions,
    const Array& arguments,
    const TypeArguments& type_arguments) const {
  const auto& klass = Class::Handle(toplevel_class());
  return klass.EvaluateCompiledExpression(kernel_buffer, type_definitions,
                                          arguments, type_arguments);
}

ObjectPtr Class::EvaluateCompiledExpression(
    const ExternalTypedData& kernel_buffer,
    const Array& type_definitions,
    const Array& arguments,
    const TypeArguments& type_arguments) const {
  auto thread = Thread::Current();
  const auto& library = Library::Handle(thread->zone(), this->library());
  return Instance::EvaluateCompiledExpression(
      thread, Instance::null_object(), library, *this, kernel_buffer,
      type_definitions, arguments, type_arguments);
}

ObjectPtr Instance::EvaluateCompiledExpression(
    const Class& klass,
    const ExternalTypedData& kernel_buffer,
    const Array& type_definitions,
    const Array& arguments,
    const TypeArguments& type_arguments) const {
  auto thread = Thread::Current();
  auto zone = thread->zone();
  const auto& library = Library::Handle(zone, klass.library());
  return Instance::EvaluateCompiledExpression(thread, *this, library, klass,
                                              kernel_buffer, type_definitions,
                                              arguments, type_arguments);
}

ObjectPtr Instance::EvaluateCompiledExpression(
    Thread* thread,
    const Object& receiver,
    const Library& library,
    const Class& klass,
    const ExternalTypedData& kernel_buffer,
    const Array& type_definitions,
    const Array& arguments,
    const TypeArguments& type_arguments) {
  Zone* zone = thread->zone();
#if defined(DART_PRECOMPILED_RUNTIME)
  const auto& error_str = String::Handle(
      zone,
      String::New("Expression evaluation not available in precompiled mode."));
  return ApiError::New(error_str);
#else
  if (IsInternalOnlyClassId(klass.id()) || (klass.id() == kTypeArgumentsCid)) {
    const auto& exception = Instance::Handle(
        zone, String::New("Expressions can be evaluated only with regular Dart "
                          "instances/classes."));
    return UnhandledException::New(exception, StackTrace::null_instance());
  }

  const auto& result = Object::Handle(
      zone, LoadExpressionEvaluationFunction(thread, kernel_buffer, klass));
  if (result.IsError()) return result.ptr();

  const auto& eval_function = Function::Cast(result);

#if defined(DEBUG)
  for (intptr_t i = 0; i < arguments.Length(); ++i) {
    ASSERT(arguments.At(i) != Object::optimized_out().ptr());
    ASSERT(arguments.At(i) != Object::sentinel().ptr());
  }
#endif  // defined(DEBUG)

  auto& all_arguments = Array::Handle(zone, arguments.ptr());
  if (!eval_function.is_static()) {
    // `this` may be optimized out (e.g. not accessible from breakpoint due to
    // not being captured by closure). We allow this as long as the evaluation
    // function doesn't actually need `this`.
    if (receiver.IsNull() || receiver.ptr() == Object::optimized_out().ptr()) {
      if (EvaluationFunctionNeedsReceiver(thread, zone, eval_function)) {
        return Object::optimized_out().ptr();
      }
    }

    all_arguments = Array::New(1 + arguments.Length());
    auto& param = PassiveObject::Handle();
    all_arguments.SetAt(0, receiver);
    for (intptr_t i = 0; i < arguments.Length(); i++) {
      param = arguments.At(i);
      all_arguments.SetAt(i + 1, param);
    }
  }

  return EvaluateCompiledExpressionHelper(zone, eval_function, type_definitions,
                                          all_arguments, type_arguments);
#endif  // !defined(DART_PRECOMPILED_RUNTIME)
}

void Class::EnsureDeclarationLoaded() const {
  if (!is_declaration_loaded()) {
#if defined(DART_DYNAMIC_MODULES)
    // Loading of class declaration can be postponed until needed
    // if class comes from bytecode.
    if (is_declared_in_bytecode()) {
      Thread* thread = Thread::Current();
      SafepointWriteRwLocker ml(thread,
                                thread->isolate_group()->program_lock());
      if (is_declaration_loaded()) {
        return;
      }
      bytecode::BytecodeReader::LoadClassDeclaration(*this);
      ASSERT(is_declaration_loaded());
      ASSERT(is_type_finalized());
      return;
    }
#endif  // defined(DART_DYNAMIC_MODULES)
#if defined(DART_PRECOMPILED_RUNTIME)
    UNREACHABLE();
#else
    FATAL("Unable to use class %s which is not loaded yet.", ToCString());
#endif  // defined(DART_PRECOMPILED_RUNTIME)
  }
}

// Ensure that top level parsing of the class has been done.
ErrorPtr Class::EnsureIsFinalized(Thread* thread) const {
#if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
  RELEASE_ASSERT(is_finalized());
  return Error::null();
#else
  {
    SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
    if (is_finalized()) {
      return Error::null();
    }
  }
  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
  if (is_finalized()) {
    return Error::null();
  }
  LeaveCompilerScope ncs(thread);
  ASSERT(thread != nullptr);
  const Error& error =
      Error::Handle(thread->zone(), ClassFinalizer::LoadClassMembers(*this));
  if (!error.IsNull()) {
    ASSERT(thread == Thread::Current());
    if (thread->long_jump_base() != nullptr) {
      Report::LongJump(error);
      UNREACHABLE();
    }
  }
  return error.ptr();
#endif  // defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
}

// Ensure that code outdated by finalized class is cleaned up, new instance of
// this class is ready to be allocated.
ErrorPtr Class::EnsureIsAllocateFinalized(Thread* thread) const {
  ASSERT(!IsNull());
  if (is_allocate_finalized()) {
    return Error::null();
  }
  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
  if (is_allocate_finalized()) {
    return Error::null();
  }
  ASSERT(thread != nullptr);
  Error& error = Error::Handle(thread->zone(), EnsureIsFinalized(thread));
  if (!error.IsNull()) {
    ASSERT(thread == Thread::Current());
    if (thread->long_jump_base() != nullptr) {
      Report::LongJump(error);
      UNREACHABLE();
    }
  }
  // May be allocate-finalized recursively during EnsureIsFinalized.
  if (is_allocate_finalized()) {
    return Error::null();
  }
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  error ^= ClassFinalizer::AllocateFinalizeClass(*this);
#endif  // defined(DART_PRECOMPILED_RUNTIME)
  return error.ptr();
}

void Class::SetFields(const Array& value) const {
  ASSERT(!value.IsNull());
#if defined(DEBUG)
  Thread* thread = Thread::Current();
  ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
  // Verify that all the fields in the array have this class as owner.
  Field& field = Field::Handle();
  intptr_t len = value.Length();
  for (intptr_t i = 0; i < len; i++) {
    field ^= value.At(i);
    ASSERT(field.IsOriginal());
    ASSERT(field.Owner() == ptr());
  }
#endif
  // The value of static fields is already initialized to null.
  set_fields(value);
}

void Class::AddField(const Field& field) const {
#if defined(DEBUG)
  Thread* thread = Thread::Current();
  ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
#endif
  const Array& arr = Array::Handle(fields());
  const Array& new_arr = Array::Handle(Array::Grow(arr, arr.Length() + 1));
  new_arr.SetAt(arr.Length(), field);
  SetFields(new_arr);
}

void Class::AddFields(const GrowableArray<const Field*>& new_fields) const {
#if defined(DEBUG)
  Thread* thread = Thread::Current();
  ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
#endif
  const intptr_t num_new_fields = new_fields.length();
  if (num_new_fields == 0) return;
  const Array& arr = Array::Handle(fields());
  const intptr_t num_old_fields = arr.Length();
  const Array& new_arr = Array::Handle(
      Array::Grow(arr, num_old_fields + num_new_fields, Heap::kOld));
  for (intptr_t i = 0; i < num_new_fields; i++) {
    new_arr.SetAt(i + num_old_fields, *new_fields.At(i));
  }
  SetFields(new_arr);
}

intptr_t Class::FindFieldIndex(const Field& needle) const {
  Thread* thread = Thread::Current();
  if (EnsureIsFinalized(thread) != Error::null()) {
    return -1;
  }
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_FIELD_HANDLESCOPE(thread);
  Array& fields = thread->ArrayHandle();
  Field& field = thread->FieldHandle();
  fields = this->fields();
  ASSERT(!fields.IsNull());
  for (intptr_t i = 0, n = fields.Length(); i < n; ++i) {
    field ^= fields.At(i);
    if (needle.ptr() == field.ptr()) {
      return i;
    }
  }
  // Not found.
  return -1;
}

FieldPtr Class::FieldFromIndex(intptr_t idx) const {
  Array& fields = Array::Handle(this->fields());
  if ((idx < 0) || (idx >= fields.Length())) {
    return Field::null();
  }
  return Field::RawCast(fields.At(idx));
}

bool Class::InjectCIDFields() const {
  if (library() != Library::InternalLibrary() ||
      Name() != Symbols::ClassID().ptr()) {
    return false;
  }

  auto thread = Thread::Current();
  auto isolate_group = thread->isolate_group();
  auto zone = thread->zone();
  Field& field = Field::Handle(zone);
  Smi& value = Smi::Handle(zone);
  String& field_name = String::Handle(zone);

  // clang-format off
  static const struct {
    const char* const field_name;
    const intptr_t cid;
  } cid_fields[] = {
#define CLASS_LIST_WITH_NULL(V)                                                \
    V(Null)                                                                    \
    CLASS_LIST_NO_OBJECT(V)
#define ADD_SET_FIELD(clazz)                                                   \
    {"cid" #clazz, k##clazz##Cid},
    CLASS_LIST_WITH_NULL(ADD_SET_FIELD)
#undef ADD_SET_FIELD
#undef CLASS_LIST_WITH_NULL
#define ADD_SET_FIELD(clazz)                                                   \
    {"cid" #clazz, kTypedData##clazz##Cid},                                    \
    {"cid" #clazz "View", kTypedData##clazz##ViewCid},                         \
    {"cidExternal" #clazz, kExternalTypedData##clazz##Cid},                    \
    {"cidUnmodifiable" #clazz "View", kUnmodifiableTypedData##clazz##ViewCid}, \
    CLASS_LIST_TYPED_DATA(ADD_SET_FIELD)
#undef ADD_SET_FIELD
    // Used in const hashing to determine whether we're dealing with a
    // user-defined const. See lib/_internal/vm/lib/compact_hash.dart.
    {"numPredefinedCids", kNumPredefinedCids},
  };
  // clang-format on

  const AbstractType& field_type = Type::Handle(zone, Type::IntType());
  for (size_t i = 0; i < ARRAY_SIZE(cid_fields); i++) {
    field_name = Symbols::New(thread, cid_fields[i].field_name);
    field = Field::New(field_name, /* is_static = */ true,
                       /* is_final = */ false,
                       /* is_const = */ true,
                       /* is_reflectable = */ false,
                       /* is_late = */ false, *this, field_type,
                       TokenPosition::kMinSource, TokenPosition::kMinSource);
    value = Smi::New(cid_fields[i].cid);
    isolate_group->RegisterStaticField(field, value);
    AddField(field);
  }

  return true;
}

template <class FakeInstance, class TargetFakeInstance>
ClassPtr Class::NewCommon(intptr_t index) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kClassCid) !=
         Class::null());
  const auto& result = Class::Handle(Object::Allocate<Class>(Heap::kOld));
  // Here kIllegalCid means not-yet-assigned.
  Object::VerifyBuiltinVtable<FakeInstance>(index == kIllegalCid ? kInstanceCid
                                                                 : index);
  NOT_IN_PRECOMPILED(result.set_token_pos(TokenPosition::kNoSource));
  NOT_IN_PRECOMPILED(result.set_end_token_pos(TokenPosition::kNoSource));
  const intptr_t host_instance_size = FakeInstance::InstanceSize();
  const intptr_t target_instance_size = compiler::target::RoundedAllocationSize(
      TargetFakeInstance::InstanceSize());
  result.set_instance_size(host_instance_size, target_instance_size);
  result.set_type_arguments_field_offset_in_words(kNoTypeArguments,
                                                  RTN::Class::kNoTypeArguments);
  const intptr_t host_next_field_offset = FakeInstance::NextFieldOffset();
  const intptr_t target_next_field_offset =
      TargetFakeInstance::NextFieldOffset();
  result.set_next_field_offset(host_next_field_offset,
                               target_next_field_offset);
  result.set_id(index);
  NOT_IN_PRECOMPILED(result.set_implementor_cid(kIllegalCid));
  result.set_num_type_arguments_unsafe(kUnknownNumTypeArguments);
  result.set_num_native_fields(0);
  result.set_state_bits(0);
  NOT_IN_PRECOMPILED(result.set_kernel_offset(0));
  result.InitEmptyFields();
  return result.ptr();
}

template <class FakeInstance, class TargetFakeInstance>
ClassPtr Class::New(intptr_t index,
                    IsolateGroup* isolate_group,
                    bool register_class,
                    bool is_abstract) {
  Class& result =
      Class::Handle(NewCommon<FakeInstance, TargetFakeInstance>(index));
  if (is_abstract) {
    result.set_is_abstract();
  }
  if (register_class) {
    isolate_group->class_table()->Register(result);
  }
  return result.ptr();
}

ClassPtr Class::New(const Library& lib,
                    const String& name,
                    const Script& script,
                    TokenPosition token_pos,
                    bool register_class) {
  Class& result =
      Class::Handle(NewCommon<Instance, RTN::Instance>(kIllegalCid));
  result.set_library(lib);
  result.set_name(name);
  result.set_script(script);
  NOT_IN_PRECOMPILED(result.set_token_pos(token_pos));

  // The size gets initialized to 0. Once the class gets finalized the class
  // finalizer will set the correct size.
  ASSERT(!result.is_finalized() && !result.is_prefinalized());
  result.set_instance_size_in_words(0, 0);

  if (register_class) {
    IsolateGroup::Current()->RegisterClass(result);
  }
  return result.ptr();
}

ClassPtr Class::NewInstanceClass() {
  return Class::New<Instance, RTN::Instance>(kIllegalCid,
                                             IsolateGroup::Current());
}

ClassPtr Class::NewNativeWrapper(const Library& library,
                                 const String& name,
                                 int field_count) {
  Class& cls = Class::Handle(library.LookupClass(name));
  if (cls.IsNull()) {
    cls = New(library, name, Script::Handle(), TokenPosition::kNoSource);
    cls.SetFields(Object::empty_array());
    cls.SetFunctions(Object::empty_array());
    // Set super class to Object.
    cls.set_super_type(Type::Handle(Type::ObjectType()));
    // Compute instance size. First word contains a pointer to a properly
    // sized typed array once the first native field has been set.
    const intptr_t host_instance_size =
        sizeof(UntaggedInstance) + kCompressedWordSize;
#if defined(DART_PRECOMPILER)
    const intptr_t target_instance_size =
        compiler::target::Instance::InstanceSize() +
        compiler::target::kCompressedWordSize;
#else
    const intptr_t target_instance_size =
        sizeof(UntaggedInstance) + compiler::target::kCompressedWordSize;
#endif
    cls.set_instance_size(
        RoundedAllocationSize(host_instance_size),
        compiler::target::RoundedAllocationSize(target_instance_size));
    cls.set_next_field_offset(host_instance_size, target_instance_size);
    cls.set_num_native_fields(field_count);
    cls.set_is_allocate_finalized();
    // The signature of the constructor yet to be added to this class will have
    // to be finalized explicitly, since the class is prematurely marked as
    // 'is_allocate_finalized' and finalization of member types will not occur.
    cls.set_is_declaration_loaded();
    cls.set_is_type_finalized();
    cls.set_is_synthesized_class();
    cls.set_is_isolate_unsendable(true);
    cls.set_is_deeply_immutable(true);
    NOT_IN_PRECOMPILED(cls.set_implementor_cid(kDynamicCid));
    library.AddClass(cls);
    return cls.ptr();
  } else {
    return Class::null();
  }
}

ClassPtr Class::NewStringClass(intptr_t class_id, IsolateGroup* isolate_group) {
  intptr_t host_instance_size, target_instance_size;
  if (class_id == kOneByteStringCid) {
    host_instance_size = OneByteString::InstanceSize();
    target_instance_size = compiler::target::RoundedAllocationSize(
        RTN::OneByteString::InstanceSize());
  } else {
    ASSERT(class_id == kTwoByteStringCid);
    host_instance_size = TwoByteString::InstanceSize();
    target_instance_size = compiler::target::RoundedAllocationSize(
        RTN::TwoByteString::InstanceSize());
  }
  Class& result = Class::Handle(New<String, RTN::String>(
      class_id, isolate_group, /*register_class=*/false));
  result.set_instance_size(host_instance_size, target_instance_size);

  const intptr_t host_next_field_offset = String::NextFieldOffset();
  const intptr_t target_next_field_offset = RTN::String::NextFieldOffset();
  result.set_next_field_offset(host_next_field_offset,
                               target_next_field_offset);
  result.set_is_prefinalized();
  ASSERT(IsDeeplyImmutableCid(class_id));
  result.set_is_deeply_immutable(true);
  isolate_group->class_table()->Register(result);
  return result.ptr();
}

ClassPtr Class::NewTypedDataClass(intptr_t class_id,
                                  IsolateGroup* isolate_group) {
  ASSERT(IsTypedDataClassId(class_id));
  const intptr_t host_instance_size = TypedData::InstanceSize();
  const intptr_t target_instance_size =
      compiler::target::RoundedAllocationSize(RTN::TypedData::InstanceSize());
  Class& result = Class::Handle(New<TypedData, RTN::TypedData>(
      class_id, isolate_group, /*register_class=*/false));
  result.set_instance_size(host_instance_size, target_instance_size);

  const intptr_t host_next_field_offset = TypedData::NextFieldOffset();
  const intptr_t target_next_field_offset = RTN::TypedData::NextFieldOffset();
  result.set_next_field_offset(host_next_field_offset,
                               target_next_field_offset);
  result.set_is_prefinalized();
  isolate_group->class_table()->Register(result);
  return result.ptr();
}

ClassPtr Class::NewTypedDataViewClass(intptr_t class_id,
                                      IsolateGroup* isolate_group) {
  ASSERT(IsTypedDataViewClassId(class_id));
  const intptr_t host_instance_size = TypedDataView::InstanceSize();
  const intptr_t target_instance_size = compiler::target::RoundedAllocationSize(
      RTN::TypedDataView::InstanceSize());
  Class& result = Class::Handle(New<TypedDataView, RTN::TypedDataView>(
      class_id, isolate_group, /*register_class=*/false));
  result.set_instance_size(host_instance_size, target_instance_size);

  const intptr_t host_next_field_offset = TypedDataView::NextFieldOffset();
  const intptr_t target_next_field_offset =
      RTN::TypedDataView::NextFieldOffset();
  result.set_next_field_offset(host_next_field_offset,
                               target_next_field_offset);
  result.set_is_prefinalized();
  isolate_group->class_table()->Register(result);
  return result.ptr();
}

ClassPtr Class::NewUnmodifiableTypedDataViewClass(intptr_t class_id,
                                                  IsolateGroup* isolate_group) {
  ASSERT(IsUnmodifiableTypedDataViewClassId(class_id));
  const intptr_t host_instance_size = TypedDataView::InstanceSize();
  const intptr_t target_instance_size = compiler::target::RoundedAllocationSize(
      RTN::TypedDataView::InstanceSize());
  Class& result = Class::Handle(New<TypedDataView, RTN::TypedDataView>(
      class_id, isolate_group, /*register_class=*/false));
  result.set_instance_size(host_instance_size, target_instance_size);

  const intptr_t host_next_field_offset = TypedDataView::NextFieldOffset();
  const intptr_t target_next_field_offset =
      RTN::TypedDataView::NextFieldOffset();
  result.set_next_field_offset(host_next_field_offset,
                               target_next_field_offset);
  result.set_is_prefinalized();
  isolate_group->class_table()->Register(result);
  return result.ptr();
}

ClassPtr Class::NewExternalTypedDataClass(intptr_t class_id,
                                          IsolateGroup* isolate_group) {
  ASSERT(IsExternalTypedDataClassId(class_id));
  const intptr_t host_instance_size = ExternalTypedData::InstanceSize();
  const intptr_t target_instance_size = compiler::target::RoundedAllocationSize(
      RTN::ExternalTypedData::InstanceSize());
  Class& result = Class::Handle(New<ExternalTypedData, RTN::ExternalTypedData>(
      class_id, isolate_group, /*register_class=*/false));

  const intptr_t host_next_field_offset = ExternalTypedData::NextFieldOffset();
  const intptr_t target_next_field_offset =
      RTN::ExternalTypedData::NextFieldOffset();
  result.set_instance_size(host_instance_size, target_instance_size);
  result.set_next_field_offset(host_next_field_offset,
                               target_next_field_offset);
  result.set_is_prefinalized();
  isolate_group->class_table()->Register(result);
  return result.ptr();
}

ClassPtr Class::NewPointerClass(intptr_t class_id,
                                IsolateGroup* isolate_group) {
  ASSERT(IsFfiPointerClassId(class_id));
  intptr_t host_instance_size = Pointer::InstanceSize();
  intptr_t target_instance_size =
      compiler::target::RoundedAllocationSize(RTN::Pointer::InstanceSize());
  Class& result = Class::Handle(New<Pointer, RTN::Pointer>(
      class_id, isolate_group, /*register_class=*/false));
  result.set_instance_size(host_instance_size, target_instance_size);
  result.set_type_arguments_field_offset(Pointer::type_arguments_offset(),
                                         RTN::Pointer::type_arguments_offset());

  const intptr_t host_next_field_offset = Pointer::NextFieldOffset();
  const intptr_t target_next_field_offset = RTN::Pointer::NextFieldOffset();

  result.set_next_field_offset(host_next_field_offset,
                               target_next_field_offset);
  result.set_is_prefinalized();
  isolate_group->class_table()->Register(result);
  return result.ptr();
}

void Class::set_name(const String& value) const {
  ASSERT(untag()->name() == String::null());
  ASSERT(value.IsSymbol());
  untag()->set_name(value.ptr());
#if !defined(PRODUCT)
  if (untag()->user_name() == String::null()) {
    // TODO(johnmccutchan): Eagerly set user name for VM isolate classes,
    // lazily set user name for the other classes.
    // Generate and set user_name.
    const String& user_name = String::Handle(
        Symbols::New(Thread::Current(), GenerateUserVisibleName()));
    set_user_name(user_name);
  }
#endif  // !defined(PRODUCT)
}

#if !defined(PRODUCT)
void Class::set_user_name(const String& value) const {
  untag()->set_user_name(value.ptr());
}
#endif  // !defined(PRODUCT)

#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
void Class::SetUserVisibleNameInClassTable() const {
  // Top level classes don't record a user visible name in the class table.
  if (IsTopLevel()) return;
  IsolateGroup* isolate_group = IsolateGroup::Current();
  auto class_table = isolate_group->class_table();
  if (class_table->UserVisibleNameFor(id()) == nullptr) {
    String& name = String::Handle(UserVisibleName());
    class_table->SetUserVisibleNameFor(id(), name.ToMallocCString());
  }
}
#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)

const char* Class::GenerateUserVisibleName() const {
  if (FLAG_show_internal_names) {
    return String::Handle(Name()).ToCString();
  }
  switch (id()) {
    case kFloat32x4Cid:
      return Symbols::Float32x4().ToCString();
    case kFloat64x2Cid:
      return Symbols::Float64x2().ToCString();
    case kInt32x4Cid:
      return Symbols::Int32x4().ToCString();
    case kTypedDataInt8ArrayCid:
    case kExternalTypedDataInt8ArrayCid:
      return Symbols::Int8List().ToCString();
    case kTypedDataUint8ArrayCid:
    case kExternalTypedDataUint8ArrayCid:
      return Symbols::Uint8List().ToCString();
    case kTypedDataUint8ClampedArrayCid:
    case kExternalTypedDataUint8ClampedArrayCid:
      return Symbols::Uint8ClampedList().ToCString();
    case kTypedDataInt16ArrayCid:
    case kExternalTypedDataInt16ArrayCid:
      return Symbols::Int16List().ToCString();
    case kTypedDataUint16ArrayCid:
    case kExternalTypedDataUint16ArrayCid:
      return Symbols::Uint16List().ToCString();
    case kTypedDataInt32ArrayCid:
    case kExternalTypedDataInt32ArrayCid:
      return Symbols::Int32List().ToCString();
    case kTypedDataUint32ArrayCid:
    case kExternalTypedDataUint32ArrayCid:
      return Symbols::Uint32List().ToCString();
    case kTypedDataInt64ArrayCid:
    case kExternalTypedDataInt64ArrayCid:
      return Symbols::Int64List().ToCString();
    case kTypedDataUint64ArrayCid:
    case kExternalTypedDataUint64ArrayCid:
      return Symbols::Uint64List().ToCString();
    case kTypedDataInt32x4ArrayCid:
    case kExternalTypedDataInt32x4ArrayCid:
      return Symbols::Int32x4List().ToCString();
    case kTypedDataFloat32x4ArrayCid:
    case kExternalTypedDataFloat32x4ArrayCid:
      return Symbols::Float32x4List().ToCString();
    case kTypedDataFloat64x2ArrayCid:
    case kExternalTypedDataFloat64x2ArrayCid:
      return Symbols::Float64x2List().ToCString();
    case kTypedDataFloat32ArrayCid:
    case kExternalTypedDataFloat32ArrayCid:
      return Symbols::Float32List().ToCString();
    case kTypedDataFloat64ArrayCid:
    case kExternalTypedDataFloat64ArrayCid:
      return Symbols::Float64List().ToCString();
    case kPointerCid:
      return Symbols::FfiPointer().ToCString();
    case kDynamicLibraryCid:
      return Symbols::FfiDynamicLibrary().ToCString();
    case kNullCid:
      return Symbols::Null().ToCString();
    case kDynamicCid:
      return Symbols::Dynamic().ToCString();
    case kVoidCid:
      return Symbols::Void().ToCString();
    case kNeverCid:
      return Symbols::Never().ToCString();
    case kClassCid:
      return Symbols::Class().ToCString();
    case kTypeParametersCid:
      return Symbols::TypeParameters().ToCString();
    case kTypeArgumentsCid:
      return Symbols::TypeArguments().ToCString();
    case kPatchClassCid:
      return Symbols::PatchClass().ToCString();
    case kFunctionCid:
      return Symbols::Function().ToCString();
    case kClosureDataCid:
      return Symbols::ClosureData().ToCString();
    case kFfiTrampolineDataCid:
      return Symbols::FfiTrampolineData().ToCString();
    case kFieldCid:
      return Symbols::Field().ToCString();
    case kScriptCid:
      return Symbols::Script().ToCString();
    case kLibraryCid:
      return Symbols::Library().ToCString();
    case kLibraryPrefixCid:
      return Symbols::LibraryPrefix().ToCString();
    case kNamespaceCid:
      return Symbols::Namespace().ToCString();
    case kKernelProgramInfoCid:
      return Symbols::KernelProgramInfo().ToCString();
    case kWeakSerializationReferenceCid:
      return Symbols::WeakSerializationReference().ToCString();
    case kWeakArrayCid:
      return Symbols::WeakArray().ToCString();
    case kCodeCid:
      return Symbols::Code().ToCString();
    case kBytecodeCid:
      return Symbols::Bytecode().ToCString();
    case kInstructionsCid:
      return Symbols::Instructions().ToCString();
    case kInstructionsSectionCid:
      return Symbols::InstructionsSection().ToCString();
    case kInstructionsTableCid:
      return Symbols::InstructionsTable().ToCString();
    case kObjectPoolCid:
      return Symbols::ObjectPool().ToCString();
    case kCodeSourceMapCid:
      return Symbols::CodeSourceMap().ToCString();
    case kPcDescriptorsCid:
      return Symbols::PcDescriptors().ToCString();
    case kCompressedStackMapsCid:
      return Symbols::CompressedStackMaps().ToCString();
    case kLocalVarDescriptorsCid:
      return Symbols::LocalVarDescriptors().ToCString();
    case kExceptionHandlersCid:
      return Symbols::ExceptionHandlers().ToCString();
    case kContextCid:
      return Symbols::Context().ToCString();
    case kContextScopeCid:
      return Symbols::ContextScope().ToCString();
    case kSentinelCid:
      return Symbols::Sentinel().ToCString();
    case kSingleTargetCacheCid:
      return Symbols::SingleTargetCache().ToCString();
    case kICDataCid:
      return Symbols::ICData().ToCString();
    case kMegamorphicCacheCid:
      return Symbols::MegamorphicCache().ToCString();
    case kSubtypeTestCacheCid:
      return Symbols::SubtypeTestCache().ToCString();
    case kLoadingUnitCid:
      return Symbols::LoadingUnit().ToCString();
    case kApiErrorCid:
      return Symbols::ApiError().ToCString();
    case kLanguageErrorCid:
      return Symbols::LanguageError().ToCString();
    case kUnhandledExceptionCid:
      return Symbols::UnhandledException().ToCString();
    case kUnwindErrorCid:
      return Symbols::UnwindError().ToCString();
    case kIntegerCid:
    case kSmiCid:
    case kMintCid:
      return Symbols::Int().ToCString();
    case kDoubleCid:
      return Symbols::Double().ToCString();
    case kOneByteStringCid:
    case kTwoByteStringCid:
      return Symbols::_String().ToCString();
    case kArrayCid:
    case kImmutableArrayCid:
    case kGrowableObjectArrayCid:
      return Symbols::List().ToCString();
  }
  String& name = String::Handle(Name());
  name = Symbols::New(Thread::Current(), String::ScrubName(name));
  if (name.ptr() == Symbols::_Future().ptr() &&
      library() == Library::AsyncLibrary()) {
    return Symbols::Future().ToCString();
  }
  return name.ToCString();
}

void Class::set_script(const Script& value) const {
  untag()->set_script(value.ptr());
}

#if !defined(DART_PRECOMPILED_RUNTIME)
KernelProgramInfoPtr Class::KernelProgramInfo() const {
  const auto& lib = Library::Handle(library());
  return lib.kernel_program_info();
}

void Class::set_token_pos(TokenPosition token_pos) const {
  ASSERT(!token_pos.IsClassifying());
  StoreNonPointer(&untag()->token_pos_, token_pos);
}

void Class::set_end_token_pos(TokenPosition token_pos) const {
  ASSERT(!token_pos.IsClassifying());
  StoreNonPointer(&untag()->end_token_pos_, token_pos);
}

void Class::set_implementor_cid(intptr_t value) const {
  ASSERT(value >= 0 && value < std::numeric_limits<classid_t>::max());
  StoreNonPointer(&untag()->implementor_cid_, value);
}

void Class::ClearImplementor() const {
  // Check raw implementor_cid_ without normalization done by
  // implementor_cid() accessor.
  if (untag()->implementor_cid_ != kVoidCid) {
    set_implementor_cid(kIllegalCid);
  }
}

bool Class::NoteImplementor(const Class& implementor) const {
  ASSERT(!implementor.is_abstract());
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  if (implementor_cid() == kDynamicCid) {
    return false;
  } else if (implementor_cid() == implementor.id()) {
    return false;
  } else if (implementor_cid() == kIllegalCid) {
    set_implementor_cid(implementor.id());
    return true;  // None -> One
  } else {
    set_implementor_cid(kDynamicCid);
    return true;  // One -> Many
  }
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

uint32_t Class::Hash() const {
  return Class::Hash(ptr());
}
uint32_t Class::Hash(ClassPtr obj) {
  return String::HashRawSymbol(obj.untag()->name());
}

int32_t Class::SourceFingerprint() const {
#if !defined(DART_PRECOMPILED_RUNTIME)
  if (is_declared_in_bytecode()) {
    return 0;
  }
  return kernel::KernelSourceFingerprintHelper::CalculateClassFingerprint(
      *this);
#else
  return 0;
#endif  // !defined(DART_PRECOMPILED_RUNTIME)
}

void Class::set_is_implemented(bool value) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_is_implemented_unsafe(value);
}

void Class::set_is_implemented_unsafe(bool value) const {
  set_state_bits(ImplementedBit::update(value, state_bits()));
}

void Class::set_is_abstract() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(AbstractBit::update(true, state_bits()));
}

void Class::set_is_declaration_loaded() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_is_declaration_loaded_unsafe();
}

void Class::set_is_declaration_loaded_unsafe() const {
  ASSERT(!is_declaration_loaded());
  set_state_bits(ClassLoadingBits::update(UntaggedClass::kDeclarationLoaded,
                                          state_bits()));
}

void Class::set_is_type_finalized() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  ASSERT(is_declaration_loaded());
  ASSERT(!is_type_finalized());
  set_state_bits(
      ClassLoadingBits::update(UntaggedClass::kTypeFinalized, state_bits()));
}

void Class::set_is_synthesized_class() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_is_synthesized_class_unsafe();
}

void Class::set_is_synthesized_class_unsafe() const {
  set_state_bits(SynthesizedClassBit::update(true, state_bits()));
}

void Class::set_is_enum_class() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(EnumBit::update(true, state_bits()));
}

void Class::set_is_const() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(ConstBit::update(true, state_bits()));
}

void Class::set_is_transformed_mixin_application() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(TransformedMixinApplicationBit::update(true, state_bits()));
}

void Class::set_is_sealed() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(SealedBit::update(true, state_bits()));
}

void Class::set_is_mixin_class() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(MixinClassBit::update(true, state_bits()));
}

void Class::set_is_base_class() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(BaseClassBit::update(true, state_bits()));
}

void Class::set_is_interface_class() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(InterfaceClassBit::update(true, state_bits()));
}

void Class::set_is_final() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(FinalBit::update(true, state_bits()));
}

void Class::set_is_fields_marked_nullable() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(FieldsMarkedNullableBit::update(true, state_bits()));
}

void Class::set_is_allocated(bool value) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_is_allocated_unsafe(value);
}

void Class::set_is_allocated_unsafe(bool value) const {
  set_state_bits(IsAllocatedBit::update(value, state_bits()));
}

void Class::set_is_loaded(bool value) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(IsLoadedBit::update(value, state_bits()));
}

#if defined(DART_DYNAMIC_MODULES)
void Class::set_is_declared_in_bytecode(bool value) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  set_state_bits(IsDeclaredInBytecodeBit::update(value, state_bits()));
}
#endif  // defined(DART_DYNAMIC_MODULES)

void Class::set_is_finalized() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  ASSERT(!is_finalized());
  set_is_finalized_unsafe();
}

void Class::set_is_finalized_unsafe() const {
  set_state_bits(
      ClassFinalizedBits::update(UntaggedClass::kFinalized, state_bits()));
}

void Class::set_is_allocate_finalized() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  ASSERT(!is_allocate_finalized());
  set_state_bits(ClassFinalizedBits::update(UntaggedClass::kAllocateFinalized,
                                            state_bits()));
}

void Class::set_is_prefinalized() const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  ASSERT(!is_finalized());
  set_state_bits(
      ClassFinalizedBits::update(UntaggedClass::kPreFinalized, state_bits()));
}

void Class::set_interfaces(const Array& value) const {
  ASSERT(!value.IsNull());
  untag()->set_interfaces(value.ptr());
}

#if !defined(DART_PRECOMPILED_RUNTIME)

void Class::AddDirectImplementor(const Class& implementor,
                                 bool is_mixin) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  ASSERT(is_implemented());
  ASSERT(!implementor.IsNull());
  GrowableObjectArray& direct_implementors =
      GrowableObjectArray::Handle(untag()->direct_implementors());
  if (direct_implementors.IsNull()) {
    direct_implementors = GrowableObjectArray::New(4, Heap::kOld);
    untag()->set_direct_implementors(direct_implementors.ptr());
  }
#if defined(DEBUG)
  // Verify that the same class is not added twice.
  // The only exception is mixins: when mixin application is transformed,
  // mixin is added to the end of interfaces list and may be duplicated:
  //   class X = A with B implements B;
  // This is rare and harmless.
  if (!is_mixin) {
    for (intptr_t i = 0; i < direct_implementors.Length(); i++) {
      ASSERT(direct_implementors.At(i) != implementor.ptr());
    }
  }
#endif
  direct_implementors.Add(implementor, Heap::kOld);
}

void Class::set_direct_implementors(
    const GrowableObjectArray& implementors) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  untag()->set_direct_implementors(implementors.ptr());
}

void Class::AddDirectSubclass(const Class& subclass) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  ASSERT(!subclass.IsNull());
  ASSERT(subclass.SuperClass() == ptr());
  // Do not keep track of the direct subclasses of class Object.
  ASSERT(!IsObjectClass());
  GrowableObjectArray& direct_subclasses =
      GrowableObjectArray::Handle(untag()->direct_subclasses());
  if (direct_subclasses.IsNull()) {
    direct_subclasses = GrowableObjectArray::New(4, Heap::kOld);
    untag()->set_direct_subclasses(direct_subclasses.ptr());
  }
#if defined(DEBUG)
  // Verify that the same class is not added twice.
  for (intptr_t i = 0; i < direct_subclasses.Length(); i++) {
    ASSERT(direct_subclasses.At(i) != subclass.ptr());
  }
#endif
  direct_subclasses.Add(subclass, Heap::kOld);
}

void Class::set_direct_subclasses(const GrowableObjectArray& subclasses) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  untag()->set_direct_subclasses(subclasses.ptr());
}

#endif  // !defined(DART_PRECOMPILED_RUNTIME)

ArrayPtr Class::constants() const {
  return untag()->constants();
}

void Class::set_constants(const Array& value) const {
  untag()->set_constants(value.ptr());
}

void Class::set_declaration_type(const Type& value) const {
  ASSERT(id() != kDynamicCid && id() != kVoidCid);
  ASSERT(!value.IsNull() && value.IsCanonical() && value.IsOld());
  ASSERT((declaration_type() == Object::null()) ||
         (declaration_type() == value.ptr()));  // Set during own finalization.
  // Since DeclarationType is used as the runtime type of instances of a
  // non-generic class, its nullability must be kNonNullable.
  // The exception is DeclarationType of Null which is kNullable.
  ASSERT(value.type_class_id() != kNullCid || value.IsNullable());
  ASSERT(value.type_class_id() == kNullCid || value.IsNonNullable());
  untag()->set_declaration_type<std::memory_order_release>(value.ptr());
}

TypePtr Class::DeclarationType() const {
  ASSERT(is_declaration_loaded());
  if (IsNullClass()) {
    return Type::NullType();
  }
  if (IsDynamicClass()) {
    return Type::DynamicType();
  }
  if (IsVoidClass()) {
    return Type::VoidType();
  }
  if (declaration_type() != Type::null()) {
    return declaration_type();
  }
  {
    auto thread = Thread::Current();
    SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
    if (declaration_type() != Type::null()) {
      return declaration_type();
    }
    // For efficiency, the runtimeType intrinsic returns the type cached by
    // DeclarationType without checking its nullability. Therefore, we
    // consistently cache the kNonNullable version of the type.
    // The exception is type Null which is stored as kNullable.
    TypeArguments& type_args = TypeArguments::Handle();
    const intptr_t num_type_params = NumTypeParameters();
    if (num_type_params > 0) {
      type_args = TypeArguments::New(num_type_params);
      TypeParameter& type_param = TypeParameter::Handle();
      for (intptr_t i = 0; i < num_type_params; i++) {
        type_param = TypeParameterAt(i);
        type_args.SetTypeAt(i, type_param);
      }
    }
    Type& type =
        Type::Handle(Type::New(*this, type_args, Nullability::kNonNullable));
    type ^= ClassFinalizer::FinalizeType(type);
    set_declaration_type(type);
    return type.ptr();
  }
}

#if !defined(DART_PRECOMPILED_RUNTIME)
void Class::set_allocation_stub(const Code& value) const {
  // Never clear the stub as it may still be a target, but will be GC-d if
  // not referenced.
  ASSERT(!value.IsNull());
  ASSERT(untag()->allocation_stub() == Code::null());
  untag()->set_allocation_stub(value.ptr());
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

void Class::DisableAllocationStub() const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  {
    const Code& existing_stub = Code::Handle(allocation_stub());
    if (existing_stub.IsNull()) {
      return;
    }
  }
  auto thread = Thread::Current();
  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
  const Code& existing_stub = Code::Handle(allocation_stub());
  if (existing_stub.IsNull()) {
    return;
  }
  ASSERT(!existing_stub.IsDisabled());
  // Change the stub so that the next caller will regenerate the stub.
  existing_stub.DisableStubCode(NumTypeParameters() > 0);
  // Disassociate the existing stub from class.
  untag()->set_allocation_stub(Code::null());
#endif  // defined(DART_PRECOMPILED_RUNTIME)
}

bool Class::IsDartFunctionClass() const {
  return ptr() == Type::Handle(Type::DartFunctionType()).type_class();
}

bool Class::IsFutureClass() const {
  // Looking up future_class in the object store would not work, because
  // this function is called during class finalization, before the object store
  // field would be initialized by InitKnownObjects().
  return (Name() == Symbols::Future().ptr()) &&
         (library() == Library::AsyncLibrary());
}

// Checks if type T0 is a subtype of type T1.
// Type T0 is specified by class 'cls' parameterized with 'type_arguments' and
// by 'nullability', and type T1 is specified by 'other' and must have a type
// class.
// [type_arguments] should be a flattened instance type arguments vector.
bool Class::IsSubtypeOf(const Class& cls,
                        const TypeArguments& type_arguments,
                        Nullability nullability,
                        const AbstractType& other,
                        Heap::Space space,
                        FunctionTypeMapping* function_type_equivalence) {
  TRACE_TYPE_CHECKS_VERBOSE("  Class::IsSubtypeOf(%s %s, %s)\n",
                            cls.ToCString(), type_arguments.ToCString(),
                            other.ToCString());
  // This function does not support Null, Never, dynamic, or void as type T0.
  classid_t this_cid = cls.id();
  ASSERT(this_cid != kNullCid && this_cid != kNeverCid &&
         this_cid != kDynamicCid && this_cid != kVoidCid);
  ASSERT(type_arguments.IsNull() ||
         (type_arguments.Length() >= cls.NumTypeArguments()));
  // Type T1 must have a type class (e.g. not a type param or a function type).
  ASSERT(other.HasTypeClass());
  const classid_t other_cid = other.type_class_id();
  if (other_cid == kDynamicCid || other_cid == kVoidCid) {
    TRACE_TYPE_CHECKS_VERBOSE("   - result: true (right is top)\n");
    return true;
  }
  // Left nullable:
  //   if T0 is S0? then:
  //     T0 <: T1 iff S0 <: T1 and Null <: T1
  if ((nullability == Nullability::kNullable) &&
      !Instance::NullIsAssignableTo(other)) {
    TRACE_TYPE_CHECKS_VERBOSE("   - result: false (nullability)\n");
    return false;
  }

  // Right Object.
  if (other_cid == kObjectCid) {
    TRACE_TYPE_CHECKS_VERBOSE("   - result: true (right is Object)\n");
    return true;
  }

  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const Class& other_class = Class::Handle(zone, other.type_class());
  const TypeArguments& other_type_arguments =
      TypeArguments::Handle(zone, other.arguments());
  // Use the 'this_class' object as if it was the receiver of this method, but
  // instead of recursing, reset it to the super class and loop.
  Class& this_class = Class::Handle(zone, cls.ptr());
  while (true) {
    // Apply additional subtyping rules if T0 or T1 are 'FutureOr'.

    // Left FutureOr:
    //   if T0 is FutureOr<S0> then:
    //     T0 <: T1 iff Future<S0> <: T1 and S0 <: T1
    if (this_cid == kFutureOrCid) {
      // Check Future<S0> <: T1.
      ObjectStore* object_store = IsolateGroup::Current()->object_store();
      const Class& future_class =
          Class::Handle(zone, object_store->future_class());
      ASSERT(!future_class.IsNull() && future_class.NumTypeParameters() == 1 &&
             this_class.NumTypeParameters() == 1);
      ASSERT(type_arguments.IsNull() || type_arguments.Length() >= 1);
      if (Class::IsSubtypeOf(future_class, type_arguments,
                             Nullability::kNonNullable, other, space,
                             function_type_equivalence)) {
        // Check S0 <: T1.
        const AbstractType& type_arg =
            AbstractType::Handle(zone, type_arguments.TypeAtNullSafe(0));
        if (type_arg.IsSubtypeOf(other, space, function_type_equivalence)) {
          TRACE_TYPE_CHECKS_VERBOSE("   - result: true (left is FutureOr)\n");
          return true;
        }
      }
    }

    // Right FutureOr:
    //   if T1 is FutureOr<S1> then:
    //     T0 <: T1 iff any of the following hold:
    //     either T0 <: Future<S1>
    //     or T0 <: S1
    //     or T0 is X0 and X0 has bound S0 and S0 <: T1  (checked elsewhere)
    if (other_cid == kFutureOrCid) {
      const AbstractType& other_type_arg =
          AbstractType::Handle(zone, other_type_arguments.TypeAtNullSafe(0));
      // Check if S1 is a top type.
      if (other_type_arg.IsTopTypeForSubtyping()) {
        TRACE_TYPE_CHECKS_VERBOSE(
            "   - result: true (right is FutureOr top)\n");
        return true;
      }
      // Check T0 <: Future<S1> when T0 is Future<S0>.
      if (this_class.IsFutureClass()) {
        const AbstractType& type_arg =
            AbstractType::Handle(zone, type_arguments.TypeAtNullSafe(0));
        // If T0 is Future<S0>, then T0 <: Future<S1>, iff S0 <: S1.
        if (type_arg.IsSubtypeOf(other_type_arg, space,
                                 function_type_equivalence)) {
          TRACE_TYPE_CHECKS_VERBOSE(
              "   - result: true (left is Future, right is FutureOr)\n");
          return true;
        }
      }
      // Check T0 <: Future<S1> when T0 is FutureOr<S0> is already done.
      // Check T0 <: S1.
      if (other_type_arg.HasTypeClass() &&
          Class::IsSubtypeOf(this_class, type_arguments, nullability,
                             other_type_arg, space,
                             function_type_equivalence)) {
        TRACE_TYPE_CHECKS_VERBOSE(
            "   - result: true (right is FutureOr, subtype of arg)\n");
        return true;
      }
    }

    // Check for reflexivity.
    if (this_class.ptr() == other_class.ptr()) {
      const intptr_t num_type_params = this_class.NumTypeParameters();
      if (num_type_params == 0) {
        TRACE_TYPE_CHECKS_VERBOSE(
            "   - result: true (same non-generic class)\n");
        return true;
      }
      // Check for covariance.
      if (other_type_arguments.IsNull()) {
        TRACE_TYPE_CHECKS_VERBOSE(
            "   - result: true (same class, dynamic type args)\n");
        return true;
      }
      const intptr_t num_type_args = this_class.NumTypeArguments();
      const intptr_t from_index = num_type_args - num_type_params;
      ASSERT(other_type_arguments.Length() == num_type_params);
      AbstractType& type = AbstractType::Handle(zone);
      AbstractType& other_type = AbstractType::Handle(zone);
      for (intptr_t i = 0; i < num_type_params; ++i) {
        type = type_arguments.TypeAtNullSafe(from_index + i);
        other_type = other_type_arguments.TypeAt(i);
        ASSERT(!type.IsNull() && !other_type.IsNull());
        if (!type.IsSubtypeOf(other_type, space, function_type_equivalence)) {
          TRACE_TYPE_CHECKS_VERBOSE(
              "   - result: false (same class, type args mismatch)\n");
          return false;
        }
      }
      TRACE_TYPE_CHECKS_VERBOSE(
          "   - result: true (same class, matching type args)\n");
      return true;
    }

    // _Closure <: Function
    if (this_class.IsClosureClass() && other_class.IsDartFunctionClass()) {
      TRACE_TYPE_CHECKS_VERBOSE(
          "   - result: true (left is closure, right is Function)\n");
      return true;
    }

    // Check for 'direct super type' specified in the implements clause
    // and check for transitivity at the same time.
    Array& interfaces = Array::Handle(zone, this_class.interfaces());
    Type& interface = Type::Handle(zone);
    Class& interface_class = Class::Handle(zone);
    TypeArguments& interface_args = TypeArguments::Handle(zone);
    for (intptr_t i = 0; i < interfaces.Length(); i++) {
      interface ^= interfaces.At(i);
      ASSERT(interface.IsFinalized());
      interface_class = interface.type_class();
      interface_args = interface.arguments();
      if (!interface_args.IsNull() && !interface_args.IsInstantiated()) {
        // This type class implements an interface that is parameterized with
        // generic type(s), e.g. it implements List<T>.
        // The uninstantiated type T must be instantiated using the type
        // parameters of this type before performing the type test.
        // The type arguments of this type that are referred to by the type
        // parameters of the interface are at the end of the type vector,
        // after the type arguments of the super type of this type.
        // The index of the type parameters is adjusted upon finalization.
        interface_args = interface_args.InstantiateFrom(
            type_arguments, Object::null_type_arguments(), kNoneFree, space);
      }
      interface_args = interface_class.GetInstanceTypeArguments(
          thread, interface_args, /*canonicalize=*/false);
      // In Dart 2, implementing Function has no meaning.
      // TODO(regis): Can we encounter and skip Object as well?
      if (interface_class.IsDartFunctionClass()) {
        continue;
      }
      if (Class::IsSubtypeOf(interface_class, interface_args,
                             Nullability::kNonNullable, other, space,
                             function_type_equivalence)) {
        TRACE_TYPE_CHECKS_VERBOSE("   - result: true (interface found)\n");
        return true;
      }
    }
    // "Recurse" up the class hierarchy until we have reached the top.
    this_class = this_class.SuperClass();
    if (this_class.IsNull()) {
      TRACE_TYPE_CHECKS_VERBOSE("   - result: false (supertype not found)\n");
      return false;
    }
    this_cid = this_class.id();
  }
  UNREACHABLE();
  return false;
}

bool Class::IsTopLevel() const {
  return Name() == Symbols::TopLevel().ptr();
}

bool Class::IsPrivate() const {
  return Library::IsPrivate(String::Handle(Name()));
}

FunctionPtr Class::LookupDynamicFunctionUnsafe(const String& name) const {
  return LookupFunctionReadLocked(name, kInstance);
}

FunctionPtr Class::LookupStaticFunction(const String& name) const {
  Thread* thread = Thread::Current();
  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
  return LookupFunctionReadLocked(name, kStatic);
}

FunctionPtr Class::LookupStaticFunctionAllowPrivate(const String& name) const {
  return LookupFunctionAllowPrivate(name, kStatic);
}

FunctionPtr Class::LookupConstructor(const String& name) const {
  Thread* thread = Thread::Current();
  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
  return LookupFunctionReadLocked(name, kConstructor);
}

FunctionPtr Class::LookupConstructorAllowPrivate(const String& name) const {
  return LookupFunctionAllowPrivate(name, kConstructor);
}

FunctionPtr Class::LookupFactory(const String& name) const {
  Thread* thread = Thread::Current();
  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
  return LookupFunctionReadLocked(name, kFactory);
}

FunctionPtr Class::LookupFactoryAllowPrivate(const String& name) const {
  return LookupFunctionAllowPrivate(name, kFactory);
}

FunctionPtr Class::LookupFunctionAllowPrivate(const String& name) const {
  return LookupFunctionAllowPrivate(name, kAny);
}

FunctionPtr Class::LookupFunction(const String& name) const {
  Thread* thread = Thread::Current();
  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
  return LookupFunctionReadLocked(name);
}

FunctionPtr Class::LookupFunctionReadLocked(const String& name) const {
  return LookupFunctionReadLocked(name, kAny);
}

FunctionPtr Class::CheckFunctionType(const Function& func, MemberKind kind) {
  if ((kind == kInstance) || (kind == kInstanceAllowAbstract)) {
    if (func.IsDynamicFunction(kind == kInstanceAllowAbstract)) {
      return func.ptr();
    }
  } else if (kind == kStatic) {
    if (func.IsStaticFunction()) {
      return func.ptr();
    }
  } else if (kind == kConstructor) {
    if (func.IsGenerativeConstructor()) {
      ASSERT(!func.is_static());
      return func.ptr();
    }
  } else if (kind == kFactory) {
    if (func.IsFactory()) {
      ASSERT(func.is_static());
      return func.ptr();
    }
  } else if (kind == kAny) {
    return func.ptr();
  }
  return Function::null();
}

FunctionPtr Class::LookupFunctionReadLocked(const String& name,
                                            MemberKind kind) const {
  ASSERT(!IsNull());
  Thread* thread = Thread::Current();
  RELEASE_ASSERT(is_finalized());
  // Caller needs to ensure they grab program_lock because this method
  // can be invoked with either ReadRwLock or WriteRwLock.
#if defined(DEBUG)
  ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadReader());
#endif
  ASSERT(functions() != Array::null());
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_FUNCTION_HANDLESCOPE(thread);
  Array& funcs = thread->ArrayHandle();
  funcs = functions();
  const intptr_t len = funcs.Length();
  Function& function = thread->FunctionHandle();
  if (len >= kFunctionLookupHashThreshold) {
    // TODO(dartbug.com/36097): We require currently a read lock in the resolver
    // to avoid read-write race access to this hash table.
    // If we want to increase resolver speed by avoiding the need for read lock,
    // we could make change this hash table to be lock-free for the reader.
    const Array& hash_table =
        Array::Handle(thread->zone(), untag()->functions_hash_table());
    if (!hash_table.IsNull()) {
      ClassFunctionsSet set(hash_table.ptr());
      REUSABLE_STRING_HANDLESCOPE(thread);
      function ^= set.GetOrNull(FunctionName(name, &(thread->StringHandle())));
      // No mutations.
      ASSERT(set.Release().ptr() == hash_table.ptr());
      return function.IsNull() ? Function::null()
                               : CheckFunctionType(function, kind);
    }
  }
  if (name.IsSymbol()) {
    // Quick Symbol compare.
    NoSafepointScope no_safepoint;
    for (intptr_t i = 0; i < len; i++) {
      function ^= funcs.At(i);
      if (function.name() == name.ptr()) {
        return CheckFunctionType(function, kind);
      }
    }
  } else {
    REUSABLE_STRING_HANDLESCOPE(thread);
    String& function_name = thread->StringHandle();
    for (intptr_t i = 0; i < len; i++) {
      function ^= funcs.At(i);
      function_name = function.name();
      if (function_name.Equals(name)) {
        return CheckFunctionType(function, kind);
      }
    }
  }
  // No function found.
  return Function::null();
}

FunctionPtr Class::LookupFunctionAllowPrivate(const String& name,
                                              MemberKind kind) const {
  ASSERT(!IsNull());
  Thread* thread = Thread::Current();
  RELEASE_ASSERT(is_finalized());
  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_FUNCTION_HANDLESCOPE(thread);
  REUSABLE_STRING_HANDLESCOPE(thread);
  Array& funcs = thread->ArrayHandle();
  funcs = current_functions();
  ASSERT(!funcs.IsNull());
  const intptr_t len = funcs.Length();
  Function& function = thread->FunctionHandle();
  String& function_name = thread->StringHandle();
  for (intptr_t i = 0; i < len; i++) {
    function ^= funcs.At(i);
    function_name = function.name();
    if (String::EqualsIgnoringPrivateKey(function_name, name)) {
      return CheckFunctionType(function, kind);
    }
  }
  // No function found.
  return Function::null();
}

FieldPtr Class::LookupInstanceField(const String& name) const {
  return LookupField(name, kInstance);
}

FieldPtr Class::LookupStaticField(const String& name) const {
  return LookupField(name, kStatic);
}

FieldPtr Class::LookupField(const String& name) const {
  return LookupField(name, kAny);
}

FieldPtr Class::LookupField(const String& name, MemberKind kind) const {
  ASSERT(!IsNull());
  Thread* thread = Thread::Current();
  if (EnsureIsFinalized(thread) != Error::null()) {
    return Field::null();
  }
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_FIELD_HANDLESCOPE(thread);
  REUSABLE_STRING_HANDLESCOPE(thread);
  Array& flds = thread->ArrayHandle();
  flds = fields();
  ASSERT(!flds.IsNull());
  intptr_t len = flds.Length();
  Field& field = thread->FieldHandle();
  if (name.IsSymbol()) {
    // Use fast raw pointer string compare for symbols.
    for (intptr_t i = 0; i < len; i++) {
      field ^= flds.At(i);
      if (name.ptr() == field.name()) {
        if (kind == kInstance) {
          return field.is_static() ? Field::null() : field.ptr();
        } else if (kind == kStatic) {
          return field.is_static() ? field.ptr() : Field::null();
        }
        ASSERT(kind == kAny);
        return field.ptr();
      }
    }
  } else {
    String& field_name = thread->StringHandle();
    for (intptr_t i = 0; i < len; i++) {
      field ^= flds.At(i);
      field_name = field.name();
      if (name.Equals(field_name)) {
        if (kind == kInstance) {
          return field.is_static() ? Field::null() : field.ptr();
        } else if (kind == kStatic) {
          return field.is_static() ? field.ptr() : Field::null();
        }
        ASSERT(kind == kAny);
        return field.ptr();
      }
    }
  }
  return Field::null();
}

FieldPtr Class::LookupFieldAllowPrivate(const String& name,
                                        bool instance_only) const {
  ASSERT(!IsNull());
  // Use slow string compare, ignoring privacy name mangling.
  Thread* thread = Thread::Current();
  if (EnsureIsFinalized(thread) != Error::null()) {
    return Field::null();
  }
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_FIELD_HANDLESCOPE(thread);
  REUSABLE_STRING_HANDLESCOPE(thread);
  Array& flds = thread->ArrayHandle();
  flds = fields();
  ASSERT(!flds.IsNull());
  intptr_t len = flds.Length();
  Field& field = thread->FieldHandle();
  String& field_name = thread->StringHandle();
  for (intptr_t i = 0; i < len; i++) {
    field ^= flds.At(i);
    field_name = field.name();
    if (field.is_static() && instance_only) {
      // If we only care about instance fields, skip statics.
      continue;
    }
    if (String::EqualsIgnoringPrivateKey(field_name, name)) {
      return field.ptr();
    }
  }
  return Field::null();
}

FieldPtr Class::LookupInstanceFieldAllowPrivate(const String& name) const {
  Field& field = Field::Handle(LookupFieldAllowPrivate(name, true));
  if (!field.IsNull() && !field.is_static()) {
    return field.ptr();
  }
  return Field::null();
}

FieldPtr Class::LookupStaticFieldAllowPrivate(const String& name) const {
  Field& field = Field::Handle(LookupFieldAllowPrivate(name));
  if (!field.IsNull() && field.is_static()) {
    return field.ptr();
  }
  return Field::null();
}

const char* Class::ToCString() const {
  ZoneTextBuffer buffer(Thread::Current()->zone());
  const Library& lib = Library::Handle(library());
  if (!lib.IsNull()) {
    buffer.Printf("%s ", lib.ToCString());
  }
  buffer.Printf("Class: %s", String::Handle(Name()).ToCString());
  return buffer.buffer();
}

// Thomas Wang, Integer Hash Functions.
// https://gist.github.com/badboy/6267743
// "64 bit to 32 bit Hash Functions"
static uword Hash64To32(uint64_t v) {
  v = ~v + (v << 18);
  v = v ^ (v >> 31);
  v = v * 21;
  v = v ^ (v >> 11);
  v = v + (v << 6);
  v = v ^ (v >> 22);
  return static_cast<uint32_t>(v);
}

InstancePtr Class::LookupCanonicalInstance(Zone* zone,
                                           const Instance& value) const {
  ASSERT(this->ptr() == value.clazz());
  ASSERT(is_finalized() || is_prefinalized());
  Instance& canonical_value = Instance::Handle(zone);
  if (this->constants() != Array::null()) {
    CanonicalInstancesSet constants(zone, this->constants());
    canonical_value ^= constants.GetOrNull(CanonicalInstanceKey(value));
    this->set_constants(constants.Release());
  }
  return canonical_value.ptr();
}

InstancePtr Class::InsertCanonicalConstant(Zone* zone,
                                           const Instance& constant) const {
  ASSERT(constant.IsCanonical());
  ASSERT(this->ptr() == constant.clazz());
  Instance& canonical_value = Instance::Handle(zone);
  if (this->constants() == Array::null()) {
    CanonicalInstancesSet constants(
        HashTables::New<CanonicalInstancesSet>(128, Heap::kOld));
    canonical_value ^= constants.InsertNewOrGet(CanonicalInstanceKey(constant));
    this->set_constants(constants.Release());
  } else {
    CanonicalInstancesSet constants(Thread::Current()->zone(),
                                    this->constants());
    canonical_value ^= constants.InsertNewOrGet(CanonicalInstanceKey(constant));
    this->set_constants(constants.Release());
  }
  return canonical_value.ptr();
}

// Scoped mapping FunctionType -> FunctionType.
// Used for tracking and updating nested generic function types
// and their type parameters.
class FunctionTypeMapping : public ValueObject {
 public:
  FunctionTypeMapping(Zone* zone,
                      FunctionTypeMapping** mapping,
                      const FunctionType& from,
                      const FunctionType& to)
      : zone_(zone), parent_(*mapping), from_(from), to_(to) {
    // Add self to the linked list.
    *mapping = this;
  }

  const FunctionType* Find(const Object& from) const {
    if (!from.IsFunctionType()) {
      return nullptr;
    }
    for (const FunctionTypeMapping* scope = this; scope != nullptr;
         scope = scope->parent_) {
      if (scope->from_.ptr() == from.ptr()) {
        return &(scope->to_);
      }
    }
    return nullptr;
  }

  TypeParameterPtr MapTypeParameter(const TypeParameter& type_param) const {
    ASSERT(type_param.IsFunctionTypeParameter());
    const FunctionType* new_owner = Find(
        FunctionType::Handle(zone_, type_param.parameterized_function_type()));
    if (new_owner != nullptr) {
      return new_owner->TypeParameterAt(type_param.index() - type_param.base(),
                                        type_param.nullability());
    }
    return type_param.ptr();
  }

  bool ContainsOwnersOfTypeParameters(const TypeParameter& p1,
                                      const TypeParameter& p2) const {
    auto& from = FunctionType::Handle(zone_, p1.parameterized_function_type());
    const FunctionType* to = Find(from);
    if (to != nullptr) {
      return to->ptr() == p2.parameterized_function_type();
    }
    from = p2.parameterized_function_type();
    to = Find(from);
    if (to != nullptr) {
      return to->ptr() == p1.parameterized_function_type();
    }
    return false;
  }

 private:
  Zone* zone_;
  const FunctionTypeMapping* const parent_;
  const FunctionType& from_;
  const FunctionType& to_;
};

intptr_t TypeParameters::Length() const {
  if (IsNull() || untag()->names() == Array::null()) return 0;
  return Smi::Value(untag()->names()->untag()->length());
}

void TypeParameters::set_names(const Array& value) const {
  ASSERT(!value.IsNull());
  untag()->set_names(value.ptr());
}

StringPtr TypeParameters::NameAt(intptr_t index) const {
  const Array& names_array = Array::Handle(names());
  return String::RawCast(names_array.At(index));
}

void TypeParameters::SetNameAt(intptr_t index, const String& value) const {
  const Array& names_array = Array::Handle(names());
  names_array.SetAt(index, value);
}

void TypeParameters::set_flags(const Array& value) const {
  untag()->set_flags(value.ptr());
}

void TypeParameters::set_bounds(const TypeArguments& value) const {
  // A null value represents a vector of dynamic.
  untag()->set_bounds(value.ptr());
}

AbstractTypePtr TypeParameters::BoundAt(intptr_t index) const {
  const TypeArguments& upper_bounds = TypeArguments::Handle(bounds());
  return upper_bounds.IsNull() ? Type::DynamicType()
                               : upper_bounds.TypeAt(index);
}

void TypeParameters::SetBoundAt(intptr_t index,
                                const AbstractType& value) const {
  const TypeArguments& upper_bounds = TypeArguments::Handle(bounds());
  upper_bounds.SetTypeAt(index, value);
}

bool TypeParameters::AllDynamicBounds() const {
  return bounds() == TypeArguments::null();
}

void TypeParameters::set_defaults(const TypeArguments& value) const {
  // The null value represents a vector of dynamic.
  untag()->set_defaults(value.ptr());
}

AbstractTypePtr TypeParameters::DefaultAt(intptr_t index) const {
  const TypeArguments& default_type_args = TypeArguments::Handle(defaults());
  return default_type_args.IsNull() ? Type::DynamicType()
                                    : default_type_args.TypeAt(index);
}

void TypeParameters::SetDefaultAt(intptr_t index,
                                  const AbstractType& value) const {
  const TypeArguments& default_type_args = TypeArguments::Handle(defaults());
  default_type_args.SetTypeAt(index, value);
}

bool TypeParameters::AllDynamicDefaults() const {
  return defaults() == TypeArguments::null();
}

void TypeParameters::AllocateFlags(Heap::Space space) const {
  const intptr_t len = (Length() + kFlagsPerSmiMask) >> kFlagsPerSmiShift;
  const Array& flags_array = Array::Handle(Array::New(len, space));
  // Initialize flags to 0.
  const Smi& zero = Smi::Handle(Smi::New(0));
  for (intptr_t i = 0; i < len; i++) {
    flags_array.SetAt(i, zero);
  }
  set_flags(flags_array);
}

void TypeParameters::OptimizeFlags() const {
  if (untag()->flags() == Array::null()) return;  // Already optimized.
  const intptr_t len = (Length() + kFlagsPerSmiMask) >> kFlagsPerSmiShift;
  const Array& flags_array = Array::Handle(flags());
  const Smi& zero = Smi::Handle(Smi::New(0));
  for (intptr_t i = 0; i < len; i++) {
    if (flags_array.At(i) != zero.ptr()) return;
  }
  set_flags(Object::null_array());
}

bool TypeParameters::IsGenericCovariantImplAt(intptr_t index) const {
  if (untag()->flags() == Array::null()) return false;
  const intptr_t flag = Smi::Value(
      Smi::RawCast(Array::Handle(flags()).At(index >> kFlagsPerSmiShift)));
  return (flag >> (index & kFlagsPerSmiMask)) != 0;
}

void TypeParameters::SetIsGenericCovariantImplAt(intptr_t index,
                                                 bool value) const {
  const Array& flg = Array::Handle(flags());
  intptr_t flag = Smi::Value(Smi::RawCast(flg.At(index >> kFlagsPerSmiShift)));
  if (value) {
    flag |= 1 << (index % kFlagsPerSmiMask);
  } else {
    flag &= ~(1 << (index % kFlagsPerSmiMask));
  }
  flg.SetAt(index >> kFlagsPerSmiShift, Smi::Handle(Smi::New(flag)));
}

void TypeParameters::Print(Thread* thread,
                           Zone* zone,
                           bool are_class_type_parameters,
                           intptr_t base,
                           NameVisibility name_visibility,
                           BaseTextBuffer* printer) const {
  String& name = String::Handle(zone);
  AbstractType& type = AbstractType::Handle(zone);
  const intptr_t num_type_params = Length();
  for (intptr_t i = 0; i < num_type_params; i++) {
    if (are_class_type_parameters) {
      name = NameAt(i);
      printer->AddString(name.ToCString());
    } else {
      printer->AddString(TypeParameter::CanonicalNameCString(
          are_class_type_parameters, base, base + i));
    }
    if (FLAG_show_internal_names || !AllDynamicBounds()) {
      type = BoundAt(i);
      // Do not print default bound.
      if (!type.IsNull() && (FLAG_show_internal_names || !type.IsObjectType() ||
                             type.IsNonNullable())) {
        printer->AddString(" extends ");
        type.PrintName(name_visibility, printer);
        if (FLAG_show_internal_names && !AllDynamicDefaults()) {
          type = DefaultAt(i);
          if (!type.IsNull() &&
              (FLAG_show_internal_names || !type.IsDynamicType())) {
            printer->AddString(" defaults to ");
            type.PrintName(name_visibility, printer);
          }
        }
      }
    }
    if (i != num_type_params - 1) {
      printer->AddString(", ");
    }
  }
}

const char* TypeParameters::ToCString() const {
  if (IsNull()) {
    return "TypeParameters: null";
  }
  auto thread = Thread::Current();
  auto zone = thread->zone();
  ZoneTextBuffer buffer(zone);
  buffer.AddString("TypeParameters: ");
  Print(thread, zone, true, 0, kInternalName, &buffer);
  return buffer.buffer();
}

TypeParametersPtr TypeParameters::New(Heap::Space space) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kTypeParametersCid) !=
         Class::null());
  return Object::Allocate<TypeParameters>(space);
}

TypeParametersPtr TypeParameters::New(intptr_t count, Heap::Space space) {
  const TypeParameters& result =
      TypeParameters::Handle(TypeParameters::New(space));
  // Create an [ Array ] of [ String ] objects to represent the names.
  // Create a [ TypeArguments ] vector representing the bounds.
  // Create a [ TypeArguments ] vector representing the defaults.
  // Create an [ Array ] of [ Smi] objects to represent the flags.
  const Array& names_array = Array::Handle(Array::New(count, space));
  result.set_names(names_array);
  TypeArguments& type_args = TypeArguments::Handle();
  type_args = TypeArguments::New(count, Heap::kNew);  // Will get canonicalized.
  result.set_bounds(type_args);
  type_args = TypeArguments::New(count, Heap::kNew);  // Will get canonicalized.
  result.set_defaults(type_args);
  result.AllocateFlags(space);  // Will get optimized.
  return result.ptr();
}

intptr_t TypeArguments::ComputeNullability() const {
  if (IsNull()) return 0;
  const intptr_t num_types = Length();
  intptr_t result = 0;
  if (num_types <= kNullabilityMaxTypes) {
    AbstractType& type = AbstractType::Handle();
    for (intptr_t i = 0; i < num_types; i++) {
      type = TypeAt(i);
      intptr_t type_bits = 0;
      if (!type.IsNull()) {
        switch (type.nullability()) {
          case Nullability::kNullable:
            type_bits = kNullableBit;
            break;
          case Nullability::kNonNullable:
            type_bits = kNonNullableBit;
            break;
          default:
            UNREACHABLE();
        }
      }
      result |= (type_bits << (i * kNullabilityBitsPerType));
    }
  }
  set_nullability(result);
  return result;
}

void TypeArguments::set_nullability(intptr_t value) const {
  untag()->set_nullability(Smi::New(value));
}

uword TypeArguments::HashForRange(intptr_t from_index, intptr_t len) const {
  if (IsNull()) return kAllDynamicHash;
  if (IsRaw(from_index, len)) return kAllDynamicHash;
  uint32_t result = 0;
  AbstractType& type = AbstractType::Handle();
  for (intptr_t i = 0; i < len; i++) {
    type = TypeAt(from_index + i);
    ASSERT(!type.IsNull());
    result = CombineHashes(result, type.Hash());
  }
  result = FinalizeHash(result, kHashBits);
  return result;
}

uword TypeArguments::ComputeHash() const {
  if (IsNull()) return kAllDynamicHash;
  const uword result = HashForRange(0, Length());
  ASSERT(result != 0);
  SetHash(result);
  return result;
}

TypeArgumentsPtr TypeArguments::Prepend(Zone* zone,
                                        const TypeArguments& other,
                                        intptr_t other_length,
                                        intptr_t total_length) const {
  if (other_length == 0) {
    ASSERT(IsCanonical());
    return ptr();
  } else if (other_length == total_length) {
    ASSERT(other.IsCanonical());
    return other.ptr();
  } else if (IsNull() && other.IsNull()) {
    return TypeArguments::null();
  }
  const TypeArguments& result =
      TypeArguments::Handle(zone, TypeArguments::New(total_length, Heap::kNew));
  AbstractType& type = AbstractType::Handle(zone);
  for (intptr_t i = 0; i < other_length; i++) {
    type = other.IsNull() ? Type::DynamicType() : other.TypeAt(i);
    result.SetTypeAt(i, type);
  }
  for (intptr_t i = other_length; i < total_length; i++) {
    type = IsNull() ? Type::DynamicType() : TypeAt(i - other_length);
    result.SetTypeAt(i, type);
  }
  return result.Canonicalize(Thread::Current());
}

InstantiationMode TypeArguments::GetInstantiationMode(Zone* zone,
                                                      const Function* function,
                                                      const Class* cls) const {
  if (IsNull() || IsInstantiated()) {
    return InstantiationMode::kIsInstantiated;
  }
  if (function != nullptr) {
    if (CanShareFunctionTypeArguments(*function)) {
      return InstantiationMode::kSharesFunctionTypeArguments;
    }
    if (cls == nullptr) {
      cls = &Class::Handle(zone, function->Owner());
    }
  }
  if (cls != nullptr) {
    if (CanShareInstantiatorTypeArguments(*cls)) {
      return InstantiationMode::kSharesInstantiatorTypeArguments;
    }
  }
  return InstantiationMode::kNeedsInstantiation;
}

StringPtr TypeArguments::Name() const {
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  PrintSubvectorName(0, Length(), kInternalName, &printer);
  return Symbols::New(thread, printer.buffer());
}

StringPtr TypeArguments::UserVisibleName() const {
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  PrintSubvectorName(0, Length(), kUserVisibleName, &printer);
  return Symbols::New(thread, printer.buffer());
}

void TypeArguments::PrintSubvectorName(intptr_t from_index,
                                       intptr_t len,
                                       NameVisibility name_visibility,
                                       BaseTextBuffer* printer) const {
  printer->AddString("<");
  AbstractType& type = AbstractType::Handle();
  for (intptr_t i = 0; i < len; i++) {
    if (from_index + i < Length()) {
      type = TypeAt(from_index + i);
      if (type.IsNull()) {
        printer->AddString("null");  // Unfinalized vector.
      } else {
        type.PrintName(name_visibility, printer);
      }
    } else {
      printer->AddString("dynamic");
    }
    if (i < len - 1) {
      printer->AddString(", ");
    }
  }
  printer->AddString(">");
}

void TypeArguments::PrintTo(BaseTextBuffer* buffer) const {
  buffer->AddString("TypeArguments: ");
  if (IsNull()) {
    return buffer->AddString("null");
  }
  buffer->Printf("(H%" Px ")", Smi::Value(untag()->hash()));
  auto& type_at = AbstractType::Handle();
  for (intptr_t i = 0; i < Length(); i++) {
    type_at = TypeAt(i);
    buffer->Printf(" [%s]", type_at.IsNull() ? "null" : type_at.ToCString());
  }
}

bool TypeArguments::IsSubvectorEquivalent(
    const TypeArguments& other,
    intptr_t from_index,
    intptr_t len,
    TypeEquality kind,
    FunctionTypeMapping* function_type_equivalence) const {
  if (this->ptr() == other.ptr()) {
    return true;
  }
  if (kind == TypeEquality::kCanonical) {
    if (IsNull()) {
      return other.IsRaw(from_index, len);
    } else if (other.IsNull()) {
      return IsRaw(from_index, len);
    }
    if (Length() != other.Length()) {
      return false;
    }
  }
  AbstractType& type = AbstractType::Handle();
  AbstractType& other_type = AbstractType::Handle();
  for (intptr_t i = from_index; i < from_index + len; i++) {
    type = IsNull() ? Type::DynamicType() : TypeAt(i);
    ASSERT(!type.IsNull());
    other_type = other.IsNull() ? Type::DynamicType() : other.TypeAt(i);
    ASSERT(!other_type.IsNull());
    if (!type.IsEquivalent(other_type, kind, function_type_equivalence)) {
      return false;
    }
  }
  return true;
}

bool TypeArguments::IsDynamicTypes(bool raw_instantiated,
                                   intptr_t from_index,
                                   intptr_t len) const {
  ASSERT(Length() >= (from_index + len));
  AbstractType& type = AbstractType::Handle();
  Class& type_class = Class::Handle();
  for (intptr_t i = 0; i < len; i++) {
    type = TypeAt(from_index + i);
    if (type.IsNull()) {
      return false;
    }
    if (!type.HasTypeClass()) {
      if (raw_instantiated && type.IsTypeParameter()) {
        // An uninstantiated type parameter is equivalent to dynamic.
        continue;
      }
      return false;
    }
    type_class = type.type_class();
    if (!type_class.IsDynamicClass()) {
      return false;
    }
  }
  return true;
}

TypeArguments::Cache::Cache(Zone* zone, const TypeArguments& source)
    : zone_(ASSERT_NOTNULL(zone)),
      cache_container_(&source),
      data_(Array::Handle(source.instantiations())),
      smi_handle_(Smi::Handle(zone)) {
  ASSERT(IsolateGroup::Current()
             ->type_arguments_canonicalization_mutex()
             ->IsOwnedByCurrentThread());
}

TypeArguments::Cache::Cache(Zone* zone, const Array& array)
    : zone_(ASSERT_NOTNULL(zone)),
      cache_container_(nullptr),
      data_(Array::Handle(array.ptr())),
      smi_handle_(Smi::Handle(zone)) {
  ASSERT(IsolateGroup::Current()
             ->type_arguments_canonicalization_mutex()
             ->IsOwnedByCurrentThread());
}

bool TypeArguments::Cache::IsHash(const Array& array) {
  return array.Length() > kMaxLinearCacheSize;
}

intptr_t TypeArguments::Cache::NumOccupied(const Array& array) {
  return NumOccupiedBits::decode(
      RawSmiValue(Smi::RawCast(array.AtAcquire(kMetadataIndex))));
}

#if defined(DEBUG)
bool TypeArguments::Cache::IsValidStorageLocked(const Array& array) {
  // We only require the mutex be held so we don't need to use acquire/release
  // semantics to access and set the number of occupied entries in the header.
  ASSERT(IsolateGroup::Current()
             ->type_arguments_canonicalization_mutex()
             ->IsOwnedByCurrentThread());
  // Quick check against the empty linear cache.
  if (array.ptr() == EmptyStorage().ptr()) return true;
  const intptr_t num_occupied = NumOccupied(array);
  // We should be using the same shared value for an empty cache.
  if (num_occupied == 0) return false;
  const intptr_t storage_len = array.Length();
  // All caches have the metadata followed by a series of entries.
  if ((storage_len % kEntrySize) != kHeaderSize) return false;
  const intptr_t num_entries = NumEntries(array);
  // Linear caches contain at least one unoccupied entry, and hash-based caches
  // grow prior to hitting 100% occupancy.
  if (num_occupied >= num_entries) return false;
  // In a linear cache, all entries with indexes smaller than [num_occupied]
  // should be occupied and ones greater than or equal should be unoccupied.
  const bool is_linear_cache = IsLinear(array);
  // The capacity of a hash-based cache must be a power of two (see
  // EnsureCapacityLocked as to why).
  if (!is_linear_cache) {
    if (!Utils::IsPowerOfTwo(num_entries)) return false;
    const intptr_t metadata =
        RawSmiValue(Smi::RawCast(array.AtAcquire(kMetadataIndex)));
    if ((1 << EntryCountLog2Bits::decode(metadata)) != num_entries) {
      return false;
    }
  }
  for (intptr_t i = 0; i < num_entries; i++) {
    const intptr_t index = kHeaderSize + i * kEntrySize;
    if (array.At(index + kSentinelIndex) == Sentinel()) {
      if (is_linear_cache && i < num_occupied) return false;
      continue;
    }
    if (is_linear_cache && i >= num_occupied) return false;
    // The elements of an occupied entry are all TypeArguments values.
    for (intptr_t j = index; j < index + kEntrySize; j++) {
      if (!array.At(j)->IsHeapObject()) return false;
      if (array.At(j) == Object::null()) continue;  // null is a valid TAV.
      if (!array.At(j)->IsTypeArguments()) return false;
    }
  }
  return true;
}
#endif

bool TypeArguments::Cache::IsOccupied(intptr_t entry) const {
  InstantiationsCacheTable table(data_);
  ASSERT(entry >= 0 && entry < table.Length());
  return table.At(entry).Get<kSentinelIndex>() != Sentinel();
}

TypeArgumentsPtr TypeArguments::Cache::Retrieve(intptr_t entry) const {
  ASSERT(IsOccupied(entry));
  InstantiationsCacheTable table(data_);
  return table.At(entry).Get<kInstantiatedTypeArgsIndex>();
}

intptr_t TypeArguments::Cache::NumEntries(const Array& array) {
  InstantiationsCacheTable table(array);
  return table.Length();
}

TypeArguments::Cache::KeyLocation TypeArguments::Cache::FindKeyOrUnused(
    const Array& array,
    const TypeArguments& instantiator_tav,
    const TypeArguments& function_tav) {
  const bool is_hash = IsHash(array);
  InstantiationsCacheTable table(array);
  const intptr_t num_entries = table.Length();
  // For a linear cache, start at the first entry and probe linearly. This can
  // be done because a linear cache always has at least one unoccupied entry
  // after all the occupied ones.
  intptr_t probe = 0;
  intptr_t probe_distance = 1;
  if (is_hash) {
    // For a hash-based cache, instead start at an entry determined by the hash
    // of the keys.
    auto hash = FinalizeHash(
        CombineHashes(instantiator_tav.Hash(), function_tav.Hash()));
    probe = hash & (num_entries - 1);
  }
  while (true) {
    const auto& tuple = table.At(probe);
    if (tuple.Get<kSentinelIndex>() == Sentinel()) break;
    if ((tuple.Get<kInstantiatorTypeArgsIndex>() == instantiator_tav.ptr()) &&
        (tuple.Get<kFunctionTypeArgsIndex>() == function_tav.ptr())) {
      return {probe, true};
    }
    // Advance probe by the current probing distance.
    probe = probe + probe_distance;
    if (is_hash) {
      // Wrap around if the probe goes off the end of the entries array.
      probe = probe & (num_entries - 1);
      // We had a collision, so increase the probe distance. See comment in
      // EnsureCapacityLocked for an explanation of how this hits all slots.
      probe_distance++;
    }
  }
  // We should always get the next slot for a linear cache.
  ASSERT(is_hash || probe == NumOccupied(array));
  return {probe, false};
}

TypeArguments::Cache::KeyLocation TypeArguments::Cache::AddEntry(
    intptr_t entry,
    const TypeArguments& instantiator_tav,
    const TypeArguments& function_tav,
    const TypeArguments& instantiated_tav) const {
  // We don't do mutating operations in tests without a TypeArguments object.
  ASSERT(cache_container_ != nullptr);
#if defined(DEBUG)
  auto loc = FindKeyOrUnused(instantiator_tav, function_tav);
  ASSERT_EQUAL(loc.entry, entry);
  ASSERT(!loc.present);
#endif
  // Double-check we got the expected entry index when adding to a linear array.
  ASSERT(!IsLinear() || entry == NumOccupied());
  const intptr_t new_occupied = NumOccupied() + 1;
  const bool storage_changed = EnsureCapacity(new_occupied);
  // Note that this call to IsLinear() may return a different result than the
  // earlier, since EnsureCapacity() may have swapped to hash-based storage.
  if (storage_changed && !IsLinear()) {
    // The capacity of the array has changed, and the capacity is used when
    // probing further into the array due to collisions. Thus, we need to redo
    // the entry index calculation.
    auto loc = FindKeyOrUnused(instantiator_tav, function_tav);
    ASSERT(!loc.present);
    entry = loc.entry;
  }

  // Go ahead and increment the number of occupied entries prior to adding the
  // entry. Use a store-release barrier in case of concurrent readers.
  const intptr_t metadata = RawSmiValue(Smi::RawCast(data_.At(kMetadataIndex)));
  smi_handle_ = Smi::New(NumOccupiedBits::update(new_occupied, metadata));
  data_.SetAtRelease(kMetadataIndex, smi_handle_);

  InstantiationsCacheTable table(data_);
  const auto& tuple = table.At(entry);
  // The parts of the tuple that aren't used for sentinel checking are only
  // retrieved if the entry is occupied. Entries in the cache are never deleted,
  // so once the entry is marked as occupied, the contents of that entry never
  // change. Thus, we don't need store-release barriers here.
  tuple.Set<kFunctionTypeArgsIndex>(function_tav);
  tuple.Set<kInstantiatedTypeArgsIndex>(instantiated_tav);
  // For the sentinel position, though, we do.
  static_assert(
      kSentinelIndex == kInstantiatorTypeArgsIndex,
      "the sentinel position is not protected with a store-release barrier");
  tuple.Set<kInstantiatorTypeArgsIndex, std::memory_order_release>(
      instantiator_tav);

  if (storage_changed) {
    // Only check for validity on growth, just to keep the overhead on DEBUG
    // builds down.
    DEBUG_ASSERT(IsValidStorageLocked(data_));
    // Update the container of the original cache to point to the new one.
    cache_container_->set_instantiations(data_);
  }

  return {entry, true};
}

SmiPtr TypeArguments::Cache::Sentinel() {
  return Smi::New(kSentinelValue);
}

bool TypeArguments::Cache::EnsureCapacity(intptr_t new_occupied) const {
  ASSERT(new_occupied > NumOccupied());
  // How many entries are in the current array (including unoccupied entries).
  const intptr_t current_capacity = NumEntries();

  // Early returns for cases where no growth is needed.
  const bool is_linear = IsLinear();
  if (is_linear) {
    // We need at least one unoccupied entry in addition to the occupied ones.
    if (current_capacity > new_occupied) return false;
  } else {
    if (LoadFactor(new_occupied, current_capacity) < kMaxLoadFactor) {
      return false;
    }
  }

  if (new_occupied <= kMaxLinearCacheEntries) {
    ASSERT(is_linear);
    // Not enough room for both the new entry and at least one unoccupied
    // entry, so grow the tuple capacity of the linear cache by about 50%,
    // ensuring that space for at least one new tuple is added, capping the
    // total number of occupied entries to the max allowed.
    const intptr_t new_capacity =
        Utils::Minimum(current_capacity + (current_capacity >> 1),
                       kMaxLinearCacheEntries) +
        1;
    const intptr_t cache_size = kHeaderSize + new_capacity * kEntrySize;
    ASSERT(cache_size <= kMaxLinearCacheSize);
    data_ = Array::Grow(data_, cache_size, Heap::kOld);
    ASSERT(!data_.IsNull());
    // No need to adjust the number of occupied entries or old entries, as they
    // are copied over by Array::Grow. Just mark any new entries as unoccupied.
    smi_handle_ = Sentinel();
    InstantiationsCacheTable table(data_);
    for (intptr_t i = current_capacity; i < new_capacity; i++) {
      const auto& tuple = table.At(i);
      tuple.Set<kSentinelIndex>(smi_handle_);
    }
    return true;
  }

  // Either we're converting a linear cache into a hash-based cache, or the
  // load factor of the hash-based cache has increased to the point where we
  // need to grow it.
  const intptr_t new_capacity =
      is_linear ? kNumInitialHashCacheEntries : 2 * current_capacity;
  // Because we use quadratic (actually triangle number) probing it is
  // important that the size is a power of two (otherwise we could fail to
  // find an empty slot).  This is described in Knuth's The Art of Computer
  // Programming Volume 2, Chapter 6.4, exercise 20 (solution in the
  // appendix, 2nd edition).
  ASSERT(Utils::IsPowerOfTwo(new_capacity));
  ASSERT(LoadFactor(new_occupied, new_capacity) < kMaxLoadFactor);
  const intptr_t new_size = kHeaderSize + new_capacity * kEntrySize;
  const auto& new_data =
      Array::Handle(zone_, Array::NewUninitialized(new_size, Heap::kOld));
  ASSERT(!new_data.IsNull());
  // First set up the metadata in new_data.
  const intptr_t metadata = RawSmiValue(Smi::RawCast(data_.At(kMetadataIndex)));
  smi_handle_ = Smi::New(EntryCountLog2Bits::update(
      Utils::ShiftForPowerOfTwo(new_capacity), metadata));
  new_data.SetAt(kMetadataIndex, smi_handle_);
  // Then mark all the entries in new_data as unoccupied.
  smi_handle_ = Sentinel();
  InstantiationsCacheTable to_table(new_data);
  for (const auto& tuple : to_table) {
    tuple.Set<kSentinelIndex>(smi_handle_);
  }
  // Finally, copy over the entries.
  auto& instantiator_tav = TypeArguments::Handle(zone_);
  auto& function_tav = TypeArguments::Handle(zone_);
  auto& result_tav = TypeArguments::Handle(zone_);
  const InstantiationsCacheTable from_table(data_);
  for (const auto& from_tuple : from_table) {
    // Skip unoccupied entries.
    if (from_tuple.Get<kSentinelIndex>() == Sentinel()) continue;
    instantiator_tav ^= from_tuple.Get<kInstantiatorTypeArgsIndex>();
    function_tav = from_tuple.Get<kFunctionTypeArgsIndex>();
    result_tav = from_tuple.Get<kInstantiatedTypeArgsIndex>();
    // Since new_data has a different total capacity, we can't use the old
    // entry indexes, but must recalculate them.
    auto loc = FindKeyOrUnused(new_data, instantiator_tav, function_tav);
    ASSERT(!loc.present);
    const auto& to_tuple = to_table.At(loc.entry);
    to_tuple.Set<kInstantiatorTypeArgsIndex>(instantiator_tav);
    to_tuple.Set<kFunctionTypeArgsIndex>(function_tav);
    to_tuple.Set<kInstantiatedTypeArgsIndex>(result_tav);
  }
  data_ = new_data.ptr();
  return true;
}

bool TypeArguments::HasInstantiations() const {
  return instantiations() != Cache::EmptyStorage().ptr();
}

ArrayPtr TypeArguments::instantiations() const {
  // We rely on the fact that any loads from the array are dependent loads and
  // avoid the load-acquire barrier here.
  return untag()->instantiations();
}

void TypeArguments::set_instantiations(const Array& value) const {
  // We have to ensure that initializing stores to the array are available
  // when releasing the pointer to the array pointer.
  // => We have to use store-release here.
  ASSERT(!value.IsNull());
  untag()->set_instantiations<std::memory_order_release>(value.ptr());
}

bool TypeArguments::HasCount(intptr_t count) const {
  if (IsNull()) {
    return true;
  }
  return Length() == count;
}

intptr_t TypeArguments::Length() const {
  if (IsNull()) {
    return 0;
  }
  return Smi::Value(untag()->length());
}

intptr_t TypeArguments::nullability() const {
  if (IsNull()) {
    return 0;
  }
  return Smi::Value(untag()->nullability());
}

AbstractTypePtr TypeArguments::TypeAt(intptr_t index) const {
  ASSERT(!IsNull());
  ASSERT((index >= 0) && (index < Length()));
  return untag()->element(index);
}

AbstractTypePtr TypeArguments::TypeAtNullSafe(intptr_t index) const {
  if (IsNull()) {
    // null vector represents infinite list of dynamics
    return Type::dynamic_type().ptr();
  }
  ASSERT((index >= 0) && (index < Length()));
  return TypeAt(index);
}

void TypeArguments::SetTypeAt(intptr_t index, const AbstractType& value) const {
  ASSERT(!IsCanonical());
  ASSERT((index >= 0) && (index < Length()));
  return untag()->set_element(index, value.ptr());
}

bool TypeArguments::IsSubvectorInstantiated(
    intptr_t from_index,
    intptr_t len,
    Genericity genericity,
    intptr_t num_free_fun_type_params) const {
  ASSERT(!IsNull());
  AbstractType& type = AbstractType::Handle();
  for (intptr_t i = 0; i < len; i++) {
    type = TypeAt(from_index + i);
    // If this type argument T is null, the type A containing T in its flattened
    // type argument vector V is recursive and is still being finalized.
    // T is the type argument of a super type of A. T is being instantiated
    // during finalization of V, which is also the instantiator. T depends
    // solely on the type parameters of A and will be replaced by a non-null
    // type before A is marked as finalized.
    if (!type.IsNull() &&
        !type.IsInstantiated(genericity, num_free_fun_type_params)) {
      return false;
    }
  }
  return true;
}

bool TypeArguments::IsUninstantiatedIdentity() const {
  AbstractType& type = AbstractType::Handle();
  const intptr_t num_types = Length();
  for (intptr_t i = 0; i < num_types; i++) {
    type = TypeAt(i);
    if (type.IsNull()) {
      return false;  // Still unfinalized, too early to tell.
    }
    if (!type.IsTypeParameter()) {
      return false;
    }
    const TypeParameter& type_param = TypeParameter::Cast(type);
    ASSERT(type_param.IsFinalized());
    if ((type_param.index() != i) || type_param.IsFunctionTypeParameter()) {
      return false;
    }
    // Instantiating nullable type parameters may change
    // nullability of a type, so type arguments vector containing such type
    // parameters cannot be substituted with instantiator type arguments.
    if (type_param.IsNullable()) {
      return false;
    }
  }
  return true;
  // Note that it is not necessary to verify at runtime that the instantiator
  // type vector is long enough, since this uninstantiated vector contains as
  // many different type parameters as it is long.
}

// Return true if this uninstantiated type argument vector, once instantiated
// at runtime, is a prefix of the type argument vector of its instantiator.
// A runtime check may be required, as indicated by with_runtime_check.
bool TypeArguments::CanShareInstantiatorTypeArguments(
    const Class& instantiator_class,
    bool* with_runtime_check) const {
  ASSERT(!IsInstantiated());
  if (with_runtime_check != nullptr) {
    *with_runtime_check = false;
  }
  const intptr_t num_type_args = Length();
  const intptr_t num_instantiator_type_args =
      instantiator_class.NumTypeArguments();
  if (num_type_args > num_instantiator_type_args) {
    // This vector cannot be a prefix of a shorter vector.
    return false;
  }
  const intptr_t num_instantiator_type_params =
      instantiator_class.NumTypeParameters();
  const intptr_t first_type_param_offset =
      num_instantiator_type_args - num_instantiator_type_params;
  // At compile time, the type argument vector of the instantiator consists of
  // the type argument vector of its super type, which may refer to the type
  // parameters of the instantiator class, followed by (or overlapping partially
  // or fully with) the type parameters of the instantiator class in declaration
  // order.
  // In other words, the only variables are the type parameters of the
  // instantiator class.
  // This uninstantiated type argument vector is also expressed in terms of the
  // type parameters of the instantiator class. Therefore, in order to be a
  // prefix once instantiated at runtime, every one of its type argument must be
  // equal to the type argument of the instantiator vector at the same index.

  // As a first requirement, the last num_instantiator_type_params type
  // arguments of this type argument vector must refer to the corresponding type
  // parameters of the instantiator class.
  AbstractType& type_arg = AbstractType::Handle();
  for (intptr_t i = first_type_param_offset; i < num_type_args; i++) {
    type_arg = TypeAt(i);
    if (!type_arg.IsTypeParameter()) {
      return false;
    }
    const TypeParameter& type_param = TypeParameter::Cast(type_arg);
    ASSERT(type_param.IsFinalized());
    if ((type_param.index() != i) || type_param.IsFunctionTypeParameter()) {
      return false;
    }
    // Instantiating nullable type parameters may change nullability
    // of a type, so type arguments vector containing such type parameters
    // cannot be substituted with instantiator type arguments, unless we check
    // at runtime the nullability of the first 1 or 2 type arguments of the
    // instantiator.
    // Note that the presence of non-overlapping super type arguments (i.e.
    // first_type_param_offset > 0) will prevent this optimization.
    if (type_param.IsNullable()) {
      if (with_runtime_check == nullptr || i >= kNullabilityMaxTypes) {
        return false;
      }
      *with_runtime_check = true;
    }
  }
  // As a second requirement, the type arguments corresponding to the super type
  // must be identical. Overlapping ones have already been checked starting at
  // first_type_param_offset.
  if (first_type_param_offset == 0) {
    return true;
  }
  Type& super_type = Type::Handle(instantiator_class.super_type());
  const TypeArguments& super_type_args =
      TypeArguments::Handle(super_type.GetInstanceTypeArguments(
          Thread::Current(), /*canonicalize=*/false));
  if (super_type_args.IsNull()) {
    ASSERT(!IsUninstantiatedIdentity());
    return false;
  }
  AbstractType& super_type_arg = AbstractType::Handle();
  for (intptr_t i = 0; (i < first_type_param_offset) && (i < num_type_args);
       i++) {
    type_arg = TypeAt(i);
    super_type_arg = super_type_args.TypeAt(i);
    if (!type_arg.Equals(super_type_arg)) {
      ASSERT(!IsUninstantiatedIdentity());
      return false;
    }
  }
  return true;
}

// Return true if this uninstantiated type argument vector, once instantiated
// at runtime, is a prefix of the enclosing function type arguments.
// A runtime check may be required, as indicated by with_runtime_check.
bool TypeArguments::CanShareFunctionTypeArguments(
    const Function& function,
    bool* with_runtime_check) const {
  ASSERT(!IsInstantiated());
  if (with_runtime_check != nullptr) {
    *with_runtime_check = false;
  }
  const intptr_t num_type_args = Length();
  const intptr_t num_parent_type_args = function.NumParentTypeArguments();
  const intptr_t num_function_type_params = function.NumTypeParameters();
  const intptr_t num_function_type_args =
      num_parent_type_args + num_function_type_params;
  if (num_type_args > num_function_type_args) {
    // This vector cannot be a prefix of a shorter vector.
    return false;
  }
  AbstractType& type_arg = AbstractType::Handle();
  for (intptr_t i = 0; i < num_type_args; i++) {
    type_arg = TypeAt(i);
    if (!type_arg.IsTypeParameter()) {
      return false;
    }
    const TypeParameter& type_param = TypeParameter::Cast(type_arg);
    ASSERT(type_param.IsFinalized());
    if ((type_param.index() != i) || !type_param.IsFunctionTypeParameter()) {
      return false;
    }
    // Instantiating nullable type parameters may change nullability
    // of a type, so type arguments vector containing such type parameters
    // cannot be substituted with the enclosing function type arguments, unless
    // we check at runtime the nullability of the first 1 or 2 type arguments of
    // the enclosing function type arguments.
    if (type_param.IsNullable()) {
      if (with_runtime_check == nullptr || i >= kNullabilityMaxTypes) {
        return false;
      }
      *with_runtime_check = true;
    }
  }
  return true;
}

TypeArgumentsPtr TypeArguments::TruncatedTo(intptr_t length) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const TypeArguments& result =
      TypeArguments::Handle(zone, TypeArguments::New(length));
  AbstractType& type = AbstractType::Handle(zone);
  for (intptr_t i = 0; i < length; i++) {
    type = TypeAt(i);
    result.SetTypeAt(i, type);
  }
  return result.Canonicalize(thread);
}

bool TypeArguments::IsFinalized() const {
  ASSERT(!IsNull());
  AbstractType& type = AbstractType::Handle();
  const intptr_t num_types = Length();
  for (intptr_t i = 0; i < num_types; i++) {
    type = TypeAt(i);
    if (!type.IsFinalized()) {
      return false;
    }
  }
  return true;
}

TypeArgumentsPtr TypeArguments::InstantiateFrom(
    const TypeArguments& instantiator_type_arguments,
    const TypeArguments& function_type_arguments,
    intptr_t num_free_fun_type_params,
    Heap::Space space,
    FunctionTypeMapping* function_type_mapping,
    intptr_t num_parent_type_args_adjustment) const {
  ASSERT(!IsInstantiated());
  if ((instantiator_type_arguments.IsNull() ||
       instantiator_type_arguments.Length() == Length()) &&
      IsUninstantiatedIdentity()) {
    return instantiator_type_arguments.ptr();
  }
  const intptr_t num_types = Length();
  TypeArguments& instantiated_array =
      TypeArguments::Handle(TypeArguments::New(num_types, space));
  AbstractType& type = AbstractType::Handle();
  for (intptr_t i = 0; i < num_types; i++) {
    type = TypeAt(i);
    // If this type argument T is null, the type A containing T in its flattened
    // type argument vector V is recursive and is still being finalized.
    // T is the type argument of a super type of A. T is being instantiated
    // during finalization of V, which is also the instantiator. T depends
    // solely on the type parameters of A and will be replaced by a non-null
    // type before A is marked as finalized.
    if (!type.IsNull() && !type.IsInstantiated()) {
      type = type.InstantiateFrom(
          instantiator_type_arguments, function_type_arguments,
          num_free_fun_type_params, space, function_type_mapping,
          num_parent_type_args_adjustment);
      // A returned null type indicates a failed instantiation in dead code that
      // must be propagated up to the caller, the optimizing compiler.
      if (type.IsNull()) {
        return Object::empty_type_arguments().ptr();
      }
    }
    instantiated_array.SetTypeAt(i, type);
  }
  return instantiated_array.ptr();
}

TypeArgumentsPtr TypeArguments::UpdateFunctionTypes(
    intptr_t num_parent_type_args_adjustment,
    intptr_t num_free_fun_type_params,
    Heap::Space space,
    FunctionTypeMapping* function_type_mapping) const {
  Zone* zone = Thread::Current()->zone();
  TypeArguments* updated_args = nullptr;
  AbstractType& type = AbstractType::Handle(zone);
  AbstractType& updated = AbstractType::Handle(zone);
  for (intptr_t i = 0, n = Length(); i < n; ++i) {
    type = TypeAt(i);
    updated = type.UpdateFunctionTypes(num_parent_type_args_adjustment,
                                       num_free_fun_type_params, space,
                                       function_type_mapping);
    if (type.ptr() != updated.ptr()) {
      if (updated_args == nullptr) {
        updated_args =
            &TypeArguments::Handle(zone, TypeArguments::New(n, space));
        for (intptr_t j = 0; j < i; ++j) {
          type = TypeAt(j);
          updated_args->SetTypeAt(j, type);
        }
      }
    }
    if (updated_args != nullptr) {
      updated_args->SetTypeAt(i, updated);
    }
  }
  return (updated_args != nullptr) ? updated_args->ptr() : ptr();
}

#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
// A local flag used only in object_test.cc that, when true, causes a failure
// when a cache entry for the given instantiator and function type arguments
// already exists. Used to check that the InstantiateTypeArguments stub found
// the cache entry instead of calling the runtime.
bool TESTING_runtime_fail_on_existing_cache_entry = false;
#endif

TypeArgumentsPtr TypeArguments::InstantiateAndCanonicalizeFrom(
    const TypeArguments& instantiator_type_arguments,
    const TypeArguments& function_type_arguments) const {
  auto thread = Thread::Current();
  auto zone = thread->zone();
  SafepointMutexLocker ml(
      thread->isolate_group()->type_arguments_canonicalization_mutex());

  ASSERT(!IsInstantiated());
  ASSERT(instantiator_type_arguments.IsNull() ||
         instantiator_type_arguments.IsCanonical());
  ASSERT(function_type_arguments.IsNull() ||
         function_type_arguments.IsCanonical());
  // Lookup instantiators and if found, return instantiated result.
  Cache cache(zone, *this);
  auto const loc = cache.FindKeyOrUnused(instantiator_type_arguments,
                                         function_type_arguments);
  if (loc.present) {
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
    if (TESTING_runtime_fail_on_existing_cache_entry) {
      TextBuffer buffer(1024);
      buffer.Printf("for\n");
      buffer.Printf("  * uninstantiated type arguments %s\n", ToCString());
      buffer.Printf("  * instantiation type arguments: %s (hash: %" Pu ")\n",
                    instantiator_type_arguments.ToCString(),
                    instantiator_type_arguments.Hash());
      buffer.Printf("  * function type arguments: %s (hash: %" Pu ")\n",
                    function_type_arguments.ToCString(),
                    function_type_arguments.Hash());
      buffer.Printf("  * number of occupied entries in cache: %" Pd "\n",
                    cache.NumOccupied());
      buffer.Printf("  * number of total entries in cache: %" Pd "\n",
                    cache.NumEntries());
      buffer.Printf("expected to find entry %" Pd
                    " of cache in stub, but reached runtime",
                    loc.entry);
      FATAL("%s", buffer.buffer());
    }
#endif
    return cache.Retrieve(loc.entry);
  }
  // Cache lookup failed. Instantiate the type arguments.
  TypeArguments& result = TypeArguments::Handle(zone);
  result = InstantiateFrom(instantiator_type_arguments, function_type_arguments,
                           kAllFree, Heap::kOld);
  // Canonicalize type arguments.
  result = result.Canonicalize(thread);
  // InstantiateAndCanonicalizeFrom is not reentrant. It cannot have been called
  // indirectly, so the prior_instantiations array cannot have grown.
  ASSERT(cache.data_.ptr() == instantiations());
  cache.AddEntry(loc.entry, instantiator_type_arguments,
                 function_type_arguments, result);
  return result.ptr();
}

TypeArgumentsPtr TypeArguments::New(intptr_t len, Heap::Space space) {
  if (len < 0 || len > kMaxElements) {
    // This should be caught before we reach here.
    FATAL("Fatal error in TypeArguments::New: invalid len %" Pd "\n", len);
  }
  TypeArguments& result = TypeArguments::Handle();
  {
    auto raw = Object::Allocate<TypeArguments>(space, len);
    NoSafepointScope no_safepoint;
    result = raw;
    // Length must be set before we start storing into the array.
    result.SetLength(len);
    result.SetHash(0);
    result.set_nullability(0);
  }
  // The array used as storage for an empty linear cache should be initialized.
  ASSERT(Cache::EmptyStorage().ptr() != Array::null());
  result.set_instantiations(Cache::EmptyStorage());
  return result.ptr();
}

void TypeArguments::SetLength(intptr_t value) const {
  ASSERT(!IsCanonical());
  // This is only safe because we create a new Smi, which does not cause
  // heap allocation.
  untag()->set_length(Smi::New(value));
}

TypeArgumentsPtr TypeArguments::Canonicalize(Thread* thread) const {
  if (IsNull() || IsCanonical()) {
    ASSERT(IsOld());
    return this->ptr();
  }
  const intptr_t num_types = Length();
  if (num_types == 0) {
    return TypeArguments::empty_type_arguments().ptr();
  } else if (IsRaw(0, num_types)) {
    return TypeArguments::null();
  }
  Zone* zone = thread->zone();
  auto isolate_group = thread->isolate_group();
  ObjectStore* object_store = isolate_group->object_store();
  TypeArguments& result = TypeArguments::Handle(zone);
  {
    SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
    CanonicalTypeArgumentsSet table(zone,
                                    object_store->canonical_type_arguments());
    result ^= table.GetOrNull(CanonicalTypeArgumentsKey(*this));
    object_store->set_canonical_type_arguments(table.Release());
  }
  if (result.IsNull()) {
    // Canonicalize each type argument.
    AbstractType& type_arg = AbstractType::Handle(zone);
    GrowableHandlePtrArray<const AbstractType> canonicalized_types(zone,
                                                                   num_types);
    for (intptr_t i = 0; i < num_types; i++) {
      type_arg = TypeAt(i);
      type_arg = type_arg.Canonicalize(thread);
      canonicalized_types.Add(type_arg);
    }
    SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
    CanonicalTypeArgumentsSet table(zone,
                                    object_store->canonical_type_arguments());
    // Since we canonicalized some type arguments above we need to lookup
    // in the table again to make sure we don't already have an equivalent
    // canonical entry.
    result ^= table.GetOrNull(CanonicalTypeArgumentsKey(*this));
    if (result.IsNull()) {
      for (intptr_t i = 0; i < num_types; i++) {
        SetTypeAt(i, canonicalized_types.At(i));
      }
      // Make sure we have an old space object and add it to the table.
      if (this->IsNew()) {
        result ^= Object::Clone(*this, Heap::kOld);
      } else {
        result = this->ptr();
      }
      ASSERT(result.IsOld());
      result.ComputeNullability();
      result.SetCanonical();  // Mark object as being canonical.
      // Now add this TypeArgument into the canonical list of type arguments.
      bool present = table.Insert(result);
      ASSERT(!present);
    }
    object_store->set_canonical_type_arguments(table.Release());
  }
  ASSERT(result.Equals(*this));
  ASSERT(!result.IsNull());
  ASSERT(result.IsTypeArguments());
  ASSERT(result.IsCanonical());
  return result.ptr();
}

TypeArgumentsPtr TypeArguments::FromInstanceTypeArguments(
    Thread* thread,
    const Class& cls) const {
  if (IsNull()) {
    return ptr();
  }
  const intptr_t num_type_arguments = cls.NumTypeArguments();
  const intptr_t num_type_parameters = cls.NumTypeParameters(thread);
  ASSERT(Length() >= num_type_arguments);
  if (Length() == num_type_parameters) {
    return ptr();
  }
  if (num_type_parameters == 0) {
    return TypeArguments::null();
  }
  Zone* zone = thread->zone();
  const auto& args =
      TypeArguments::Handle(zone, TypeArguments::New(num_type_parameters));
  const intptr_t offset = num_type_arguments - num_type_parameters;
  auto& type = AbstractType::Handle(zone);
  for (intptr_t i = 0; i < num_type_parameters; ++i) {
    type = TypeAt(offset + i);
    args.SetTypeAt(i, type);
  }
  return args.ptr();
}

TypeArgumentsPtr TypeArguments::ToInstantiatorTypeArguments(
    Thread* thread,
    const Class& cls) const {
  if (IsNull()) {
    return ptr();
  }
  const intptr_t num_type_arguments = cls.NumTypeArguments();
  const intptr_t num_type_parameters = cls.NumTypeParameters(thread);
  ASSERT(Length() == num_type_parameters);
  if (num_type_arguments == num_type_parameters) {
    return ptr();
  }
  Zone* zone = thread->zone();
  const auto& args =
      TypeArguments::Handle(zone, TypeArguments::New(num_type_arguments));
  const intptr_t offset = num_type_arguments - num_type_parameters;
  auto& type = AbstractType::Handle(zone);
  for (intptr_t i = 0; i < num_type_parameters; ++i) {
    type = TypeAt(i);
    args.SetTypeAt(offset + i, type);
  }
  return args.ptr();
}

void TypeArguments::EnumerateURIs(URIs* uris) const {
  if (IsNull()) {
    return;
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  AbstractType& type = AbstractType::Handle(zone);
  const intptr_t num_types = Length();
  for (intptr_t i = 0; i < num_types; i++) {
    type = TypeAt(i);
    type.EnumerateURIs(uris);
  }
}

const char* TypeArguments::ToCString() const {
  if (IsNull()) {
    return "TypeArguments: null";  // Optimizing the frequent case.
  }
  ZoneTextBuffer buffer(Thread::Current()->zone());
  PrintTo(&buffer);
  return buffer.buffer();
}

const char* PatchClass::ToCString() const {
  const Class& cls = Class::Handle(wrapped_class());
  const char* cls_name = cls.ToCString();
  return OS::SCreate(Thread::Current()->zone(), "PatchClass for %s", cls_name);
}

PatchClassPtr PatchClass::New(const Class& wrapped_class,
                              const KernelProgramInfo& info,
                              const Script& script) {
  const PatchClass& result = PatchClass::Handle(PatchClass::New());
  result.set_wrapped_class(wrapped_class);
  NOT_IN_PRECOMPILED_RUNTIME(
      result.untag()->set_kernel_program_info(info.ptr()));
  result.set_script(script);
  result.set_kernel_library_index(-1);
  return result.ptr();
}

PatchClassPtr PatchClass::New() {
  ASSERT(IsolateGroup::Current()->class_table()->At(kPatchClassCid) !=
         Class::null());
  return Object::Allocate<PatchClass>(Heap::kOld);
}

void PatchClass::set_wrapped_class(const Class& value) const {
  untag()->set_wrapped_class(value.ptr());
}

#if !defined(DART_PRECOMPILED_RUNTIME)
void PatchClass::set_kernel_program_info(const KernelProgramInfo& info) const {
  untag()->set_kernel_program_info(info.ptr());
}
#endif

void PatchClass::set_script(const Script& value) const {
  untag()->set_script(value.ptr());
}

uword Function::Hash() const {
  uint32_t hash = String::HashRawSymbol(name());
  if (IsNonImplicitClosureFunction()) {
    hash = CombineHashes(hash, token_pos().Hash());
  }
  if (Owner()->IsClass()) {
    hash = CombineHashes(hash, Class::Hash(Class::RawCast(Owner())));
  }
  return hash;
}

bool Function::HasBreakpoint() const {
#if defined(PRODUCT)
  return false;
#else
  auto thread = Thread::Current();
  return thread->isolate_group()->debugger()->HasBreakpoint(thread, *this);
#endif
}

void Function::InstallOptimizedCode(const Code& code) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  // We may not have previous code if FLAG_precompile is set.
  // Hot-reload may have already disabled the current code.
  if (HasCode() && !Code::Handle(CurrentCode()).IsDisabled()) {
    Code::Handle(CurrentCode()).DisableDartCode();
  }
  AttachCode(code);
}

void Function::SetInstructions(const Code& value) const {
  // Ensure that nobody is executing this function when we install it.
  if (untag()->code() != Code::null() && HasCode()) {
    GcSafepointOperationScope safepoint(Thread::Current());
    SetInstructionsSafe(value);
  } else {
    ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
    SetInstructionsSafe(value);
  }
}

void Function::SetInstructionsSafe(const Code& value) const {
  untag()->set_code<std::memory_order_release>(value.ptr());
  StoreNonPointer(&untag()->entry_point_, value.EntryPoint());
  StoreNonPointer(&untag()->unchecked_entry_point_,
                  value.UncheckedEntryPoint());
}

void Function::AttachCode(const Code& value) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  SetInstructions(value);
  ASSERT(Function::Handle(value.function()).IsNull() ||
         (value.function() == this->ptr()));
}

bool Function::HasCode() const {
  NoSafepointScope no_safepoint;
  ASSERT(untag()->code() != Code::null());
  return untag()->code() != StubCode::LazyCompile().ptr();
}

#if defined(DART_DYNAMIC_MODULES)

void Function::AttachBytecode(const Bytecode& value) const {
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  ASSERT(!value.IsNull());
  // Finish setting up code before activating it.
  value.set_function(*this);
  ASSERT(untag()->ic_data_array_or_bytecode() == Object::null());
  untag()->set_ic_data_array_or_bytecode(value.ptr());

  // Set the code entry_point to InterpretCall stub.
  SetInstructions(StubCode::InterpretCall());
}

void Function::ClearBytecode() const {
  ASSERT(HasBytecode());
  untag()->set_ic_data_array_or_bytecode(Object::null());
  ClearCode();
}

bool Function::IsInterpreted(FunctionPtr function) {
  return function->untag()->code() == StubCode::InterpretCall().ptr();
}

#endif  // defined(DART_DYNAMIC_MODULES)

bool Function::HasCode(FunctionPtr function) {
  NoSafepointScope no_safepoint;
  ASSERT(function->untag()->code() != Code::null());
  return function->untag()->code() != StubCode::LazyCompile().ptr();
}

void Function::ClearCode() const {
#if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
  UNREACHABLE();
#else
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  ClearCodeSafe();
#endif  // defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
}

void Function::ClearCodeSafe() const {
#if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
  UNREACHABLE();
#else
  // This may get called when lazily creating dynamic invocation forwarders
  // in the precompiled runtime, which can be done with dynamic modules.
  // While the original function from which the forwarder is cloned may have
  // been compiled, the forwarder is instead interpreted once complete.
#if !defined(DART_PRECOMPILED_RUNTIME)
  untag()->set_unoptimized_code(Code::null());
#endif  // !defined(DART_PRECOMPILED_RUNTIME)
  SetInstructionsSafe(StubCode::LazyCompile());
#endif  // defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
}

void Function::EnsureHasCompiledUnoptimizedCode() const {
  ASSERT(!ForceOptimize());
  Thread* thread = Thread::Current();
  ASSERT(thread->IsDartMutatorThread());
  DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame());
  Zone* zone = thread->zone();

  const Error& error =
      Error::Handle(zone, EnsureHasCompiledUnoptimizedCodeNoThrow());
  if (!error.IsNull()) {
    Exceptions::PropagateError(error);
  }
}

ErrorPtr Function::EnsureHasCompiledUnoptimizedCodeNoThrow() const {
  ASSERT(!ForceOptimize());
  Thread* thread = Thread::Current();
  ASSERT(thread->IsDartMutatorThread());

  return Compiler::EnsureUnoptimizedCode(thread, *this);
}

void Function::SwitchToUnoptimizedCode() const {
  ASSERT(HasOptimizedCode());
  ASSERT(!ForceOptimize());
  Thread* thread = Thread::Current();
  DEBUG_ASSERT(
      thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
  Zone* zone = thread->zone();
  // TODO(35224): DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame());
  const Code& current_code = Code::Handle(zone, CurrentCode());

  if (FLAG_trace_deoptimization_verbose) {
    THR_Print("Disabling optimized code: '%s' entry: %#" Px "\n",
              ToFullyQualifiedCString(), current_code.EntryPoint());
  }
  current_code.DisableDartCode();
  const Error& error =
      Error::Handle(zone, Compiler::EnsureUnoptimizedCode(thread, *this));
  if (!error.IsNull()) {
    Exceptions::PropagateError(error);
  }
  const Code& unopt_code = Code::Handle(zone, unoptimized_code());
  unopt_code.Enable();
  AttachCode(unopt_code);
}

void Function::SwitchToLazyCompiledUnoptimizedCode() const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  if (!HasOptimizedCode()) {
    return;
  }

  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  ASSERT(thread->IsDartMutatorThread());

  const Code& current_code = Code::Handle(zone, CurrentCode());
  TIR_Print("Disabling optimized code for %s\n", ToCString());
  current_code.DisableDartCode();

  const Code& unopt_code = Code::Handle(zone, unoptimized_code());
  if (unopt_code.IsNull()) {
    // Set the lazy compile stub code.
    TIR_Print("Switched to lazy compile stub for %s\n", ToCString());
    SetInstructions(StubCode::LazyCompile());
    return;
  }

  TIR_Print("Switched to unoptimized code for %s\n", ToCString());

  AttachCode(unopt_code);
  unopt_code.Enable();
#endif
}

void Function::set_unoptimized_code(const Code& value) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  DEBUG_ASSERT(IsMutatorOrAtDeoptSafepoint());
  ASSERT(value.IsNull() || !value.is_optimized());
  untag()->set_unoptimized_code(value.ptr());
#endif
}

ContextScopePtr Function::context_scope() const {
  if (IsClosureFunction()) {
    const Object& obj = Object::Handle(untag()->data());
    ASSERT(!obj.IsNull());
    return ClosureData::Cast(obj).context_scope();
  }
  return ContextScope::null();
}

void Function::set_context_scope(const ContextScope& value) const {
  if (IsClosureFunction()) {
    const Object& obj = Object::Handle(untag()->data());
    ASSERT(!obj.IsNull());
    ClosureData::Cast(obj).set_context_scope(value);
    return;
  }
  UNREACHABLE();
}

Function::AwaiterLink Function::awaiter_link() const {
  if (IsClosureFunction()) {
    const Object& obj = Object::Handle(untag()->data());
    ASSERT(!obj.IsNull());
    return ClosureData::Cast(obj).awaiter_link();
  }
  UNREACHABLE();
  return {};
}

void Function::set_awaiter_link(Function::AwaiterLink link) const {
  if (IsClosureFunction()) {
    const Object& obj = Object::Handle(untag()->data());
    ASSERT(!obj.IsNull());
    ClosureData::Cast(obj).set_awaiter_link(link);
    return;
  }
  UNREACHABLE();
}

bool Function::captures_only_final_not_late_vars() const {
  if (IsClosureFunction()) {
    const Object& obj = Object::Handle(untag()->data());
    ASSERT(!obj.IsNull());
    return ClosureData::Cast(obj).captures_only_final_not_late_vars();
  }
  UNREACHABLE();
}

void Function::set_captures_only_final_not_late_vars(bool value) const {
  if (IsClosureFunction()) {
    const Object& obj = Object::Handle(untag()->data());
    ASSERT(!obj.IsNull());
    ClosureData::Cast(obj).set_captures_only_final_not_late_vars(value);
    return;
  }
  UNREACHABLE();
}

ClosurePtr Function::implicit_static_closure() const {
  if (IsImplicitStaticClosureFunction()) {
    const Object& obj = Object::Handle(untag()->data());
    ASSERT(!obj.IsNull());
    return ClosureData::Cast(obj).implicit_static_closure();
  }
  return Closure::null();
}

void Function::set_implicit_static_closure(const Closure& closure) const {
  if (IsImplicitStaticClosureFunction()) {
    const Object& obj = Object::Handle(untag()->data());
    ASSERT(!obj.IsNull());
    ClosureData::Cast(obj).set_implicit_static_closure(closure);
    return;
  }
  UNREACHABLE();
}

FunctionPtr Function::extracted_method_closure() const {
  ASSERT(kind() == UntaggedFunction::kMethodExtractor);
  const Object& obj = Object::Handle(untag()->data());
  ASSERT(obj.IsFunction());
  return Function::Cast(obj).ptr();
}

void Function::set_extracted_method_closure(const Function& value) const {
  ASSERT(kind() == UntaggedFunction::kMethodExtractor);
  ASSERT(untag()->data() == Object::null());
  set_data(value);
}

ArrayPtr Function::saved_args_desc() const {
  if (kind() == UntaggedFunction::kDynamicInvocationForwarder) {
    return Array::null();
  }
  ASSERT(kind() == UntaggedFunction::kNoSuchMethodDispatcher ||
         kind() == UntaggedFunction::kInvokeFieldDispatcher);
  return Array::RawCast(untag()->data());
}

void Function::set_saved_args_desc(const Array& value) const {
  ASSERT(kind() == UntaggedFunction::kNoSuchMethodDispatcher ||
         kind() == UntaggedFunction::kInvokeFieldDispatcher);
  ASSERT(untag()->data() == Object::null());
  set_data(value);
}

FieldPtr Function::accessor_field() const {
  ASSERT(kind() == UntaggedFunction::kImplicitGetter ||
         kind() == UntaggedFunction::kImplicitSetter ||
         kind() == UntaggedFunction::kImplicitStaticGetter ||
         kind() == UntaggedFunction::kFieldInitializer);
  return Field::RawCast(untag()->data());
}

void Function::set_accessor_field(const Field& value) const {
  ASSERT(kind() == UntaggedFunction::kImplicitGetter ||
         kind() == UntaggedFunction::kImplicitSetter ||
         kind() == UntaggedFunction::kImplicitStaticGetter ||
         kind() == UntaggedFunction::kFieldInitializer);
  // Top level classes may be finalized multiple times.
  ASSERT(untag()->data() == Object::null() || untag()->data() == value.ptr());
  set_data(value);
}

FunctionPtr Function::parent_function() const {
  if (!IsClosureFunction()) return Function::null();
  Object& obj = Object::Handle(untag()->data());
  ASSERT(!obj.IsNull());
  return ClosureData::Cast(obj).parent_function();
}

void Function::set_parent_function(const Function& value) const {
  ASSERT(IsClosureFunction());
  const Object& obj = Object::Handle(untag()->data());
  ASSERT(!obj.IsNull());
  ClosureData::Cast(obj).set_parent_function(value);
}

TypeArgumentsPtr Function::DefaultTypeArguments(Zone* zone) const {
  if (type_parameters() == TypeParameters::null()) {
    return Object::empty_type_arguments().ptr();
  }
  return TypeParameters::Handle(zone, type_parameters()).defaults();
}

InstantiationMode Function::default_type_arguments_instantiation_mode() const {
  if (!IsClosureFunction()) {
    UNREACHABLE();
  }
  return ClosureData::DefaultTypeArgumentsInstantiationMode(
      ClosureData::RawCast(data()));
}

void Function::set_default_type_arguments_instantiation_mode(
    InstantiationMode value) const {
  if (!IsClosureFunction()) {
    UNREACHABLE();
  }
  const auto& closure_data = ClosureData::Handle(ClosureData::RawCast(data()));
  ASSERT(!closure_data.IsNull());
  closure_data.set_default_type_arguments_instantiation_mode(value);
}

// Enclosing outermost function of this local function.
FunctionPtr Function::GetOutermostFunction() const {
  FunctionPtr parent = parent_function();
  if (parent == Object::null()) {
    return ptr();
  }
  Function& function = Function::Handle();
  do {
    function = parent;
    parent = function.parent_function();
  } while (parent != Object::null());
  return function.ptr();
}

FunctionPtr Function::implicit_closure_function() const {
  if (IsClosureFunction() || IsDispatcherOrImplicitAccessor() ||
      IsFieldInitializer() || IsFfiCallbackTrampoline() ||
      IsMethodExtractor()) {
    return Function::null();
  }
  const Object& obj = Object::Handle(data());
  ASSERT(obj.IsNull() || obj.IsScript() || obj.IsFunction() || obj.IsArray());
  if (obj.IsNull() || obj.IsScript()) {
    return Function::null();
  }
  if (obj.IsFunction()) {
    return Function::Cast(obj).ptr();
  }
  ASSERT(is_native());
  ASSERT(obj.IsArray());
  const Object& res = Object::Handle(Array::Cast(obj).AtAcquire(1));
  return res.IsNull() ? Function::null() : Function::Cast(res).ptr();
}

void Function::set_implicit_closure_function(const Function& value) const {
  DEBUG_ASSERT(
      IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  ASSERT(!IsClosureFunction());
  const Object& old_data = Object::Handle(data());
  if (is_old_native()) {
    ASSERT(old_data.IsArray());
    const auto& pair = Array::Cast(old_data);
    ASSERT(pair.AtAcquire(NativeFunctionData::kTearOff) == Object::null() ||
           value.IsNull());
    pair.SetAtRelease(NativeFunctionData::kTearOff, value);
  } else {
    ASSERT(old_data.IsNull() || value.IsNull());
    set_data(value);
  }
}

void Function::SetFfiCSignature(const FunctionType& sig) const {
  ASSERT(IsFfiCallbackTrampoline());
  const Object& obj = Object::Handle(data());
  ASSERT(!obj.IsNull());
  FfiTrampolineData::Cast(obj).set_c_signature(sig);
}

FunctionTypePtr Function::FfiCSignature() const {
  auto* const zone = Thread::Current()->zone();
  if (IsFfiCallbackTrampoline()) {
    const Object& obj = Object::Handle(zone, data());
    ASSERT(!obj.IsNull());
    return FfiTrampolineData::Cast(obj).c_signature();
  }
  auto& pragma_value = Instance::Handle(zone);
  if (is_ffi_native()) {
    pragma_value = GetNativeAnnotation();
  } else if (IsFfiCallClosure()) {
    pragma_value = GetFfiCallClosurePragmaValue();
  } else {
    UNREACHABLE();
  }
  const auto& type_args =
      TypeArguments::Handle(zone, pragma_value.GetTypeArguments());
  ASSERT(type_args.Length() == 1);
  const auto& native_type =
      FunctionType::Cast(AbstractType::ZoneHandle(zone, type_args.TypeAt(0)));
  return native_type.ptr();
}

bool Function::FfiCSignatureContainsHandles() const {
  const FunctionType& c_signature = FunctionType::Handle(FfiCSignature());
  return c_signature.ContainsHandles();
}

bool FunctionType::ContainsHandles() const {
  const intptr_t num_params = num_fixed_parameters();
  for (intptr_t i = 0; i < num_params; i++) {
    const bool is_handle =
        AbstractType::Handle(ParameterTypeAt(i)).type_class_id() ==
        kFfiHandleCid;
    if (is_handle) {
      return true;
    }
  }
  return AbstractType::Handle(result_type()).type_class_id() == kFfiHandleCid;
}

// Keep consistent with BaseMarshaller::IsCompound.
bool Function::FfiCSignatureReturnsStruct() const {
  ASSERT(IsFfiCallbackTrampoline());
  Zone* zone = Thread::Current()->zone();
  const auto& c_signature = FunctionType::Handle(zone, FfiCSignature());
  const auto& type = AbstractType::Handle(zone, c_signature.result_type());
  if (IsFfiTypeClassId(type.type_class_id())) {
    return false;
  }
  const auto& cls = Class::Handle(zone, type.type_class());
  const auto& superClass = Class::Handle(zone, cls.SuperClass());
  const bool is_abi_specific_int =
      String::Handle(zone, superClass.UserVisibleName())
          .Equals(Symbols::AbiSpecificInteger());
  if (is_abi_specific_int) {
    return false;
  }
#ifdef DEBUG
  const bool is_struct = String::Handle(zone, superClass.UserVisibleName())
                             .Equals(Symbols::Struct());
  const bool is_union = String::Handle(zone, superClass.UserVisibleName())
                            .Equals(Symbols::Union());
  ASSERT(is_struct || is_union);
#endif
  return true;
}

bool Function::FfiCSignatureReturnsHandle() const {
  ASSERT(IsFfiCallbackTrampoline());
  const auto& c_signature = FunctionType::Handle(FfiCSignature());
  const auto& type = AbstractType::Handle(c_signature.result_type());
  return type.type_class_id() == kFfiHandleCid;
}

int32_t Function::FfiCallbackId() const {
  ASSERT(IsFfiCallbackTrampoline());

  const auto& obj = Object::Handle(data());
  ASSERT(!obj.IsNull());
  const auto& trampoline_data = FfiTrampolineData::Cast(obj);

  ASSERT(trampoline_data.callback_id() != -1);

  return trampoline_data.callback_id();
}

void Function::AssignFfiCallbackId(int32_t callback_id) const {
  ASSERT(IsFfiCallbackTrampoline());

  const auto& obj = Object::Handle(data());
  ASSERT(!obj.IsNull());
  const auto& trampoline_data = FfiTrampolineData::Cast(obj);

  ASSERT(trampoline_data.callback_id() == -1);
  trampoline_data.set_callback_id(callback_id);
}

bool Function::FfiIsLeaf() const {
  Zone* zone = Thread::Current()->zone();
  auto& pragma_value = Instance::Handle(zone);
  if (is_ffi_native()) {
    pragma_value = GetNativeAnnotation();
  } else if (IsFfiCallClosure()) {
    pragma_value = GetFfiCallClosurePragmaValue();
  } else {
    UNREACHABLE();
  }
  const auto& pragma_value_class = Class::Handle(zone, pragma_value.clazz());
  const auto& is_leaf_field = Field::Handle(
      zone, pragma_value_class.LookupFieldAllowPrivate(Symbols::isLeaf()));
  ASSERT(!is_leaf_field.IsNull());
  return Bool::Handle(zone, Bool::RawCast(pragma_value.GetField(is_leaf_field)))
      .value();
}

FunctionPtr Function::FfiCallbackTarget() const {
  ASSERT(IsFfiCallbackTrampoline());
  const Object& obj = Object::Handle(data());
  ASSERT(!obj.IsNull());
  return FfiTrampolineData::Cast(obj).callback_target();
}

void Function::SetFfiCallbackTarget(const Function& target) const {
  ASSERT(IsFfiCallbackTrampoline());
  const Object& obj = Object::Handle(data());
  ASSERT(!obj.IsNull());
  FfiTrampolineData::Cast(obj).set_callback_target(target);
}

InstancePtr Function::FfiCallbackExceptionalReturn() const {
  ASSERT(IsFfiCallbackTrampoline());
  const Object& obj = Object::Handle(data());
  ASSERT(!obj.IsNull());
  return FfiTrampolineData::Cast(obj).callback_exceptional_return();
}

void Function::SetFfiCallbackExceptionalReturn(const Instance& value) const {
  ASSERT(IsFfiCallbackTrampoline());
  const Object& obj = Object::Handle(data());
  ASSERT(!obj.IsNull());
  FfiTrampolineData::Cast(obj).set_callback_exceptional_return(value);
}

FfiCallbackKind Function::GetFfiCallbackKind() const {
  ASSERT(IsFfiCallbackTrampoline());
  const Object& obj = Object::Handle(data());
  ASSERT(!obj.IsNull());
  return FfiTrampolineData::Cast(obj).ffi_function_kind();
}

void Function::SetFfiCallbackKind(FfiCallbackKind value) const {
  ASSERT(IsFfiCallbackTrampoline());
  const Object& obj = Object::Handle(data());
  ASSERT(!obj.IsNull());
  FfiTrampolineData::Cast(obj).set_ffi_function_kind(value);
}

const char* Function::KindToCString(UntaggedFunction::Kind kind) {
  return UntaggedFunction::KindToCString(kind);
}

FunctionPtr Function::ForwardingTarget() const {
  ASSERT(kind() == UntaggedFunction::kDynamicInvocationForwarder);
  return Function::RawCast(WeakSerializationReference::Unwrap(data()));
}

void Function::SetForwardingTarget(const Function& target) const {
  ASSERT(kind() == UntaggedFunction::kDynamicInvocationForwarder);
  set_data(target);
}

// This field is heavily overloaded:
//   kernel eval function:    Array[0] = Script
//                            Array[1] = KernelProgramInfo
//                            Array[2] = Kernel index of enclosing library
//   method extractor:        Function extracted closure function
//   implicit getter:         Field
//   implicit setter:         Field
//   impl. static final gttr: Field
//   field initializer:       Field
//   noSuchMethod dispatcher: Array arguments descriptor
//   invoke-field dispatcher: Array arguments descriptor
//   closure function:        ClosureData
//   irregexp function:       Array[0] = RegExp
//                            Array[1] = Smi string specialization cid
//   native function:         Array[0] = String native name
//                            Array[1] = Function implicit closure function
//   regular function:        Function for implicit closure function
//   constructor, factory:    Function for implicit closure function
//   ffi trampoline function: FfiTrampolineData  (Dart->C)
//   dyn inv forwarder:       Forwarding target, a WSR pointing to it or null
//                            (null can only occur if forwarding target was
//                            dropped)
void Function::set_data(const Object& value) const {
  untag()->set_data<std::memory_order_release>(value.ptr());
}

void Function::set_name(const String& value) const {
  ASSERT(value.IsSymbol());
  untag()->set_name(value.ptr());
}

void Function::set_owner(const Object& value) const {
  ASSERT(!value.IsNull());
  untag()->set_owner(value.ptr());
}

RegExpPtr Function::regexp() const {
  ASSERT(kind() == UntaggedFunction::kIrregexpFunction);
  const Array& pair = Array::Cast(Object::Handle(data()));
  return RegExp::RawCast(pair.At(0));
}

using StickySpecialization = BitField<intptr_t, bool>;
using StringSpecializationCid = BitField<intptr_t,
                                         intptr_t,
                                         StickySpecialization::kNextBit,
                                         UntaggedObject::ClassIdTag::bitsize()>;

intptr_t Function::string_specialization_cid() const {
  ASSERT(kind() == UntaggedFunction::kIrregexpFunction);
  const Array& pair = Array::Cast(Object::Handle(data()));
  return StringSpecializationCid::decode(Smi::Value(Smi::RawCast(pair.At(1))));
}

bool Function::is_sticky_specialization() const {
  ASSERT(kind() == UntaggedFunction::kIrregexpFunction);
  const Array& pair = Array::Cast(Object::Handle(data()));
  return StickySpecialization::decode(Smi::Value(Smi::RawCast(pair.At(1))));
}

void Function::SetRegExpData(const RegExp& regexp,
                             intptr_t string_specialization_cid,
                             bool sticky) const {
  ASSERT(kind() == UntaggedFunction::kIrregexpFunction);
  ASSERT(IsStringClassId(string_specialization_cid));
  ASSERT(data() == Object::null());
  const Array& pair = Array::Handle(Array::New(2, Heap::kOld));
  pair.SetAt(0, regexp);
  pair.SetAt(1, Smi::Handle(Smi::New(StickySpecialization::encode(sticky) |
                                     StringSpecializationCid::encode(
                                         string_specialization_cid))));
  set_data(pair);
}

StringPtr Function::native_name() const {
  ASSERT(is_native());
  const Object& obj = Object::Handle(data());
  ASSERT(obj.IsArray());
  return String::RawCast(Array::Cast(obj).At(0));
}

void Function::set_native_name(const String& value) const {
  ASSERT(is_native());
  const auto& pair = Array::Cast(Object::Handle(data()));
  ASSERT(pair.At(0) == Object::null());
  pair.SetAt(NativeFunctionData::kNativeName, value);
}

InstancePtr Function::GetNativeAnnotation() const {
  ASSERT(is_ffi_native());
  Zone* zone = Thread::Current()->zone();
  auto& pragma_value = Object::Handle(zone);
  Library::FindPragma(dart::Thread::Current(), /*only_core=*/false,
                      Object::Handle(zone, ptr()),
                      String::Handle(zone, Symbols::vm_ffi_native().ptr()),
                      /*multiple=*/false, &pragma_value);
  auto const& native_instance = Instance::Cast(pragma_value);
  ASSERT(!native_instance.IsNull());
#if defined(DEBUG)
  const auto& native_class = Class::Handle(zone, native_instance.clazz());
  ASSERT(String::Handle(zone, native_class.UserVisibleName())
             .Equals(Symbols::FfiNative()));
#endif
  return native_instance.ptr();
}

bool Function::is_old_native() const {
  return is_native() && !is_external();
}

bool Function::is_ffi_native() const {
  return is_native() && is_external();
}

void Function::SetSignature(const FunctionType& value) const {
  set_signature(value);
  ASSERT(NumImplicitParameters() == value.num_implicit_parameters());
  if (IsClosureFunction() && value.IsGeneric()) {
    Zone* zone = Thread::Current()->zone();
    const TypeParameters& type_params =
        TypeParameters::Handle(zone, value.type_parameters());
    const TypeArguments& defaults =
        TypeArguments::Handle(zone, type_params.defaults());
    auto mode = defaults.GetInstantiationMode(zone, this);
    set_default_type_arguments_instantiation_mode(mode);
  }
}

TypeParameterPtr FunctionType::TypeParameterAt(intptr_t index,
                                               Nullability nullability) const {
  ASSERT(index >= 0 && index < NumTypeParameters());
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  TypeParameter& type_param = TypeParameter::Handle(
      zone, TypeParameter::New(*this, NumParentTypeArguments(),
                               NumParentTypeArguments() + index, nullability));
  type_param.SetIsFinalized();
  if (IsFinalized()) {
    type_param ^= type_param.Canonicalize(thread);
  }
  return type_param.ptr();
}

void FunctionType::set_result_type(const AbstractType& value) const {
  ASSERT(!value.IsNull());
  untag()->set_result_type(value.ptr());
}

AbstractTypePtr Function::ParameterTypeAt(intptr_t index) const {
  const Array& types = Array::Handle(parameter_types());
  return AbstractType::RawCast(types.At(index));
}

AbstractTypePtr FunctionType::ParameterTypeAt(intptr_t index) const {
  const Array& parameter_types = Array::Handle(untag()->parameter_types());
  return AbstractType::RawCast(parameter_types.At(index));
}

void FunctionType::SetParameterTypeAt(intptr_t index,
                                      const AbstractType& value) const {
  ASSERT(!value.IsNull());
  const Array& parameter_types = Array::Handle(untag()->parameter_types());
  parameter_types.SetAt(index, value);
}

void FunctionType::set_parameter_types(const Array& value) const {
  ASSERT(value.IsNull() || value.Length() > 0);
  untag()->set_parameter_types(value.ptr());
}

StringPtr Function::ParameterNameAt(intptr_t index) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  if (signature() == FunctionType::null()) {
    // Without the signature, we're guaranteed not to have any name information.
    return Symbols::OptimizedOut().ptr();
  }
#endif
  const intptr_t num_fixed = num_fixed_parameters();
  if (HasOptionalNamedParameters() && index >= num_fixed) {
    const Array& parameter_names =
        Array::Handle(signature()->untag()->named_parameter_names());
    return String::RawCast(parameter_names.At(index - num_fixed));
  }
#if defined(DART_PRECOMPILED_RUNTIME)
  return Symbols::OptimizedOut().ptr();
#else
  const Array& names = Array::Handle(untag()->positional_parameter_names());
  return String::RawCast(names.At(index));
#endif
}

void Function::SetParameterNameAt(intptr_t index, const String& value) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  ASSERT(!value.IsNull() && value.IsSymbol());
  if (HasOptionalNamedParameters() && index >= num_fixed_parameters()) {
    // These should be set on the signature, not the function.
    UNREACHABLE();
  }
  const Array& parameter_names =
      Array::Handle(untag()->positional_parameter_names());
  parameter_names.SetAt(index, value);
#endif
}

#if !defined(DART_PRECOMPILED_RUNTIME)
void Function::set_positional_parameter_names(const Array& value) const {
  ASSERT(value.ptr() == Object::empty_array().ptr() || value.Length() > 0);
  untag()->set_positional_parameter_names(value.ptr());
}
#endif

StringPtr FunctionType::ParameterNameAt(intptr_t index) const {
  const intptr_t num_fixed = num_fixed_parameters();
  if (!HasOptionalNamedParameters() || index < num_fixed) {
    // The positional parameter names are stored on the function, not here.
    UNREACHABLE();
  }
  const Array& parameter_names =
      Array::Handle(untag()->named_parameter_names());
  return String::RawCast(parameter_names.At(index - num_fixed));
}

void FunctionType::SetParameterNameAt(intptr_t index,
                                      const String& value) const {
#if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
  UNREACHABLE();
#else
  ASSERT(!value.IsNull() && value.IsSymbol());
  const intptr_t num_fixed = num_fixed_parameters();
  if (!HasOptionalNamedParameters() || index < num_fixed) {
    UNREACHABLE();
  }
  const Array& parameter_names =
      Array::Handle(untag()->named_parameter_names());
  parameter_names.SetAt(index - num_fixed, value);
#endif
}

void FunctionType::set_named_parameter_names(const Array& value) const {
  ASSERT(value.ptr() == Object::empty_array().ptr() || value.Length() > 0);
  untag()->set_named_parameter_names(value.ptr());
}

void Function::CreateNameArray(Heap::Space space) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  const intptr_t num_positional_params =
      num_fixed_parameters() + NumOptionalPositionalParameters();
  if (num_positional_params == 0) {
    set_positional_parameter_names(Object::empty_array());
  } else {
    set_positional_parameter_names(
        Array::Handle(Array::New(num_positional_params, space)));
  }
#endif
}

void FunctionType::CreateNameArrayIncludingFlags(Heap::Space space) const {
#if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
  UNREACHABLE();
#else
  const intptr_t num_named_parameters = NumOptionalNamedParameters();
  if (num_named_parameters == 0) {
    return set_named_parameter_names(Object::empty_array());
  }
  // Currently, we only store flags for named parameters.
  const intptr_t last_index = (num_named_parameters - 1) /
                              compiler::target::kNumParameterFlagsPerElement;
  const intptr_t num_flag_slots = last_index + 1;
  intptr_t num_total_slots = num_named_parameters + num_flag_slots;
  auto& array = Array::Handle(Array::New(num_total_slots, space));
  // Set flag slots to Smi 0 before handing off.
  auto& empty_flags_smi = Smi::Handle(Smi::New(0));
  for (intptr_t i = num_named_parameters; i < num_total_slots; i++) {
    array.SetAt(i, empty_flags_smi);
  }
  set_named_parameter_names(array);
#endif
}

intptr_t FunctionType::GetRequiredFlagIndex(intptr_t index,
                                            intptr_t* flag_mask) const {
  // If these calculations change, also change
  // FlowGraphBuilder::BuildClosureCallHasRequiredNamedArgumentsCheck.
  ASSERT(HasOptionalNamedParameters());
  ASSERT(flag_mask != nullptr);
  ASSERT(index >= num_fixed_parameters());
  index -= num_fixed_parameters();
  *flag_mask = (1 << compiler::target::kRequiredNamedParameterFlag)
               << ((static_cast<uintptr_t>(index) %
                    compiler::target::kNumParameterFlagsPerElement) *
                   compiler::target::kNumParameterFlags);
  return NumOptionalNamedParameters() +
         index / compiler::target::kNumParameterFlagsPerElement;
}

bool Function::HasRequiredNamedParameters() const {
#if defined(DART_PRECOMPILED_RUNTIME)
  if (signature() == FunctionType::null()) {
    // Signatures for functions with required named parameters are not dropped.
    return false;
  }
#endif
  return FunctionType::Handle(signature()).HasRequiredNamedParameters();
}

bool Function::IsRequiredAt(intptr_t index) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  if (signature() == FunctionType::null()) {
    // Signature is not dropped in aot when any named parameter is required.
    return false;
  }
#endif
  if (!HasOptionalNamedParameters() || index < num_fixed_parameters()) {
    return false;
  }
  const FunctionType& sig = FunctionType::Handle(signature());
  return sig.IsRequiredAt(index);
}

bool FunctionType::IsRequiredAt(intptr_t index) const {
  if (!HasOptionalNamedParameters() || index < num_fixed_parameters()) {
    return false;
  }
  intptr_t flag_mask;
  const intptr_t flag_index = GetRequiredFlagIndex(index, &flag_mask);
  const Array& parameter_names =
      Array::Handle(untag()->named_parameter_names());
  if (flag_index >= parameter_names.Length()) {
    return false;
  }
  const intptr_t flags =
      Smi::Value(Smi::RawCast(parameter_names.At(flag_index)));
  return (flags & flag_mask) != 0;
}

void FunctionType::SetIsRequiredAt(intptr_t index) const {
#if defined(DART_PRECOMPILER_RUNTIME)
  UNREACHABLE();
#else
  intptr_t flag_mask;
  const intptr_t flag_index = GetRequiredFlagIndex(index, &flag_mask);
  const Array& parameter_names =
      Array::Handle(untag()->named_parameter_names());
  ASSERT(flag_index < parameter_names.Length());
  const intptr_t flags =
      Smi::Value(Smi::RawCast(parameter_names.At(flag_index)));
  parameter_names.SetAt(flag_index, Smi::Handle(Smi::New(flags | flag_mask)));
#endif
}

void FunctionType::FinalizeNameArray() const {
#if defined(DART_PRECOMPILER_RUNTIME)
  UNREACHABLE();
#else
  const intptr_t num_named_parameters = NumOptionalNamedParameters();
  if (num_named_parameters == 0) {
    ASSERT(untag()->named_parameter_names() == Object::empty_array().ptr());
    return;
  }
  const Array& parameter_names =
      Array::Handle(untag()->named_parameter_names());
  // Truncate the parameter names array to remove unused flags from the end.
  intptr_t last_used = parameter_names.Length() - 1;
  for (; last_used >= num_named_parameters; --last_used) {
    if (Smi::Value(Smi::RawCast(parameter_names.At(last_used))) != 0) {
      break;
    }
  }
  parameter_names.Truncate(last_used + 1);
#endif
}

bool FunctionType::HasRequiredNamedParameters() const {
  const intptr_t num_named_params = NumOptionalNamedParameters();
  if (num_named_params == 0) return false;
  // Check for flag slots in the named parameter names array.
  const auto& parameter_names = Array::Handle(named_parameter_names());
  ASSERT(!parameter_names.IsNull());
  return parameter_names.Length() > num_named_params;
}

static void ReportTooManyTypeParameters(const FunctionType& sig) {
  Report::MessageF(Report::kError, Script::Handle(), TokenPosition::kNoSource,
                   Report::AtLocation,
                   "too many type parameters declared in signature '%s' or in "
                   "its enclosing signatures",
                   sig.ToUserVisibleCString());
  UNREACHABLE();
}

void FunctionType::SetTypeParameters(const TypeParameters& value) const {
  untag()->set_type_parameters(value.ptr());
  const intptr_t count = value.Length();
  if (!UntaggedFunctionType::PackedNumTypeParameters::is_valid(count)) {
    ReportTooManyTypeParameters(*this);
  }
  untag()->packed_type_parameter_counts_.Update<PackedNumTypeParameters>(count);
}

void FunctionType::SetNumParentTypeArguments(intptr_t value) const {
  ASSERT(value >= 0);
  if (!PackedNumParentTypeArguments::is_valid(value)) {
    ReportTooManyTypeParameters(*this);
  }
  untag()->packed_type_parameter_counts_.Update<PackedNumParentTypeArguments>(
      value);
}

bool Function::IsGeneric() const {
  return FunctionType::IsGeneric(signature());
}
intptr_t Function::NumTypeParameters() const {
  return FunctionType::NumTypeParametersOf(signature());
}
intptr_t Function::NumParentTypeArguments() const {
  return FunctionType::NumParentTypeArgumentsOf(signature());
}
intptr_t Function::NumTypeArguments() const {
  return FunctionType::NumTypeArgumentsOf(signature());
}
intptr_t Function::num_fixed_parameters() const {
  return FunctionType::NumFixedParametersOf(signature());
}
bool Function::HasOptionalParameters() const {
  return FunctionType::HasOptionalParameters(signature());
}
bool Function::HasOptionalNamedParameters() const {
  return FunctionType::HasOptionalNamedParameters(signature());
}
bool Function::HasOptionalPositionalParameters() const {
  return FunctionType::HasOptionalPositionalParameters(signature());
}
intptr_t Function::NumOptionalParameters() const {
  return FunctionType::NumOptionalParametersOf(signature());
}
intptr_t Function::NumOptionalPositionalParameters() const {
  return FunctionType::NumOptionalPositionalParametersOf(signature());
}
intptr_t Function::NumOptionalNamedParameters() const {
  return FunctionType::NumOptionalNamedParametersOf(signature());
}
intptr_t Function::NumParameters() const {
  return FunctionType::NumParametersOf(signature());
}

TypeParameterPtr Function::TypeParameterAt(intptr_t index,
                                           Nullability nullability) const {
  const FunctionType& sig = FunctionType::Handle(signature());
  return sig.TypeParameterAt(index, nullability);
}

void Function::set_kind(UntaggedFunction::Kind value) const {
  untag()->kind_tag_.Update<KindBits>(value);
}

void Function::set_modifier(UntaggedFunction::AsyncModifier value) const {
  untag()->kind_tag_.Update<ModifierBits>(value);
}

void Function::set_recognized_kind(MethodRecognizer::Kind value) const {
  // Prevent multiple settings of kind.
  ASSERT((value == MethodRecognizer::kUnknown) || !IsRecognized());
  untag()->kind_tag_.Update<RecognizedBits>(value);
}

void Function::set_token_pos(TokenPosition token_pos) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  ASSERT(!token_pos.IsClassifying() || IsMethodExtractor());
  StoreNonPointer(&untag()->token_pos_, token_pos);
#endif
}

void Function::set_kind_tag(uint32_t value) const {
  untag()->kind_tag_ = value;
}

bool Function::is_eval_function() const {
  if (data()->IsArray()) {
    const intptr_t len = Array::LengthOf(Array::RawCast(data()));
    return len == static_cast<intptr_t>(EvalFunctionData::kLength);
  }
  return false;
}

bool Function::IsOptimizable() const {
  if (FLAG_precompiled_mode) {
    return true;
  }
  if (ForceOptimize()) return true;
  if (is_old_native()) {
    // Native methods don't need to be optimized.
    return false;
  }
  if (is_optimizable() && (script() != Script::null())) {
    // Additional check needed for implicit getters.
    return (unoptimized_code() == Object::null()) ||
           (Code::Handle(unoptimized_code()).Size() <
            FLAG_huge_method_cutoff_in_code_size);
  }
  return false;
}

bool Function::IsTypedDataViewFactory() const {
  switch (recognized_kind()) {
    case MethodRecognizer::kTypedData_ByteDataView_factory:
    case MethodRecognizer::kTypedData_Int8ArrayView_factory:
    case MethodRecognizer::kTypedData_Uint8ArrayView_factory:
    case MethodRecognizer::kTypedData_Uint8ClampedArrayView_factory:
    case MethodRecognizer::kTypedData_Int16ArrayView_factory:
    case MethodRecognizer::kTypedData_Uint16ArrayView_factory:
    case MethodRecognizer::kTypedData_Int32ArrayView_factory:
    case MethodRecognizer::kTypedData_Uint32ArrayView_factory:
    case MethodRecognizer::kTypedData_Int64ArrayView_factory:
    case MethodRecognizer::kTypedData_Uint64ArrayView_factory:
    case MethodRecognizer::kTypedData_Float32ArrayView_factory:
    case MethodRecognizer::kTypedData_Float64ArrayView_factory:
    case MethodRecognizer::kTypedData_Float32x4ArrayView_factory:
    case MethodRecognizer::kTypedData_Int32x4ArrayView_factory:
    case MethodRecognizer::kTypedData_Float64x2ArrayView_factory:
      return true;
    default:
      return false;
  }
}

bool Function::IsUnmodifiableTypedDataViewFactory() const {
  switch (recognized_kind()) {
    case MethodRecognizer::kTypedData_UnmodifiableByteDataView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableInt8ArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableUint8ArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableUint8ClampedArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableInt16ArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableUint16ArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableInt32ArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableUint32ArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableInt64ArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableUint64ArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableFloat32ArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableFloat64ArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableFloat32x4ArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableInt32x4ArrayView_factory:
    case MethodRecognizer::kTypedData_UnmodifiableFloat64x2ArrayView_factory:
      return true;
    default:
      return false;
  }
}

static bool InVmTests(const Function& function) {
#if defined(TESTING)
  return true;
#else
  auto* zone = Thread::Current()->zone();
  const auto& cls = Class::Handle(zone, function.Owner());
  const auto& lib = Library::Handle(zone, cls.library());
  const auto& url = String::Handle(zone, lib.url());
  const bool in_vm_tests =
      strstr(url.ToCString(), "runtime/tests/vm/") != nullptr;
  return in_vm_tests;
#endif
}

bool Function::ForceOptimize() const {
  if (RecognizedKindForceOptimize() || IsFfiCallClosure() ||
      IsFfiCallbackTrampoline() || is_ffi_native() ||
      IsTypedDataViewFactory() || IsUnmodifiableTypedDataViewFactory()) {
    return true;
  }

  if (!has_pragma()) return false;

  const bool has_vm_pragma = Library::FindPragma(
      Thread::Current(), false, *this, Symbols::vm_force_optimize());
  if (!has_vm_pragma) return false;

  // For run_vm_tests and runtime/tests/vm allow marking arbitrary functions as
  // force-optimize via `@pragma('vm:force-optimize')`.
  return InVmTests(*this);
}

bool Function::IsPreferInline() const {
  if (!has_pragma()) return false;

  return Library::FindPragma(Thread::Current(), /*only_core=*/false, *this,
                             Symbols::vm_prefer_inline());
}

bool Function::IsIdempotent() const {
  if (!has_pragma()) return false;

#if defined(TESTING)
  const bool kAllowOnlyForCoreLibFunctions = false;
#else
  const bool kAllowOnlyForCoreLibFunctions = true;
#endif  // defined(TESTING)

  return Library::FindPragma(Thread::Current(), kAllowOnlyForCoreLibFunctions,
                             *this, Symbols::vm_idempotent());
}

bool Function::IsCachableIdempotent() const {
  if (!has_pragma()) return false;

  const bool has_vm_pragma =
      Library::FindPragma(Thread::Current(), /*only_core=*/false, *this,
                          Symbols::vm_cachable_idempotent());
  if (!has_vm_pragma) return false;

  // For run_vm_tests and runtime/tests/vm allow marking arbitrary functions.
  return InVmTests(*this);
}

bool Function::IsExternalEffect() const {
  if (!has_pragma()) return false;

  return Library::FindPragma(dart::Thread::Current(), /*only_core=*/false,
                             *this, Symbols::external_effect());
}

bool Function::IsFfiCallClosure() const {
  if (!IsNonImplicitClosureFunction()) return false;
  if (!has_pragma()) return false;
  return Library::FindPragma(Thread::Current(), /*only_core=*/false, *this,
                             Symbols::vm_ffi_call_closure());
}

InstancePtr Function::GetFfiCallClosurePragmaValue() const {
  ASSERT(IsFfiCallClosure());
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  auto& pragma_value = Object::Handle(zone);
  Library::FindPragma(thread, /*only_core=*/false, *this,
                      Symbols::vm_ffi_call_closure(),
                      /*multiple=*/false, &pragma_value);
  ASSERT(!pragma_value.IsNull());
  return Instance::Cast(pragma_value).ptr();
}

bool Function::RecognizedKindForceOptimize() const {
  switch (recognized_kind()) {
    // Uses unboxed/untagged data not supported in unoptimized, or uses
    // LoadIndexed/StoreIndexed/MemoryCopy instructions with typed data
    // arrays, which requires optimization for payload extraction.
    case MethodRecognizer::kObjectArrayGetIndexed:
    case MethodRecognizer::kGrowableArrayGetIndexed:
#define TYPED_DATA_GET_INDEXED_CASES(clazz)                                    \
  case MethodRecognizer::k##clazz##ArrayGetIndexed:                            \
    FALL_THROUGH;                                                              \
  case MethodRecognizer::kExternal##clazz##ArrayGetIndexed:                    \
    FALL_THROUGH;                                                              \
  case MethodRecognizer::k##clazz##ArrayViewGetIndexed:                        \
    FALL_THROUGH;
      DART_CLASS_LIST_TYPED_DATA(TYPED_DATA_GET_INDEXED_CASES)
#undef TYPED_DATA_GET_INDEXED_CASES
    case MethodRecognizer::kCopyRangeFromUint8ListToOneByteString:
    case MethodRecognizer::kFinalizerBase_getIsolateFinalizers:
    case MethodRecognizer::kFinalizerBase_setIsolate:
    case MethodRecognizer::kFinalizerBase_setIsolateFinalizers:
    case MethodRecognizer::kFinalizerEntry_getExternalSize:
    case MethodRecognizer::kProfiler_getCurrentTag:
    case MethodRecognizer::kUserTag_defaultTag:
    case MethodRecognizer::kExtensionStreamHasListener:
    case MethodRecognizer::kTimeline_isDartStreamEnabled:
    case MethodRecognizer::kFfiLoadInt8:
    case MethodRecognizer::kFfiLoadInt16:
    case MethodRecognizer::kFfiLoadInt32:
    case MethodRecognizer::kFfiLoadInt64:
    case MethodRecognizer::kFfiLoadUint8:
    case MethodRecognizer::kFfiLoadUint16:
    case MethodRecognizer::kFfiLoadUint32:
    case MethodRecognizer::kFfiLoadUint64:
    case MethodRecognizer::kFfiLoadFloat:
    case MethodRecognizer::kFfiLoadFloatUnaligned:
    case MethodRecognizer::kFfiLoadDouble:
    case MethodRecognizer::kFfiLoadDoubleUnaligned:
    case MethodRecognizer::kFfiLoadPointer:
    case MethodRecognizer::kFfiStoreInt8:
    case MethodRecognizer::kFfiStoreInt16:
    case MethodRecognizer::kFfiStoreInt32:
    case MethodRecognizer::kFfiStoreInt64:
    case MethodRecognizer::kFfiStoreUint8:
    case MethodRecognizer::kFfiStoreUint16:
    case MethodRecognizer::kFfiStoreUint32:
    case MethodRecognizer::kFfiStoreUint64:
    case MethodRecognizer::kFfiStoreFloat:
    case MethodRecognizer::kFfiStoreFloatUnaligned:
    case MethodRecognizer::kFfiStoreDouble:
    case MethodRecognizer::kFfiStoreDoubleUnaligned:
    case MethodRecognizer::kFfiStorePointer:
    case MethodRecognizer::kFfiFromAddress:
    case MethodRecognizer::kFfiGetAddress:
    case MethodRecognizer::kFfiAsExternalTypedDataInt8:
    case MethodRecognizer::kFfiAsExternalTypedDataInt16:
    case MethodRecognizer::kFfiAsExternalTypedDataInt32:
    case MethodRecognizer::kFfiAsExternalTypedDataInt64:
    case MethodRecognizer::kFfiAsExternalTypedDataUint8:
    case MethodRecognizer::kFfiAsExternalTypedDataUint16:
    case MethodRecognizer::kFfiAsExternalTypedDataUint32:
    case MethodRecognizer::kFfiAsExternalTypedDataUint64:
    case MethodRecognizer::kFfiAsExternalTypedDataFloat:
    case MethodRecognizer::kFfiAsExternalTypedDataDouble:
    case MethodRecognizer::kGetNativeField:
    case MethodRecognizer::kRecord_fieldNames:
    case MethodRecognizer::kRecord_numFields:
    case MethodRecognizer::kStringBaseCodeUnitAt:
    case MethodRecognizer::kUtf8DecoderScan:
    case MethodRecognizer::kDouble_hashCode:
    case MethodRecognizer::kTypedList_GetInt8:
    case MethodRecognizer::kTypedList_SetInt8:
    case MethodRecognizer::kTypedList_GetUint8:
    case MethodRecognizer::kTypedList_SetUint8:
    case MethodRecognizer::kTypedList_GetInt16:
    case MethodRecognizer::kTypedList_SetInt16:
    case MethodRecognizer::kTypedList_GetUint16:
    case MethodRecognizer::kTypedList_SetUint16:
    case MethodRecognizer::kTypedList_GetInt32:
    case MethodRecognizer::kTypedList_SetInt32:
    case MethodRecognizer::kTypedList_GetUint32:
    case MethodRecognizer::kTypedList_SetUint32:
    case MethodRecognizer::kTypedList_GetInt64:
    case MethodRecognizer::kTypedList_SetInt64:
    case MethodRecognizer::kTypedList_GetUint64:
    case MethodRecognizer::kTypedList_SetUint64:
    case MethodRecognizer::kTypedList_GetFloat32:
    case MethodRecognizer::kTypedList_SetFloat32:
    case MethodRecognizer::kTypedList_GetFloat64:
    case MethodRecognizer::kTypedList_SetFloat64:
    case MethodRecognizer::kTypedList_GetInt32x4:
    case MethodRecognizer::kTypedList_SetInt32x4:
    case MethodRecognizer::kTypedList_GetFloat32x4:
    case MethodRecognizer::kTypedList_SetFloat32x4:
    case MethodRecognizer::kTypedList_GetFloat64x2:
    case MethodRecognizer::kTypedList_SetFloat64x2:
    case MethodRecognizer::kTypedData_memMove1:
    case MethodRecognizer::kTypedData_memMove2:
    case MethodRecognizer::kTypedData_memMove4:
    case MethodRecognizer::kTypedData_memMove8:
    case MethodRecognizer::kTypedData_memMove16:
    case MethodRecognizer::kMemCopy:
    // Prevent the GC from running so that the operation is atomic from
    // a GC point of view. Always double check implementation in
    // kernel_to_il.cc that no GC can happen in between the relevant IL
    // instructions.
    // TODO(https://dartbug.com/48527): Support inlining.
    case MethodRecognizer::kFinalizerBase_exchangeEntriesCollectedWithNull:
    // Both unboxed/untagged data and atomic-to-GC operation.
    case MethodRecognizer::kFinalizerEntry_allocate:
      return true;
    default:
      return false;
  }
}

#if !defined(DART_PRECOMPILED_RUNTIME)
bool Function::CanBeInlined() const {
  if (ForceOptimize()) {
    if (IsFfiCallClosure() || IsFfiCallbackTrampoline() || is_ffi_native()) {
      // We currently don't support inlining FFI trampolines. Some of them
      // are naturally non-inlinable because they contain a try/catch block,
      // but this condition is broader than strictly necessary.
      // The work necessary for inlining FFI trampolines is tracked by
      // http://dartbug.com/45055.
      return false;
    }
    if (CompilerState::Current().is_aot()) {
      return true;
    }
    // Inlining of force-optimized functions requires target function to be
    // idempotent becase if deoptimization is needed in inlined body, the
    // execution of the force-optimized will be restarted at the beginning of
    // the function.
    ASSERT(!IsPreferInline() || IsIdempotent());
    return IsIdempotent();
  }

  if (HasBreakpoint()) {
    return false;
  }

  return is_inlinable();
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

intptr_t Function::NumImplicitParameters() const {
  const UntaggedFunction::Kind k = kind();
  if ((k == UntaggedFunction::kClosureFunction) ||
      (k == UntaggedFunction::kImplicitClosureFunction) ||
      (k == UntaggedFunction::kFfiTrampoline)) {
    return 1;  // Closure object.
  }
  if (!is_static()) {
    // Closure functions defined inside instance (i.e. non-static) functions are
    // marked as non-static, but they do not have a receiver.
    // Closures are handled above.
    ASSERT((k != UntaggedFunction::kClosureFunction) &&
           (k != UntaggedFunction::kImplicitClosureFunction));
    return 1;  // Receiver.
  }
  return 0;  // No implicit parameters.
}

bool Function::AreValidArgumentCounts(intptr_t num_type_arguments,
                                      intptr_t num_arguments,
                                      intptr_t num_named_arguments,
                                      String* error_message) const {
  if ((num_type_arguments != 0) &&
      (num_type_arguments != NumTypeParameters())) {
    if (error_message != nullptr) {
      const intptr_t kMessageBufferSize = 64;
      char message_buffer[kMessageBufferSize];
      Utils::SNPrint(message_buffer, kMessageBufferSize,
                     "%" Pd " type arguments passed, but %" Pd " expected",
                     num_type_arguments, NumTypeParameters());
      // Allocate in old space because it can be invoked in background
      // optimizing compilation.
      *error_message = String::New(message_buffer, Heap::kOld);
    }
    return false;  // Too many type arguments.
  }
  if (num_named_arguments > NumOptionalNamedParameters()) {
    if (error_message != nullptr) {
      const intptr_t kMessageBufferSize = 64;
      char message_buffer[kMessageBufferSize];
      Utils::SNPrint(message_buffer, kMessageBufferSize,
                     "%" Pd " named passed, at most %" Pd " expected",
                     num_named_arguments, NumOptionalNamedParameters());
      // Allocate in old space because it can be invoked in background
      // optimizing compilation.
      *error_message = String::New(message_buffer, Heap::kOld);
    }
    return false;  // Too many named arguments.
  }
  const intptr_t num_pos_args = num_arguments - num_named_arguments;
  const intptr_t num_opt_pos_params = NumOptionalPositionalParameters();
  const intptr_t num_pos_params = num_fixed_parameters() + num_opt_pos_params;
  if (num_pos_args > num_pos_params) {
    if (error_message != nullptr) {
      const intptr_t kMessageBufferSize = 64;
      char message_buffer[kMessageBufferSize];
      // Hide implicit parameters to the user.
      const intptr_t num_hidden_params = NumImplicitParameters();
      Utils::SNPrint(message_buffer, kMessageBufferSize,
                     "%" Pd "%s passed, %s%" Pd " expected",
                     num_pos_args - num_hidden_params,
                     num_opt_pos_params > 0 ? " positional" : "",
                     num_opt_pos_params > 0 ? "at most " : "",
                     num_pos_params - num_hidden_params);
      // Allocate in old space because it can be invoked in background
      // optimizing compilation.
      *error_message = String::New(message_buffer, Heap::kOld);
    }
    return false;  // Too many fixed and/or positional arguments.
  }
  if (num_pos_args < num_fixed_parameters()) {
    if (error_message != nullptr) {
      const intptr_t kMessageBufferSize = 64;
      char message_buffer[kMessageBufferSize];
      // Hide implicit parameters to the user.
      const intptr_t num_hidden_params = NumImplicitParameters();
      Utils::SNPrint(message_buffer, kMessageBufferSize,
                     "%" Pd "%s passed, %s%" Pd " expected",
                     num_pos_args - num_hidden_params,
                     num_opt_pos_params > 0 ? " positional" : "",
                     num_opt_pos_params > 0 ? "at least " : "",
                     num_fixed_parameters() - num_hidden_params);
      // Allocate in old space because it can be invoked in background
      // optimizing compilation.
      *error_message = String::New(message_buffer, Heap::kOld);
    }
    return false;  // Too few fixed and/or positional arguments.
  }
  return true;
}

bool Function::AreValidArguments(intptr_t num_type_arguments,
                                 intptr_t num_arguments,
                                 const Array& argument_names,
                                 String* error_message) const {
  const Array& args_desc_array = Array::Handle(ArgumentsDescriptor::NewBoxed(
      num_type_arguments, num_arguments, argument_names, Heap::kNew));
  ArgumentsDescriptor args_desc(args_desc_array);
  return AreValidArguments(args_desc, error_message);
}

bool Function::AreValidArguments(const ArgumentsDescriptor& args_desc,
                                 String* error_message) const {
  const intptr_t num_type_arguments = args_desc.TypeArgsLen();
  const intptr_t num_arguments = args_desc.Count();
  const intptr_t num_named_arguments = args_desc.NamedCount();

  if (!AreValidArgumentCounts(num_type_arguments, num_arguments,
                              num_named_arguments, error_message)) {
    return false;
  }
  // Verify that all argument names are valid parameter names.
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  String& argument_name = String::Handle(zone);
  String& parameter_name = String::Handle(zone);
  const intptr_t num_positional_args = num_arguments - num_named_arguments;
  const intptr_t num_parameters = NumParameters();
  for (intptr_t i = 0; i < num_named_arguments; i++) {
    argument_name = args_desc.NameAt(i);
    ASSERT(argument_name.IsSymbol());
    bool found = false;
    for (intptr_t j = num_positional_args; j < num_parameters; j++) {
      parameter_name = ParameterNameAt(j);
      ASSERT(parameter_name.IsSymbol());
      if (argument_name.Equals(parameter_name)) {
        found = true;
        break;
      }
    }
    if (!found) {
      if (error_message != nullptr) {
        const intptr_t kMessageBufferSize = 64;
        char message_buffer[kMessageBufferSize];
        Utils::SNPrint(message_buffer, kMessageBufferSize,
                       "no optional formal parameter named '%s'",
                       argument_name.ToCString());
        *error_message = String::New(message_buffer);
      }
      return false;
    }
  }
  // Verify that all required named parameters are filled.
  for (intptr_t j = num_parameters - NumOptionalNamedParameters();
       j < num_parameters; j++) {
    if (IsRequiredAt(j)) {
      parameter_name = ParameterNameAt(j);
      ASSERT(parameter_name.IsSymbol());
      bool found = false;
      for (intptr_t i = 0; i < num_named_arguments; i++) {
        argument_name = args_desc.NameAt(i);
        ASSERT(argument_name.IsSymbol());
        if (argument_name.Equals(parameter_name)) {
          found = true;
          break;
        }
      }
      if (!found) {
        if (error_message != nullptr) {
          const intptr_t kMessageBufferSize = 64;
          char message_buffer[kMessageBufferSize];
          Utils::SNPrint(message_buffer, kMessageBufferSize,
                         "missing required named parameter '%s'",
                         parameter_name.ToCString());
          *error_message = String::New(message_buffer);
        }
        return false;
      }
    }
  }
  return true;
}

// Retrieves the function type arguments, if any. This could be explicitly
// passed type from the arguments array, delayed type arguments in closures,
// or instantiated bounds for the type parameters if no other source for
// function type arguments are found.
static TypeArgumentsPtr RetrieveFunctionTypeArguments(
    Thread* thread,
    Zone* zone,
    const Function& function,
    const Instance& receiver,
    const TypeArguments& instantiator_type_args,
    const Array& args,
    const ArgumentsDescriptor& args_desc) {
  ASSERT(!function.IsNull());

  const intptr_t kNumCurrentTypeArgs = function.NumTypeParameters();
  const intptr_t kNumParentTypeArgs = function.NumParentTypeArguments();
  const intptr_t kNumTypeArgs = kNumCurrentTypeArgs + kNumParentTypeArgs;
  // Non-generic functions don't receive type arguments.
  if (kNumTypeArgs == 0) return Object::empty_type_arguments().ptr();
  // Closure functions require that the receiver be provided (and is a closure).
  ASSERT(!function.IsClosureFunction() || receiver.IsClosure());

  // Only closure functions should have possibly generic parents.
  ASSERT(function.IsClosureFunction() || kNumParentTypeArgs == 0);
  const auto& parent_type_args =
      function.IsClosureFunction()
          ? TypeArguments::Handle(
                zone, Closure::Cast(receiver).function_type_arguments())
          : Object::empty_type_arguments();
  // We don't try to instantiate the parent type parameters to their bounds
  // if not provided or check any closed-over type arguments against the parent
  // type parameter bounds (since they have been type checked already).
  if (kNumCurrentTypeArgs == 0) return parent_type_args.ptr();

  auto& function_type_args = TypeArguments::Handle(zone);
  // First check for delayed type arguments before using either provided or
  // default type arguments.
  bool has_delayed_type_args = false;
  if (function.IsClosureFunction()) {
    const auto& closure = Closure::Cast(receiver);
    function_type_args = closure.delayed_type_arguments();
    has_delayed_type_args =
        function_type_args.ptr() != Object::empty_type_arguments().ptr();
  }

  if (args_desc.TypeArgsLen() > 0) {
    // We should never end up here when the receiver is a closure with delayed
    // type arguments unless this dynamically called closure function was
    // retrieved directly from the closure instead of going through
    // DartEntry::ResolveCallable, which appropriately checks for this case.
    ASSERT(!has_delayed_type_args);
    function_type_args ^= args.At(0);
  } else if (!has_delayed_type_args) {
    // We have no explicitly provided function type arguments, so instantiate
    // the type parameters to bounds or replace as appropriate.
    function_type_args = function.DefaultTypeArguments(zone);
    auto const mode =
        function.IsClosureFunction()
            ? function.default_type_arguments_instantiation_mode()
            : function_type_args.GetInstantiationMode(zone, &function);
    switch (mode) {
      case InstantiationMode::kIsInstantiated:
        // Nothing left to do.
        break;
      case InstantiationMode::kNeedsInstantiation:
        function_type_args = function_type_args.InstantiateAndCanonicalizeFrom(
            instantiator_type_args, parent_type_args);
        break;
      case InstantiationMode::kSharesInstantiatorTypeArguments:
        function_type_args = instantiator_type_args.ptr();
        break;
      case InstantiationMode::kSharesFunctionTypeArguments:
        function_type_args = parent_type_args.ptr();
        break;
    }
  }

  return function_type_args.Prepend(zone, parent_type_args, kNumParentTypeArgs,
                                    kNumTypeArgs);
}

// Retrieves the instantiator type arguments, if any, from the receiver.
static TypeArgumentsPtr RetrieveInstantiatorTypeArguments(
    Zone* zone,
    const Function& function,
    const Instance& receiver) {
  if (function.IsClosureFunction()) {
    ASSERT(receiver.IsClosure());
    const auto& closure = Closure::Cast(receiver);
    return closure.instantiator_type_arguments();
  }
  if (!receiver.IsNull()) {
    const auto& cls = Class::Handle(zone, receiver.clazz());
    if (cls.NumTypeArguments() > 0) {
      return receiver.GetTypeArguments();
    }
  }
  return Object::empty_type_arguments().ptr();
}

ObjectPtr Function::DoArgumentTypesMatch(
    const Array& args,
    const ArgumentsDescriptor& args_desc) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  if (signature() == FunctionType::null()) {
    // Precompiler deleted signature because of missing entry point pragma.
    return EntryPointFunctionInvocationError(*this);
  }
#endif
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();

  auto& receiver = Instance::Handle(zone);
  if (IsClosureFunction() || HasThisParameter()) {
    receiver ^= args.At(args_desc.FirstArgIndex());
  }
  const auto& instantiator_type_arguments = TypeArguments::Handle(
      zone, RetrieveInstantiatorTypeArguments(zone, *this, receiver));
  return Function::DoArgumentTypesMatch(args, args_desc,
                                        instantiator_type_arguments);
}

ObjectPtr Function::DoArgumentTypesMatch(
    const Array& args,
    const ArgumentsDescriptor& args_desc,
    const TypeArguments& instantiator_type_arguments) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  if (signature() == FunctionType::null()) {
    // Precompiler deleted signature because of missing entry point pragma.
    return EntryPointFunctionInvocationError(*this);
  }
#endif
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();

  auto& receiver = Instance::Handle(zone);
  if (IsClosureFunction() || HasThisParameter()) {
    receiver ^= args.At(args_desc.FirstArgIndex());
  }

  const auto& function_type_arguments = TypeArguments::Handle(
      zone, RetrieveFunctionTypeArguments(thread, zone, *this, receiver,
                                          instantiator_type_arguments, args,
                                          args_desc));
  return Function::DoArgumentTypesMatch(
      args, args_desc, instantiator_type_arguments, function_type_arguments);
}

ObjectPtr Function::DoArgumentTypesMatch(
    const Array& args,
    const ArgumentsDescriptor& args_desc,
    const TypeArguments& instantiator_type_arguments,
    const TypeArguments& function_type_arguments) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  if (signature() == FunctionType::null()) {
    // Precompiler deleted signature because of missing entry point pragma.
    return EntryPointFunctionInvocationError(*this);
  }
#endif
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();

  // Perform any non-covariant bounds checks on the provided function type
  // arguments to make sure they are appropriate subtypes of the bounds.
  const intptr_t kNumLocalTypeArgs = NumTypeParameters();
  if (kNumLocalTypeArgs > 0) {
    const intptr_t kNumParentTypeArgs = NumParentTypeArguments();
    ASSERT(function_type_arguments.HasCount(kNumParentTypeArgs +
                                            kNumLocalTypeArgs));
    const auto& params = TypeParameters::Handle(zone, type_parameters());
    // No checks are needed if all bounds are dynamic.
    if (!params.AllDynamicBounds()) {
      auto& param = AbstractType::Handle(zone);
      auto& bound = AbstractType::Handle(zone);
      for (intptr_t i = 0; i < kNumLocalTypeArgs; i++) {
        bound = params.BoundAt(i);
        // Only perform non-covariant checks where the bound is not
        // the top type.
        if (params.IsGenericCovariantImplAt(i) ||
            bound.IsTopTypeForSubtyping()) {
          continue;
        }
        param = TypeParameterAt(i);
        if (!AbstractType::InstantiateAndTestSubtype(
                &param, &bound, instantiator_type_arguments,
                function_type_arguments)) {
          const auto& names = Array::Handle(zone, params.names());
          auto& name = String::Handle(zone);
          name ^= names.At(i);
          return Error::RawCast(
              ThrowTypeError(token_pos(), param, bound, name));
        }
      }
    }
  } else {
    ASSERT(function_type_arguments.HasCount(NumParentTypeArguments()));
  }

  AbstractType& type = AbstractType::Handle(zone);
  Instance& argument = Instance::Handle(zone);

  auto check_argument = [](const Instance& argument, const AbstractType& type,
                           const TypeArguments& instantiator_type_args,
                           const TypeArguments& function_type_args) -> bool {
    // If the argument type is the top type, no need to check.
    if (type.IsTopTypeForSubtyping()) return true;
    return argument.IsInstanceOf(type, instantiator_type_args,
                                 function_type_args);
  };

  // Check types of the provided arguments against the expected parameter types.
  const intptr_t arg_offset = args_desc.FirstArgIndex();
  // Only check explicit arguments.
  const intptr_t arg_start = arg_offset + NumImplicitParameters();
  const intptr_t end_positional_args = arg_offset + args_desc.PositionalCount();
  for (intptr_t arg_index = arg_start; arg_index < end_positional_args;
       ++arg_index) {
    argument ^= args.At(arg_index);
    // Adjust for type arguments when they're present.
    const intptr_t param_index = arg_index - arg_offset;
    type = ParameterTypeAt(param_index);
    if (!check_argument(argument, type, instantiator_type_arguments,
                        function_type_arguments)) {
      auto& name = String::Handle(zone, ParameterNameAt(param_index));
      if (!type.IsInstantiated()) {
        type =
            type.InstantiateFrom(instantiator_type_arguments,
                                 function_type_arguments, kAllFree, Heap::kNew);
      }
      return ThrowTypeError(token_pos(), argument, type, name);
    }
  }

  const intptr_t num_named_arguments = args_desc.NamedCount();
  if (num_named_arguments == 0) {
    return Error::null();
  }

  const int num_parameters = NumParameters();
  const int num_fixed_params = num_fixed_parameters();

  String& argument_name = String::Handle(zone);
  String& parameter_name = String::Handle(zone);

  // Check types of named arguments against expected parameter type.
  for (intptr_t named_index = 0; named_index < num_named_arguments;
       named_index++) {
    argument_name = args_desc.NameAt(named_index);
    ASSERT(argument_name.IsSymbol());
    argument ^= args.At(arg_offset + args_desc.PositionAt(named_index));

    // Try to find the named parameter that matches the provided argument.
    // Even when annotated with @required, named parameters are still stored
    // as if they were optional and so come after the fixed parameters.
    // Currently O(n^2) as there's no guarantee from either the CFE or the
    // VM that named parameters and named arguments are sorted in the same way.
    intptr_t param_index = num_fixed_params;
    for (; param_index < num_parameters; param_index++) {
      parameter_name = ParameterNameAt(param_index);
      ASSERT(parameter_name.IsSymbol());

      if (!parameter_name.Equals(argument_name)) continue;

      type = ParameterTypeAt(param_index);
      if (!check_argument(argument, type, instantiator_type_arguments,
                          function_type_arguments)) {
        auto& name = String::Handle(zone, ParameterNameAt(param_index));
        if (!type.IsInstantiated()) {
          type = type.InstantiateFrom(instantiator_type_arguments,
                                      function_type_arguments, kAllFree,
                                      Heap::kNew);
        }
        return ThrowTypeError(token_pos(), argument, type, name);
      }
      break;
    }
    // Only should fail if AreValidArguments returns a false positive.
    ASSERT(param_index < num_parameters);
  }
  return Error::null();
}

// Helper allocating a C string buffer in the zone, printing the fully qualified
// name of a function in it, and replacing ':' by '_' to make sure the
// constructed name is a valid C++ identifier for debugging purpose.
// Set 'chars' to allocated buffer and return number of written characters.

enum QualifiedFunctionLibKind {
  kQualifiedFunctionLibKindLibUrl,
  kQualifiedFunctionLibKindLibName
};

static intptr_t ConstructFunctionFullyQualifiedCString(
    const Function& function,
    char** chars,
    intptr_t reserve_len,
    bool with_lib,
    QualifiedFunctionLibKind lib_kind) {
  Zone* zone = Thread::Current()->zone();
  const char* name = String::Handle(zone, function.name()).ToCString();
  const char* function_format = (reserve_len == 0) ? "%s" : "%s_";
  reserve_len += Utils::SNPrint(nullptr, 0, function_format, name);
  const Function& parent = Function::Handle(zone, function.parent_function());
  intptr_t written = 0;
  if (parent.IsNull()) {
    const Class& function_class = Class::Handle(zone, function.Owner());
    ASSERT(!function_class.IsNull());
    const char* class_name =
        String::Handle(zone, function_class.Name()).ToCString();
    ASSERT(class_name != nullptr);
    const char* library_name = nullptr;
    const char* lib_class_format = nullptr;
    if (with_lib) {
      const Library& library = Library::Handle(zone, function_class.library());
      ASSERT(!library.IsNull());
      switch (lib_kind) {
        case kQualifiedFunctionLibKindLibUrl:
          library_name = String::Handle(zone, library.url()).ToCString();
          break;
        case kQualifiedFunctionLibKindLibName:
          library_name = String::Handle(zone, library.name()).ToCString();
          break;
        default:
          UNREACHABLE();
      }
      ASSERT(library_name != nullptr);
      lib_class_format = (library_name[0] == '\0') ? "%s%s_" : "%s_%s_";
    } else {
      library_name = "";
      lib_class_format = "%s%s.";
    }
    reserve_len +=
        Utils::SNPrint(nullptr, 0, lib_class_format, library_name, class_name);
    ASSERT(chars != nullptr);
    *chars = zone->Alloc<char>(reserve_len + 1);
    written = Utils::SNPrint(*chars, reserve_len + 1, lib_class_format,
                             library_name, class_name);
  } else {
    written = ConstructFunctionFullyQualifiedCString(parent, chars, reserve_len,
                                                     with_lib, lib_kind);
  }
  ASSERT(*chars != nullptr);
  char* next = *chars + written;
  written += Utils::SNPrint(next, reserve_len + 1, function_format, name);
  // Replace ":" with "_".
  while (true) {
    next = strchr(next, ':');
    if (next == nullptr) break;
    *next = '_';
  }
  return written;
}

const char* Function::ToFullyQualifiedCString() const {
  char* chars = nullptr;
  ConstructFunctionFullyQualifiedCString(*this, &chars, 0, true,
                                         kQualifiedFunctionLibKindLibUrl);
  return chars;
}

const char* Function::ToLibNamePrefixedQualifiedCString() const {
  char* chars = nullptr;
  ConstructFunctionFullyQualifiedCString(*this, &chars, 0, true,
                                         kQualifiedFunctionLibKindLibName);
  return chars;
}

const char* Function::ToQualifiedCString() const {
  char* chars = nullptr;
  ConstructFunctionFullyQualifiedCString(*this, &chars, 0, false,
                                         kQualifiedFunctionLibKindLibUrl);
  return chars;
}

AbstractTypePtr FunctionType::InstantiateFrom(
    const TypeArguments& instantiator_type_arguments,
    const TypeArguments& function_type_arguments,
    intptr_t num_free_fun_type_params,
    Heap::Space space,
    FunctionTypeMapping* function_type_mapping,
    intptr_t num_parent_type_args_adjustment) const {
  ASSERT(IsFinalized());
  Zone* zone = Thread::Current()->zone();
  const intptr_t num_parent_type_args = NumParentTypeArguments();
  bool delete_type_parameters = false;
  if (num_free_fun_type_params == kCurrentAndEnclosingFree) {
    // See the comment on kCurrentAndEnclosingFree to understand why we don't
    // adjust 'num_free_fun_type_params' downward in this case.
    num_free_fun_type_params = kAllFree;
    delete_type_parameters = true;
  } else {
    ASSERT(!IsInstantiated(kAny, num_free_fun_type_params));
    // We only consider the function type parameters declared by the parents
    // of this signature function as free.
    if (num_parent_type_args < num_free_fun_type_params) {
      num_free_fun_type_params = num_parent_type_args;
    }
  }

  // The number of parent type parameters that remain uninstantiated.
  const intptr_t remaining_parent_type_params =
      num_free_fun_type_params < num_parent_type_args
          ? num_parent_type_args - num_free_fun_type_params
          : 0;

  // Adjust number of parent type arguments for all nested substituted types.
  num_parent_type_args_adjustment =
      remaining_parent_type_params +
      (delete_type_parameters ? 0 : NumTypeParameters());

  FunctionType& sig = FunctionType::Handle(
      FunctionType::New(remaining_parent_type_params, nullability(), space));
  AbstractType& type = AbstractType::Handle(zone);

  FunctionTypeMapping scope(zone, &function_type_mapping, *this, sig);

  // Copy the type parameters and instantiate their bounds and defaults.
  if (!delete_type_parameters) {
    const TypeParameters& type_params =
        TypeParameters::Handle(zone, type_parameters());
    if (!type_params.IsNull()) {
      const TypeParameters& sig_type_params =
          TypeParameters::Handle(zone, TypeParameters::New());
      // No need to set names that are ignored in a signature, however, the
      // length of the names array defines the number of type parameters.
      sig_type_params.set_names(Array::Handle(zone, type_params.names()));
      sig_type_params.set_flags(Array::Handle(zone, type_params.flags()));
      sig.SetTypeParameters(sig_type_params);
      TypeArguments& type_args = TypeArguments::Handle(zone);
      type_args = type_params.bounds();
      if (!type_args.IsNull() && !type_args.IsInstantiated()) {
        type_args = type_args.InstantiateFrom(
            instantiator_type_arguments, function_type_arguments,
            num_free_fun_type_params, space, function_type_mapping,
            num_parent_type_args_adjustment);
      }
      sig_type_params.set_bounds(type_args);
      type_args = type_params.defaults();
      if (!type_args.IsNull() && !type_args.IsInstantiated()) {
        type_args = type_args.InstantiateFrom(
            instantiator_type_arguments, function_type_arguments,
            num_free_fun_type_params, space, function_type_mapping,
            num_parent_type_args_adjustment);
      }
      sig_type_params.set_defaults(type_args);
    }
  }

  type = result_type();
  if (!type.IsInstantiated()) {
    type = type.InstantiateFrom(
        instantiator_type_arguments, function_type_arguments,
        num_free_fun_type_params, space, function_type_mapping,
        num_parent_type_args_adjustment);
    // A returned null type indicates a failed instantiation in dead code that
    // must be propagated up to the caller, the optimizing compiler.
    if (type.IsNull()) {
      return FunctionType::null();
    }
  }
  sig.set_result_type(type);
  const intptr_t num_params = NumParameters();
  sig.set_num_implicit_parameters(num_implicit_parameters());
  sig.set_num_fixed_parameters(num_fixed_parameters());
  sig.SetNumOptionalParameters(NumOptionalParameters(),
                               HasOptionalPositionalParameters());
  sig.set_parameter_types(Array::Handle(Array::New(num_params, space)));
  for (intptr_t i = 0; i < num_params; i++) {
    type = ParameterTypeAt(i);
    if (!type.IsInstantiated()) {
      type = type.InstantiateFrom(
          instantiator_type_arguments, function_type_arguments,
          num_free_fun_type_params, space, function_type_mapping,
          num_parent_type_args_adjustment);
      // A returned null type indicates a failed instantiation in dead code that
      // must be propagated up to the caller, the optimizing compiler.
      if (type.IsNull()) {
        return FunctionType::null();
      }
    }
    sig.SetParameterTypeAt(i, type);
  }
  sig.set_named_parameter_names(Array::Handle(zone, named_parameter_names()));

  if (delete_type_parameters) {
    ASSERT(sig.IsInstantiated(kFunctions));
  }

  sig.SetIsFinalized();

  // Canonicalization is not part of instantiation.
  return sig.ptr();
}

AbstractTypePtr FunctionType::UpdateFunctionTypes(
    intptr_t num_parent_type_args_adjustment,
    intptr_t num_free_fun_type_params,
    Heap::Space space,
    FunctionTypeMapping* function_type_mapping) const {
  ASSERT(num_parent_type_args_adjustment >= 0);
  ASSERT(IsFinalized());
  Zone* zone = Thread::Current()->zone();

  const intptr_t old_num_parent_type_args = NumParentTypeArguments();
  // From now on, adjust all type parameter types
  // which belong to this or nested function types.
  if (num_free_fun_type_params > old_num_parent_type_args) {
    num_free_fun_type_params = old_num_parent_type_args;
  }

  FunctionType& new_type = FunctionType::Handle(
      zone, FunctionType::New(
                NumParentTypeArguments() + num_parent_type_args_adjustment,
                nullability(), space));
  AbstractType& type = AbstractType::Handle(zone);

  FunctionTypeMapping scope(zone, &function_type_mapping, *this, new_type);

  const TypeParameters& type_params =
      TypeParameters::Handle(zone, type_parameters());
  if (!type_params.IsNull()) {
    const TypeParameters& new_type_params =
        TypeParameters::Handle(zone, TypeParameters::New());
    // No need to set names that are ignored in a signature, however, the
    // length of the names array defines the number of type parameters.
    new_type_params.set_names(Array::Handle(zone, type_params.names()));
    new_type_params.set_flags(Array::Handle(zone, type_params.flags()));
    TypeArguments& type_args = TypeArguments::Handle(zone);
    type_args = type_params.bounds();
    if (!type_args.IsNull()) {
      type_args = type_args.UpdateFunctionTypes(num_parent_type_args_adjustment,
                                                num_free_fun_type_params, space,
                                                function_type_mapping);
    }
    new_type_params.set_bounds(type_args);
    type_args = type_params.defaults();
    if (!type_args.IsNull()) {
      type_args = type_args.UpdateFunctionTypes(num_parent_type_args_adjustment,
                                                num_free_fun_type_params, space,
                                                function_type_mapping);
    }
    new_type_params.set_defaults(type_args);
    new_type.SetTypeParameters(new_type_params);
  }

  type = result_type();
  type = type.UpdateFunctionTypes(num_parent_type_args_adjustment,
                                  num_free_fun_type_params, space,
                                  function_type_mapping);
  new_type.set_result_type(type);

  const intptr_t num_params = NumParameters();
  new_type.set_num_implicit_parameters(num_implicit_parameters());
  new_type.set_num_fixed_parameters(num_fixed_parameters());
  new_type.SetNumOptionalParameters(NumOptionalParameters(),
                                    HasOptionalPositionalParameters());
  new_type.set_parameter_types(Array::Handle(Array::New(num_params, space)));
  for (intptr_t i = 0; i < num_params; i++) {
    type = ParameterTypeAt(i);
    type = type.UpdateFunctionTypes(num_parent_type_args_adjustment,
                                    num_free_fun_type_params, space,
                                    function_type_mapping);
    new_type.SetParameterTypeAt(i, type);
  }
  new_type.set_named_parameter_names(
      Array::Handle(zone, named_parameter_names()));
  new_type.SetIsFinalized();

  return new_type.ptr();
}

// Checks if the type of the specified parameter of this signature is a
// supertype of the type of the specified parameter of the other signature
// (i.e. check parameter contravariance).
// Note that types marked as covariant are already dealt with in the front-end.
bool FunctionType::IsContravariantParameter(
    intptr_t parameter_position,
    const FunctionType& other,
    intptr_t other_parameter_position,
    Heap::Space space,
    FunctionTypeMapping* function_type_equivalence) const {
  const AbstractType& param_type =
      AbstractType::Handle(ParameterTypeAt(parameter_position));
  if (param_type.IsTopTypeForSubtyping()) {
    return true;
  }
  const AbstractType& other_param_type =
      AbstractType::Handle(other.ParameterTypeAt(other_parameter_position));
  return other_param_type.IsSubtypeOf(param_type, space,
                                      function_type_equivalence);
}

bool FunctionType::HasSameTypeParametersAndBounds(
    const FunctionType& other,
    TypeEquality kind,
    FunctionTypeMapping* function_type_equivalence) const {
  Zone* const zone = Thread::Current()->zone();
  TRACE_TYPE_CHECKS_VERBOSE(
      "  FunctionType::HasSameTypeParametersAndBounds(%s, %s)\n", ToCString(),
      other.ToCString());

  const intptr_t num_type_params = NumTypeParameters();
  if (num_type_params != other.NumTypeParameters()) {
    TRACE_TYPE_CHECKS_VERBOSE(
        "   - result: false (number of type parameters)\n");
    return false;
  }
  if (num_type_params > 0) {
    const TypeParameters& type_params =
        TypeParameters::Handle(zone, type_parameters());
    ASSERT(!type_params.IsNull());
    const TypeParameters& other_type_params =
        TypeParameters::Handle(zone, other.type_parameters());
    ASSERT(!other_type_params.IsNull());
    if (kind == TypeEquality::kInSubtypeTest) {
      if (!type_params.AllDynamicBounds() ||
          !other_type_params.AllDynamicBounds()) {
        AbstractType& bound = AbstractType::Handle(zone);
        AbstractType& other_bound = AbstractType::Handle(zone);
        for (intptr_t i = 0; i < num_type_params; i++) {
          bound = type_params.BoundAt(i);
          other_bound = other_type_params.BoundAt(i);
          // Bounds that are mutual subtypes are considered equal.
          if (!bound.IsSubtypeOf(other_bound, Heap::kOld,
                                 function_type_equivalence) ||
              !other_bound.IsSubtypeOf(bound, Heap::kOld,
                                       function_type_equivalence)) {
            TRACE_TYPE_CHECKS_VERBOSE(
                "   - result: false (bounds are not mutual subtypes)\n");
            return false;
          }
        }
      }
    } else {
      if (NumParentTypeArguments() != other.NumParentTypeArguments()) {
        TRACE_TYPE_CHECKS_VERBOSE(
            "   - result: false (mismatch in number of type arguments)\n");
        return false;
      }
      const TypeArguments& bounds =
          TypeArguments::Handle(zone, type_params.bounds());
      const TypeArguments& other_bounds =
          TypeArguments::Handle(zone, other_type_params.bounds());
      if (!bounds.IsEquivalent(other_bounds, kind, function_type_equivalence)) {
        TRACE_TYPE_CHECKS_VERBOSE(
            "   - result: false (bounds are not equivalent)\n");
        return false;
      }
      if (kind == TypeEquality::kCanonical) {
        // Compare default arguments.
        const TypeArguments& defaults =
            TypeArguments::Handle(zone, type_params.defaults());
        const TypeArguments& other_defaults =
            TypeArguments::Handle(zone, other_type_params.defaults());
        if (!defaults.IsEquivalent(other_defaults, kind,
                                   function_type_equivalence)) {
          TRACE_TYPE_CHECKS_VERBOSE(
              "   - result: false (default types are not equivalent,"
              " %s - %s)\n",
              defaults.ToCString(), other_defaults.ToCString());
          return false;
        }
      }
    }
    if (kind != TypeEquality::kInSubtypeTest) {
      // Compare flags (IsGenericCovariantImpl).
      if (!Array::Equals(type_params.flags(), other_type_params.flags())) {
        TRACE_TYPE_CHECKS_VERBOSE("   - result: false (flags are not equal)\n");
        return false;
      }
    }
  }
  TRACE_TYPE_CHECKS_VERBOSE("   - result: true\n");
  return true;
}

bool FunctionType::IsSubtypeOf(
    const FunctionType& other,
    Heap::Space space,
    FunctionTypeMapping* function_type_equivalence) const {
  TRACE_TYPE_CHECKS_VERBOSE("  FunctionType::IsSubtypeOf(%s, %s)\n",
                            ToCString(), other.ToCString());
  const intptr_t num_fixed_params = num_fixed_parameters();
  const intptr_t num_opt_pos_params = NumOptionalPositionalParameters();
  const intptr_t num_opt_named_params = NumOptionalNamedParameters();
  const intptr_t other_num_fixed_params = other.num_fixed_parameters();
  const intptr_t other_num_opt_pos_params =
      other.NumOptionalPositionalParameters();
  const intptr_t other_num_opt_named_params =
      other.NumOptionalNamedParameters();
  // This signature requires the same arguments or less and accepts the same
  // arguments or more. We can ignore implicit parameters.
  const intptr_t num_ignored_params = num_implicit_parameters();
  const intptr_t other_num_ignored_params = other.num_implicit_parameters();
  if (((num_fixed_params - num_ignored_params) >
       (other_num_fixed_params - other_num_ignored_params)) ||
      ((num_fixed_params - num_ignored_params + num_opt_pos_params) <
       (other_num_fixed_params - other_num_ignored_params +
        other_num_opt_pos_params)) ||
      (num_opt_named_params < other_num_opt_named_params)) {
    TRACE_TYPE_CHECKS_VERBOSE(
        "   - result: false (mismatch in number of parameters)\n");
    return false;
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  FunctionTypeMapping scope(zone, &function_type_equivalence, *this, other);

  // Check the type parameters and bounds of generic functions.
  if (!HasSameTypeParametersAndBounds(other, TypeEquality::kInSubtypeTest,
                                      function_type_equivalence)) {
    TRACE_TYPE_CHECKS_VERBOSE(
        "   - result: false (mismatch in type parameters)\n");
    return false;
  }
  // Check the result type.
  const AbstractType& other_res_type =
      AbstractType::Handle(zone, other.result_type());
  // 'void Function()' is a subtype of 'Object Function()'.
  if (!other_res_type.IsTopTypeForSubtyping()) {
    const AbstractType& res_type = AbstractType::Handle(zone, result_type());
    if (!res_type.IsSubtypeOf(other_res_type, space,
                              function_type_equivalence)) {
      TRACE_TYPE_CHECKS_VERBOSE("   - result: false (result type)\n");
      return false;
    }
  }
  // Check the types of fixed and optional positional parameters.
  for (intptr_t i = 0; i < (other_num_fixed_params - other_num_ignored_params +
                            other_num_opt_pos_params);
       i++) {
    if (!IsContravariantParameter(i + num_ignored_params, other,
                                  i + other_num_ignored_params, space,
                                  function_type_equivalence)) {
      TRACE_TYPE_CHECKS_VERBOSE("   - result: false (parameter type)\n");
      return false;
    }
  }
  // Check that for each optional named parameter of type T of the other
  // function type, there exists an optional named parameter of this function
  // type with an identical name and with a type S that is a supertype of T.
  // Note that SetParameterNameAt() guarantees that names are symbols, so we
  // can compare their raw pointers.
  const int num_params = num_fixed_params + num_opt_named_params;
  const int other_num_params =
      other_num_fixed_params + other_num_opt_named_params;
  bool found_param_name;
  String& other_param_name = String::Handle(zone);
  for (intptr_t i = other_num_fixed_params; i < other_num_params; i++) {
    other_param_name = other.ParameterNameAt(i);
    ASSERT(other_param_name.IsSymbol());
    found_param_name = false;
    for (intptr_t j = num_fixed_params; j < num_params; j++) {
      ASSERT(String::Handle(zone, ParameterNameAt(j)).IsSymbol());
      if (ParameterNameAt(j) == other_param_name.ptr()) {
        found_param_name = true;
        if (!IsContravariantParameter(j, other, i, space,
                                      function_type_equivalence)) {
          TRACE_TYPE_CHECKS_VERBOSE(
              "   - result: false (optional parameter type)\n");
          return false;
        }
        break;
      }
    }
    if (!found_param_name) {
      TRACE_TYPE_CHECKS_VERBOSE(
          "   - result: false (named parameter not found)\n");
      return false;
    }
  }
  // Check that for each required named parameter in this function, there's a
  // corresponding required named parameter in the other function.
  String& param_name = other_param_name;
  for (intptr_t j = num_params - num_opt_named_params; j < num_params; j++) {
    if (IsRequiredAt(j)) {
      param_name = ParameterNameAt(j);
      ASSERT(param_name.IsSymbol());
      bool found = false;
      for (intptr_t i = other_num_fixed_params; i < other_num_params; i++) {
        ASSERT(String::Handle(zone, other.ParameterNameAt(i)).IsSymbol());
        if (other.ParameterNameAt(i) == param_name.ptr()) {
          found = true;
          if (!other.IsRequiredAt(i)) {
            TRACE_TYPE_CHECKS_VERBOSE(
                "   - result: false (mismatch in required named "
                "parameters)\n");
            return false;
          }
        }
      }
      if (!found) {
        TRACE_TYPE_CHECKS_VERBOSE(
            "   - result: false (required named parameter not found)\n");
        return false;
      }
    }
  }
  TRACE_TYPE_CHECKS_VERBOSE("   - result: true\n");
  return true;
}

// The compiler generates an implicit constructor if a class definition
// does not contain an explicit constructor or factory. The implicit
// constructor has the same token position as the owner class.
bool Function::IsImplicitConstructor() const {
  return IsGenerativeConstructor() && (token_pos() == end_token_pos());
}

bool Function::IsImplicitStaticClosureFunction(FunctionPtr func) {
  NoSafepointScope no_safepoint;
  uint32_t kind_tag = func->untag()->kind_tag_.load(std::memory_order_relaxed);
  return (KindBits::decode(kind_tag) ==
          UntaggedFunction::kImplicitClosureFunction) &&
         StaticBit::decode(kind_tag);
}

bool Function::IsImplicitInstanceClosureFunction(FunctionPtr func) {
  NoSafepointScope no_safepoint;
  uint32_t kind_tag = func->untag()->kind_tag_.load(std::memory_order_relaxed);
  return (KindBits::decode(kind_tag) ==
          UntaggedFunction::kImplicitClosureFunction) &&
         !StaticBit::decode(kind_tag);
}

FunctionPtr Function::New(Heap::Space space) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kFunctionCid) !=
         Class::null());
  return Object::Allocate<Function>(space);
}

FunctionPtr Function::New(const FunctionType& signature,
                          const String& name,
                          UntaggedFunction::Kind kind,
                          bool is_static,
                          bool is_const,
                          bool is_abstract,
                          bool is_external,
                          bool is_native,
                          const Object& owner,
                          TokenPosition token_pos,
                          Heap::Space space) {
  ASSERT(!owner.IsNull());
  ASSERT(!signature.IsNull());
  const Function& result = Function::Handle(Function::New(space));
  result.set_kind_tag(0);
  result.set_name(name);
  result.set_kind_tag(0);  // Ensure determinism of uninitialized bits.
  result.set_kind(kind);
  result.set_recognized_kind(MethodRecognizer::kUnknown);
  result.set_modifier(UntaggedFunction::kNoModifier);
  result.set_is_static(is_static);
  result.set_is_const(is_const);
  result.set_is_abstract(is_abstract);
  result.set_is_external(is_external);
  result.set_is_native(is_native);
  result.set_is_reflectable(true);  // Will be computed later.
  result.set_is_visible(true);      // Will be computed later.
  result.set_is_debuggable(true);   // Will be computed later.
  result.set_is_intrinsic(false);
  result.set_has_pragma(false);
  result.set_is_polymorphic_target(false);
  result.set_is_synthetic(false);
  NOT_IN_PRECOMPILED(result.set_state_bits(0));
  result.set_owner(owner);
  NOT_IN_PRECOMPILED(result.set_token_pos(token_pos));
  NOT_IN_PRECOMPILED(result.set_end_token_pos(token_pos));
  NOT_IN_PRECOMPILED(result.set_usage_counter(0));
  NOT_IN_PRECOMPILED(result.set_deoptimization_counter(0));
  NOT_IN_PRECOMPILED(result.set_optimized_instruction_count(0));
  NOT_IN_PRECOMPILED(result.set_optimized_call_site_count(0));
  NOT_IN_PRECOMPILED(result.set_inlining_depth(0));
  NOT_IN_PRECOMPILED(result.set_kernel_offset(0));
  NOT_IN_PRECOMPILED(result.set_is_optimizable(is_native ? false : true));
  result.set_is_inlinable(true);
  result.reset_unboxed_parameters_and_return();
  result.SetInstructionsSafe(StubCode::LazyCompile());

  // See Function::set_data() for more information.
  if (kind == UntaggedFunction::kClosureFunction ||
      kind == UntaggedFunction::kImplicitClosureFunction) {
    ASSERT(space == Heap::kOld);
    const ClosureData& data = ClosureData::Handle(ClosureData::New());
    data.set_awaiter_link({});
    result.set_data(data);
  } else if (kind == UntaggedFunction::kFfiTrampoline) {
    const FfiTrampolineData& data =
        FfiTrampolineData::Handle(FfiTrampolineData::New());
    result.set_data(data);
  } else if (result.is_old_native()) {
    const auto& data =
        Array::Handle(Array::New(NativeFunctionData::kLength, Heap::kOld));
    result.set_data(data);
  } else {
    // Functions other than signature functions have no reason to be allocated
    // in new space.
    ASSERT(space == Heap::kOld);
  }

  // Force-optimized functions are not debuggable because they cannot
  // deoptimize.
  if (result.ForceOptimize()) {
    result.set_is_debuggable(false);
  }
  signature.set_num_implicit_parameters(result.NumImplicitParameters());
  result.SetSignature(signature);
  NOT_IN_PRECOMPILED(
      result.set_positional_parameter_names(Object::empty_array()));
  return result.ptr();
}

FunctionPtr Function::NewClosureFunctionWithKind(UntaggedFunction::Kind kind,
                                                 const String& name,
                                                 const Function& parent,
                                                 bool is_static,
                                                 TokenPosition token_pos,
                                                 const Object& owner) {
  ASSERT((kind == UntaggedFunction::kClosureFunction) ||
         (kind == UntaggedFunction::kImplicitClosureFunction));
  ASSERT(!parent.IsNull());
  ASSERT(!owner.IsNull());
  const FunctionType& signature = FunctionType::Handle(FunctionType::New(
      kind == UntaggedFunction::kClosureFunction ? parent.NumTypeArguments()
                                                 : 0));
  const Function& result = Function::Handle(
      Function::New(signature, name, kind,
                    /* is_static = */ is_static,
                    /* is_const = */ false,
                    /* is_abstract = */ false,
                    /* is_external = */ false,
                    /* is_native = */ false, owner, token_pos));
  result.set_parent_function(parent);
  return result.ptr();
}

FunctionPtr Function::NewClosureFunction(const String& name,
                                         const Function& parent,
                                         TokenPosition token_pos) {
  // Use the owner defining the parent function and not the class containing it.
  const Object& parent_owner = Object::Handle(parent.RawOwner());
  return NewClosureFunctionWithKind(UntaggedFunction::kClosureFunction, name,
                                    parent, parent.is_static(), token_pos,
                                    parent_owner);
}

FunctionPtr Function::NewImplicitClosureFunction(const String& name,
                                                 const Function& parent,
                                                 TokenPosition token_pos) {
  // Use the owner defining the parent function and not the class containing it.
  const Object& parent_owner = Object::Handle(parent.RawOwner());
  return NewClosureFunctionWithKind(
      UntaggedFunction::kImplicitClosureFunction, name, parent,
      parent.is_static() || parent.IsConstructor(), token_pos, parent_owner);
}

bool Function::SafeToClosurize() const {
#if defined(DART_PRECOMPILED_RUNTIME)
  return HasImplicitClosureFunction();
#else
  return true;
#endif
}

bool Function::IsDynamicClosureCallDispatcher() const {
  if (!IsInvokeFieldDispatcher()) return false;
  if (!Class::IsClosureClass(Owner())) return false;
  return name() == Symbols::DynamicCall().ptr();
}

bool Function::IsClosureCallDispatcher() const {
  if (!IsInvokeFieldDispatcher()) return false;
  if (!Class::IsClosureClass(Owner())) return false;
  return name() == Symbols::call().ptr();
}

FunctionPtr Function::ImplicitClosureFunction() const {
  // Return the existing implicit closure function if any.
  if (implicit_closure_function() != Function::null()) {
    return implicit_closure_function();
  }

#if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
  // In AOT mode all implicit closures are pre-created.
  FATAL("Cannot create implicit closure in AOT!");
  return Function::null();
#else
  ASSERT(!IsClosureFunction());
  Thread* thread = Thread::Current();
  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());

  if (implicit_closure_function() != Function::null()) {
    return implicit_closure_function();
  }

  // Create closure function.
  Zone* zone = thread->zone();
  const String& closure_name = String::Handle(zone, name());
  const Function& closure_function = Function::Handle(
      zone, NewImplicitClosureFunction(closure_name, *this, token_pos()));

  // Set closure function's context scope.
#if !defined(DART_PRECOMPILED_RUNTIME)
  if (is_static() || IsConstructor()) {
    closure_function.set_context_scope(Object::empty_context_scope());
  } else {
    const ContextScope& context_scope = ContextScope::Handle(
        zone, LocalScope::CreateImplicitClosureScope(*this));
    closure_function.set_context_scope(context_scope);
  }
#endif

  FunctionType& closure_signature =
      FunctionType::Handle(zone, closure_function.signature());

  const auto& cls = Class::Handle(zone, Owner());

  if (!is_static() && !IsConstructor() &&
      StackTraceUtils::IsPossibleAwaiterLink(cls)) {
    closure_function.set_awaiter_link({0, 0});
  }

  const intptr_t num_type_params =
      IsGenerativeConstructor() ? cls.NumTypeParameters() : NumTypeParameters();

  TypeArguments& instantiator_type_arguments = TypeArguments::Handle(zone);
  TypeArguments& function_type_arguments = TypeArguments::Handle(zone);

  FunctionTypeMapping* function_type_mapping = nullptr;
  FunctionTypeMapping scope(zone, &function_type_mapping,
                            FunctionType::Handle(zone, signature()),
                            closure_signature);

  auto transform_type = [&](AbstractType& type) {
    if (num_type_params > 0) {
      if (IsGenerativeConstructor()) {
        type = type.UpdateFunctionTypes(num_type_params, kAllFree, Heap::kOld,
                                        nullptr);
        if (!type.IsInstantiated(kCurrentClass)) {
          type = type.InstantiateFrom(
              instantiator_type_arguments, function_type_arguments,
              kNoneFree /* avoid truncating parent type args */, Heap::kOld);
        }
      } else {
        type = type.UpdateFunctionTypes(0, kNoneFree, Heap::kOld,
                                        function_type_mapping);
      }
    }
  };

  auto transform_type_args = [&](TypeArguments& type_args) {
    ASSERT(num_type_params > 0);
    if (!type_args.IsNull()) {
      if (IsGenerativeConstructor()) {
        type_args = type_args.UpdateFunctionTypes(num_type_params, kAllFree,
                                                  Heap::kOld, nullptr);
        if (!type_args.IsInstantiated(kCurrentClass)) {
          type_args = type_args.InstantiateFrom(
              instantiator_type_arguments, function_type_arguments,
              kNoneFree /* avoid truncating parent type args */, Heap::kOld);
        }
      } else {
        type_args = type_args.UpdateFunctionTypes(0, kNoneFree, Heap::kOld,
                                                  function_type_mapping);
      }
    }
  };

  // Set closure function's type parameters.
  if (num_type_params > 0) {
    const TypeParameters& old_type_params = TypeParameters::Handle(
        zone,
        IsGenerativeConstructor() ? cls.type_parameters() : type_parameters());
    const TypeParameters& new_type_params =
        TypeParameters::Handle(zone, TypeParameters::New());
    // No need to set names that are ignored in a signature, however, the
    // length of the names array defines the number of type parameters.
    new_type_params.set_names(Array::Handle(zone, old_type_params.names()));
    new_type_params.set_flags(Array::Handle(zone, old_type_params.flags()));

    closure_signature.SetTypeParameters(new_type_params);
    ASSERT(closure_signature.NumTypeParameters() == num_type_params);

    TypeArguments& type_args = TypeArguments::Handle(zone);
    type_args = TypeArguments::New(num_type_params);
    TypeParameter& type_param = TypeParameter::Handle(zone);
    for (intptr_t i = 0; i < num_type_params; i++) {
      type_param = closure_signature.TypeParameterAt(i);
      type_args.SetTypeAt(i, type_param);
    }

    if (IsGenerativeConstructor()) {
      instantiator_type_arguments =
          type_args.ToInstantiatorTypeArguments(thread, cls);
    } else {
      ASSERT(NumTypeArguments() == type_args.Length());
      function_type_arguments = type_args.ptr();
    }

    type_args = old_type_params.bounds();
    transform_type_args(type_args);
    new_type_params.set_bounds(type_args);

    type_args = old_type_params.defaults();
    transform_type_args(type_args);
    new_type_params.set_defaults(type_args);
  }

  // Set closure function's result type.
  AbstractType& result_type = AbstractType::Handle(zone);
  if (IsGenerativeConstructor()) {
    result_type = cls.DeclarationType();
  } else {
    result_type = this->result_type();
  }
  transform_type(result_type);
  closure_signature.set_result_type(result_type);

  // Set closure function's end token to this end token.
  NOT_IN_PRECOMPILED(closure_function.set_end_token_pos(end_token_pos()));

  // The closurized method stub just calls into the original method and should
  // therefore be skipped by the debugger and in stack traces.
  closure_function.set_is_debuggable(false);
  closure_function.set_is_visible(false);

  // Set closure function's formal parameters to this formal parameters,
  // removing the receiver if this is an instance method and adding the closure
  // object as first parameter.
  const int kClosure = 1;
  const int num_implicit_params = NumImplicitParameters();
  const int num_fixed_params =
      kClosure - num_implicit_params + num_fixed_parameters();
  const int num_opt_params = NumOptionalParameters();
  const bool has_opt_pos_params = HasOptionalPositionalParameters();
  const int num_params = num_fixed_params + num_opt_params;
  const int num_pos_params = has_opt_pos_params ? num_params : num_fixed_params;
  closure_signature.set_num_fixed_parameters(num_fixed_params);
  closure_signature.SetNumOptionalParameters(num_opt_params,
                                             has_opt_pos_params);
  closure_signature.set_parameter_types(
      Array::Handle(zone, Array::New(num_params, Heap::kOld)));
  NOT_IN_PRECOMPILED(closure_function.CreateNameArray());
  closure_signature.CreateNameArrayIncludingFlags();
  AbstractType& param_type = AbstractType::Handle(zone);
  String& param_name = String::Handle(zone);
  // Add implicit closure object parameter.
  param_type = Type::DynamicType();
  closure_signature.SetParameterTypeAt(0, param_type);
  NOT_IN_PRECOMPILED(
      closure_function.SetParameterNameAt(0, Symbols::ClosureParameter()));
  for (int i = kClosure; i < num_pos_params; i++) {
    param_type = ParameterTypeAt(num_implicit_params - kClosure + i);
    transform_type(param_type);
    closure_signature.SetParameterTypeAt(i, param_type);
    param_name = ParameterNameAt(num_implicit_params - kClosure + i);
    // Set the name in the function for positional parameters.
    NOT_IN_PRECOMPILED(closure_function.SetParameterNameAt(i, param_name));
  }
  for (int i = num_pos_params; i < num_params; i++) {
    param_type = ParameterTypeAt(num_implicit_params - kClosure + i);
    transform_type(param_type);
    closure_signature.SetParameterTypeAt(i, param_type);
    param_name = ParameterNameAt(num_implicit_params - kClosure + i);
    // Set the name in the signature for named parameters.
    closure_signature.SetParameterNameAt(i, param_name);
    if (IsRequiredAt(num_implicit_params - kClosure + i)) {
      closure_signature.SetIsRequiredAt(i);
    }
  }
  closure_signature.FinalizeNameArray();
  closure_function.InheritKernelOffsetFrom(*this);

  if (!is_static() && !IsConstructor()) {
    // Change covariant parameter types to Object?.
    BitVector is_covariant(zone, NumParameters());
    BitVector is_generic_covariant_impl(zone, NumParameters());
    ReadParameterCovariance(&is_covariant, &is_generic_covariant_impl);

    ObjectStore* object_store = IsolateGroup::Current()->object_store();
    const auto& object_type =
        Type::Handle(zone, object_store->nullable_object_type());
    ASSERT(object_type.IsCanonical());
    for (intptr_t i = kClosure; i < num_params; ++i) {
      const intptr_t original_param_index = num_implicit_params - kClosure + i;
      if (is_covariant.Contains(original_param_index) ||
          is_generic_covariant_impl.Contains(original_param_index)) {
        closure_signature.SetParameterTypeAt(i, object_type);
      }
    }
  }

#if defined(DART_DYNAMIC_MODULES)
#if defined(DART_PRECOMPILED_RUNTIME)
  const bool attach_bytecode = true;
#else
  const bool attach_bytecode = is_declared_in_bytecode();
#endif
  if (attach_bytecode) {
    if (is_static()) {
      closure_function.AttachBytecode(
          Object::implicit_static_closure_bytecode());
    } else if (IsConstructor()) {
      closure_function.AttachBytecode(
          Object::implicit_constructor_closure_bytecode());
    } else {
      closure_function.AttachBytecode(
          Object::implicit_instance_closure_bytecode());
    }
  }
#endif

  ASSERT(!closure_signature.IsFinalized());
  closure_signature ^= ClassFinalizer::FinalizeType(closure_signature);
  closure_function.SetSignature(closure_signature);
  set_implicit_closure_function(closure_function);
  ASSERT(closure_function.IsImplicitClosureFunction());
  ASSERT(HasImplicitClosureFunction());
  return closure_function.ptr();
#endif  // defined(DART_PRECOMPILED_RUNTIME)
}

void Function::DropUncompiledImplicitClosureFunction() const {
  if (implicit_closure_function() != Function::null()) {
    const Function& func = Function::Handle(implicit_closure_function());
    if (!func.HasCode()) {
      set_implicit_closure_function(Function::Handle());
    }
  }
}

StringPtr Function::InternalSignature() const {
#if defined(DART_PRECOMPILED_RUNTIME)
  if (signature() == FunctionType::null()) {
    return String::null();
  }
#endif
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  const FunctionType& sig = FunctionType::Handle(signature());
  sig.Print(kInternalName, &printer);
  return Symbols::New(thread, printer.buffer());
}

StringPtr Function::UserVisibleSignature() const {
#if defined(DART_PRECOMPILED_RUNTIME)
  if (signature() == FunctionType::null()) {
    return String::null();
  }
#endif
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  const FunctionType& sig = FunctionType::Handle(signature());
  sig.Print(kUserVisibleName, &printer);
  return Symbols::New(thread, printer.buffer());
}

void FunctionType::PrintParameters(Thread* thread,
                                   Zone* zone,
                                   NameVisibility name_visibility,
                                   BaseTextBuffer* printer) const {
  AbstractType& param_type = AbstractType::Handle(zone);
  const intptr_t num_params = NumParameters();
  const intptr_t num_fixed_params = num_fixed_parameters();
  const intptr_t num_opt_pos_params = NumOptionalPositionalParameters();
  const intptr_t num_opt_named_params = NumOptionalNamedParameters();
  const intptr_t num_opt_params = num_opt_pos_params + num_opt_named_params;
  ASSERT((num_fixed_params + num_opt_params) == num_params);
  intptr_t i = 0;
  if (name_visibility == kUserVisibleName) {
    // Hide implicit parameters.
    i = num_implicit_parameters();
  }
  String& name = String::Handle(zone);
  while (i < num_fixed_params) {
    param_type = ParameterTypeAt(i);
    ASSERT(!param_type.IsNull());
    param_type.PrintName(name_visibility, printer);
    if (i != (num_params - 1)) {
      printer->AddString(", ");
    }
    i++;
  }
  if (num_opt_params > 0) {
    if (num_opt_pos_params > 0) {
      printer->AddString("[");
    } else {
      printer->AddString("{");
    }
    for (intptr_t i = num_fixed_params; i < num_params; i++) {
      if (num_opt_named_params > 0 && IsRequiredAt(i)) {
        printer->AddString("required ");
      }
      param_type = ParameterTypeAt(i);
      ASSERT(!param_type.IsNull());
      param_type.PrintName(name_visibility, printer);
      // The parameter name of an optional positional parameter does not need
      // to be part of the signature, since it is not used.
      if (num_opt_named_params > 0) {
        name = ParameterNameAt(i);
        printer->AddString(" ");
        printer->AddString(name.ToCString());
      }
      if (i != (num_params - 1)) {
        printer->AddString(", ");
      }
    }
    if (num_opt_pos_params > 0) {
      printer->AddString("]");
    } else {
      printer->AddString("}");
    }
  }
}

ClosurePtr Function::ImplicitStaticClosure() const {
  ASSERT(IsImplicitStaticClosureFunction());
  if (implicit_static_closure() != Closure::null()) {
    return implicit_static_closure();
  }

  auto thread = Thread::Current();
  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());

  if (implicit_static_closure() != Closure::null()) {
    return implicit_static_closure();
  }

  Zone* zone = thread->zone();
  const auto& closure =
      Closure::Handle(zone, Closure::New(Object::null_type_arguments(),
                                         Object::null_type_arguments(), *this,
                                         Object::null_object(), Heap::kOld));
  set_implicit_static_closure(closure);
  return implicit_static_closure();
}

ClosurePtr Function::ImplicitInstanceClosure(const Instance& receiver) const {
  ASSERT(IsImplicitClosureFunction());
  Zone* zone = Thread::Current()->zone();
  TypeArguments& instantiator_type_arguments = TypeArguments::Handle(zone);
  if (!HasInstantiatedSignature(kCurrentClass)) {
    instantiator_type_arguments = receiver.GetTypeArguments();
  }
  ASSERT(!HasGenericParent());  // No generic parent function.
  return Closure::New(instantiator_type_arguments,
                      Object::null_type_arguments(), *this, receiver);
}

FunctionPtr Function::ImplicitClosureTarget(Zone* zone) const {
  const auto& parent = Function::Handle(zone, parent_function());
  const auto& func_name = String::Handle(zone, parent.name());
  const auto& owner = Class::Handle(zone, parent.Owner());
  Thread* thread = Thread::Current();
  const auto& error = owner.EnsureIsFinalized(thread);
  ASSERT(error == Error::null());
  auto& target =
      Function::Handle(zone, Resolver::ResolveFunction(zone, owner, func_name));

  if (!target.IsNull() && (target.ptr() != parent.ptr())) {
    DEBUG_ASSERT(IsolateGroup::Current()->HasAttemptedReload());
    if ((target.is_static() != parent.is_static()) ||
        (target.kind() != parent.kind())) {
      target = Function::null();
    }
  }

  return target.ptr();
}

void FunctionType::Print(NameVisibility name_visibility,
                         BaseTextBuffer* printer) const {
  if (IsNull()) {
    printer->AddString("null");  // Signature optimized out in precompiler.
    return;
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const TypeParameters& type_params =
      TypeParameters::Handle(zone, type_parameters());
  if (!type_params.IsNull()) {
    printer->AddString("<");
    const intptr_t base = NumParentTypeArguments();
    const bool kIsClassTypeParameter = false;
    // Type parameter names are meaningless after canonicalization.
    type_params.Print(thread, zone, kIsClassTypeParameter, base,
                      name_visibility, printer);
    printer->AddString(">");
  }
  printer->AddString("(");
  PrintParameters(thread, zone, name_visibility, printer);
  printer->AddString(") => ");
  const AbstractType& res_type = AbstractType::Handle(zone, result_type());
  if (!res_type.IsNull()) {
    res_type.PrintName(name_visibility, printer);
  } else {
    printer->AddString("null");
  }
}

bool Function::HasInstantiatedSignature(
    Genericity genericity,
    intptr_t num_free_fun_type_params) const {
  return FunctionType::Handle(signature())
      .IsInstantiated(genericity, num_free_fun_type_params);
}

bool FunctionType::IsInstantiated(Genericity genericity,
                                  intptr_t num_free_fun_type_params) const {
  if (num_free_fun_type_params == kCurrentAndEnclosingFree) {
    num_free_fun_type_params = kAllFree;
  } else if (genericity != kCurrentClass) {
    const intptr_t num_parent_type_args = NumParentTypeArguments();
    if (num_parent_type_args > 0 && num_free_fun_type_params > 0) {
      // The number of parent type arguments is cached in the FunctionType, so
      // we can't consider any FunctionType with free parent type arguments as
      // fully instantiated. Instead, the FunctionType must be instantiated to
      // reduce the number of parent type arguments, even if they're unused in
      // its component types.
      return false;
    }
    // Don't consider local function type parameters as free.
    if (num_free_fun_type_params > num_parent_type_args) {
      num_free_fun_type_params = num_parent_type_args;
    }
  }
  AbstractType& type = AbstractType::Handle(result_type());
  if (!type.IsInstantiated(genericity, num_free_fun_type_params)) {
    return false;
  }
  const intptr_t num_parameters = NumParameters();
  for (intptr_t i = 0; i < num_parameters; i++) {
    type = ParameterTypeAt(i);
    if (!type.IsInstantiated(genericity, num_free_fun_type_params)) {
      return false;
    }
  }
  const intptr_t num_type_params = NumTypeParameters();
  if (num_type_params > 0) {
    TypeParameters& type_params = TypeParameters::Handle(type_parameters());
    if (!type_params.AllDynamicBounds()) {
      for (intptr_t i = 0; i < type_params.Length(); ++i) {
        type = type_params.BoundAt(i);
        if (!type.IsInstantiated(genericity, num_free_fun_type_params)) {
          return false;
        }
      }
    }
  }
  return true;
}

bool Function::IsPrivate() const {
  return Library::IsPrivate(String::Handle(name()));
}

ClassPtr Function::Owner(FunctionPtr function) {
  ObjectPtr owner = function->untag()->owner();
  ASSERT(owner != Object::null());
  if (owner->IsClass()) {
    return Class::RawCast(owner);
  }
  ASSERT(owner->IsPatchClass());
  return PatchClass::RawCast(owner)->untag()->wrapped_class();
}

#if defined(DART_DYNAMIC_MODULES)
bool Function::is_declared_in_bytecode() const {
  return Class::Handle(Owner()).is_declared_in_bytecode();
}
#endif

void Function::InheritKernelOffsetFrom(const Function& src) const {
#if defined(DART_PRECOMPILED_RUNTIME)
#if !defined(DART_DYNAMIC_MODULES)
  UNREACHABLE();
#endif
#else
  StoreNonPointer(&untag()->kernel_offset_, src.untag()->kernel_offset_);
#endif
}

void Function::InheritKernelOffsetFrom(const Field& src) const {
#if defined(DART_PRECOMPILED_RUNTIME)
#if !defined(DART_DYNAMIC_MODULES)
  UNREACHABLE();
#endif
#else
  set_kernel_offset(src.kernel_offset());
#endif
}

void Function::SetKernelLibraryAndEvalScript(
    const Script& script,
    const class KernelProgramInfo& kernel_program_info,
    intptr_t index) const {
  Array& data_field = Array::Handle(
      Array::New(static_cast<intptr_t>(EvalFunctionData::kLength)));
  data_field.SetAt(static_cast<intptr_t>(EvalFunctionData::kScript), script);
  data_field.SetAt(static_cast<intptr_t>(EvalFunctionData::kKernelProgramInfo),
                   kernel_program_info);
  data_field.SetAt(static_cast<intptr_t>(EvalFunctionData::kKernelLibraryIndex),
                   Smi::Handle(Smi::New(index)));
  set_data(data_field);
}

ScriptPtr Function::script() const {
  // NOTE(turnidge): If you update this function, you probably want to
  // update Class::PatchFieldsAndFunctions() at the same time.
  if (IsDynamicInvocationForwarder()) {
    const Function& target = Function::Handle(ForwardingTarget());
    return target.IsNull() ? Script::null() : target.script();
  }
  if (IsImplicitGetterOrSetter()) {
    const auto& field = Field::Handle(accessor_field());
    return field.IsNull() ? Script::null() : field.Script();
  }
  if (is_eval_function()) {
    const auto& fdata = Array::Handle(Array::RawCast(data()));
    return Script::RawCast(
        fdata.At(static_cast<intptr_t>(EvalFunctionData::kScript)));
  }
  const Object& obj = Object::Handle(untag()->owner());
  if (obj.IsPatchClass()) {
    return PatchClass::Cast(obj).script();
  }
  if (IsClosureFunction()) {
    const Function& function = Function::Handle(parent_function());
    if (function.IsNull()) return Script::null();
    return function.script();
  }
  ASSERT(obj.IsClass());
  return Class::Cast(obj).script();
}

#if !defined(DART_PRECOMPILED_RUNTIME)
KernelProgramInfoPtr Function::KernelProgramInfo() const {
  if (is_eval_function()) {
    const auto& fdata = Array::Handle(Array::RawCast(data()));
    return KernelProgramInfo::RawCast(
        fdata.At(static_cast<intptr_t>(EvalFunctionData::kKernelProgramInfo)));
  }
  if (IsClosureFunction()) {
    const auto& parent = Function::Handle(parent_function());
    return parent.KernelProgramInfo();
  }
  const auto& owner = Object::Handle(RawOwner());
  if (owner.IsClass()) {
    return Class::Cast(owner).KernelProgramInfo();
  }
  return PatchClass::Cast(owner).kernel_program_info();
}

TypedDataViewPtr Function::KernelLibrary() const {
  const intptr_t kernel_library_index = KernelLibraryIndex();
  if (kernel_library_index == -1) return TypedDataView::null();
  const auto& info = KernelProgramInfo::Handle(KernelProgramInfo());
  return info.KernelLibrary(kernel_library_index);
}

intptr_t Function::KernelLibraryOffset() const {
  const intptr_t kernel_library_index = KernelLibraryIndex();
  if (kernel_library_index == -1) return 0;
  const auto& info = KernelProgramInfo::Handle(KernelProgramInfo());
  return info.KernelLibraryStartOffset(kernel_library_index);
}

intptr_t Function::KernelLibraryIndex() const {
  ASSERT(!is_declared_in_bytecode());
  if (IsNoSuchMethodDispatcher() || IsInvokeFieldDispatcher() ||
      IsFfiCallbackTrampoline()) {
    return -1;
  }
  if (is_eval_function()) {
    const auto& fdata = Array::Handle(Array::RawCast(data()));
    return Smi::Value(static_cast<SmiPtr>(fdata.At(
        static_cast<intptr_t>(EvalFunctionData::kKernelLibraryIndex))));
  }
  if (IsClosureFunction()) {
    const auto& parent = Function::Handle(parent_function());
    ASSERT(!parent.IsNull());
    return parent.KernelLibraryIndex();
  }

  const auto& obj = Object::Handle(untag()->owner());
  if (obj.IsClass()) {
    const auto& lib = Library::Handle(Class::Cast(obj).library());
    return lib.kernel_library_index();
  }
  ASSERT(obj.IsPatchClass());
  return PatchClass::Cast(obj).kernel_library_index();
}
#endif

bool Function::HasOptimizedCode() const {
  return HasCode() && Code::Handle(CurrentCode()).is_optimized();
}

const char* Function::NameCString(NameVisibility name_visibility) const {
  switch (name_visibility) {
    case kInternalName:
      return String::Handle(name()).ToCString();
    case kScrubbedName:
    case kUserVisibleName:
      return UserVisibleNameCString();
  }
  UNREACHABLE();
  return nullptr;
}

const char* Function::UserVisibleNameCString() const {
  if (FLAG_show_internal_names) {
    return String::Handle(name()).ToCString();
  }
  return String::ScrubName(String::Handle(name()),
                           is_extension_member() || is_extension_type_member());
}

StringPtr Function::UserVisibleName() const {
  if (FLAG_show_internal_names) {
    return name();
  }
  return Symbols::New(
      Thread::Current(),
      String::ScrubName(String::Handle(name()),
                        is_extension_member() || is_extension_type_member()));
}

StringPtr Function::QualifiedScrubbedName() const {
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  PrintName(NameFormattingParams(kScrubbedName), &printer);
  return Symbols::New(thread, printer.buffer());
}

const char* Function::QualifiedScrubbedNameCString() const {
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  PrintName(NameFormattingParams(kScrubbedName), &printer);
  return printer.buffer();
}

StringPtr Function::QualifiedUserVisibleName() const {
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  PrintName(NameFormattingParams(kUserVisibleName), &printer);
  return Symbols::New(thread, printer.buffer());
}

const char* Function::QualifiedUserVisibleNameCString() const {
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  PrintName(NameFormattingParams(kUserVisibleName), &printer);
  return printer.buffer();
}

static void FunctionPrintNameHelper(const Function& fun,
                                    const NameFormattingParams& params,
                                    BaseTextBuffer* printer) {
  if (fun.IsNonImplicitClosureFunction()) {
    if (params.include_parent_name) {
      const auto& parent = Function::Handle(fun.parent_function());
      if (parent.IsNull()) {
        printer->AddString(Symbols::OptimizedOut().ToCString());
      } else {
        parent.PrintName(params, printer);
      }
      // A function's scrubbed name and its user visible name are identical.
      printer->AddString(".");
    }
    if (params.disambiguate_names &&
        fun.name() == Symbols::AnonymousClosure().ptr()) {
      if (fun.token_pos().IsReal()) {
        printer->Printf("<anonymous closure @%" Pd ">", fun.token_pos().Pos());
      } else {
        printer->Printf("<anonymous closure @no position>");
      }
    } else {
      printer->AddString(fun.NameCString(params.name_visibility));
      if (params.disambiguate_names) {
        if (fun.token_pos().IsReal()) {
          printer->Printf("@<%" Pd ">", fun.token_pos().Pos());
        } else {
          printer->Printf("@<no position>");
        }
      }
    }
    return;
  }
  if (params.disambiguate_names) {
    if (fun.IsInvokeFieldDispatcher()) {
      printer->AddString("[invoke-field] ");
    }
    if (fun.IsNoSuchMethodDispatcher()) {
      printer->AddString("[no-such-method] ");
    }
    if (fun.IsImplicitClosureFunction()) {
      printer->AddString("[tear-off] ");
    }
    if (fun.IsMethodExtractor()) {
      printer->AddString("[tear-off-extractor] ");
    }
  }

  if (fun.kind() == UntaggedFunction::kConstructor) {
    printer->AddString("new ");
  } else if (params.include_class_name) {
    const Class& cls = Class::Handle(fun.Owner());
    if (!cls.IsTopLevel()) {
      const Class& mixin = Class::Handle(cls.Mixin());
      printer->AddString(params.name_visibility == Object::kUserVisibleName
                             ? mixin.UserVisibleNameCString()
                             : cls.NameCString(params.name_visibility));
      printer->AddString(".");
    }
  }

  printer->AddString(fun.NameCString(params.name_visibility));

  // Dispatchers that are created with an arguments descriptor need both the
  // name and the saved arguments descriptor to disambiguate.
  if (params.disambiguate_names && fun.HasSavedArgumentsDescriptor()) {
    const auto& args_desc_array = Array::Handle(fun.saved_args_desc());
    const ArgumentsDescriptor args_desc(args_desc_array);
    args_desc.PrintTo(printer);
  }
}

void Function::PrintName(const NameFormattingParams& params,
                         BaseTextBuffer* printer) const {
  if (!IsLocalFunction()) {
    FunctionPrintNameHelper(*this, params, printer);
    return;
  }
  auto& fun = Function::Handle(ptr());
  FunctionPrintNameHelper(fun, params, printer);
}

bool Function::NamePassesFilter(const char* name_filter) const {
  if (name_filter == nullptr) return true;

  char* save_ptr;  // Needed for strtok_r.
  const char* scrubbed_name = QualifiedScrubbedNameCString();
  const char* function_name = ToFullyQualifiedCString();
  intptr_t function_name_len = strlen(function_name);

  intptr_t len = strlen(name_filter) + 1;  // Length with \0.
  char* filter_buffer = new char[len];
  strncpy(filter_buffer, name_filter, len);  // strtok modifies arg 1.
  char* token = strtok_r(filter_buffer, ",", &save_ptr);
  bool found = false;
  while (token != nullptr) {
    if ((strstr(function_name, token) != nullptr) ||
        (strstr(scrubbed_name, token) != nullptr)) {
      found = true;
      break;
    }
    const intptr_t token_len = strlen(token);
    if (token[token_len - 1] == '%') {
      if (function_name_len > token_len) {
        const char* suffix =
            function_name + (function_name_len - token_len + 1);
        if (strncmp(suffix, token, token_len - 1) == 0) {
          found = true;
          break;
        }
      }
    }
    token = strtok_r(nullptr, ",", &save_ptr);
  }
  delete[] filter_buffer;

  return found;
}

StringPtr Function::GetSource() const {
  if (IsImplicitConstructor() || is_synthetic()) {
    // We may need to handle more cases when the restrictions on mixins are
    // relaxed. In particular we might start associating some source with the
    // forwarding constructors when it becomes possible to specify a particular
    // constructor from the mixin to use.
    return String::null();
  }
  Zone* zone = Thread::Current()->zone();
  const Script& func_script = Script::Handle(zone, script());

  intptr_t from_line, from_col;
  if (!func_script.GetTokenLocation(token_pos(), &from_line, &from_col)) {
    return String::null();
  }
  intptr_t to_line, to_col;
  if (!func_script.GetTokenLocation(end_token_pos(), &to_line, &to_col)) {
    return String::null();
  }
  intptr_t to_length = func_script.GetTokenLength(end_token_pos());
  if (to_length < 0) {
    return String::null();
  }

  if (to_length == 1) {
    // Handle special cases for end tokens of closures (where we exclude the
    // last token):
    // (1) "foo(() => null, bar);": End token is `,', but we don't print it.
    // (2) "foo(() => null);": End token is ')`, but we don't print it.
    // (3) "var foo = () => null;": End token is `;', but in this case the
    // token semicolon belongs to the assignment so we skip it.
    const String& src = String::Handle(func_script.Source());
    if (src.IsNull() || src.Length() == 0) {
      return Symbols::OptimizedOut().ptr();
    }
    uint16_t end_char = src.CharAt(end_token_pos().Pos());
    if ((end_char == ',') ||  // Case 1.
        (end_char == ')') ||  // Case 2.
        (end_char == ';' && String::Handle(zone, name())
                                .Equals("<anonymous closure>"))) {  // Case 3.
      to_length = 0;
    }
  }

  return func_script.GetSnippet(from_line, from_col, to_line,
                                to_col + to_length);
}

// Construct fingerprint from token stream. The token stream contains also
// arguments.
int32_t Function::SourceFingerprint() const {
#if !defined(DART_PRECOMPILED_RUNTIME)
  if (is_declared_in_bytecode()) {
    return 0;
  }
  return kernel::KernelSourceFingerprintHelper::CalculateFunctionFingerprint(
      *this);
#else
  return 0;
#endif  // !defined(DART_PRECOMPILED_RUNTIME)
}

void Function::SaveICDataMap(
    const ZoneGrowableArray<const ICData*>& deopt_id_to_ic_data,
    const Array& edge_counters_array,
    const TypedData& coverage_array) const {
#if !defined(DART_PRECOMPILED_RUNTIME)
  // Already installed nothing to do.
  if (ic_data_array() != Array::null()) {
    ASSERT(coverage_array.ptr() == GetCoverageArray());
    return;
  }

  // Compute number of ICData objects to save.
  intptr_t count = 0;
  for (intptr_t i = 0; i < deopt_id_to_ic_data.length(); i++) {
    if (deopt_id_to_ic_data[i] != nullptr) {
      count++;
    }
  }

  // Compress sparse deopt_id_to_ic_data mapping into a linear sequence of
  // ICData objects.
  const Array& array = Array::Handle(
      Array::New(ICDataArrayIndices::kFirstICData + count, Heap::kOld));
  for (intptr_t i = 0, pos = ICDataArrayIndices::kFirstICData;
       i < deopt_id_to_ic_data.length(); i++) {
    if (deopt_id_to_ic_data[i] != nullptr) {
      ASSERT(i == deopt_id_to_ic_data[i]->deopt_id());
      array.SetAt(pos++, *deopt_id_to_ic_data[i]);
    }
  }
  array.SetAt(ICDataArrayIndices::kEdgeCounters, edge_counters_array);
  // Preserve coverage_array which is stored early after graph construction.
  array.SetAt(ICDataArrayIndices::kCoverageData, coverage_array);
  set_ic_data_array(array);
#else   // DART_PRECOMPILED_RUNTIME
  UNREACHABLE();
#endif  // DART_PRECOMPILED_RUNTIME
}

void Function::RestoreICDataMap(
    ZoneGrowableArray<const ICData*>* deopt_id_to_ic_data,
    bool clone_ic_data) const {
#if !defined(DART_PRECOMPILED_RUNTIME)
  if (FLAG_force_clone_compiler_objects) {
    clone_ic_data = true;
  }
  ASSERT(deopt_id_to_ic_data->is_empty());
  Zone* zone = Thread::Current()->zone();
  const Array& saved_ic_data = Array::Handle(zone, ic_data_array());
  if (saved_ic_data.IsNull()) {
    // Could happen with not-yet compiled unoptimized code or force-optimized
    // functions.
    return;
  }
  const intptr_t saved_length = saved_ic_data.Length();
  ASSERT(saved_length > 0);
  if (saved_length > ICDataArrayIndices::kFirstICData) {
    const intptr_t restored_length =
        ICData::Cast(Object::Handle(zone, saved_ic_data.At(saved_length - 1)))
            .deopt_id() +
        1;
    deopt_id_to_ic_data->SetLength(restored_length);
    for (intptr_t i = 0; i < restored_length; i++) {
      (*deopt_id_to_ic_data)[i] = nullptr;
    }
    for (intptr_t i = ICDataArrayIndices::kFirstICData; i < saved_length; i++) {
      ICData& ic_data = ICData::ZoneHandle(zone);
      ic_data ^= saved_ic_data.At(i);
      if (clone_ic_data) {
        const ICData& original_ic_data = ICData::Handle(zone, ic_data.ptr());
        ic_data = ICData::Clone(ic_data);
        ic_data.SetOriginal(original_ic_data);
      }
      ASSERT(deopt_id_to_ic_data->At(ic_data.deopt_id()) == nullptr);
      (*deopt_id_to_ic_data)[ic_data.deopt_id()] = &ic_data;
    }
  }
#else   // DART_PRECOMPILED_RUNTIME
  UNREACHABLE();
#endif  // DART_PRECOMPILED_RUNTIME
}

TypedDataPtr Function::GetCoverageArray() const {
#if defined(DART_DYNAMIC_MODULES)
  if (HasBytecode()) {
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
    const auto& bytecode = Bytecode::Handle(GetBytecode());
    if (bytecode.HasRecordedCoverage()) {
      return bytecode.EnsureCoverageArray(Thread::Current());
    }
#endif
    return TypedData::null();
  }
#endif
  const Array& arr = Array::Handle(ic_data_array());
  if (arr.IsNull()) {
    return TypedData::null();
  }
  return TypedData::RawCast(arr.At(ICDataArrayIndices::kCoverageData));
}

void Function::set_ic_data_array(const Array& value) const {
#if defined(DART_DYNAMIC_MODULES)
  ASSERT(!HasBytecode());
#endif
  untag()->set_ic_data_array_or_bytecode<std::memory_order_release>(
      value.ptr());
}

ArrayPtr Function::ic_data_array() const {
  ObjectPtr value =
      untag()->ic_data_array_or_bytecode<std::memory_order_acquire>();
#if defined(DART_DYNAMIC_MODULES)
  if (value->IsBytecode()) {
    return Array::null();
  }
#endif
  return Array::RawCast(value);
}

void Function::ClearICDataArray() const {
  set_ic_data_array(Array::null_array());
}

ICDataPtr Function::FindICData(intptr_t deopt_id) const {
  const Array& array = Array::Handle(ic_data_array());
  ICData& ic_data = ICData::Handle();
  for (intptr_t i = ICDataArrayIndices::kFirstICData; i < array.Length(); i++) {
    ic_data ^= array.At(i);
    if (ic_data.deopt_id() == deopt_id) {
      return ic_data.ptr();
    }
  }
  return ICData::null();
}

void Function::SetDeoptReasonForAll(intptr_t deopt_id,
                                    ICData::DeoptReasonId reason) {
  const Array& array = Array::Handle(ic_data_array());
  ICData& ic_data = ICData::Handle();
  for (intptr_t i = ICDataArrayIndices::kFirstICData; i < array.Length(); i++) {
    ic_data ^= array.At(i);
    if (ic_data.deopt_id() == deopt_id) {
      ic_data.AddDeoptReason(reason);
    }
  }
}

bool Function::CheckSourceFingerprint(int32_t fp, const char* kind) const {
#if defined(DEBUG)
#if !defined(DART_PRECOMPILED_RUNTIME)
  // Check that the function is marked as recognized via the vm:recognized
  // pragma. This is so that optimizations that change the signature will know
  // not to touch it.
  if (kind != nullptr && !MethodRecognizer::IsMarkedAsRecognized(*this, kind)) {
    OS::PrintErr(
        "Recognized method %s should be marked with: "
        "@pragma(\"vm:recognized\", \"%s\")\n",
        ToQualifiedCString(), kind);
    return false;
  }
#endif

  if (IsolateGroup::Current()->obfuscate() || FLAG_precompiled_mode) {
    return true;  // The kernel structure has been altered, skip checking.
  }

  ASSERT(!is_declared_in_bytecode());

  if (SourceFingerprint() != fp) {
    // This output can be copied into a file, then used with sed
    // to replace the old values.
    // sed -i.bak -f /tmp/newkeys \
    //    runtime/vm/compiler/recognized_methods_list.h
    THR_Print("s/0x%08x/0x%08x/\n", fp, SourceFingerprint());
    return false;
  }
#endif  // defined(DEBUG)
  return true;
}

CodePtr Function::EnsureHasCode() const {
  if (HasCode()) {
    return CurrentCode();
  }
  Thread* thread = Thread::Current();
  ASSERT(thread->IsDartMutatorThread());
  DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame());
  Zone* zone = thread->zone();
  const Object& result = Object::Handle(zone, EnsureHasCodeNoThrow());
  if (result.IsError()) {
    if (result.ptr() == Object::out_of_memory_error().ptr()) {
      Exceptions::ThrowOOM();
      UNREACHABLE();
    }
    if (result.IsLanguageError()) {
      Exceptions::ThrowCompileTimeError(LanguageError::Cast(result));
      UNREACHABLE();
    }
    Exceptions::PropagateError(Error::Cast(result));
    UNREACHABLE();
  } else {
    return Code::Cast(result).ptr();
  }
}

ObjectPtr Function::EnsureHasCodeNoThrow() const {
  if (HasCode()) {
    return CurrentCode();
  }
  Thread* thread = Thread::Current();
  ASSERT(thread->IsDartMutatorThread());
  Zone* zone = thread->zone();
  const Object& result =
      Object::Handle(zone, Compiler::CompileFunction(thread, *this));
  if (result.IsError()) {
    return result.ptr();
  }
  // Compiling in unoptimized mode should never fail if there are no errors.
  RELEASE_ASSERT(HasCode());
  ASSERT(ForceOptimize() || unoptimized_code() == result.ptr());
  return CurrentCode();
}

bool Function::NeedsMonomorphicCheckedEntry(Zone* zone) const {
#if !defined(DART_PRECOMPILED_RUNTIME)
  if (!IsDynamicFunction()) {
    return false;
  }

  // For functions which need an args descriptor the switchable call sites will
  // transition directly to calling via a stub (and therefore never call the
  // monomorphic entry).
  //
  // See runtime_entry.cc:DEFINE_RUNTIME_ENTRY(UnlinkedCall)
  if (PrologueNeedsArgumentsDescriptor()) {
    return false;
  }

  // All dyn:* forwarders are called via SwitchableCalls and all except the ones
  // with `PrologueNeedsArgumentsDescriptor()` transition into monomorphic
  // state.
  if (Function::IsDynamicInvocationForwarderName(name())) {
    return true;
  }

  // AOT mode uses table dispatch.
  // In JIT mode all instance calls use switchable calls.
  if (!FLAG_precompiled_mode) {
    return true;
  }

  // Any method from the class with a dynamically loaded subtype
  // can be called via switchable call (when cid range check fails
  // during conditional table dispatch).
  if (Class::Handle(zone, Owner()).has_dynamically_extendable_subtypes()) {
    return true;
  }

  // Only if there are dynamic callers and if we didn't create a dyn:* forwarder
  // for it do we need the monomorphic checked entry.
  return HasDynamicCallers(zone) && !NeedsDynamicInvocationForwarder();
#else
  UNREACHABLE();
  return true;
#endif
}

bool Function::HasDynamicCallers(Zone* zone) const {
#if !defined(DART_PRECOMPILED_RUNTIME)
  // Use the results of TFA to determine whether this function is ever
  // called dynamically, i.e. using switchable calls.
  kernel::ProcedureAttributesMetadata metadata;
  metadata = kernel::ProcedureAttributesOf(*this, zone);
  if (IsGetterFunction() || IsImplicitGetterFunction() || IsMethodExtractor()) {
    // Dynamic method call through field/getter involves dynamic call of
    // the field/getter.
    return metadata.getter_called_dynamically ||
           metadata.method_or_setter_called_dynamically;
  } else {
    return metadata.method_or_setter_called_dynamically;
  }
#else
  UNREACHABLE();
  return true;
#endif
}

bool Function::PrologueNeedsArgumentsDescriptor() const {
#if defined(DART_DYNAMIC_MODULES)
  // Entering interpreter needs arguments descriptor.
  if (is_declared_in_bytecode()) {
    return true;
  }
#endif
  // These functions have a saved compile-time arguments descriptor that is
  // used in lieu of the runtime arguments descriptor in generated IL.
  if (HasSavedArgumentsDescriptor()) {
    return false;
  }
  // The prologue of those functions need to examine the arg descriptor for
  // various purposes.
  return IsGeneric() || HasOptionalParameters();
}

bool Function::MayHaveUncheckedEntryPoint() const {
  return FLAG_enable_multiple_entrypoints &&
         (NeedsTypeArgumentTypeChecks() || NeedsArgumentTypeChecks());
}

intptr_t Function::SourceSize() const {
  const TokenPosition& start = token_pos();
  const TokenPosition& end = end_token_pos();
  if (!end.IsReal() || start.IsNoSource() || start.IsClassifying()) {
    // No source information, so just return 0.
    return 0;
  }
  if (start.IsSynthetic()) {
    // Try and approximate the source size using the parent's source size.
    const auto& parent = Function::Handle(parent_function());
    ASSERT(!parent.IsNull());
    const intptr_t parent_size = parent.SourceSize();
    if (parent_size == 0) {
      return parent_size;
    }
    // Parent must have a real ending position.
    return parent_size - (parent.end_token_pos().Pos() - end.Pos());
  }
  return end.Pos() - start.Pos();
}

const char* Function::ToCString() const {
  if (IsNull()) {
    return "Function: null";
  }
  Zone* zone = Thread::Current()->zone();
  ZoneTextBuffer buffer(zone);
  buffer.Printf("Function '%s':", String::Handle(zone, name()).ToCString());
  if (is_static()) {
    buffer.AddString(" static");
  }
  if (is_abstract()) {
    buffer.AddString(" abstract");
  }
  switch (kind()) {
    case UntaggedFunction::kRegularFunction:
    case UntaggedFunction::kClosureFunction:
    case UntaggedFunction::kImplicitClosureFunction:
    case UntaggedFunction::kGetterFunction:
    case UntaggedFunction::kSetterFunction:
      break;
    case UntaggedFunction::kConstructor:
      buffer.AddString(is_static() ? " factory" : " constructor");
      break;
    case UntaggedFunction::kImplicitGetter:
      buffer.AddString(" getter");
      break;
    case UntaggedFunction::kImplicitSetter:
      buffer.AddString(" setter");
      break;
    case UntaggedFunction::kImplicitStaticGetter:
      buffer.AddString(" static-getter");
      break;
    case UntaggedFunction::kFieldInitializer:
      buffer.AddString(" field-initializer");
      break;
    case UntaggedFunction::kMethodExtractor:
      buffer.AddString(" method-extractor");
      break;
    case UntaggedFunction::kNoSuchMethodDispatcher:
      buffer.AddString(" no-such-method-dispatcher");
      break;
    case UntaggedFunction::kDynamicInvocationForwarder:
      buffer.AddString(" dynamic-invocation-forwarder");
      break;
    case UntaggedFunction::kInvokeFieldDispatcher:
      buffer.AddString(" invoke-field-dispatcher");
      break;
    case UntaggedFunction::kIrregexpFunction:
      buffer.AddString(" irregexp-function");
      break;
    case UntaggedFunction::kFfiTrampoline:
      buffer.AddString(" ffi-trampoline-function");
      break;
    case UntaggedFunction::kRecordFieldGetter:
      buffer.AddString(" record-field-getter");
      break;
    default:
      UNREACHABLE();
  }
  if (HasSavedArgumentsDescriptor()) {
    const auto& args_desc_array = Array::Handle(zone, saved_args_desc());
    const ArgumentsDescriptor args_desc(args_desc_array);
    buffer.AddChar('[');
    args_desc.PrintTo(&buffer);
    buffer.AddChar(']');
  }
  if (is_const()) {
    buffer.AddString(" const");
  }
  buffer.AddChar('.');
  return buffer.buffer();
}

void FunctionType::set_packed_parameter_counts(
    uint32_t packed_parameter_counts) const {
  untag()->packed_parameter_counts_ = packed_parameter_counts;
}

void FunctionType::set_packed_type_parameter_counts(
    uint16_t packed_type_parameter_counts) const {
  untag()->packed_type_parameter_counts_ = packed_type_parameter_counts;
}

void FunctionType::set_num_implicit_parameters(intptr_t value) const {
  ASSERT(value >= 0);
  untag()->packed_parameter_counts_.Update<PackedNumImplicitParameters>(value);
}

void ClosureData::set_default_type_arguments_instantiation_mode(
    InstantiationMode value) const {
  untag()->packed_fields_.Update<PackedInstantiationMode>(value);
}

Function::AwaiterLink ClosureData::awaiter_link() const {
  const uint8_t depth =
      untag()
          ->packed_fields_.Read<UntaggedClosureData::PackedAwaiterLinkDepth>();
  const uint8_t index =
      untag()
          ->packed_fields_.Read<UntaggedClosureData::PackedAwaiterLinkIndex>();
  return {depth, index};
}

void ClosureData::set_awaiter_link(Function::AwaiterLink link) const {
  untag()->packed_fields_.Update<UntaggedClosureData::PackedAwaiterLinkDepth>(
      link.depth);
  untag()->packed_fields_.Update<UntaggedClosureData::PackedAwaiterLinkIndex>(
      link.index);
}

bool ClosureData::captures_only_final_not_late_vars() const {
  return untag()
      ->packed_fields_.Read<UntaggedClosureData::CapturesOnlySharedFields>();
}

void ClosureData::set_captures_only_final_not_late_vars(bool value) const {
  untag()->packed_fields_.Update<UntaggedClosureData::CapturesOnlySharedFields>(
      value);
}

ClosureDataPtr ClosureData::New() {
  ASSERT(IsolateGroup::Current()->class_table()->At(kClosureDataCid) !=
         Class::null());
  return Object::Allocate<ClosureData>(Heap::kOld);
}

const char* ClosureData::ToCString() const {
  if (IsNull()) {
    return "ClosureData: null";
  }
  auto const zone = Thread::Current()->zone();
  ZoneTextBuffer buffer(zone);
  buffer.Printf("ClosureData: context_scope: 0x%" Px "",
                static_cast<uword>(context_scope()));
  buffer.AddString(" parent_function: ");
  if (parent_function() == Object::null()) {
    buffer.AddString("null");
  } else {
    buffer.AddString(Object::Handle(parent_function()).ToCString());
  }
  buffer.Printf(" implicit_static_closure: 0x%" Px "",
                static_cast<uword>(implicit_static_closure()));
  return buffer.buffer();
}

void FunctionType::set_num_fixed_parameters(intptr_t value) const {
  ASSERT(value >= 0);
  untag()->packed_parameter_counts_.Update<PackedNumFixedParameters>(value);
}

void FfiTrampolineData::set_callback_target(const Function& value) const {
  untag()->set_callback_target(value.ptr());
}

void FunctionType::SetNumOptionalParameters(
    intptr_t value,
    bool are_optional_positional) const {
  // HasOptionalNamedParameters only checks this bit, so only set it if there
  // are actual named parameters.
  untag()->packed_parameter_counts_.Update<PackedHasNamedOptionalParameters>(
      (value > 0) && !are_optional_positional);
  untag()->packed_parameter_counts_.Update<PackedNumOptionalParameters>(value);
}

FunctionTypePtr FunctionType::New(Heap::Space space) {
  return Object::Allocate<FunctionType>(space);
}

FunctionTypePtr FunctionType::New(intptr_t num_parent_type_arguments,
                                  Nullability nullability,
                                  Heap::Space space) {
  Zone* Z = Thread::Current()->zone();
  const FunctionType& result =
      FunctionType::Handle(Z, FunctionType::New(space));
  result.set_packed_parameter_counts(0);
  result.set_packed_type_parameter_counts(0);
  result.set_named_parameter_names(Object::empty_array());
  result.SetNumParentTypeArguments(num_parent_type_arguments);
  result.SetHash(0);
  result.set_flags(0);
  result.set_nullability(nullability);
  result.set_type_state(UntaggedAbstractType::kAllocated);
  result.InitializeTypeTestingStubNonAtomic(
      Code::Handle(Z, TypeTestingStubGenerator::DefaultCodeForType(result)));
  return result.ptr();
}

FunctionTypePtr FunctionType::Clone(const FunctionType& orig,
                                    Heap::Space space) {
  if (orig.IsGeneric()) {
    // Need a deep clone in order to update owners of type parameters.
    return FunctionType::RawCast(
        orig.UpdateFunctionTypes(0, kAllFree, space, nullptr));
  } else {
    return FunctionType::RawCast(Object::Clone(orig, space));
  }
}

const char* FunctionType::ToUserVisibleCString() const {
  Zone* zone = Thread::Current()->zone();
  ZoneTextBuffer printer(zone);
  Print(kUserVisibleName, &printer);
  return printer.buffer();
}

StringPtr FunctionType::ToUserVisibleString() const {
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  Print(kUserVisibleName, &printer);
  return Symbols::New(thread, printer.buffer());
}

const char* FunctionType::ToCString() const {
  if (IsNull()) {
    return "FunctionType: null";
  }
  Zone* zone = Thread::Current()->zone();
  ZoneTextBuffer printer(zone);
  const char* suffix = NullabilitySuffix(kInternalName);
  if (suffix[0] != '\0') {
    printer.AddString("(");
  }
  Print(kInternalName, &printer);
  if (suffix[0] != '\0') {
    printer.AddString(")");
    printer.AddString(suffix);
  }
  return printer.buffer();
}

void ClosureData::set_context_scope(const ContextScope& value) const {
  untag()->set_context_scope<std::memory_order_release>(value.ptr());
}

void ClosureData::set_implicit_static_closure(const Closure& closure) const {
  ASSERT(!closure.IsNull());
  ASSERT(untag()->closure() == Closure::null());
  untag()->set_closure<std::memory_order_release>(closure.ptr());
}

void FfiTrampolineData::set_c_signature(const FunctionType& value) const {
  untag()->set_c_signature(value.ptr());
}

void FfiTrampolineData::set_callback_id(int32_t callback_id) const {
  StoreNonPointer(&untag()->callback_id_, callback_id);
}

void FfiTrampolineData::set_callback_exceptional_return(
    const Instance& value) const {
  untag()->set_callback_exceptional_return(value.ptr());
}

void FfiTrampolineData::set_ffi_function_kind(FfiCallbackKind kind) const {
  StoreNonPointer(&untag()->ffi_function_kind_, static_cast<uint8_t>(kind));
}

FfiTrampolineDataPtr FfiTrampolineData::New() {
  ASSERT(IsolateGroup::Current()->class_table()->At(kFfiTrampolineDataCid) !=
         Class::null());
  const auto& data = FfiTrampolineData::Handle(
      Object::Allocate<FfiTrampolineData>(Heap::kOld));
  data.set_callback_id(-1);
  return data.ptr();
}

const char* FfiTrampolineData::ToCString() const {
  const FunctionType& c_sig = FunctionType::Handle(c_signature());
  return OS::SCreate(Thread::Current()->zone(),
                     "TrampolineData: c_signature=%s",
                     c_sig.ToUserVisibleCString());
}

FieldPtr Field::CloneFromOriginal() const {
  return this->Clone(*this);
}

FieldPtr Field::Original() const {
  if (IsNull()) {
    return Field::null();
  }
  if (untag()->owner()->IsField()) {
    return static_cast<FieldPtr>(untag()->owner());
  }
  return this->ptr();
}

intptr_t Field::guarded_cid() const {
#if defined(DEBUG)
  // This assertion ensures that the cid seen by the background compiler is
  // consistent. So the assertion passes if the field is a clone. It also
  // passes if the field is static, because we don't use field guards on
  // static fields. It also passes if we're compiling unoptimized
  // code (in which case the caller might get different answers if it obtains
  // the guarded cid multiple times).
  Thread* thread = Thread::Current();
#if defined(DART_PRECOMPILED_RUNTIME)
  ASSERT(!thread->IsInsideCompiler() || is_static());
#else
  ASSERT(!thread->IsInsideCompiler() ||
         ((CompilerState::Current().should_clone_fields() == !IsOriginal())) ||
         is_static());
#endif
#endif
  return LoadNonPointer<ClassIdTagType, std::memory_order_relaxed>(
      &untag()->guarded_cid_);
}

bool Field::is_nullable() const {
#if defined(DEBUG)
  // Same assert as guarded_cid(), because is_nullable() also needs to be
  // consistent for the background compiler.
  Thread* thread = Thread::Current();
#if defined(DART_PRECOMPILED_RUNTIME)
  ASSERT(!thread->IsInsideCompiler() || is_static());
#else
  ASSERT(!thread->IsInsideCompiler() ||
         ((CompilerState::Current().should_clone_fields() == !IsOriginal())) ||
         is_static());
#endif
#endif
  return is_nullable_unsafe();
}

void Field::SetOriginal(const Field& value) const {
  ASSERT(value.IsOriginal());
  ASSERT(!value.IsNull());
  untag()->set_owner(static_cast<ObjectPtr>(value.ptr()));
}

StringPtr Field::GetterName(const String& field_name) {
  return String::Concat(Symbols::GetterPrefix(), field_name);
}

StringPtr Field::GetterSymbol(const String& field_name) {
  return Symbols::FromGet(Thread::Current(), field_name);
}

StringPtr Field::LookupGetterSymbol(const String& field_name) {
  return Symbols::LookupFromGet(Thread::Current(), field_name);
}

StringPtr Field::SetterName(const String& field_name) {
  return String::Concat(Symbols::SetterPrefix(), field_name);
}

StringPtr Field::SetterSymbol(const String& field_name) {
  return Symbols::FromSet(Thread::Current(), field_name);
}

StringPtr Field::LookupSetterSymbol(const String& field_name) {
  return Symbols::LookupFromSet(Thread::Current(), field_name);
}

StringPtr Field::NameFromGetter(const String& getter_name) {
  return Symbols::New(Thread::Current(), getter_name, kGetterPrefixLength,
                      getter_name.Length() - kGetterPrefixLength);
}

StringPtr Field::NameFromSetter(const String& setter_name) {
  return Symbols::New(Thread::Current(), setter_name, kSetterPrefixLength,
                      setter_name.Length() - kSetterPrefixLength);
}

StringPtr Field::NameFromInit(const String& init_name) {
  return Symbols::New(Thread::Current(), init_name, kInitPrefixLength,
                      init_name.Length() - kInitPrefixLength);
}

bool Field::IsGetterName(const String& function_name) {
  return function_name.StartsWith(Symbols::GetterPrefix());
}

bool Field::IsSetterName(const String& function_name) {
  return function_name.StartsWith(Symbols::SetterPrefix());
}

bool Field::IsInitName(const String& function_name) {
  return function_name.StartsWith(Symbols::InitPrefix());
}

void Field::set_name(const String& value) const {
  ASSERT(value.IsSymbol());
  ASSERT(IsOriginal());
  untag()->set_name(value.ptr());
}

ObjectPtr Field::RawOwner() const {
  if (IsOriginal()) {
    return untag()->owner();
  } else {
    const Field& field = Field::Handle(Original());
    ASSERT(field.IsOriginal());
    ASSERT(!Object::Handle(field.untag()->owner()).IsField());
    return field.untag()->owner();
  }
}

ClassPtr Field::Owner() const {
  const Field& field = Field::Handle(Original());
  ASSERT(field.IsOriginal());
  const Object& obj = Object::Handle(field.untag()->owner());
  if (obj.IsClass()) {
    return Class::Cast(obj).ptr();
  }
  ASSERT(obj.IsPatchClass());
  return PatchClass::Cast(obj).wrapped_class();
}

ScriptPtr Field::Script() const {
  // NOTE(turnidge): If you update this function, you probably want to
  // update Class::PatchFieldsAndFunctions() at the same time.
  const Field& field = Field::Handle(Original());
  ASSERT(field.IsOriginal());
  const Object& obj = Object::Handle(field.untag()->owner());
  if (obj.IsClass()) {
    return Class::Cast(obj).script();
  }
  ASSERT(obj.IsPatchClass());
  return PatchClass::Cast(obj).script();
}

#if !defined(DART_PRECOMPILED_RUNTIME)
KernelProgramInfoPtr Field::KernelProgramInfo() const {
  const auto& owner = Object::Handle(RawOwner());
  if (owner.IsClass()) {
    return Class::Cast(owner).KernelProgramInfo();
  }
  return PatchClass::Cast(owner).kernel_program_info();
}
#endif

uint32_t Field::Hash() const {
  return String::HashRawSymbol(name());
}

#if defined(DART_DYNAMIC_MODULES)
bool Field::is_declared_in_bytecode() const {
  return Class::Handle(Owner()).is_declared_in_bytecode();
}
#endif

void Field::InheritKernelOffsetFrom(const Field& src) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  StoreNonPointer(&untag()->kernel_offset_, src.untag()->kernel_offset_);
#endif
}

#if !defined(DART_PRECOMPILED_RUNTIME)
TypedDataViewPtr Field::KernelLibrary() const {
  const intptr_t kernel_library_index = KernelLibraryIndex();
  if (kernel_library_index == -1) return TypedDataView::null();
  const auto& info = KernelProgramInfo::Handle(KernelProgramInfo());
  return info.KernelLibrary(kernel_library_index);
}

intptr_t Field::KernelLibraryOffset() const {
  const intptr_t kernel_library_index = KernelLibraryIndex();
  if (kernel_library_index == -1) return 0;
  const auto& info = KernelProgramInfo::Handle(KernelProgramInfo());
  return info.KernelLibraryStartOffset(kernel_library_index);
}

intptr_t Field::KernelLibraryIndex() const {
  const Object& obj = Object::Handle(untag()->owner());
  // During background JIT compilation field objects are copied
  // and copy points to the original field via the owner field.
  if (obj.IsField()) {
    return Field::Cast(obj).KernelLibraryIndex();
  } else if (obj.IsClass()) {
    const auto& lib = Library::Handle(Class::Cast(obj).library());
    return lib.kernel_library_index();
  }
  ASSERT(obj.IsPatchClass());
  return PatchClass::Cast(obj).kernel_library_index();
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

void Field::SetFieldTypeSafe(const AbstractType& value) const {
  ASSERT(IsOriginal());
  ASSERT(!value.IsNull());
  if (value.ptr() != type()) {
    untag()->set_type(value.ptr());
  }
}

// Called at finalization time
void Field::SetFieldType(const AbstractType& value) const {
  DEBUG_ASSERT(
      IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  SetFieldTypeSafe(value);
}

#if !defined(DART_PRECOMPILED_RUNTIME)
void Field::set_exact_type(const AbstractType& value) const {
  DEBUG_ASSERT(
      IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  ASSERT(IsOriginal());
  untag()->set_exact_type(value.ptr());
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

FieldPtr Field::New() {
  ASSERT(IsolateGroup::Current()->class_table()->At(kFieldCid) !=
         Class::null());
  return Object::Allocate<Field>(Heap::kOld);
}

void Field::InitializeNew(const Field& result,
                          const String& name,
                          bool is_static,
                          bool is_final,
                          bool is_const,
                          bool is_reflectable,
                          bool is_late,
                          const Object& owner,
                          TokenPosition token_pos,
                          TokenPosition end_token_pos) {
  result.set_name(name);
  result.set_is_static(is_static);
  if (is_static) {
    result.set_field_id_unsafe(-1);
  } else {
    result.SetOffset(0, 0);
  }
  result.set_is_final(is_final);
  result.set_is_const(is_const);
  result.set_is_reflectable(is_reflectable);
  result.set_is_late(is_late);
  result.set_owner(owner);
  result.set_token_pos(token_pos);
  result.set_end_token_pos(end_token_pos);
  result.set_has_nontrivial_initializer_unsafe(false);
  result.set_has_initializer_unsafe(false);
  // We will make unboxing decision once we read static type or
  // in KernelLoader::ReadInferredType.
  result.set_is_unboxed_unsafe(false);
  result.set_initializer_changed_after_initialization(false);
  NOT_IN_PRECOMPILED(result.set_kernel_offset(0));
  result.set_has_pragma(false);
  result.set_static_type_exactness_state_unsafe(
      StaticTypeExactnessState::NotTracking());
  auto isolate_group = IsolateGroup::Current();

// Use field guards if they are enabled and the isolate has never reloaded.
// TODO(johnmccutchan): The reload case assumes the worst case (everything is
// dynamic and possibly null). Attempt to relax this later.
//
// Do not use field guards for late fields as late field initialization
// doesn't update guarded cid and length.
#if defined(PRODUCT)
  const bool use_guarded_cid =
      FLAG_precompiled_mode || (isolate_group->use_field_guards() && !is_late);
#else
  const bool use_guarded_cid =
      FLAG_precompiled_mode ||
      (isolate_group->use_field_guards() &&
       !isolate_group->HasAttemptedReload() && !is_late);
#endif  // !defined(PRODUCT)
  result.set_guarded_cid_unsafe(use_guarded_cid ? kIllegalCid : kDynamicCid);
  result.set_is_nullable_unsafe(use_guarded_cid ? false : true);
  result.set_guarded_list_length_in_object_offset_unsafe(
      Field::kUnknownLengthOffset);
  // Presently, we only attempt to remember the list length for final fields.
  if (is_final && use_guarded_cid) {
    result.set_guarded_list_length_unsafe(Field::kUnknownFixedLength);
  } else {
    result.set_guarded_list_length_unsafe(Field::kNoFixedLength);
  }
}

FieldPtr Field::New(const String& name,
                    bool is_static,
                    bool is_final,
                    bool is_const,
                    bool is_reflectable,
                    bool is_late,
                    const Object& owner,
                    const AbstractType& type,
                    TokenPosition token_pos,
                    TokenPosition end_token_pos) {
  ASSERT(!owner.IsNull());
  const Field& result = Field::Handle(Field::New());
  InitializeNew(result, name, is_static, is_final, is_const, is_reflectable,
                is_late, owner, token_pos, end_token_pos);
  result.SetFieldTypeSafe(type);
#if !defined(DART_PRECOMPILED_RUNTIME)
  compiler::target::UnboxFieldIfSupported(result, type);
#endif
  return result.ptr();
}

FieldPtr Field::NewTopLevel(const String& name,
                            bool is_final,
                            bool is_const,
                            bool is_late,
                            const Object& owner,
                            TokenPosition token_pos,
                            TokenPosition end_token_pos) {
  ASSERT(!owner.IsNull());
  const Field& result = Field::Handle(Field::New());
  InitializeNew(result, name, true,       /* is_static */
                is_final, is_const, true, /* is_reflectable */
                is_late, owner, token_pos, end_token_pos);
  return result.ptr();
}

FieldPtr Field::Clone(const Field& original) const {
  if (original.IsNull()) {
    return Field::null();
  }
  ASSERT(original.IsOriginal());
  Field& clone = Field::Handle();
  // Using relaxed loading is fine because concurrent fields changes are all
  // guarded, will be reconciled during optimized code installation.
  clone ^= Object::Clone(*this, Heap::kOld, /*load_with_relaxed_atomics=*/true);
  clone.SetOriginal(original);
  clone.InheritKernelOffsetFrom(original);
  return clone.ptr();
}

int32_t Field::SourceFingerprint() const {
#if !defined(DART_PRECOMPILED_RUNTIME)
  if (is_declared_in_bytecode()) {
    return 0;
  }
  return kernel::KernelSourceFingerprintHelper::CalculateFieldFingerprint(
      *this);
#else
  return 0;
#endif  // !defined(DART_PRECOMPILED_RUNTIME)
}

const char* Field::UserVisibleNameCString() const {
  NoSafepointScope no_safepoint;
  if (FLAG_show_internal_names) {
    return String::Handle(name()).ToCString();
  }
  return String::ScrubName(String::Handle(name()),
                           is_extension_member() || is_extension_type_member());
}

StringPtr Field::UserVisibleName() const {
  if (FLAG_show_internal_names) {
    return name();
  }
  return Symbols::New(
      Thread::Current(),
      String::ScrubName(String::Handle(name()),
                        is_extension_member() || is_extension_type_member()));
}

intptr_t Field::guarded_list_length() const {
  return Smi::Value(untag()->guarded_list_length());
}

void Field::set_guarded_list_length_unsafe(intptr_t list_length) const {
  ASSERT(IsOriginal());
  untag()->set_guarded_list_length(Smi::New(list_length));
}

intptr_t Field::guarded_list_length_in_object_offset() const {
  return untag()->guarded_list_length_in_object_offset_ + kHeapObjectTag;
}

void Field::set_guarded_list_length_in_object_offset_unsafe(
    intptr_t list_length_offset) const {
  ASSERT(IsOriginal());
  StoreNonPointer<int8_t, int8_t, std::memory_order_relaxed>(
      &untag()->guarded_list_length_in_object_offset_,
      static_cast<int8_t>(list_length_offset - kHeapObjectTag));
  ASSERT(guarded_list_length_in_object_offset() == list_length_offset);
}

bool Field::NeedsSetter() const {
  // According to the Dart language specification, final fields don't have
  // a setter, except late final fields without initializer.
  if (is_final()) {
    // Late final fields without initializer always need a setter to check
    // if they are already initialized.
    if (is_late() && !has_initializer()) {
      return true;
    }
    return false;
  }

  // Instance non-final fields always need a setter.
  if (!is_static()) {
    return true;
  }

  // Otherwise, setters for static fields can be omitted
  // and fields can be accessed directly.
  return false;
}

bool Field::NeedsGetter() const {
  // All instance fields need a getter.
  if (!is_static()) return true;

  // Static fields also need a getter if they have a non-trivial initializer,
  // because it needs to be initialized lazily.
  if (has_nontrivial_initializer()) return true;

  // Static late fields with no initializer also need a getter, to check if it's
  // been initialized.
  return is_late() && !has_initializer();
}

const char* Field::ToCString() const {
  NoSafepointScope no_safepoint;
  if (IsNull()) {
    return "Field: null";
  }
  const char* kF0 = is_static() ? " static" : "";
  const char* kF1 = is_late() ? " late" : "";
  const char* kF2 = is_final() ? " final" : "";
  const char* kF3 = is_const() ? " const" : "";
  const char* kF4 = is_shared() ? " shared" : "";
  const char* field_name = String::Handle(name()).ToCString();
  const Class& cls = Class::Handle(Owner());
  const char* cls_name = String::Handle(cls.Name()).ToCString();
  return OS::SCreate(Thread::Current()->zone(), "Field <%s.%s>:%s%s%s%s%s",
                     cls_name, field_name, kF0, kF1, kF2, kF3, kF4);
}

WeakArrayPtr Field::dependent_code() const {
  DEBUG_ASSERT(
      IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
  return untag()->dependent_code();
}

void Field::set_dependent_code(const WeakArray& array) const {
  ASSERT(IsOriginal());
  DEBUG_ASSERT(
      IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  // relaxed: races with Field::Clone.
  untag()->set_dependent_code<std::memory_order_relaxed>(array.ptr());
}

class FieldDependentArray : public WeakCodeReferences {
 public:
  explicit FieldDependentArray(const Field& field)
      : WeakCodeReferences(WeakArray::Handle(field.dependent_code())),
        field_(field) {}

  virtual void UpdateArrayTo(const WeakArray& value) {
    field_.set_dependent_code(value);
  }

  virtual void ReportDeoptimization(const Code& code) {
    if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) {
      Function& function = Function::Handle(code.function());
      THR_Print("Deoptimizing %s because guard on field %s failed.\n",
                function.ToFullyQualifiedCString(), field_.ToCString());
    }
  }

  virtual void ReportSwitchingCode(const Code& code) {
    if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) {
      Function& function = Function::Handle(code.function());
      THR_Print(
          "Switching '%s' to unoptimized code because guard"
          " on field '%s' was violated.\n",
          function.ToFullyQualifiedCString(), field_.ToCString());
    }
  }

 private:
  const Field& field_;
  DISALLOW_COPY_AND_ASSIGN(FieldDependentArray);
};

void Field::RegisterDependentCode(const Code& code) const {
  ASSERT(IsOriginal());
  DEBUG_ASSERT(IsMutatorOrAtDeoptSafepoint());
  ASSERT(code.is_optimized());
  FieldDependentArray a(*this);
  a.Register(code);
}

void Field::DeoptimizeDependentCode(bool are_mutators_stopped) const {
  DEBUG_ASSERT(
      IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  ASSERT(IsOriginal());
  FieldDependentArray a(*this);
  if (FLAG_trace_deoptimization && a.HasCodes()) {
    THR_Print("Deopt for field guard (field %s)\n", ToCString());
  }
  a.DisableCode(are_mutators_stopped);
}

bool Field::IsConsistentWith(const Field& other) const {
  return (untag()->guarded_cid_ == other.untag()->guarded_cid_) &&
         (untag()->is_nullable_ == other.untag()->is_nullable_) &&
         (untag()->guarded_list_length() ==
          other.untag()->guarded_list_length()) &&
         (is_unboxed() == other.is_unboxed()) &&
         (static_type_exactness_state().Encode() ==
          other.static_type_exactness_state().Encode());
}

bool Field::IsUninitialized() const {
  Thread* thread = Thread::Current();
  const FieldTable* field_table = thread->isolate()->field_table();
  const ObjectPtr raw_value = field_table->At(field_id());
  return raw_value == Object::sentinel().ptr();
}

FunctionPtr Field::EnsureInitializerFunction() const {
  ASSERT(has_nontrivial_initializer());
  ASSERT(IsOriginal());
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  Function& initializer = Function::Handle(zone, InitializerFunction());
  if (initializer.IsNull()) {
#if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
    UNREACHABLE();
#else
    SafepointMutexLocker ml(
        thread->isolate_group()->initializer_functions_mutex());
    // Double check after grabbing the lock.
    initializer = InitializerFunction();
    if (initializer.IsNull()) {
      initializer = CreateFieldInitializerFunction(thread);
    }
#endif
  }
  return initializer.ptr();
}

#if !defined(DART_PRECOMPILED_RUNTIME) || defined(DART_DYNAMIC_MODULES)

FunctionPtr Field::CreateFieldInitializerFunction(Thread* thread) const {
  Zone* zone = thread->zone();
  ASSERT(InitializerFunction() == Function::null());

  String& init_name = String::Handle(zone, name());
  init_name = Symbols::FromConcat(thread, Symbols::InitPrefix(), init_name);

  const auto& field_owner = Class::Handle(zone, Owner());

#if defined(DART_PRECOMPILED_RUNTIME)
  const auto& initializer_owner = Class::Handle(zone, field_owner.ptr());
#else
  // Static field initializers are not added as members of their owning class,
  // so they must be preemptively given a patch class to avoid the meaning of
  // their kernel/token position changing during a reload. Compare
  // Class::PatchFieldsAndFunctions().
  // This might also be necessary for lazy computation of local var descriptors.
  // Compare https://codereview.chromium.org//1317753004
  const auto& script = Script::Handle(zone, Script());
  const auto& kernel_program_info =
      KernelProgramInfo::Handle(zone, KernelProgramInfo());
  const auto& initializer_owner = PatchClass::Handle(
      zone, PatchClass::New(field_owner, kernel_program_info, script));
  if (!is_declared_in_bytecode()) {
    const Library& lib = Library::Handle(zone, field_owner.library());
    initializer_owner.set_kernel_library_index(lib.kernel_library_index());
  }
#endif

  // Create a static initializer.
  FunctionType& signature = FunctionType::Handle(zone, FunctionType::New());
  const Function& initializer_fun = Function::Handle(
      zone,
      Function::New(signature, init_name, UntaggedFunction::kFieldInitializer,
                    is_static(),  // is_static
                    false,        // is_const
                    false,        // is_abstract
                    false,        // is_external
                    false,        // is_native
                    initializer_owner, TokenPosition::kNoSource));
  if (!is_static()) {
    signature.set_num_fixed_parameters(1);
    signature.set_parameter_types(
        Array::Handle(zone, Array::New(1, Heap::kOld)));
    signature.SetParameterTypeAt(
        0, AbstractType::Handle(zone, field_owner.DeclarationType()));
    NOT_IN_PRECOMPILED(initializer_fun.CreateNameArray());
    NOT_IN_PRECOMPILED(initializer_fun.SetParameterNameAt(0, Symbols::This()));
  }
  signature.set_result_type(AbstractType::Handle(zone, type()));
  initializer_fun.set_is_reflectable(false);
  initializer_fun.set_is_inlinable(false);
  NOT_IN_PRECOMPILED(initializer_fun.set_token_pos(token_pos()));
  NOT_IN_PRECOMPILED(initializer_fun.set_end_token_pos(end_token_pos()));
  initializer_fun.set_accessor_field(*this);
  initializer_fun.InheritKernelOffsetFrom(*this);
  initializer_fun.set_is_extension_member(is_extension_member());
  initializer_fun.set_is_extension_type_member(is_extension_type_member());

  signature ^= ClassFinalizer::FinalizeType(signature);
  initializer_fun.SetSignature(signature);

  SetInitializerFunction(initializer_fun);
  return initializer_fun.ptr();
}

void Field::SetInitializerFunction(const Function& initializer) const {
  ASSERT(IsOriginal());
  ASSERT(IsolateGroup::Current()
             ->initializer_functions_mutex()
             ->IsOwnedByCurrentThread());
  // We have to ensure that all stores into the initializer function object
  // happen before releasing the pointer to the initializer as it may be
  // accessed without grabbing the lock.
  untag()->set_initializer_function<std::memory_order_release>(
      initializer.ptr());
}

#endif  // !defined(DART_PRECOMPILED_RUNTIME) || defined(DART_DYNAMIC_MODULES)

bool Field::HasInitializerFunction() const {
  return untag()->initializer_function() != Function::null();
}

ErrorPtr Field::InitializeInstance(const Instance& instance) const {
  ASSERT(IsOriginal());
  ASSERT(is_instance());
  ASSERT(instance.GetField(*this) == Object::sentinel().ptr());
  Object& value = Object::Handle();

  if (has_nontrivial_initializer()) {
    const Function& initializer = Function::Handle(EnsureInitializerFunction());
    const Array& args = Array::Handle(Array::New(1));
    args.SetAt(0, instance);
    value = DartEntry::InvokeFunction(initializer, args);
    if (!value.IsNull() && value.IsError()) {
      return Error::Cast(value).ptr();
    }
  } else {
    if (is_late() && !has_initializer()) {
      Exceptions::ThrowLateFieldNotInitialized(String::Handle(name()));
      UNREACHABLE();
    }
#if defined(DART_PRECOMPILED_RUNTIME)
    UNREACHABLE();
#else
    // Our trivial initializer is `null`. Any non-`null` initializer is
    // non-trivial (see `KernelLoader::CheckForInitializer()`).
    value = Object::null();
#endif
  }
  ASSERT(value.IsNull() || value.IsInstance());
  if (is_late() && is_final() &&
      (instance.GetField(*this) != Object::sentinel().ptr())) {
    Exceptions::ThrowLateFieldAssignedDuringInitialization(
        String::Handle(name()));
    UNREACHABLE();
  }
  instance.SetField(*this, value);
  return Error::null();
}

ErrorPtr Field::InitializeStatic() const {
  ASSERT(IsOriginal());
  ASSERT(is_static());
  if (StaticValue() == Object::sentinel().ptr()) {
    ASSERT(is_late());
    auto& value = Object::Handle();
    if (!has_initializer()) {
      Exceptions::ThrowLateFieldNotInitialized(String::Handle(name()));
      UNREACHABLE();
    }
    value = EvaluateInitializer();
    if (value.IsError()) {
      return Error::Cast(value).ptr();
    }
    if (is_final() && (StaticValue() != Object::sentinel().ptr())) {
      Exceptions::ThrowLateFieldAssignedDuringInitialization(
          String::Handle(name()));
      UNREACHABLE();
    }
    ASSERT(value.IsNull() || value.IsInstance());
    SetStaticValue(value.IsNull() ? Instance::null_instance()
                                  : Instance::Cast(value));
    return Error::null();
  }
  return Error::null();
}

ObjectPtr Field::StaticConstFieldValue() const {
  ASSERT(is_static() &&
         (is_const() || (is_final() && has_trivial_initializer())));

  auto thread = Thread::Current();
  auto zone = thread->zone();
  auto initial_field_table =
      is_shared() ? thread->isolate_group()->shared_initial_field_table()
                  : thread->isolate_group()->initial_field_table();

  // We can safely cache the value of the static const field in the initial
  // field table.
  auto& value = Object::Handle(
      zone, initial_field_table->At(field_id(), /*concurrent_use=*/true));
  if (value.ptr() == Object::sentinel().ptr()) {
    // Fields with trivial initializers get their initial value
    // eagerly when they are registered.
    ASSERT(is_const());
    ASSERT(has_initializer());
    ASSERT(has_nontrivial_initializer());
    value = EvaluateInitializer();
    if (!value.IsError()) {
      ASSERT(value.IsNull() || value.IsInstance());
      SetStaticConstFieldValue(value.IsNull() ? Instance::null_instance()
                                              : Instance::Cast(value));
    }
  }
  return value.ptr();
}

void Field::SetStaticConstFieldValue(const Instance& value,
                                     bool assert_initializing_store) const {
  ASSERT(is_static());
  auto thread = Thread::Current();
  auto initial_field_table =
      is_shared() ? thread->isolate_group()->shared_initial_field_table()
                  : thread->isolate_group()->initial_field_table();

  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
  ASSERT(initial_field_table->At(field_id()) == Object::sentinel().ptr() ||
         initial_field_table->At(field_id()) == value.ptr() ||
         !assert_initializing_store);
  initial_field_table->SetAt(field_id(),
                             value.IsNull() ? Instance::null_instance().ptr()
                                            : Instance::Cast(value).ptr(),
                             /*concurrent_use=*/true);
}

ObjectPtr Field::EvaluateInitializer() const {
  ASSERT(Thread::Current()->IsDartMutatorThread());

#if !defined(DART_PRECOMPILED_RUNTIME)
  if (is_static() && is_const()) {
#if defined(DART_DYNAMIC_MODULES)
    if (is_declared_in_bytecode()) {
      const auto& initializer = Function::Handle(InitializerFunction());
      ASSERT(!initializer.IsNull());
      const auto& bytecode = Bytecode::Handle(initializer.GetBytecode());
      ASSERT(!bytecode.IsNull());
      const auto& pool = ObjectPool::Handle(bytecode.object_pool());
      ASSERT(!pool.IsNull());
      ASSERT(pool.Length() == 1);
      return pool.ObjectAt(0);
    }
#endif  // defined(DART_DYNAMIC_MODULES)
    return kernel::EvaluateStaticConstFieldInitializer(*this);
  }
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

  const Function& initializer = Function::Handle(EnsureInitializerFunction());
  return DartEntry::InvokeFunction(initializer, Object::empty_array());
}

static intptr_t GetListLength(const Object& value) {
  if (value.IsTypedDataBase()) {
    return TypedDataBase::Cast(value).Length();
  } else if (value.IsArray()) {
    return Array::Cast(value).Length();
  } else if (value.IsGrowableObjectArray()) {
    // List length is variable.
    return Field::kNoFixedLength;
  }
  return Field::kNoFixedLength;
}

static intptr_t GetListLengthOffset(intptr_t cid) {
  if (IsTypedDataClassId(cid) || IsTypedDataViewClassId(cid) ||
      IsUnmodifiableTypedDataViewClassId(cid) ||
      IsExternalTypedDataClassId(cid)) {
    return TypedData::length_offset();
  } else if (cid == kArrayCid || cid == kImmutableArrayCid) {
    return Array::length_offset();
  } else if (cid == kGrowableObjectArrayCid) {
    // List length is variable.
    return Field::kUnknownLengthOffset;
  }
  return Field::kUnknownLengthOffset;
}

const char* Field::GuardedPropertiesAsCString() const {
  if (guarded_cid() == kIllegalCid) {
    return "<?>";
  } else if (guarded_cid() == kDynamicCid) {
    ASSERT(!static_type_exactness_state().IsExactOrUninitialized());
    return "<*>";
  }

  Zone* zone = Thread::Current()->zone();

  const char* exactness = "";
  if (static_type_exactness_state().IsTracking()) {
    exactness =
        zone->PrintToString(" {%s}", static_type_exactness_state().ToCString());
  }

  const Class& cls =
      Class::Handle(IsolateGroup::Current()->class_table()->At(guarded_cid()));
  const char* class_name = String::Handle(cls.Name()).ToCString();

  if (IsBuiltinListClassId(guarded_cid()) && !is_nullable() && is_final()) {
    ASSERT(guarded_list_length() != kUnknownFixedLength);
    if (guarded_list_length() == kNoFixedLength) {
      return zone->PrintToString("<%s [*]%s>", class_name, exactness);
    } else {
      return zone->PrintToString(
          "<%s [%" Pd " @%" Pd "]%s>", class_name, guarded_list_length(),
          guarded_list_length_in_object_offset(), exactness);
    }
  }

  return zone->PrintToString("<%s %s%s>",
                             is_nullable() ? "nullable" : "not-nullable",
                             class_name, exactness);
}

void Field::InitializeGuardedListLengthInObjectOffset(bool unsafe) const {
  auto setter = unsafe ? &Field::set_guarded_list_length_in_object_offset_unsafe
                       : &Field::set_guarded_list_length_in_object_offset;
  ASSERT(IsOriginal());
  if (needs_length_check() &&
      (guarded_list_length() != Field::kUnknownFixedLength)) {
    const intptr_t offset = GetListLengthOffset(guarded_cid());
    (this->*setter)(offset);
    ASSERT(offset != Field::kUnknownLengthOffset);
  } else {
    (this->*setter)(Field::kUnknownLengthOffset);
  }
}

class FieldGuardUpdater {
 public:
  FieldGuardUpdater(const Field* field, const Object& value);

  bool IsUpdateNeeded() {
    return does_guarded_cid_need_update_ || does_is_nullable_need_update_ ||
           does_list_length_and_offset_need_update_ ||
           does_static_type_exactness_state_need_update_;
  }
  void DoUpdate();

 private:
  void ReviewExactnessState();
  void ReviewGuards();

  intptr_t guarded_cid() { return guarded_cid_; }
  void set_guarded_cid(intptr_t guarded_cid) {
    guarded_cid_ = guarded_cid;
    does_guarded_cid_need_update_ = true;
  }

  bool is_nullable() { return is_nullable_; }
  void set_is_nullable(bool is_nullable) {
    is_nullable_ = is_nullable;
    does_is_nullable_need_update_ = true;
  }

  intptr_t guarded_list_length() { return list_length_; }
  void set_guarded_list_length_and_offset(
      intptr_t list_length,
      intptr_t list_length_in_object_offset) {
    list_length_ = list_length;
    list_length_in_object_offset_ = list_length_in_object_offset;
    does_list_length_and_offset_need_update_ = true;
  }

  StaticTypeExactnessState static_type_exactness_state() {
    return static_type_exactness_state_;
  }
  void set_static_type_exactness_state(StaticTypeExactnessState state) {
    static_type_exactness_state_ = state;
    does_static_type_exactness_state_need_update_ = true;
  }

  const Field* field_;
  const Object& value_;

  intptr_t guarded_cid_;
  bool is_nullable_;
  intptr_t list_length_;
  intptr_t list_length_in_object_offset_;
  StaticTypeExactnessState static_type_exactness_state_;

  bool does_guarded_cid_need_update_ = false;
  bool does_is_nullable_need_update_ = false;
  bool does_list_length_and_offset_need_update_ = false;
  bool does_static_type_exactness_state_need_update_ = false;
};

void FieldGuardUpdater::ReviewGuards() {
  ASSERT(field_->IsOriginal());
  const intptr_t cid = value_.GetClassId();

  if (guarded_cid() == kIllegalCid) {
    set_guarded_cid(cid);
    set_is_nullable(cid == kNullCid);

    // Start tracking length if needed.
    ASSERT((guarded_list_length() == Field::kUnknownFixedLength) ||
           (guarded_list_length() == Field::kNoFixedLength));
    if (field_->needs_length_check()) {
      ASSERT(guarded_list_length() == Field::kUnknownFixedLength);
      set_guarded_list_length_and_offset(GetListLength(value_),
                                         GetListLengthOffset(cid));
    }

    if (FLAG_trace_field_guards) {
      THR_Print("    => %s\n", field_->GuardedPropertiesAsCString());
    }
    return;
  }

  if ((cid == guarded_cid()) || ((cid == kNullCid) && is_nullable())) {
    // Class id of the assigned value matches expected class id and nullability.

    // If we are tracking length check if it has matches.
    if (field_->needs_length_check() &&
        (guarded_list_length() != GetListLength(value_))) {
      ASSERT(guarded_list_length() != Field::kUnknownFixedLength);
      set_guarded_list_length_and_offset(Field::kNoFixedLength,
                                         Field::kUnknownLengthOffset);
      return;
    }

    // Everything matches.
    return;
  }

  if ((cid == kNullCid) && !is_nullable()) {
    // Assigning null value to a non-nullable field makes it nullable.
    set_is_nullable(true);
  } else if ((cid != kNullCid) && (guarded_cid() == kNullCid)) {
    // Assigning non-null value to a field that previously contained only null
    // turns it into a nullable field with the given class id.
    ASSERT(is_nullable());
    set_guarded_cid(cid);
  } else {
    // Give up on tracking class id of values contained in this field.
    ASSERT(guarded_cid() != cid);
    set_guarded_cid(kDynamicCid);
    set_is_nullable(true);
  }

  // If we were tracking length drop collected feedback.
  if (field_->needs_length_check()) {
    ASSERT(guarded_list_length() != Field::kUnknownFixedLength);
    set_guarded_list_length_and_offset(Field::kNoFixedLength,
                                       Field::kUnknownLengthOffset);
  }
}

bool Class::FindInstantiationOf(Zone* zone,
                                const Class& cls,
                                GrowableArray<const Type*>* path,
                                bool consider_only_super_classes) const {
  ASSERT(cls.is_type_finalized());
  if (cls.ptr() == ptr()) {
    return true;  // Found instantiation.
  }

  Class& cls2 = Class::Handle(zone);
  Type& super = Type::Handle(zone, super_type());
  if (!super.IsNull() && !super.IsObjectType()) {
    cls2 = super.type_class();
    if (path != nullptr) {
      path->Add(&super);
    }
    if (cls2.FindInstantiationOf(zone, cls, path,
                                 consider_only_super_classes)) {
      return true;  // Found instantiation.
    }
    if (path != nullptr) {
      path->RemoveLast();
    }
  }

  if (!consider_only_super_classes) {
    Array& super_interfaces = Array::Handle(zone, interfaces());
    for (intptr_t i = 0; i < super_interfaces.Length(); i++) {
      super ^= super_interfaces.At(i);
      cls2 = super.type_class();
      if (path != nullptr) {
        path->Add(&super);
      }
      if (cls2.FindInstantiationOf(zone, cls, path)) {
        return true;  // Found instantiation.
      }
      if (path != nullptr) {
        path->RemoveLast();
      }
    }
  }

  return false;  // Not found.
}

bool Class::FindInstantiationOf(Zone* zone,
                                const Type& type,
                                GrowableArray<const Type*>* path,
                                bool consider_only_super_classes) const {
  return FindInstantiationOf(zone, Class::Handle(zone, type.type_class()), path,
                             consider_only_super_classes);
}

TypePtr Class::GetInstantiationOf(Zone* zone, const Class& cls) const {
  if (ptr() == cls.ptr()) {
    return DeclarationType();
  }
  if (FindInstantiationOf(zone, cls, /*consider_only_super_classes=*/true)) {
    // Since [cls] is a superclass of [this], use [cls]'s declaration type.
    return cls.DeclarationType();
  }
  const auto& decl_type = Type::Handle(zone, DeclarationType());
  GrowableArray<const Type*> path(zone, 0);
  if (!FindInstantiationOf(zone, cls, &path)) {
    return Type::null();
  }
  Thread* thread = Thread::Current();
  ASSERT(!path.is_empty());
  auto& calculated_type = Type::Handle(zone, decl_type.ptr());
  auto& calculated_type_class =
      Class::Handle(zone, calculated_type.type_class());
  auto& calculated_type_args =
      TypeArguments::Handle(zone, calculated_type.arguments());
  calculated_type_args = calculated_type_args.ToInstantiatorTypeArguments(
      thread, calculated_type_class);
  for (auto* const type : path) {
    calculated_type ^= type->ptr();
    if (!calculated_type.IsInstantiated()) {
      calculated_type ^= calculated_type.InstantiateFrom(
          calculated_type_args, Object::null_type_arguments(), kAllFree,
          Heap::kNew);
    }
    calculated_type_class = calculated_type.type_class();
    calculated_type_args = calculated_type.arguments();
    calculated_type_args = calculated_type_args.ToInstantiatorTypeArguments(
        thread, calculated_type_class);
  }
  ASSERT_EQUAL(calculated_type.type_class_id(), cls.id());
  return calculated_type.ptr();
}

TypePtr Class::GetInstantiationOf(Zone* zone, const Type& type) const {
  return GetInstantiationOf(zone, Class::Handle(zone, type.type_class()));
}

void Field::SetStaticValue(const Object& value) const {
  auto thread = Thread::Current();
  ASSERT(thread->IsDartMutatorThread());
  ASSERT(value.IsNull() || value.IsSentinel() || value.IsInstance() ||
         value.IsSmi());

  ASSERT(is_static());  // Valid only for static dart fields.
  const intptr_t id = field_id();
  ASSERT(id >= 0);

  if (FLAG_experimental_shared_data && is_shared()) {
    value.EnsureDeeplyImmutable(thread->zone());
  }
  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
  if (is_shared()) {
    thread->isolate_group()->shared_field_table()->SetAt(
        id, value.ptr(),
        /*concurrent_use=*/true);
  } else {
    thread->isolate()->field_table()->SetAt(id, value.ptr());
  }
}

static StaticTypeExactnessState TrivialTypeExactnessFor(const Class& cls) {
  const intptr_t type_arguments_offset = cls.host_type_arguments_field_offset();
  ASSERT(type_arguments_offset != Class::kNoTypeArguments);
  if (StaticTypeExactnessState::CanRepresentAsTriviallyExact(
          type_arguments_offset / kCompressedWordSize)) {
    return StaticTypeExactnessState::TriviallyExact(type_arguments_offset /
                                                    kCompressedWordSize);
  } else {
    return StaticTypeExactnessState::NotExact();
  }
}

static const char* SafeTypeArgumentsToCString(const TypeArguments& args) {
  return (args.ptr() == TypeArguments::null()) ? "<null>" : args.ToCString();
}

StaticTypeExactnessState StaticTypeExactnessState::Compute(
    const Type& static_type,
    const Instance& value,
    bool print_trace /* = false */) {
  ASSERT(!value.IsNull());  // Should be handled by the caller.
  ASSERT(value.ptr() != Object::sentinel().ptr());

  Thread* thread = Thread::Current();
  Zone* const zone = thread->zone();
  const TypeArguments& static_type_args =
      TypeArguments::Handle(zone, static_type.GetInstanceTypeArguments(thread));

  TypeArguments& args = TypeArguments::Handle(zone);

  ASSERT(static_type.IsFinalized());
  const Class& cls = Class::Handle(zone, value.clazz());
  GrowableArray<const Type*> path(10);

  bool is_super_class = true;
  if (!cls.FindInstantiationOf(zone, static_type, &path,
                               /*consider_only_super_classes=*/true)) {
    is_super_class = false;
    bool found_super_interface =
        cls.FindInstantiationOf(zone, static_type, &path);
    ASSERT(found_super_interface);
  }

  // Trivial case: field has type G<T0, ..., Tn> and value has type
  // G<U0, ..., Un>. Check if type arguments match.
  if (path.is_empty()) {
    ASSERT(cls.ptr() == static_type.type_class());
    args = value.GetTypeArguments();
    // TODO(dartbug.com/34170) Evaluate if comparing relevant subvectors (that
    // disregards superclass own arguments) improves precision of the
    // tracking.
    if (args.ptr() == static_type_args.ptr()) {
      return TrivialTypeExactnessFor(cls);
    }

    if (print_trace) {
      THR_Print("  expected %s got %s type arguments\n",
                SafeTypeArgumentsToCString(static_type_args),
                SafeTypeArgumentsToCString(args));
    }
    return StaticTypeExactnessState::NotExact();
  }

  // Value has type C<U0, ..., Un> and field has type G<T0, ..., Tn> and G != C.
  // Compute C<X0, ..., Xn> at G (Xi are free type arguments).
  // Path array contains a chain of immediate supertypes S0 <: S1 <: ... Sn,
  // such that S0 is an immediate supertype of C and Sn is G<...>.
  // Each Si might depend on type parameters of the previous supertype S{i-1}.
  // To compute C<X0, ..., Xn> at G we walk the chain backwards and
  // instantiate Si using type parameters of S{i-1} which gives us a type
  // depending on type parameters of S{i-2}.
  Type& type = Type::Handle(zone, path.Last()->ptr());
  for (intptr_t i = path.length() - 2; (i >= 0) && !type.IsInstantiated();
       i--) {
    args = path[i]->GetInstanceTypeArguments(thread, /*canonicalize=*/false);
    type ^= type.InstantiateFrom(args, TypeArguments::null_type_arguments(),
                                 kAllFree, Heap::kNew);
  }

  if (type.IsInstantiated()) {
    // C<X0, ..., Xn> at G is fully instantiated and does not depend on
    // Xi. In this case just check if type arguments match.
    args = type.GetInstanceTypeArguments(thread, /*canonicalize=*/false);
    if (args.Equals(static_type_args)) {
      return is_super_class ? StaticTypeExactnessState::HasExactSuperClass()
                            : StaticTypeExactnessState::HasExactSuperType();
    }

    if (print_trace) {
      THR_Print("  expected %s got %s type arguments\n",
                SafeTypeArgumentsToCString(static_type_args),
                SafeTypeArgumentsToCString(args));
    }

    return StaticTypeExactnessState::NotExact();
  }

  // The most complicated case: C<X0, ..., Xn> at G depends on
  // Xi values. To compare type arguments we would need to instantiate
  // it fully from value's type arguments and compare with <U0, ..., Un>.
  // However this would complicate fast path in the native code. To avoid this
  // complication we would optimize for the trivial case: we check if
  // C<X0, ..., Xn> at G is exactly G<X0, ..., Xn> which means we can simply
  // compare values type arguments (<T0, ..., Tn>) to fields type arguments
  // (<U0, ..., Un>) to establish if field type is exact.
  ASSERT(cls.IsGeneric());
  const intptr_t num_type_params = cls.NumTypeParameters();
  bool trivial_case =
      (num_type_params ==
       Class::Handle(zone, static_type.type_class()).NumTypeParameters()) &&
      (value.GetTypeArguments() == static_type_args.ptr());
  if (!trivial_case && FLAG_trace_field_guards) {
    THR_Print("Not a simple case: %" Pd " vs %" Pd
              " type parameters, %s vs %s type arguments\n",
              num_type_params,
              Class::Handle(zone, static_type.type_class()).NumTypeParameters(),
              SafeTypeArgumentsToCString(
                  TypeArguments::Handle(zone, value.GetTypeArguments())),
              SafeTypeArgumentsToCString(static_type_args));
  }

  AbstractType& type_arg = AbstractType::Handle(zone);
  args = type.GetInstanceTypeArguments(thread, /*canonicalize=*/false);
  for (intptr_t i = 0; (i < num_type_params) && trivial_case; i++) {
    type_arg = args.TypeAt(i);
    if (!type_arg.IsTypeParameter() ||
        (TypeParameter::Cast(type_arg).index() != i)) {
      if (FLAG_trace_field_guards) {
        THR_Print("  => encountered %s at index % " Pd "\n",
                  type_arg.ToCString(), i);
      }
      trivial_case = false;
    }
  }

  return trivial_case ? TrivialTypeExactnessFor(cls)
                      : StaticTypeExactnessState::NotExact();
}

const char* StaticTypeExactnessState::ToCString() const {
  if (!IsTracking()) {
    return "not-tracking";
  } else if (!IsExactOrUninitialized()) {
    return "not-exact";
  } else if (IsTriviallyExact()) {
    return Thread::Current()->zone()->PrintToString(
        "trivially-exact(%hhu)", GetTypeArgumentsOffsetInWords());
  } else if (IsHasExactSuperType()) {
    return "has-exact-super-type";
  } else if (IsHasExactSuperClass()) {
    return "has-exact-super-class";
  } else {
    ASSERT(IsUninitialized());
    return "uninitialized-exactness";
  }
}

void FieldGuardUpdater::ReviewExactnessState() {
  if (!static_type_exactness_state().IsExactOrUninitialized()) {
    // Nothing to update.
    return;
  }

  if (guarded_cid() == kDynamicCid) {
    if (FLAG_trace_field_guards) {
      THR_Print(
          "  => switching off exactness tracking because guarded cid is "
          "dynamic\n");
    }
    set_static_type_exactness_state(StaticTypeExactnessState::NotExact());
    return;
  }

  // If we are storing null into a field or we have an exact super type
  // then there is nothing to do.
  if (value_.IsNull() || static_type_exactness_state().IsHasExactSuperType() ||
      static_type_exactness_state().IsHasExactSuperClass()) {
    return;
  }

  // If we are storing a non-null value into a field that is considered
  // to be trivially exact then we need to check if value has an appropriate
  // type.
  ASSERT(guarded_cid() != kNullCid);

  const Type& field_type = Type::Cast(AbstractType::Handle(field_->type()));
  const Instance& instance = Instance::Cast(value_);

  if (static_type_exactness_state().IsTriviallyExact()) {
    const TypeArguments& args =
        TypeArguments::Handle(instance.GetTypeArguments());
    const TypeArguments& field_type_args = TypeArguments::Handle(
        field_type.GetInstanceTypeArguments(Thread::Current()));
    if (args.ptr() == field_type_args.ptr()) {
      return;
    }

    if (FLAG_trace_field_guards) {
      THR_Print("  expected %s got %s type arguments\n",
                field_type_args.ToCString(), args.ToCString());
    }

    set_static_type_exactness_state(StaticTypeExactnessState::NotExact());
    return;
  }

  ASSERT(static_type_exactness_state().IsUninitialized());
  set_static_type_exactness_state(StaticTypeExactnessState::Compute(
      field_type, instance, FLAG_trace_field_guards));
  return;
}

FieldGuardUpdater::FieldGuardUpdater(const Field* field, const Object& value)
    : field_(field),
      value_(value),
      guarded_cid_(field->guarded_cid()),
      is_nullable_(field->is_nullable()),
      list_length_(field->guarded_list_length()),
      list_length_in_object_offset_(
          field->guarded_list_length_in_object_offset()),
      static_type_exactness_state_(field->static_type_exactness_state()) {
  ReviewGuards();
  ReviewExactnessState();
}

void FieldGuardUpdater::DoUpdate() {
  if (does_guarded_cid_need_update_) {
    field_->set_guarded_cid(guarded_cid_);
  }
  if (does_is_nullable_need_update_) {
    field_->set_is_nullable(is_nullable_);
  }
  if (does_list_length_and_offset_need_update_) {
    field_->set_guarded_list_length(list_length_);
    field_->set_guarded_list_length_in_object_offset(
        list_length_in_object_offset_);
  }
  if (does_static_type_exactness_state_need_update_) {
    field_->set_static_type_exactness_state(static_type_exactness_state_);
  }
}

void Field::RecordStore(const Object& value) const {
  ASSERT(IsOriginal());
  Thread* const thread = Thread::Current();
  if (!thread->isolate_group()->use_field_guards()) {
    return;
  }

  // We should never try to record a sentinel.
  ASSERT(value.ptr() != Object::sentinel().ptr());

  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
  if ((guarded_cid() == kDynamicCid) ||
      (is_nullable() && value.ptr() == Object::null())) {
    // Nothing to do: the field is not guarded or we are storing null into
    // a nullable field.
    return;
  }

  if (FLAG_trace_field_guards) {
    THR_Print("Store %s %s <- %s\n", ToCString(), GuardedPropertiesAsCString(),
              value.ToCString());
  }

  FieldGuardUpdater updater(this, value);
  if (updater.IsUpdateNeeded()) {
    if (FLAG_trace_field_guards) {
      THR_Print("    => %s\n", GuardedPropertiesAsCString());
    }
    // Nobody else could have updated guard state since we are holding write
    // program lock. But we need to ensure we stop mutators as we update
    // guard state as we can't have optimized code running with updated fields.
    auto isolate_group = IsolateGroup::Current();
    isolate_group->RunWithStoppedMutators([&]() {
      updater.DoUpdate();
      DeoptimizeDependentCode(/*are_mutators_stopped=*/true);
    });
  }
}

void Field::ForceDynamicGuardedCidAndLength() const {
  if (!is_unboxed()) {
    set_guarded_cid(kDynamicCid);
    set_is_nullable(true);
  }
  set_guarded_list_length(Field::kNoFixedLength);
  set_guarded_list_length_in_object_offset(Field::kUnknownLengthOffset);
  if (static_type_exactness_state().IsTracking()) {
    set_static_type_exactness_state(StaticTypeExactnessState::NotExact());
  }
  // Drop any code that relied on the above assumptions.
  DeoptimizeDependentCode();
}

StringPtr Script::resolved_url() const {
#if defined(DART_PRECOMPILER)
  return String::RawCast(
      WeakSerializationReference::Unwrap(untag()->resolved_url()));
#else
  return untag()->resolved_url();
#endif
}

bool Script::HasSource() const {
  return untag()->source() != String::null();
}

StringPtr Script::Source() const {
  return untag()->source();
}

bool Script::IsPartOfDartColonLibrary() const {
  const String& script_url = String::Handle(url());
  return (script_url.StartsWith(Symbols::DartScheme()) ||
          script_url.StartsWith(Symbols::DartSchemePrivate()));
}

#if !defined(DART_PRECOMPILED_RUNTIME)
void Script::LoadSourceFromKernel(const uint8_t* kernel_buffer,
                                  intptr_t kernel_buffer_len) const {
  String& uri = String::Handle(resolved_url());
  String& source = String::Handle(kernel::KernelLoader::FindSourceForScript(
      kernel_buffer, kernel_buffer_len, uri));
  set_source(source);
}

void Script::InitializeFromKernel(
    const KernelProgramInfo& info,
    intptr_t script_index,
    const TypedData& line_starts,
    const TypedDataView& constant_coverage) const {
  StoreNonPointer(&untag()->kernel_script_index_, script_index);
  untag()->set_kernel_program_info(info.ptr());
  untag()->set_line_starts(line_starts.ptr());
  untag()->set_debug_positions(Array::null_array().ptr());
  NOT_IN_PRODUCT(untag()->set_constant_coverage(constant_coverage.ptr()));
}
#endif

GrowableObjectArrayPtr Script::GenerateLineNumberArray() const {
  Zone* zone = Thread::Current()->zone();
  const GrowableObjectArray& info =
      GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
  const Object& line_separator = Object::Handle(zone);
  if (line_starts() == TypedData::null()) {
    // Scripts in the AOT snapshot do not have a line starts array.
    // A well-formed line number array has a leading null.
    info.Add(line_separator);  // New line.
    return info.ptr();
  }
#if !defined(DART_PRECOMPILED_RUNTIME)
  Smi& value = Smi::Handle(zone);
  const TypedData& line_starts_data = TypedData::Handle(zone, line_starts());
  intptr_t line_count = line_starts_data.Length();
  const Array& debug_positions_array = Array::Handle(debug_positions());
  ASSERT(!debug_positions_array.IsNull());
  intptr_t token_count = debug_positions_array.Length();
  int token_index = 0;

  LineStartsReader line_starts_reader(line_starts_data);
  for (int line_index = 0; line_index < line_count; ++line_index) {
    intptr_t start = line_starts_reader.At(line_index);
    // Output the rest of the tokens if we have no next line.
    intptr_t end = TokenPosition::kMaxSourcePos;
    if (line_index + 1 < line_count) {
      end = line_starts_reader.At(line_index + 1);
    }
    bool first = true;
    while (token_index < token_count) {
      value ^= debug_positions_array.At(token_index);
      intptr_t debug_position = value.Value();
      if (debug_position >= end) break;

      if (first) {
        info.Add(line_separator);          // New line.
        value = Smi::New(line_index + 1);  // Line number.
        info.Add(value);
        first = false;
      }

      value ^= debug_positions_array.At(token_index);
      info.Add(value);                               // Token position.
      value = Smi::New(debug_position - start + 1);  // Column.
      info.Add(value);
      ++token_index;
    }
  }
#endif  // !defined(DART_PRECOMPILED_RUNTIME)
  return info.ptr();
}

TokenPosition Script::MaxPosition() const {
#if !defined(DART_PRECOMPILED_RUNTIME)
  if (HasCachedMaxPosition()) {
    return TokenPosition::Deserialize(
        UntaggedScript::CachedMaxPositionBitField::decode(
            untag()->flags_and_max_position_));
  }
  if (!HasCachedMaxPosition() && line_starts() != TypedData::null()) {
    auto const zone = Thread::Current()->zone();
    const auto& starts = TypedData::Handle(zone, line_starts());
    LineStartsReader reader(starts);
    const intptr_t max_position = reader.MaxPosition();
    SetCachedMaxPosition(max_position);
    SetHasCachedMaxPosition(true);
    return TokenPosition::Deserialize(max_position);
  }
#endif
  return TokenPosition::kNoSource;
}

void Script::set_url(const String& value) const {
  untag()->set_url(value.ptr());
}

void Script::set_resolved_url(const String& value) const {
  untag()->set_resolved_url(value.ptr());
}

void Script::set_source(const String& value) const {
  untag()->set_source(value.ptr());
}

#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
TypedDataViewPtr Script::kernel_constant_coverage() const {
  return TypedDataView::RawCast(untag()->constant_coverage());
}
#if defined(DART_DYNAMIC_MODULES)
ArrayPtr Script::collected_constant_coverage() const {
  return Array::RawCast(untag()->constant_coverage());
}
void Script::set_collected_constant_coverage(const Array& value) const {
  ASSERT(!value.IsNull());
  // Collected constant coverage should only be initialized once,
  // whether empty or non-empty.
  ASSERT(untag()->constant_coverage() == Object::null());
  untag()->set_constant_coverage(value.ptr());
}
bool Script::HasCollectedConstantCoverage() const {
  // Constant coverage should always be initialized to a non-null value.
  ASSERT(untag()->constant_coverage() != Object::null());
  return untag()->constant_coverage()->IsArray() ||
         untag()->constant_coverage()->IsImmutableArray();
}
#endif  // defined(DART_DYNAMIC_MODULES)
#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)

TypedDataPtr Script::line_starts() const {
  return untag()->line_starts();
}

void Script::set_line_starts(const TypedData& value) const {
  untag()->set_line_starts(value.ptr());
}

#if !defined(DART_PRECOMPILED_RUNTIME)

static int LowestFirst(const intptr_t* a, const intptr_t* b) {
  return *a - *b;
}

static ArrayPtr SortAndDeduplicate(GrowableArray<intptr_t>* source) {
  intptr_t size = source->length();
  if (size == 0) {
    return Object::empty_array().ptr();
  }

  source->Sort(LowestFirst);

  intptr_t last = 0;
  for (intptr_t current = 1; current < size; ++current) {
    if (source->At(last) != source->At(current)) {
      (*source)[++last] = source->At(current);
    }
  }
  Array& array_object = Array::Handle();
  array_object = Array::New(last + 1, Heap::kOld);
  Smi& smi_value = Smi::Handle();
  for (intptr_t i = 0; i <= last; ++i) {
    smi_value = Smi::New(source->At(i));
    array_object.SetAt(i, smi_value);
  }
  return array_object.ptr();
}

void Script::CollectDebugTokenPositions() const {
  GrowableArray<intptr_t> token_positions(10);
  if (kernel_program_info() != Object::null()) {
    kernel::CollectScriptTokenPositionsFromKernel(*this, &token_positions);
  } else {
#if defined(DART_DYNAMIC_MODULES)
    bytecode::BytecodeReader::CollectScriptTokenPositionsFromBytecode(
        *this, &token_positions);
#else
    UNREACHABLE();
#endif
  }
  const auto& debug_positions =
      Array::Handle(SortAndDeduplicate(&token_positions));
  set_debug_positions(debug_positions);
}

#endif  // !defined(DART_PRECOMPILED_RUNTIME)

ArrayPtr Script::CollectConstConstructorCoverageFrom() const {
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
#if defined(DART_DYNAMIC_MODULES)
  if (HasCollectedConstantCoverage()) {
    return Array::RawCast(untag()->constant_coverage());
  }
#endif  // defined(DART_DYNAMIC_MODULES)
  return CollectConstConstructorCoverageFromKernel();
#else
  return Object::empty_array().ptr();
#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
}

ArrayPtr Script::debug_positions() const {
#if !defined(DART_PRECOMPILED_RUNTIME)
  Array& debug_positions_array = Array::Handle(untag()->debug_positions());
  if (debug_positions_array.IsNull()) {
    // This is created lazily. Now we need it.
    CollectDebugTokenPositions();
  }
#endif  // !defined(DART_PRECOMPILED_RUNTIME)
  return untag()->debug_positions();
}

void Script::set_debug_positions(const Array& value) const {
  untag()->set_debug_positions(value.ptr());
}

#if !defined(DART_PRECOMPILED_RUNTIME)
bool Script::HasCachedMaxPosition() const {
  return UntaggedScript::HasCachedMaxPositionBit::decode(
      untag()->flags_and_max_position_);
}

void Script::SetHasCachedMaxPosition(bool value) const {
  StoreNonPointer(&untag()->flags_and_max_position_,
                  UntaggedScript::HasCachedMaxPositionBit::update(
                      value, untag()->flags_and_max_position_));
}

void Script::SetCachedMaxPosition(intptr_t value) const {
  StoreNonPointer(&untag()->flags_and_max_position_,
                  UntaggedScript::CachedMaxPositionBitField::update(
                      value, untag()->flags_and_max_position_));
}
#endif

void Script::set_load_timestamp(int64_t value) const {
  StoreNonPointer(&untag()->load_timestamp_, value);
}

bool Script::IsValidTokenPosition(TokenPosition token_pos) const {
  const TokenPosition& max_position = MaxPosition();
  // We may end up with scripts that have the empty string as a source file
  // in testing and the like, so allow any token position when the max position
  // is 0 as well as when it is kNoSource.
  return !max_position.IsReal() || !token_pos.IsReal() ||
         max_position.Pos() == 0 || token_pos <= max_position;
}

#if !defined(DART_PRECOMPILED_RUNTIME)
static bool IsLetter(int32_t c) {
  return (('A' <= c) && (c <= 'Z')) || (('a' <= c) && (c <= 'z'));
}

static bool IsDecimalDigit(int32_t c) {
  return '0' <= c && c <= '9';
}

static bool IsIdentStartChar(int32_t c) {
  return IsLetter(c) || (c == '_') || (c == '$');
}

static bool IsIdentChar(int32_t c) {
  return IsLetter(c) || IsDecimalDigit(c) || (c == '_') || (c == '$');
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

bool Script::GetTokenLocation(const TokenPosition& token_pos,
                              intptr_t* line,
                              intptr_t* column) const {
  ASSERT(line != nullptr);
#if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
  // Scripts in the AOT snapshot do not have a line starts array.
  return false;
#else
  if (!token_pos.IsReal()) return false;

  auto const zone = Thread::Current()->zone();
  const TypedData& line_starts_data = TypedData::Handle(zone, line_starts());
  if (line_starts_data.IsNull()) return false;
  LineStartsReader line_starts_reader(line_starts_data);
  return line_starts_reader.LocationForPosition(token_pos.Pos(), line, column);
#endif  // defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
}

intptr_t Script::GetTokenLength(const TokenPosition& token_pos) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  // Scripts in the AOT snapshot do not have their source.
  return -1;
#else
  if (!HasSource() || !token_pos.IsReal()) return -1;
  auto const zone = Thread::Current()->zone();
  // We don't explicitly save this data: Load the source and find it from there.
  const String& source = String::Handle(zone, Source());
  const intptr_t start = token_pos.Pos();
  if (start >= source.Length()) return -1;  // Can't determine token_len.
  intptr_t end = start;
  if (IsIdentStartChar(source.CharAt(end++))) {
    for (; end < source.Length(); ++end) {
      if (!IsIdentChar(source.CharAt(end))) break;
    }
  }
  return end - start;
#endif
}

bool Script::TokenRangeAtLine(intptr_t line_number,
                              TokenPosition* first_token_index,
                              TokenPosition* last_token_index) const {
  ASSERT(first_token_index != nullptr && last_token_index != nullptr);
#if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
  // Scripts in the AOT snapshot do not have a line starts array.
  return false;
#else
  // Line numbers are 1-indexed.
  if (line_number <= 0) return false;
  Zone* zone = Thread::Current()->zone();
  const TypedData& line_starts_data = TypedData::Handle(zone, line_starts());
  if (line_starts_data.IsNull()) return false;
  LineStartsReader line_starts_reader(line_starts_data);
  if (!line_starts_reader.TokenRangeAtLine(line_number, first_token_index,
                                           last_token_index)) {
    return false;
  }
#if defined(DEBUG)
  intptr_t source_length;
  if (!HasSource()) {
    Smi& value = Smi::Handle(zone);
    const Array& debug_positions_array = Array::Handle(zone, debug_positions());
    ASSERT(!debug_positions_array.IsNull());
    value ^= debug_positions_array.At(debug_positions_array.Length() - 1);
    source_length = value.Value();
  } else {
    const String& source = String::Handle(zone, Source());
    source_length = source.Length();
  }
  ASSERT(last_token_index->Serialize() <= source_length);
#endif
  return true;
#endif  // defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES)
}

// Returns the index in the given source string for the given (1-based) absolute
// line and column numbers. The line and column offsets are used to calculate
// the absolute line and column number for the starting index in the source.
//
// If the given line number is outside the range of lines represented by the
// source, the given column number invalid for the given line, or a negative
// starting index is given, a negative value is returned to indicate failure.
static intptr_t GetRelativeSourceIndex(const String& src,
                                       intptr_t line,
                                       intptr_t line_offset = 0,
                                       intptr_t column = 1,
                                       intptr_t column_offset = 0,
                                       intptr_t starting_index = 0) {
  if (starting_index < 0 || line < 1 || column < 1 || line <= line_offset ||
      (line == line_offset + 1 && column <= column_offset)) {
    return -1;
  }
  intptr_t len = src.Length();
  intptr_t current_line = line_offset + 1;
  intptr_t current_index = starting_index;
  for (; current_index < len; current_index++) {
    if (current_line == line) {
      break;
    }
    const uint16_t c = src.CharAt(current_index);
    if (c == '\n' || c == '\r') {
      current_line++;
    }
    if (c == '\r' && current_index + 1 < len &&
        src.CharAt(current_index + 1) == '\n') {
      // \r\n is treated as a single line terminator.
      current_index++;
    }
  }
  if (current_line != line) {
    return -1;
  }
  // Only adjust with column offset when still on the first line.
  intptr_t current_column = 1 + (line == line_offset + 1 ? column_offset : 0);
  for (; current_index < len; current_index++, current_column++) {
    if (current_column == column) {
      return current_index;
    }
    const uint16_t c = src.CharAt(current_index);
    if (c == '\n' || c == '\r') {
      break;
    }
  }
  // Check for a column value representing the source's end.
  if (current_column == column) {
    return current_index;
  }
  return -1;
}

StringPtr Script::GetLine(intptr_t line_number, Heap::Space space) const {
  if (!HasSource()) {
    return Symbols::OptimizedOut().ptr();
  }
  const String& src = String::Handle(Source());
  const intptr_t start =
      GetRelativeSourceIndex(src, line_number, line_offset());
  if (start < 0) {
    return Symbols::Empty().ptr();
  }
  intptr_t end = start;
  for (; end < src.Length(); end++) {
    const uint16_t c = src.CharAt(end);
    if (c == '\n' || c == '\r') {
      break;
    }
  }
  return String::SubString(src, start, end - start, space);
}

StringPtr Script::GetSnippet(intptr_t from_line,
                             intptr_t from_column,
                             intptr_t to_line,
                             intptr_t to_column) const {
  if (!HasSource()) {
    return Symbols::OptimizedOut().ptr();
  }
  const String& src = String::Handle(Source());
  const intptr_t start = GetRelativeSourceIndex(src, from_line, line_offset(),
                                                from_column, col_offset());
  // Lines and columns are 1-based, so need to subtract one to get offsets.
  const intptr_t end = GetRelativeSourceIndex(
      src, to_line, from_line - 1, to_column, from_column - 1, start);
  // Only need to check end, because a negative start results in a negative end.
  if (end < 0) {
    return String::null();
  }
  return String::SubString(src, start, end - start);
}

ScriptPtr Script::New(const String& url, const String& source) {
  return Script::New(url, url, source);
}

ScriptPtr Script::New(const String& url,
                      const String& resolved_url,
                      const String& source) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kScriptCid) !=
         Class::null());
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const Script& result =
      Script::Handle(zone, Object::Allocate<Script>(Heap::kOld));
  result.set_url(String::Handle(zone, Symbols::New(thread, url)));
  result.set_resolved_url(
      String::Handle(zone, Symbols::New(thread, resolved_url)));
  result.set_source(source);
  NOT_IN_PRECOMPILED(ASSERT_EQUAL(result.HasCachedMaxPosition(), false));
  ASSERT_EQUAL(result.kernel_script_index(), 0);
  if (FLAG_remove_script_timestamps_for_test) {
    ASSERT_EQUAL(result.load_timestamp(), 0);
  } else {
    result.set_load_timestamp(OS::GetCurrentTimeMillis());
  }
  return result.ptr();
}

const char* Script::ToCString() const {
  const String& name = String::Handle(url());
  return OS::SCreate(Thread::Current()->zone(), "Script(%s)", name.ToCString());
}

LibraryPtr Script::FindLibrary() const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  auto isolate_group = thread->isolate_group();
  const GrowableObjectArray& libs = GrowableObjectArray::Handle(
      zone, isolate_group->object_store()->libraries());
  Library& lib = Library::Handle(zone);
  Array& scripts = Array::Handle(zone);
  for (intptr_t i = 0; i < libs.Length(); i++) {
    lib ^= libs.At(i);
    scripts = lib.LoadedScripts();
    for (intptr_t j = 0; j < scripts.Length(); j++) {
      if (scripts.At(j) == ptr()) {
        return lib.ptr();
      }
    }
  }
  return Library::null();
}

DictionaryIterator::DictionaryIterator(const Library& library)
    : array_(Array::Handle(library.dictionary())),
      // Last element in array is a Smi indicating the number of entries used.
      size_(Array::Handle(library.dictionary()).Length() - 1),
      next_ix_(0) {
  MoveToNextObject();
}

ObjectPtr DictionaryIterator::GetNext() {
  ASSERT(HasNext());
  int ix = next_ix_++;
  MoveToNextObject();
  ASSERT(array_.At(ix) != Object::null());
  return array_.At(ix);
}

void DictionaryIterator::MoveToNextObject() {
  Object& obj = Object::Handle(array_.At(next_ix_));
  while (obj.IsNull() && HasNext()) {
    next_ix_++;
    obj = array_.At(next_ix_);
  }
}

ClassDictionaryIterator::ClassDictionaryIterator(const Library& library,
                                                 IterationKind kind)
    : DictionaryIterator(library),
      toplevel_class_(Class::Handle((kind == kIteratePrivate)
                                        ? library.toplevel_class()
                                        : Class::null())) {
  MoveToNextClass();
}

ClassPtr ClassDictionaryIterator::GetNextClass() {
  ASSERT(HasNext());
  Class& cls = Class::Handle();
  if (next_ix_ < size_) {
    int ix = next_ix_++;
    cls ^= array_.At(ix);
    MoveToNextClass();
    return cls.ptr();
  }
  ASSERT(!toplevel_class_.IsNull());
  cls = toplevel_class_.ptr();
  toplevel_class_ = Class::null();
  return cls.ptr();
}

void ClassDictionaryIterator::MoveToNextClass() {
  Object& obj = Object::Handle();
  while (next_ix_ < size_) {
    obj = array_.At(next_ix_);
    if (obj.IsClass()) {
      return;
    }
    next_ix_++;
  }
}

static void ReportTooManyImports(const Library& lib) {
  const String& url = String::Handle(lib.url());
  Report::MessageF(Report::kError, Script::Handle(lib.LookupScript(url)),
                   TokenPosition::kNoSource, Report::AtLocation,
                   "too many imports in library '%s'", url.ToCString());
  UNREACHABLE();
}

bool Library::IsAnyCoreLibrary() const {
  String& url_str = Thread::Current()->StringHandle();
  url_str = url();
  return url_str.StartsWith(Symbols::DartScheme()) ||
         url_str.StartsWith(Symbols::DartSchemePrivate());
}

void Library::set_num_imports(intptr_t value) const {
  if (!Utils::IsUint(16, value)) {
    ReportTooManyImports(*this);
  }
  StoreNonPointer(&untag()->num_imports_, value);
}

void Library::set_name(const String& name) const {
  ASSERT(name.IsSymbol());
  untag()->set_name(name.ptr());
}

void Library::set_url(const String& url) const {
  untag()->set_url(url.ptr());
}

void Library::set_private_key(const String& key) const {
  untag()->set_private_key(key.ptr());
}

#if !defined(DART_PRECOMPILED_RUNTIME)
void Library::set_kernel_program_info(const KernelProgramInfo& info) const {
  untag()->set_kernel_program_info(info.ptr());
}

TypedDataViewPtr Library::KernelLibrary() const {
  const auto& info = KernelProgramInfo::Handle(kernel_program_info());
  return info.KernelLibrary(kernel_library_index());
}

intptr_t Library::KernelLibraryOffset() const {
  const auto& info = KernelProgramInfo::Handle(kernel_program_info());
  return info.KernelLibraryStartOffset(kernel_library_index());
}
#endif

void Library::set_loading_unit(const LoadingUnit& value) const {
  untag()->set_loading_unit(value.ptr());
}

void Library::SetName(const String& name) const {
  // Only set name once.
  ASSERT(!Loaded());
  set_name(name);
}

void Library::SetLoadInProgress() const {
  // Must not already be in the process of being loaded.
  ASSERT(untag()->load_state_ <= UntaggedLibrary::kLoadRequested);
  StoreNonPointer(&untag()->load_state_, UntaggedLibrary::kLoadInProgress);
}

void Library::SetLoadRequested() const {
  // Must not be already loaded.
  ASSERT(untag()->load_state_ == UntaggedLibrary::kAllocated);
  StoreNonPointer(&untag()->load_state_, UntaggedLibrary::kLoadRequested);
}

void Library::SetLoaded() const {
  // Should not be already loaded or just allocated.
  ASSERT(LoadInProgress() || LoadRequested());
  StoreNonPointer(&untag()->load_state_, UntaggedLibrary::kLoaded);
}

void Library::AddMetadata(const Object& declaration,
                          intptr_t kernel_offset) const {
  AddMetadata(declaration, Smi::Handle(Smi::New(kernel_offset)));
}

void Library::AddMetadata(const Object& declaration,
                          const Object& metadata_value) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  Thread* thread = Thread::Current();
  ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());

  MetadataMap map(metadata());
  map.UpdateOrInsert(declaration, metadata_value);
  set_metadata(map.Release());
#endif  // defined(DART_PRECOMPILED_RUNTIME)
}

ObjectPtr Library::GetMetadata(const Object& declaration) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  return Object::empty_array().ptr();
#else
  RELEASE_ASSERT(declaration.IsClass() || declaration.IsField() ||
                 declaration.IsFunction() || declaration.IsLibrary() ||
                 declaration.IsTypeParameter() || declaration.IsNamespace());

  auto thread = Thread::Current();
  auto zone = thread->zone();

  if (declaration.IsLibrary()) {
    // Ensure top-level class is loaded as it may contain annotations of
    // a library.
    const auto& cls = Class::Handle(zone, toplevel_class());
    if (!cls.IsNull()) {
      cls.EnsureDeclarationLoaded();
    }
  }
  Object& value = Object::Handle(zone);
  {
    SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
    MetadataMap map(metadata());
    value = map.GetOrNull(declaration);
    set_metadata(map.Release());
  }
  if (value.IsNull()) {
    // There is no metadata for this object.
    return Object::empty_array().ptr();
  }
  if (!value.IsSmi()) {
    // Metadata is already evaluated.
    ASSERT(value.IsArray() || value.IsLanguageError());
    return value.ptr();
  }
  const auto& smi_value = Smi::Cast(value);
  intptr_t kernel_offset = smi_value.Value();
  ASSERT(kernel_offset > 0);
  const auto& evaluated_value = Object::Handle(
      zone, kernel::EvaluateMetadata(
                *this, kernel_offset,
                /* is_annotations_offset = */ declaration.IsLibrary() ||
                    declaration.IsNamespace()));
  if (evaluated_value.IsArray() || evaluated_value.IsNull() ||
      evaluated_value.IsLanguageError()) {
    ASSERT(evaluated_value.ptr() != Object::empty_array().ptr());
    SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
    MetadataMap map(metadata());
    if (map.GetOrNull(declaration) == smi_value.ptr()) {
      map.UpdateOrInsert(declaration, evaluated_value);
    } else {
      ASSERT(map.GetOrNull(declaration) == evaluated_value.ptr());
    }
    set_metadata(map.Release());
  }
  return evaluated_value.ptr();
#endif  // defined(DART_PRECOMPILED_RUNTIME)
}

#if !defined(DART_PRECOMPILED_RUNTIME)
static bool HasPragma(const Object& declaration) {
  return (declaration.IsClass() && Class::Cast(declaration).has_pragma()) ||
         (declaration.IsFunction() &&
          Function::Cast(declaration).has_pragma()) ||
         (declaration.IsField() && Field::Cast(declaration).has_pragma());
}

void Library::EvaluatePragmas() {
  Object& declaration = Object::Handle();
  const GrowableObjectArray& declarations =
      GrowableObjectArray::Handle(GrowableObjectArray::New());
  {
    auto thread = Thread::Current();
    SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
    MetadataMap map(metadata());
    MetadataMap::Iterator it(&map);
    while (it.MoveNext()) {
      const intptr_t entry = it.Current();
      ASSERT(entry != -1);
      declaration = map.GetKey(entry);
      if (HasPragma(declaration)) {
        declarations.Add(declaration);
      }
    }
    set_metadata(map.Release());
  }
  for (intptr_t i = 0; i < declarations.Length(); ++i) {
    declaration = declarations.At(i);
    GetMetadata(declaration);
  }
}

void Library::CopyPragmas(const Library& old_lib) {
  auto thread = Thread::Current();
  SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
  MetadataMap new_map(metadata());
  MetadataMap old_map(old_lib.metadata());
  Object& declaration = Object::Handle();
  Object& value = Object::Handle();
  MetadataMap::Iterator it(&old_map);
  while (it.MoveNext()) {
    const intptr_t entry = it.Current();
    ASSERT(entry != -1);
    declaration = old_map.GetKey(entry);
    if (HasPragma(declaration)) {
      value = old_map.GetPayload(entry, 0);
      ASSERT(!value.IsNull());
      // Pragmas should be evaluated during hot reload phase 1
      // (when checkpointing libraries).
      ASSERT(!value.IsSmi());
      new_map.UpdateOrInsert(declaration, value);
    }
  }
  old_lib.set_metadata(old_map.Release());
  set_metadata(new_map.Release());
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

static bool ShouldBePrivate(const String& name) {
  // _foo
  if (name.Length() >= 1 && name.CharAt(0) == '_') {
    return true;
  }
  // get:_foo, set:_foo or dyn:_foo
  if (name.Length() >= 5 && name.CharAt(3) == ':' && name.CharAt(4) == '_') {
    if ((name.CharAt(0) == 'g' || name.CharAt(0) == 's') &&
        name.CharAt(1) == 'e' && name.CharAt(2) == 't') {
      return true;
    }
    if (name.CharAt(0) == 'd' && name.CharAt(1) == 'y' &&
        name.CharAt(2) == 'n') {
      return true;
    }
  }
  // dyn:get:_foo, dyn:set:_foo
  if (name.Length() >= 9 && name.CharAt(0) == 'd' && name.CharAt(1) == 'y' &&
      name.CharAt(2) == 'n' && name.CharAt(3) == ':' &&
      (name.CharAt(4) == 'g' || name.CharAt(4) == 's') &&
      name.CharAt(5) == 'e' && name.CharAt(6) == 't' && name.CharAt(7) == ':' &&
      name.CharAt(8) == '_') {
    return true;
  }
  return false;
}

void Library::RehashDictionary(const Array& old_dict,
                               intptr_t new_dict_size) const {
  intptr_t old_dict_size = old_dict.Length() - 1;
  const Array& new_dict =
      Array::Handle(Array::New(new_dict_size + 1, Heap::kOld));
  // Rehash all elements from the original dictionary
  // to the newly allocated array.
  Object& entry = Class::Handle();
  String& entry_name = String::Handle();
  Object& new_entry = Object::Handle();
  intptr_t used = 0;
  for (intptr_t i = 0; i < old_dict_size; i++) {
    entry = old_dict.At(i);
    if (!entry.IsNull()) {
      entry_name = entry.DictionaryName();
      ASSERT(!entry_name.IsNull());
      const intptr_t hash = entry_name.Hash();
      intptr_t index = hash % new_dict_size;
      new_entry = new_dict.At(index);
      while (!new_entry.IsNull()) {
        index = (index + 1) % new_dict_size;  // Move to next element.
        new_entry = new_dict.At(index);
      }
      new_dict.SetAt(index, entry);
      used++;
    }
  }
  // Set used count.
  ASSERT(used < new_dict_size);  // Need at least one empty slot.
  new_entry = Smi::New(used);
  new_dict.SetAt(new_dict_size, new_entry);
  // Remember the new dictionary now.
  untag()->set_dictionary(new_dict.ptr());
}

void Library::AddObject(const Object& obj, const String& name) const {
  ASSERT(Thread::Current()->IsDartMutatorThread());
  ASSERT(obj.IsClass() || obj.IsFunction() || obj.IsField() ||
         obj.IsLibraryPrefix());
  ASSERT(name.Equals(String::Handle(obj.DictionaryName())));
  ASSERT(LookupLocalObject(name) == Object::null());
  const Array& dict = Array::Handle(dictionary());
  intptr_t dict_size = dict.Length() - 1;
  intptr_t index = name.Hash() % dict_size;

  Object& entry = Object::Handle();
  entry = dict.At(index);
  // An empty spot will be found because we keep the hash set at most 75% full.
  while (!entry.IsNull()) {
    index = (index + 1) % dict_size;
    entry = dict.At(index);
  }

  // Insert the object at the empty slot.
  dict.SetAt(index, obj);
  // One more element added.
  intptr_t used_elements = Smi::Value(Smi::RawCast(dict.At(dict_size))) + 1;
  const Smi& used = Smi::Handle(Smi::New(used_elements));
  dict.SetAt(dict_size, used);  // Update used count.

  // Rehash if symbol_table is 75% full.
  if (used_elements > ((dict_size / 4) * 3)) {
    // TODO(iposva): Avoid exponential growth.
    RehashDictionary(dict, 2 * dict_size);
  }

  // Invalidate the cache of loaded scripts.
  if (loaded_scripts() != Array::null()) {
    untag()->set_loaded_scripts(Array::null());
  }
}

// Lookup a name in the library's re-export namespace.
// This lookup can occur from two different threads: background compiler and
// mutator thread.
ObjectPtr Library::LookupReExport(const String& name,
                                  ZoneGrowableArray<intptr_t>* trail) const {
  if (!HasExports()) {
    return Object::null();
  }

  if (trail == nullptr) {
    trail = new ZoneGrowableArray<intptr_t>();
  }
  Object& obj = Object::Handle();

  const intptr_t lib_id = this->index();
  ASSERT(lib_id >= 0);  // We use -1 to indicate that a cycle was found.
  trail->Add(lib_id);
  const Array& exports = Array::Handle(this->exports());
  Namespace& ns = Namespace::Handle();
  for (int i = 0; i < exports.Length(); i++) {
    ns ^= exports.At(i);
    obj = ns.Lookup(name, trail);
    if (!obj.IsNull()) {
      // The Lookup call above may return a setter x= when we are looking
      // for the name x. Make sure we only return when a matching name
      // is found.
      String& obj_name = String::Handle(obj.DictionaryName());
      if (Field::IsSetterName(obj_name) == Field::IsSetterName(name)) {
        break;
      }
    }
  }
  trail->RemoveLast();
  return obj.ptr();
}

ObjectPtr Library::LookupEntry(const String& name, intptr_t* index) const {
  ASSERT(!IsNull());
  Thread* thread = Thread::Current();
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_OBJECT_HANDLESCOPE(thread);
  REUSABLE_STRING_HANDLESCOPE(thread);
  Array& dict = thread->ArrayHandle();
  dict = dictionary();
  intptr_t dict_size = dict.Length() - 1;
  *index = name.Hash() % dict_size;
  Object& entry = thread->ObjectHandle();
  String& entry_name = thread->StringHandle();
  entry = dict.At(*index);
  // Search the entry in the hash set.
  while (!entry.IsNull()) {
    entry_name = entry.DictionaryName();
    ASSERT(!entry_name.IsNull());
    if (entry_name.Equals(name)) {
      return entry.ptr();
    }
    *index = (*index + 1) % dict_size;
    entry = dict.At(*index);
  }
  return Object::null();
}

void Library::AddClass(const Class& cls) const {
  ASSERT(!Compiler::IsBackgroundCompilation());
  const String& class_name = String::Handle(cls.Name());
  AddObject(cls, class_name);
  // Link class to this library.
  cls.set_library(*this);
}

static void AddScriptIfUnique(const GrowableObjectArray& scripts,
                              const Script& candidate) {
  if (candidate.IsNull()) {
    return;
  }
  Script& script_obj = Script::Handle();

  for (int i = 0; i < scripts.Length(); i++) {
    script_obj ^= scripts.At(i);
    if (script_obj.ptr() == candidate.ptr()) {
      // We already have a reference to this script.
      return;
    }
  }
  // Add script to the list of scripts.
  scripts.Add(candidate);
}

ArrayPtr Library::LoadedScripts() const {
  // We compute the list of loaded scripts lazily. The result is
  // cached in loaded_scripts_.
  if (loaded_scripts() == Array::null()) {
    // TODO(jensj): This can be cleaned up.
    // It really should just return the content of `used_scripts`, and there
    // should be no need to do the O(n) call to `AddScriptIfUnique` per script.

    // Iterate over the library dictionary and collect all scripts.
    const GrowableObjectArray& scripts =
        GrowableObjectArray::Handle(GrowableObjectArray::New(8));
    Object& entry = Object::Handle();
    Class& cls = Class::Handle();
    Script& owner_script = Script::Handle();
    DictionaryIterator it(*this);
    while (it.HasNext()) {
      entry = it.GetNext();
      if (entry.IsClass()) {
        owner_script = Class::Cast(entry).script();
      } else if (entry.IsFunction()) {
        owner_script = Function::Cast(entry).script();
      } else if (entry.IsField()) {
        owner_script = Field::Cast(entry).Script();
      } else {
        continue;
      }
      AddScriptIfUnique(scripts, owner_script);
    }

    // Add all scripts from patch classes.
    GrowableObjectArray& patches = GrowableObjectArray::Handle(used_scripts());
    for (intptr_t i = 0; i < patches.Length(); i++) {
      entry = patches.At(i);
      if (entry.IsClass()) {
        owner_script = Class::Cast(entry).script();
      } else {
        ASSERT(entry.IsScript());
        owner_script = Script::Cast(entry).ptr();
      }
      AddScriptIfUnique(scripts, owner_script);
    }

    cls = toplevel_class();
    if (!cls.IsNull()) {
      owner_script = cls.script();
      AddScriptIfUnique(scripts, owner_script);
      // Special case: Scripts that only contain external top-level functions
      // are not included above, but can be referenced through a library's
      // anonymous classes. Example: dart-core:identical.dart.
      Function& func = Function::Handle();
      Array& functions = Array::Handle(cls.current_functions());
      for (intptr_t j = 0; j < functions.Length(); j++) {
        func ^= functions.At(j);
        if (func.is_external()) {
          owner_script = func.script();
          AddScriptIfUnique(scripts, owner_script);
        }
      }
    }

    // Create the array of scripts and cache it in loaded_scripts_.
    const Array& scripts_array = Array::Handle(Array::MakeFixedLength(scripts));
    untag()->set_loaded_scripts(scripts_array.ptr());
  }
  return loaded_scripts();
}

// TODO(hausner): we might want to add a script dictionary to the
// library class to make this lookup faster.
ScriptPtr Library::LookupScript(const String& url,
                                bool useResolvedUri /* = false */) const {
  const intptr_t url_length = url.Length();
  if (url_length == 0) {
    return Script::null();
  }
  const Array& scripts = Array::Handle(LoadedScripts());
  Script& script = Script::Handle();
  String& script_url = String::Handle();
  const intptr_t num_scripts = scripts.Length();
  for (int i = 0; i < num_scripts; i++) {
    script ^= scripts.At(i);
    if (useResolvedUri) {
      // Use for urls with 'org-dartlang-sdk:' or 'file:' schemes
      script_url = script.resolved_url();
    } else {
      // Use for urls with 'dart:', 'package:', or 'file:' schemes
      script_url = script.url();
    }
    const intptr_t start_idx = script_url.Length() - url_length;
    if ((start_idx == 0) && url.Equals(script_url)) {
      return script.ptr();
    } else if (start_idx > 0) {
      // If we do a suffix match, only match if the partial path
      // starts at or immediately after the path separator.
      if (((url.CharAt(0) == '/') ||
           (script_url.CharAt(start_idx - 1) == '/')) &&
          url.Equals(script_url, start_idx, url_length)) {
        return script.ptr();
      }
    }
  }
  return Script::null();
}

void Library::EnsureTopLevelClassIsFinalized() const {
  if (toplevel_class() == Object::null()) {
    return;
  }
  Thread* thread = Thread::Current();
  const Class& cls = Class::Handle(thread->zone(), toplevel_class());
  if (cls.is_finalized()) {
    return;
  }
  const Error& error =
      Error::Handle(thread->zone(), cls.EnsureIsFinalized(thread));
  if (!error.IsNull()) {
    Exceptions::PropagateError(error);
  }
}

ObjectPtr Library::LookupLocalObject(const String& name) const {
  intptr_t index;
  return LookupEntry(name, &index);
}

ObjectPtr Library::LookupLocalOrReExportObject(const String& name) const {
  intptr_t index;
  EnsureTopLevelClassIsFinalized();
  const Object& result = Object::Handle(LookupEntry(name, &index));
  if (!result.IsNull() && !result.IsLibraryPrefix()) {
    return result.ptr();
  }
  return LookupReExport(name);
}

FieldPtr Library::LookupFieldAllowPrivate(const String& name) const {
  EnsureTopLevelClassIsFinalized();
  Object& obj = Object::Handle(LookupLocalObjectAllowPrivate(name));
  if (obj.IsField()) {
    return Field::Cast(obj).ptr();
  }
  return Field::null();
}

FunctionPtr Library::LookupFunctionAllowPrivate(const String& name) const {
  EnsureTopLevelClassIsFinalized();
  Object& obj = Object::Handle(LookupLocalObjectAllowPrivate(name));
  if (obj.IsFunction()) {
    return Function::Cast(obj).ptr();
  }
  return Function::null();
}

ObjectPtr Library::LookupLocalObjectAllowPrivate(const String& name) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  Object& obj = Object::Handle(zone, Object::null());
  obj = LookupLocalObject(name);
  if (obj.IsNull() && ShouldBePrivate(name)) {
    String& private_name = String::Handle(zone, PrivateName(name));
    obj = LookupLocalObject(private_name);
  }
  return obj.ptr();
}

ClassPtr Library::LookupClass(const String& name) const {
  Object& obj = Object::Handle(LookupLocalObject(name));
  if (obj.IsClass()) {
    return Class::Cast(obj).ptr();
  }
  return Class::null();
}

ClassPtr Library::LookupClassAllowPrivate(const String& name) const {
  Object& obj = Object::Handle(LookupLocalObjectAllowPrivate(name));
  if (obj.IsClass()) {
    return Class::Cast(obj).ptr();
  }
  return Class::null();
}

LibraryPrefixPtr Library::LookupLocalLibraryPrefix(const String& name) const {
  const Object& obj = Object::Handle(LookupLocalObject(name));
  if (obj.IsLibraryPrefix()) {
    return LibraryPrefix::Cast(obj).ptr();
  }
  return LibraryPrefix::null();
}

void Library::set_toplevel_class(const Class& value) const {
  ASSERT(untag()->toplevel_class() == Class::null());
  untag()->set_toplevel_class(value.ptr());
}

void Library::set_dependencies(const Array& deps) const {
  untag()->set_dependencies(deps.ptr());
}

void Library::set_metadata(const Array& value) const {
  if (untag()->metadata() != value.ptr()) {
    DEBUG_ASSERT(
        IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
    untag()->set_metadata(value.ptr());
  }
}

LibraryPtr Library::ImportLibraryAt(intptr_t index) const {
  Namespace& import = Namespace::Handle(ImportAt(index));
  if (import.IsNull()) {
    return Library::null();
  }
  return import.target();
}

NamespacePtr Library::ImportAt(intptr_t index) const {
  if ((index < 0) || index >= num_imports()) {
    return Namespace::null();
  }
  const Array& import_list = Array::Handle(imports());
  return Namespace::RawCast(import_list.At(index));
}

void Library::DropDependenciesAndCaches() const {
  untag()->set_imports(Object::empty_array().ptr());
  untag()->set_exports(Object::empty_array().ptr());
  StoreNonPointer(&untag()->num_imports_, 0);
  untag()->set_loaded_scripts(Array::null());
  untag()->set_dependencies(Array::null());
#if defined(PRODUCT)
  // used_scripts is only used by vm-service.
  untag()->set_used_scripts(GrowableObjectArray::null());
#endif
}

void Library::AddImport(const Namespace& ns) const {
  Array& imports = Array::Handle(this->imports());
  intptr_t capacity = imports.Length();
  if (num_imports() == capacity) {
    capacity = capacity + kImportsCapacityIncrement + (capacity >> 2);
    imports = Array::Grow(imports, capacity);
    untag()->set_imports(imports.ptr());
  }
  intptr_t index = num_imports();
  imports.SetAt(index, ns);
  set_num_imports(index + 1);
}

// Convenience function to determine whether the export list is
// non-empty.
bool Library::HasExports() const {
  return exports() != Object::empty_array().ptr();
}

// We add one namespace at a time to the exports array and don't
// pre-allocate any unused capacity. The assumption is that
// re-exports are quite rare.
void Library::AddExport(const Namespace& ns) const {
  Array& exports = Array::Handle(this->exports());
  intptr_t num_exports = exports.Length();
  exports = Array::Grow(exports, num_exports + 1);
  untag()->set_exports(exports.ptr());
  exports.SetAt(num_exports, ns);
}

static ArrayPtr NewDictionary(intptr_t initial_size) {
  const Array& dict = Array::Handle(Array::New(initial_size + 1, Heap::kOld));
  // The last element of the dictionary specifies the number of in use slots.
  dict.SetAt(initial_size, Object::smi_zero());
  return dict.ptr();
}

void Library::InitClassDictionary() const {
  Thread* thread = Thread::Current();
  ASSERT(thread->IsDartMutatorThread());
  REUSABLE_FUNCTION_HANDLESCOPE(thread);
  Array& dictionary = thread->ArrayHandle();
  // TODO(iposva): Find reasonable initial size.
  const int kInitialElementCount = 16;
  dictionary = NewDictionary(kInitialElementCount);
  untag()->set_dictionary(dictionary.ptr());
}

void Library::InitImportList() const {
  const Array& imports =
      Array::Handle(Array::New(kInitialImportsCapacity, Heap::kOld));
  untag()->set_imports(imports.ptr());
  StoreNonPointer(&untag()->num_imports_, 0);
}

LibraryPtr Library::New() {
  ASSERT(IsolateGroup::Current()->class_table()->At(kLibraryCid) !=
         Class::null());
  return Object::Allocate<Library>(Heap::kOld);
}

LibraryPtr Library::NewLibraryHelper(const String& url, bool import_core_lib) {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  ASSERT(thread->IsDartMutatorThread());
  // Force the url to have a hash code.
  url.Hash();
  const bool dart_scheme = url.StartsWith(Symbols::DartScheme());
  const Library& result = Library::Handle(zone, Library::New());
  result.untag()->set_name(Symbols::Empty().ptr());
  result.untag()->set_url(url.ptr());
  result.untag()->set_dictionary(Object::empty_array().ptr());
  Array& array = Array::Handle(zone);
  array = HashTables::New<MetadataMap>(4, Heap::kOld);
  result.untag()->set_metadata(array.ptr());
  result.untag()->set_toplevel_class(Class::null());
  GrowableObjectArray& list = GrowableObjectArray::Handle(zone);
  list = GrowableObjectArray::New(Object::empty_array(), Heap::kOld);
  result.untag()->set_used_scripts(list.ptr());
  result.untag()->set_imports(Object::empty_array().ptr());
  result.untag()->set_exports(Object::empty_array().ptr());
  NOT_IN_PRECOMPILED_RUNTIME(
      result.untag()->set_kernel_program_info(KernelProgramInfo::null()));
  result.untag()->set_loaded_scripts(Array::null());
  result.set_native_entry_resolver(nullptr);
  result.set_native_entry_symbol_resolver(nullptr);
  result.set_ffi_native_resolver(nullptr);
  result.set_flags(0);
  // This logic is also in the DAP debug adapter in DDS to avoid needing
  // to call setLibraryDebuggable for every library for every isolate.
  // If these defaults change, the same should be done there in
  // dap/IsolateManager._getIsLibraryDebuggableByDefault.
  if (dart_scheme) {
    // Only debug dart: libraries if we have been requested to show invisible
    // frames.
    result.set_debuggable(FLAG_show_invisible_frames);
  } else {
    // Default to debuggable for all other libraries.
    result.set_debuggable(true);
  }
  result.set_is_dart_scheme(dart_scheme);
  NOT_IN_PRECOMPILED(
      result.StoreNonPointer(&result.untag()->kernel_library_index_, -1));
  result.StoreNonPointer(&result.untag()->load_state_,
                         UntaggedLibrary::kAllocated);
  result.StoreNonPointer(&result.untag()->index_, -1);
  result.InitClassDictionary();
  result.InitImportList();
  result.AllocatePrivateKey();
  if (import_core_lib) {
    const Library& core_lib = Library::Handle(zone, Library::CoreLibrary());
    ASSERT(!core_lib.IsNull());
    const Namespace& ns =
        Namespace::Handle(zone, Namespace::New(core_lib, Object::null_array(),
                                               Object::null_array(), result));
    result.AddImport(ns);
  }
  return result.ptr();
}

LibraryPtr Library::New(const String& url) {
  return NewLibraryHelper(url, false);
}

void Library::set_flags(uint8_t flags) const {
  StoreNonPointer(&untag()->flags_, flags);
}

void Library::InitCoreLibrary(IsolateGroup* isolate_group) {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const String& core_lib_url = Symbols::DartCore();
  const Library& core_lib =
      Library::Handle(zone, Library::NewLibraryHelper(core_lib_url, false));
  core_lib.SetLoadRequested();
  core_lib.Register(thread);
  isolate_group->object_store()->set_bootstrap_library(ObjectStore::kCore,
                                                       core_lib);
  isolate_group->object_store()->set_root_library(Library::Handle());
}

// Invoke the function, or noSuchMethod if it is null.
static ObjectPtr InvokeInstanceFunction(
    Thread* thread,
    const Instance& receiver,
    const Function& function,
    const String& target_name,
    const Array& args,
    const Array& args_descriptor_array,
    bool check_is_entrypoint,
    EntryPointPragma pragma,
    bool respect_reflectable,
    const TypeArguments& instantiator_type_args) {
  // Note "args" is already the internal arguments with the receiver as the
  // first element.
  ArgumentsDescriptor args_descriptor(args_descriptor_array);
  if (function.IsNull() ||
      !function.AreValidArguments(args_descriptor, nullptr) ||
      (respect_reflectable && !function.is_reflectable())) {
    return DartEntry::InvokeNoSuchMethod(thread, receiver, target_name, args,
                                         args_descriptor_array);
  }
  ObjectPtr type_error = function.DoArgumentTypesMatch(args, args_descriptor,
                                                       instantiator_type_args);
  if (type_error != Error::null()) {
    return type_error;
  }
  if (check_is_entrypoint) {
    CHECK_ERROR(function.VerifyEntryPoint(pragma));
  }
  return DartEntry::InvokeFunction(function, args, args_descriptor_array);
}

static bool IsLookupOfMainFunctionInRootLibrary(const Library& lib,
                                                const String& name) {
  return name.Equals(Symbols::main()) &&
         lib.ptr() == IsolateGroup::Current()->object_store()->root_library();
}

ObjectPtr Library::InvokeGetter(const String& getter_name,
                                bool check_is_entrypoint,
                                bool respect_reflectable,
                                bool for_invocation) const {
  Object& obj = Object::Handle(LookupLocalOrReExportObject(getter_name));
  Function& getter = Function::Handle();
  if (obj.IsField()) {
    const Field& field = Field::Cast(obj);
    if (!field.IsUninitialized()) {
      if (check_is_entrypoint) {
        CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
      }
      return field.StaticValue();
    }
    // An uninitialized field was found.  Check for a getter in the field's
    // owner class.
    const Class& klass = Class::Handle(field.Owner());
    const String& internal_getter_name =
        String::Handle(Field::GetterName(getter_name));
    getter = klass.LookupStaticFunction(internal_getter_name);
  } else {
    // No field found. Check for a getter in the lib.
    const String& internal_getter_name =
        String::Handle(Field::GetterName(getter_name));
    obj = LookupLocalOrReExportObject(internal_getter_name);
    if (obj.IsFunction()) {
      getter = Function::Cast(obj).ptr();
    } else if (!for_invocation) {
      // No need to re-lookup the getter name if coming from Invoke(), since
      // it already failed there.
      obj = LookupLocalOrReExportObject(getter_name);
      if (obj.IsFunction()) {
        const auto& function = Function::Cast(obj);
        if (function.SafeToClosurize()) {
          // The main function of the root library always has a retained
          // implicit static closure.
          if (check_is_entrypoint &&
              !IsLookupOfMainFunctionInRootLibrary(*this, getter_name)) {
            CHECK_ERROR(
                function.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
          }
          // Looking for a getter but found a regular method: closurize it.
          const auto& closure_function =
              Function::Handle(function.ImplicitClosureFunction());
          return closure_function.ImplicitStaticClosure();
        }
      }
    }
  }

  if (getter.IsNull() || (respect_reflectable && !getter.is_reflectable())) {
    if (for_invocation) {
      // Instead of throwing an NSM, indicate that no getter was found by
      // returning a value that cannot be returned by a getter (here,
      // the sentinel value).
      return Object::sentinel().ptr();
    }
    return ThrowNoSuchMethod(Object::null_string(), getter_name,
                             Object::null_array(), Object::null_array(),
                             InvocationMirror::kTopLevel,
                             InvocationMirror::kGetter);
  }

  if (check_is_entrypoint) {
    CHECK_ERROR(getter.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
  }
  // Invoke the getter and return the result.
  return DartEntry::InvokeFunction(getter, Object::empty_array());
}

ObjectPtr Library::InvokeSetter(const String& setter_name,
                                const Instance& value,
                                bool check_is_entrypoint,
                                bool respect_reflectable) const {
  Object& obj = Object::Handle(LookupLocalOrReExportObject(setter_name));
  const String& internal_setter_name =
      String::Handle(Field::SetterName(setter_name));
  AbstractType& setter_type = AbstractType::Handle();
  AbstractType& argument_type = AbstractType::Handle(value.GetType(Heap::kOld));
  if (obj.IsField()) {
    const Field& field = Field::Cast(obj);
    setter_type = field.type();
    if (!argument_type.IsNullType() && !setter_type.IsDynamicType() &&
        !value.IsInstanceOf(setter_type, Object::null_type_arguments(),
                            Object::null_type_arguments())) {
      return ThrowTypeError(field.token_pos(), value, setter_type, setter_name);
    }
    if (field.is_final() || (respect_reflectable && !field.is_reflectable())) {
      const int kNumArgs = 1;
      const Array& args = Array::Handle(Array::New(kNumArgs));
      args.SetAt(0, value);

      return ThrowNoSuchMethod(Object::null_string(), internal_setter_name,
                               args, Object::null_array(),
                               InvocationMirror::kTopLevel,
                               InvocationMirror::kSetter);
    }
    if (check_is_entrypoint) {
      CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kSetterOnly));
    }
    field.SetStaticValue(value);
    return value.ptr();
  }

  Function& setter = Function::Handle();
  obj = LookupLocalOrReExportObject(internal_setter_name);
  if (obj.IsFunction()) {
    setter ^= obj.ptr();
  }

  const int kNumArgs = 1;
  const Array& args = Array::Handle(Array::New(kNumArgs));
  args.SetAt(0, value);
  if (setter.IsNull() || (respect_reflectable && !setter.is_reflectable())) {
    return ThrowNoSuchMethod(Object::null_string(), internal_setter_name, args,
                             Object::null_array(), InvocationMirror::kTopLevel,
                             InvocationMirror::kSetter);
  }

  setter_type = setter.ParameterTypeAt(0);
  if (!argument_type.IsNullType() && !setter_type.IsDynamicType() &&
      !value.IsInstanceOf(setter_type, Object::null_type_arguments(),
                          Object::null_type_arguments())) {
    return ThrowTypeError(setter.token_pos(), value, setter_type, setter_name);
  }

  if (check_is_entrypoint) {
    CHECK_ERROR(setter.VerifyEntryPoint(EntryPointPragma::kSetterOnly));
  }
  return DartEntry::InvokeFunction(setter, args);
}

ObjectPtr Library::Invoke(const String& function_name,
                          const Array& args,
                          const Array& arg_names,
                          bool check_is_entrypoint,
                          bool respect_reflectable) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();

  // We don't pass any explicit type arguments, which will be understood as
  // using dynamic for any function type arguments by lower layers.
  const int kTypeArgsLen = 0;
  const Array& args_descriptor_array = Array::Handle(
      zone, ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(),
                                          arg_names, Heap::kNew));
  ArgumentsDescriptor args_descriptor(args_descriptor_array);

  auto& function = Function::Handle(zone);
  auto& result =
      Object::Handle(zone, LookupLocalOrReExportObject(function_name));
  if (result.IsFunction()) {
    function ^= result.ptr();
  }

  if (function.IsNull()) {
    // Didn't find a method: try to find a getter and invoke call on its result.
    const Object& getter_result =
        Object::Handle(zone, InvokeGetter(function_name, check_is_entrypoint,
                                          respect_reflectable,
                                          /*for_invocation=*/true));
    if (getter_result.IsError()) {
      return getter_result.ptr();
    } else if (getter_result.ptr() != Object::sentinel().ptr()) {
      const auto& call_args_descriptor_array = Array::Handle(
          zone, ArgumentsDescriptor::NewBoxed(args_descriptor.TypeArgsLen(),
                                              args_descriptor.Count() + 1,
                                              arg_names, Heap::kNew));
      const auto& call_args = Array::Handle(
          zone,
          CreateCallableArgumentsFromStatic(zone, Instance::Cast(getter_result),
                                            args, arg_names, args_descriptor));
      return DartEntry::InvokeClosure(thread, call_args,
                                      call_args_descriptor_array);
    }
  }

  if (function.IsNull() ||
      (respect_reflectable && !function.is_reflectable())) {
    return ThrowNoSuchMethod(Object::null_string(), function_name, args,
                             arg_names, InvocationMirror::kTopLevel,
                             InvocationMirror::kMethod);
  }
  if (!function.AreValidArguments(args_descriptor, nullptr)) {
    return ThrowNoSuchMethod(
        String::Handle(function.UserVisibleSignature()), function_name, args,
        arg_names, InvocationMirror::kTopLevel, InvocationMirror::kMethod);
  }
  // This is a static function, so we pass an empty instantiator tav.
  ASSERT(function.is_static());
  ObjectPtr type_error = function.DoArgumentTypesMatch(
      args, args_descriptor, Object::empty_type_arguments());
  if (type_error != Error::null()) {
    return type_error;
  }
  // The main function of the root library is always callable.
  if (check_is_entrypoint &&
      !IsLookupOfMainFunctionInRootLibrary(*this, function_name)) {
    CHECK_ERROR(function.VerifyEntryPoint(EntryPointPragma::kCallOnly));
  }
  return DartEntry::InvokeFunction(function, args, args_descriptor_array);
}

void Library::InitNativeWrappersLibrary(IsolateGroup* isolate_group,
                                        bool is_kernel) {
  const int kNumNativeWrappersClasses = 4;
  COMPILE_ASSERT((kNumNativeWrappersClasses > 0) &&
                 (kNumNativeWrappersClasses < 10));
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const String& native_flds_lib_url = Symbols::DartNativeWrappers();
  const Library& native_flds_lib = Library::Handle(
      zone, Library::NewLibraryHelper(native_flds_lib_url, false));
  const String& native_flds_lib_name = Symbols::DartNativeWrappersLibName();
  native_flds_lib.SetName(native_flds_lib_name);
  native_flds_lib.SetLoadRequested();
  native_flds_lib.Register(thread);
  native_flds_lib.SetLoadInProgress();
  isolate_group->object_store()->set_native_wrappers_library(native_flds_lib);
  const char* const kNativeWrappersClass = "NativeFieldWrapperClass";
  const int kNameLength = 25;
  ASSERT(kNameLength == (strlen(kNativeWrappersClass) + 1 + 1));
  char name_buffer[kNameLength];
  String& cls_name = String::Handle(zone);
  for (int fld_cnt = 1; fld_cnt <= kNumNativeWrappersClasses; fld_cnt++) {
    Utils::SNPrint(name_buffer, kNameLength, "%s%d", kNativeWrappersClass,
                   fld_cnt);
    cls_name = Symbols::New(thread, name_buffer);
    Class::NewNativeWrapper(native_flds_lib, cls_name, fld_cnt);
  }
  // NOTE: If we bootstrap from a Kernel IR file we want to generate the
  // synthetic constructors for the native wrapper classes.  We leave this up to
  // the [KernelLoader] who will take care of it later.
  if (!is_kernel) {
    native_flds_lib.SetLoaded();
  }
}

// LibraryLookupSet maps URIs to libraries.
class LibraryLookupTraits {
 public:
  static const char* Name() { return "LibraryLookupTraits"; }
  static bool ReportStats() { return false; }

  static bool IsMatch(const Object& a, const Object& b) {
    const String& a_str = String::Cast(a);
    const String& b_str = String::Cast(b);

    ASSERT(a_str.HasHash() && b_str.HasHash());
    return a_str.Equals(b_str);
  }

  static uword Hash(const Object& key) { return String::Cast(key).Hash(); }

  static ObjectPtr NewKey(const String& str) { return str.ptr(); }
};
typedef UnorderedHashMap<LibraryLookupTraits> LibraryLookupMap;

// Returns library with given url in current isolate, or nullptr.
LibraryPtr Library::LookupLibrary(Thread* thread, const String& url) {
  Zone* zone = thread->zone();
  ObjectStore* object_store = thread->isolate_group()->object_store();

  // Make sure the URL string has an associated hash code
  // to speed up the repeated equality checks.
  url.Hash();

  // Use the libraries map to lookup the library by URL.
  Library& lib = Library::Handle(zone);
  SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
  if (object_store->libraries_map() == Array::null()) {
    return Library::null();
  } else {
    LibraryLookupMap map(object_store->libraries_map());
    lib ^= map.GetOrNull(url);
    ASSERT(map.Release().ptr() == object_store->libraries_map());
  }
  return lib.ptr();
}

bool Library::IsPrivate(const String& name) {
  if (ShouldBePrivate(name)) return true;
  // Factory names: List._fromLiteral.
  for (intptr_t i = 1; i < name.Length() - 1; i++) {
    if (name.CharAt(i) == '.') {
      if (name.CharAt(i + 1) == '_') {
        return true;
      }
    }
  }
  return false;
}

// Create a private key for this library. It is based on the hash of the
// library URI and the sequence number of the library to guarantee unique
// private keys without having to verify.
void Library::AllocatePrivateKey() const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  auto isolate_group = thread->isolate_group();

#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
  if (isolate_group->IsReloading()) {
    // When reloading, we need to make sure we use the original private key
    // if this library previously existed.
    ProgramReloadContext* program_reload_context =
        isolate_group->program_reload_context();
    const String& original_key =
        String::Handle(program_reload_context->FindLibraryPrivateKey(*this));
    if (!original_key.IsNull()) {
      untag()->set_private_key(original_key.ptr());
      return;
    }
  }
#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)

  // Format of the private key is: "@<sequence number><6 digits of hash>
  const intptr_t hash_mask = 0x7FFFF;

  const String& url = String::Handle(zone, this->url());
  intptr_t hash_value = url.Hash() & hash_mask;

  const GrowableObjectArray& libs = GrowableObjectArray::Handle(
      zone, isolate_group->object_store()->libraries());
  intptr_t sequence_value = libs.Length();

  char private_key[32];
  Utils::SNPrint(private_key, sizeof(private_key), "%c%" Pd "%06" Pd "",
                 kPrivateKeySeparator, sequence_value, hash_value);
  const String& key =
      String::Handle(zone, String::New(private_key, Heap::kOld));
  key.Hash();  // This string may end up in the VM isolate.
  untag()->set_private_key(key.ptr());
}

const String& Library::PrivateCoreLibName(const String& member) {
  const Library& core_lib = Library::Handle(Library::CoreLibrary());
  const String& private_name = String::ZoneHandle(core_lib.PrivateName(member));
  return private_name;
}

bool Library::IsPrivateCoreLibName(const String& name, const String& member) {
  Zone* zone = Thread::Current()->zone();
  const auto& core_lib = Library::Handle(zone, Library::CoreLibrary());
  const auto& private_key = String::Handle(zone, core_lib.private_key());

  ASSERT(core_lib.IsPrivate(member));
  return name.EqualsConcat(member, private_key);
}

ClassPtr Library::LookupCoreClass(const String& class_name) {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const Library& core_lib = Library::Handle(zone, Library::CoreLibrary());
  String& name = String::Handle(zone, class_name.ptr());
  if (class_name.CharAt(0) == kPrivateIdentifierStart) {
    // Private identifiers are mangled on a per library basis.
    name = Symbols::FromConcat(thread, name,
                               String::Handle(zone, core_lib.private_key()));
  }
  return core_lib.LookupClass(name);
}

// Cannot handle qualified names properly as it only appends private key to
// the end (e.g. _Alfa.foo -> _Alfa.foo@...).
StringPtr Library::PrivateName(const String& name) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  ASSERT(IsPrivate(name));
  // ASSERT(strchr(name, '@') == nullptr);
  String& str = String::Handle(zone);
  str = name.ptr();
  str = Symbols::FromConcat(thread, str,
                            String::Handle(zone, this->private_key()));
  return str.ptr();
}

LibraryPtr Library::GetLibrary(intptr_t index) {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  auto isolate_group = thread->isolate_group();
  const GrowableObjectArray& libs = GrowableObjectArray::Handle(
      zone, isolate_group->object_store()->libraries());
  ASSERT(!libs.IsNull());
  if ((0 <= index) && (index < libs.Length())) {
    Library& lib = Library::Handle(zone);
    lib ^= libs.At(index);
    return lib.ptr();
  }
  return Library::null();
}

void Library::Register(Thread* thread) const {
  Zone* zone = thread->zone();
  auto isolate_group = thread->isolate_group();
  ObjectStore* object_store = isolate_group->object_store();

  // A library is "registered" in two places:
  // - A growable array mapping from index to library.
  const String& lib_url = String::Handle(zone, url());
  ASSERT(Library::LookupLibrary(thread, lib_url) == Library::null());
  ASSERT(lib_url.HasHash());
  SafepointWriteRwLocker ml(thread, isolate_group->program_lock());
  GrowableObjectArray& libs =
      GrowableObjectArray::Handle(zone, object_store->libraries());
  ASSERT(!libs.IsNull());
  set_index(libs.Length());
  libs.Add(*this);

  // - A map from URL string to library.
  if (object_store->libraries_map() == Array::null()) {
    LibraryLookupMap map(HashTables::New<LibraryLookupMap>(16, Heap::kOld));
    object_store->set_libraries_map(map.Release());
  }

  LibraryLookupMap map(object_store->libraries_map());
  bool present = map.UpdateOrInsert(lib_url, *this);
  ASSERT(!present);
  object_store->set_libraries_map(map.Release());
}

void Library::RegisterLibraries(Thread* thread,
                                const GrowableObjectArray& libs) {
  Zone* zone = thread->zone();
  auto isolate_group = thread->isolate_group();
  Library& lib = Library::Handle(zone);
  String& lib_url = String::Handle(zone);

  LibraryLookupMap map(HashTables::New<LibraryLookupMap>(16, Heap::kOld));

  intptr_t len = libs.Length();
  for (intptr_t i = 0; i < len; i++) {
    lib ^= libs.At(i);
    lib_url = lib.url();
    map.InsertNewOrGetValue(lib_url, lib);
  }
  // Now remember these in the isolate's object store.
  isolate_group->object_store()->set_libraries(libs);
  isolate_group->object_store()->set_libraries_map(map.Release());
}

LibraryPtr Library::AsyncLibrary() {
  return IsolateGroup::Current()->object_store()->async_library();
}

LibraryPtr Library::ConcurrentLibrary() {
  return IsolateGroup::Current()->object_store()->concurrent_library();
}

LibraryPtr Library::ConvertLibrary() {
  return IsolateGroup::Current()->object_store()->convert_library();
}

LibraryPtr Library::CoreLibrary() {
  return IsolateGroup::Current()->object_store()->core_library();
}

LibraryPtr Library::CollectionLibrary() {
  return IsolateGroup::Current()->object_store()->collection_library();
}

LibraryPtr Library::CompactHashLibrary() {
  return IsolateGroup::Current()->object_store()->_compact_hash_library();
}

LibraryPtr Library::DeveloperLibrary() {
  return IsolateGroup::Current()->object_store()->developer_library();
}

LibraryPtr Library::FfiLibrary() {
  return IsolateGroup::Current()->object_store()->ffi_library();
}

LibraryPtr Library::InternalLibrary() {
  return IsolateGroup::Current()->object_store()->_internal_library();
}

LibraryPtr Library::VMLibrary() {
  return IsolateGroup::Current()->object_store()->_vm_library();
}

LibraryPtr Library::IsolateLibrary() {
  return IsolateGroup::Current()->object_store()->isolate_library();
}

LibraryPtr Library::MathLibrary() {
  return IsolateGroup::Current()->object_store()->math_library();
}

#if !defined(DART_PRECOMPILED_RUNTIME)
LibraryPtr Library::MirrorsLibrary() {
  return IsolateGroup::Current()->object_store()->mirrors_library();
}
#endif

LibraryPtr Library::NativeWrappersLibrary() {
  return IsolateGroup::Current()->object_store()->native_wrappers_library();
}

LibraryPtr Library::TypedDataLibrary() {
  return IsolateGroup::Current()->object_store()->typed_data_library();
}

LibraryPtr Library::VMServiceLibrary() {
  return IsolateGroup::Current()->object_store()->_vmservice_library();
}

const char* Library::ToCString() const {
  NoSafepointScope no_safepoint;
  const String& name = String::Handle(url());
  return OS::SCreate(Thread::Current()->zone(), "Library:'%s'",
                     name.ToCString());
}

LibraryPtr LibraryPrefix::GetLibrary(int index) const {
  if ((index >= 0) || (index < num_imports())) {
    const Array& imports = Array::Handle(this->imports());
    Namespace& import = Namespace::Handle();
    import ^= imports.At(index);
    return import.target();
  }
  return Library::null();
}

void LibraryPrefix::AddImport(const Namespace& import) const {
  intptr_t num_current_imports = num_imports();

  // Prefixes with deferred libraries can only contain one library.
  ASSERT((num_current_imports == 0) || !is_deferred_load());

  // The library needs to be added to the list.
  Array& imports = Array::Handle(this->imports());
  const intptr_t length = (imports.IsNull()) ? 0 : imports.Length();
  // Grow the list if it is full.
  if (num_current_imports >= length) {
    const intptr_t new_length = length + kIncrementSize + (length >> 2);
    imports = Array::Grow(imports, new_length, Heap::kOld);
    set_imports(imports);
  }
  imports.SetAt(num_current_imports, import);
  set_num_imports(num_current_imports + 1);
}

LibraryPrefixPtr LibraryPrefix::New() {
  return Object::Allocate<LibraryPrefix>(Heap::kOld);
}

LibraryPrefixPtr LibraryPrefix::New(const String& name,
                                    const Namespace& import,
                                    bool deferred_load,
                                    const Library& importer) {
  const LibraryPrefix& result = LibraryPrefix::Handle(LibraryPrefix::New());
  result.set_name(name);
  result.set_num_imports(0);
  result.set_importer(importer);
  result.StoreNonPointer(&result.untag()->is_deferred_load_, deferred_load);
  result.set_imports(Array::Handle(Array::New(kInitialSize)));
  result.AddImport(import);
  return result.ptr();
}

void LibraryPrefix::set_name(const String& value) const {
  ASSERT(value.IsSymbol());
  untag()->set_name(value.ptr());
}

void LibraryPrefix::set_imports(const Array& value) const {
  untag()->set_imports(value.ptr());
}

void LibraryPrefix::set_num_imports(intptr_t value) const {
  if (!Utils::IsUint(16, value)) {
    ReportTooManyImports(Library::Handle(importer()));
  }
  StoreNonPointer(&untag()->num_imports_, value);
}

void LibraryPrefix::set_importer(const Library& value) const {
  untag()->set_importer(value.ptr());
}

const char* LibraryPrefix::ToCString() const {
  const String& prefix = String::Handle(name());
  return prefix.ToCString();
}

const char* Namespace::ToCString() const {
  const Library& lib = Library::Handle(target());
  return OS::SCreate(Thread::Current()->zone(), "Namespace for library '%s'",
                     lib.ToCString());
}

bool Namespace::HidesName(const String& name) const {
  // Quick check for common case with no combinators.
  if (hide_names() == show_names()) {
    ASSERT(hide_names() == Array::null());
    return false;
  }
  const String* plain_name = &name;
  if (Field::IsGetterName(name)) {
    plain_name = &String::Handle(Field::NameFromGetter(name));
  } else if (Field::IsSetterName(name)) {
    plain_name = &String::Handle(Field::NameFromSetter(name));
  }
  // Check whether the name is in the list of explicitly hidden names.
  if (hide_names() != Array::null()) {
    const Array& names = Array::Handle(hide_names());
    String& hidden = String::Handle();
    intptr_t num_names = names.Length();
    for (intptr_t i = 0; i < num_names; i++) {
      hidden ^= names.At(i);
      if (plain_name->Equals(hidden)) {
        return true;
      }
    }
  }
  // The name is not explicitly hidden. Now check whether it is in the
  // list of explicitly visible names, if there is one.
  if (show_names() != Array::null()) {
    const Array& names = Array::Handle(show_names());
    String& shown = String::Handle();
    intptr_t num_names = names.Length();
    for (intptr_t i = 0; i < num_names; i++) {
      shown ^= names.At(i);
      if (plain_name->Equals(shown)) {
        return false;
      }
    }
    // There is a list of visible names. The name we're looking for is not
    // contained in the list, so it is hidden.
    return true;
  }
  // The name is not filtered out.
  return false;
}

// Look up object with given name in library and filter out hidden
// names. Also look up getters and setters.
ObjectPtr Namespace::Lookup(const String& name,
                            ZoneGrowableArray<intptr_t>* trail) const {
  Zone* zone = Thread::Current()->zone();
  const Library& lib = Library::Handle(zone, target());

  if (trail != nullptr) {
    // Look for cycle in reexport graph.
    for (int i = 0; i < trail->length(); i++) {
      if (trail->At(i) == lib.index()) {
        for (int j = i + 1; j < trail->length(); j++) {
          (*trail)[j] = -1;
        }
        return Object::null();
      }
    }
  }

  lib.EnsureTopLevelClassIsFinalized();

  intptr_t ignore = 0;
  // Lookup the name in the library's symbols.
  Object& obj = Object::Handle(zone, lib.LookupEntry(name, &ignore));
  if (!Field::IsGetterName(name) && !Field::IsSetterName(name) &&
      (obj.IsNull() || obj.IsLibraryPrefix())) {
    String& accessor_name = String::Handle(zone);
    accessor_name = Field::LookupGetterSymbol(name);
    if (!accessor_name.IsNull()) {
      obj = lib.LookupEntry(accessor_name, &ignore);
    }
    if (obj.IsNull()) {
      accessor_name = Field::LookupSetterSymbol(name);
      if (!accessor_name.IsNull()) {
        obj = lib.LookupEntry(accessor_name, &ignore);
      }
    }
  }

  // Library prefixes are not exported.
  if (obj.IsNull() || obj.IsLibraryPrefix()) {
    // Lookup in the re-exported symbols.
    obj = lib.LookupReExport(name, trail);
    if (obj.IsNull() && !Field::IsSetterName(name)) {
      // LookupReExport() only returns objects that match the given name.
      // If there is no field/func/getter, try finding a setter.
      const String& setter_name =
          String::Handle(zone, Field::LookupSetterSymbol(name));
      if (!setter_name.IsNull()) {
        obj = lib.LookupReExport(setter_name, trail);
      }
    }
  }
  if (obj.IsNull() || HidesName(name) || obj.IsLibraryPrefix()) {
    return Object::null();
  }
  return obj.ptr();
}

NamespacePtr Namespace::New() {
  ASSERT(IsolateGroup::Current()->class_table()->At(kNamespaceCid) !=
         Class::null());
  return Object::Allocate<Namespace>(Heap::kOld);
}

NamespacePtr Namespace::New(const Library& target,
                            const Array& show_names,
                            const Array& hide_names,
                            const Library& owner) {
  ASSERT(show_names.IsNull() || (show_names.Length() > 0));
  ASSERT(hide_names.IsNull() || (hide_names.Length() > 0));
  const Namespace& result = Namespace::Handle(Namespace::New());
  result.untag()->set_target(target.ptr());
  result.untag()->set_show_names(show_names.ptr());
  result.untag()->set_hide_names(hide_names.ptr());
  result.untag()->set_owner(owner.ptr());
  return result.ptr();
}

KernelProgramInfoPtr KernelProgramInfo::New() {
  return Object::Allocate<KernelProgramInfo>(Heap::kOld);
}

KernelProgramInfoPtr KernelProgramInfo::New(
    const TypedDataBase& kernel_component,
    const TypedDataView& string_data,
    const TypedDataView& metadata_payloads,
    const TypedDataView& metadata_mappings,
    const TypedDataView& constants_table,
    const TypedData& string_offsets,
    const TypedData& canonical_names,
    const Array& scripts,
    const Array& libraries_cache,
    const Array& classes_cache) {
  ASSERT(kernel_component.IsExternalOrExternalView());
  ASSERT(string_data.IsExternalOrExternalView());
  ASSERT(metadata_payloads.IsExternalOrExternalView());
  ASSERT(metadata_mappings.IsExternalOrExternalView());
  ASSERT(constants_table.IsExternalOrExternalView());

  const auto& info = KernelProgramInfo::Handle(KernelProgramInfo::New());
  info.untag()->set_kernel_component(kernel_component.ptr());
  info.untag()->set_string_offsets(string_offsets.ptr());
  info.untag()->set_string_data(string_data.ptr());
  info.untag()->set_canonical_names(canonical_names.ptr());
  info.untag()->set_metadata_payloads(metadata_payloads.ptr());
  info.untag()->set_metadata_mappings(metadata_mappings.ptr());
  info.untag()->set_scripts(scripts.ptr());
  info.untag()->set_constants_table(constants_table.ptr());
  info.untag()->set_libraries_cache(libraries_cache.ptr());
  info.untag()->set_classes_cache(classes_cache.ptr());
  return info.ptr();
}

const char* KernelProgramInfo::ToCString() const {
  return "[KernelProgramInfo]";
}

ScriptPtr KernelProgramInfo::ScriptAt(intptr_t index) const {
  const Array& all_scripts = Array::Handle(scripts());
  ObjectPtr script = all_scripts.At(index);
  return Script::RawCast(script);
}

void KernelProgramInfo::set_scripts(const Array& scripts) const {
  untag()->set_scripts(scripts.ptr());
}

void KernelProgramInfo::set_constants(const Array& constants) const {
  untag()->set_constants(constants.ptr());
}

intptr_t KernelProgramInfo::KernelLibraryStartOffset(
    intptr_t library_index) const {
  const auto& blob = TypedDataBase::Handle(kernel_component());
  const intptr_t library_count =
      Utils::BigEndianToHost32(LoadUnaligned(reinterpret_cast<uint32_t*>(
          blob.DataAddr(blob.LengthInBytes() - 2 * 4))));
  ASSERT((library_index >= 0) && (library_index < library_count));
  const intptr_t library_start =
      Utils::BigEndianToHost32(LoadUnaligned(reinterpret_cast<uint32_t*>(
          blob.DataAddr(blob.LengthInBytes() -
                        (2 + 1 + (library_count - library_index)) * 4))));
  return library_start;
}

TypedDataViewPtr KernelProgramInfo::KernelLibrary(
    intptr_t library_index) const {
  ASSERT(library_index >= 0);
  const intptr_t start_offset = KernelLibraryStartOffset(library_index);
  const intptr_t end_offset = KernelLibraryEndOffset(library_index);
  const auto& component = TypedDataBase::Handle(kernel_component());
  return component.ViewFromTo(start_offset, end_offset);
}

intptr_t KernelProgramInfo::KernelLibraryEndOffset(
    intptr_t library_index) const {
  const auto& blob = TypedDataBase::Handle(kernel_component());
  const intptr_t library_count =
      Utils::BigEndianToHost32(LoadUnaligned(reinterpret_cast<uint32_t*>(
          blob.DataAddr(blob.LengthInBytes() - 2 * 4))));
  ASSERT((library_index >= 0) && (library_index < library_count));
  const intptr_t library_end = Utils::BigEndianToHost32(
      LoadUnaligned(reinterpret_cast<uint32_t*>(blob.DataAddr(
          blob.LengthInBytes() - (2 + (library_count - library_index)) * 4))));
  return library_end;
}

void KernelProgramInfo::set_constants_table(const TypedDataView& value) const {
  untag()->set_constants_table(value.ptr());
}

void KernelProgramInfo::set_libraries_cache(const Array& cache) const {
  untag()->set_libraries_cache(cache.ptr());
}

LibraryPtr KernelProgramInfo::LookupLibrary(Thread* thread,
                                            const Smi& name_index) const {
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_LIBRARY_HANDLESCOPE(thread);
  REUSABLE_OBJECT_HANDLESCOPE(thread);
  REUSABLE_SMI_HANDLESCOPE(thread);
  Array& data = thread->ArrayHandle();
  Library& result = thread->LibraryHandle();
  Object& key = thread->ObjectHandle();
  Smi& value = thread->SmiHandle();
  {
    SafepointMutexLocker ml(
        thread->isolate_group()->kernel_data_lib_cache_mutex());
    data = libraries_cache();
    ASSERT(!data.IsNull());
    IntHashMap table(&key, &value, &data);
    result ^= table.GetOrNull(name_index);
    table.Release();
  }
  return result.ptr();
}

LibraryPtr KernelProgramInfo::InsertLibrary(Thread* thread,
                                            const Smi& name_index,
                                            const Library& lib) const {
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_LIBRARY_HANDLESCOPE(thread);
  REUSABLE_OBJECT_HANDLESCOPE(thread);
  REUSABLE_SMI_HANDLESCOPE(thread);
  Array& data = thread->ArrayHandle();
  Library& result = thread->LibraryHandle();
  Object& key = thread->ObjectHandle();
  Smi& value = thread->SmiHandle();
  {
    SafepointMutexLocker ml(
        thread->isolate_group()->kernel_data_lib_cache_mutex());
    data = libraries_cache();
    ASSERT(!data.IsNull());
    IntHashMap table(&key, &value, &data);
    result ^= table.InsertOrGetValue(name_index, lib);
    set_libraries_cache(table.Release());
  }
  return result.ptr();
}

void KernelProgramInfo::set_classes_cache(const Array& cache) const {
  untag()->set_classes_cache(cache.ptr());
}

ClassPtr KernelProgramInfo::LookupClass(Thread* thread,
                                        const Smi& name_index) const {
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_CLASS_HANDLESCOPE(thread);
  REUSABLE_OBJECT_HANDLESCOPE(thread);
  REUSABLE_SMI_HANDLESCOPE(thread);
  Array& data = thread->ArrayHandle();
  Class& result = thread->ClassHandle();
  Object& key = thread->ObjectHandle();
  Smi& value = thread->SmiHandle();
  {
    SafepointMutexLocker ml(
        thread->isolate_group()->kernel_data_class_cache_mutex());
    data = classes_cache();
    ASSERT(!data.IsNull());
    IntHashMap table(&key, &value, &data);
    result ^= table.GetOrNull(name_index);
    table.Release();
  }
  return result.ptr();
}

ClassPtr KernelProgramInfo::InsertClass(Thread* thread,
                                        const Smi& name_index,
                                        const Class& klass) const {
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  REUSABLE_CLASS_HANDLESCOPE(thread);
  REUSABLE_OBJECT_HANDLESCOPE(thread);
  REUSABLE_SMI_HANDLESCOPE(thread);
  Array& data = thread->ArrayHandle();
  Class& result = thread->ClassHandle();
  Object& key = thread->ObjectHandle();
  Smi& value = thread->SmiHandle();
  {
    SafepointMutexLocker ml(
        thread->isolate_group()->kernel_data_class_cache_mutex());
    data = classes_cache();
    ASSERT(!data.IsNull());
    IntHashMap table(&key, &value, &data);
    result ^= table.InsertOrGetValue(name_index, klass);
    set_classes_cache(table.Release());
  }
  return result.ptr();
}

ErrorPtr Library::CompileAll(bool ignore_error /* = false */) {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  Error& error = Error::Handle(zone);
  const GrowableObjectArray& libs = GrowableObjectArray::Handle(
      IsolateGroup::Current()->object_store()->libraries());
  Library& lib = Library::Handle(zone);
  Class& cls = Class::Handle(zone);
  for (int i = 0; i < libs.Length(); i++) {
    lib ^= libs.At(i);
    ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
    while (it.HasNext()) {
      cls = it.GetNextClass();
      error = cls.EnsureIsFinalized(thread);
      if (!error.IsNull()) {
        if (ignore_error) continue;
        return error.ptr();
      }
      error = Compiler::CompileAllFunctions(cls);
      if (!error.IsNull()) {
        if (ignore_error) continue;
        return error.ptr();
      }
    }
  }

  Object& result = Object::Handle(zone);
  ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& func) {
    if (!func.HasCode()) {
      result = Compiler::CompileFunction(thread, func);
      if (result.IsError()) {
        error = Error::Cast(result).ptr();
        return false;  // Stop iteration.
      }
    }
    return true;  // Continue iteration.
  });
  return error.ptr();
}

#if !defined(DART_PRECOMPILED_RUNTIME)

ErrorPtr Library::FinalizeAllClasses() {
  Thread* thread = Thread::Current();
  ASSERT(thread->IsDartMutatorThread());
  Zone* zone = thread->zone();
  Error& error = Error::Handle(zone);
  const GrowableObjectArray& libs = GrowableObjectArray::Handle(
      IsolateGroup::Current()->object_store()->libraries());
  Library& lib = Library::Handle(zone);
  Class& cls = Class::Handle(zone);
  for (int i = 0; i < libs.Length(); i++) {
    lib ^= libs.At(i);
    if (!lib.Loaded()) {
      String& uri = String::Handle(zone, lib.url());
      String& msg = String::Handle(
          zone,
          String::NewFormatted("Library '%s' is not loaded. "
                               "Did you forget to call Dart_FinalizeLoading?",
                               uri.ToCString()));
      return ApiError::New(msg);
    }
    ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
    while (it.HasNext()) {
      cls = it.GetNextClass();
      error = cls.EnsureIsFinalized(thread);
      if (!error.IsNull()) {
        return error.ptr();
      }
    }
  }
  return Error::null();
}

#endif  // !defined(DART_PRECOMPILED_RUNTIME)

// Return Function::null() if function does not exist in libs.
FunctionPtr Library::GetFunction(const Library& lib,
                                 const char* class_name,
                                 const char* function_name) {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  Function& func = Function::Handle(zone);
  String& class_str = String::Handle(zone);
  String& func_str = String::Handle(zone);
  Class& cls = Class::Handle(zone);
  if (strcmp(class_name, "::") == 0) {
    cls = lib.toplevel_class();
  } else {
    class_str = String::New(class_name);
    cls = lib.LookupClassAllowPrivate(class_str);
  }
  if (!cls.IsNull()) {
    if (cls.EnsureIsFinalized(thread) == Error::null()) {
      func_str = String::New(function_name);
      if (function_name[0] == '.') {
        func_str = String::Concat(class_str, func_str);
      }
      func = cls.LookupFunctionAllowPrivate(func_str);
    }
  }
  return func.ptr();
}

#if defined(DEBUG) && !defined(DART_PRECOMPILED_RUNTIME)
void Library::CheckFunctionFingerprints() {
  Library& lib = Library::Handle();
  Function& func = Function::Handle();
  bool fingerprints_match = true;

#define CHECK_FINGERPRINTS_INNER(library, class_name, function_name, dest, fp, \
                                 kind)                                         \
  lib = Library::library();                                                    \
  func = Library::GetFunction(lib, #class_name, #function_name);               \
  if (func.IsNull()) {                                                         \
    fingerprints_match = false;                                                \
    OS::PrintErr("Function not found %s.%s\n", #class_name, #function_name);   \
  } else {                                                                     \
    fingerprints_match =                                                       \
        func.CheckSourceFingerprint(fp, kind) && fingerprints_match;           \
  }

#define CHECK_FINGERPRINTS(library, class_name, function_name, dest, fp)       \
  CHECK_FINGERPRINTS_INNER(library, class_name, function_name, dest, fp,       \
                           nullptr)
#define CHECK_FINGERPRINTS_ASM_INTRINSIC(library, class_name, function_name,   \
                                         dest, fp)                             \
  CHECK_FINGERPRINTS_INNER(library, class_name, function_name, dest, fp,       \
                           "asm-intrinsic")
#define CHECK_FINGERPRINTS_GRAPH_INTRINSIC(library, class_name, function_name, \
                                           dest, fp)                           \
  CHECK_FINGERPRINTS_INNER(library, class_name, function_name, dest, fp,       \
                           "graph-intrinsic")
#define CHECK_FINGERPRINTS_OTHER(library, class_name, function_name, dest, fp) \
  CHECK_FINGERPRINTS_INNER(library, class_name, function_name, dest, fp,       \
                           "other")

  POLYMORPHIC_TARGET_LIST(CHECK_FINGERPRINTS)
  ASM_INTRINSICS_LIST(CHECK_FINGERPRINTS_ASM_INTRINSIC)
  GRAPH_INTRINSICS_LIST(CHECK_FINGERPRINTS_GRAPH_INTRINSIC)
  OTHER_RECOGNIZED_LIST(CHECK_FINGERPRINTS_OTHER)

#undef CHECK_FINGERPRINTS_INNER
#undef CHECK_FINGERPRINTS
#undef CHECK_FINGERPRINTS_ASM_INTRINSIC
#undef CHECK_FINGERPRINTS_GRAPH_INTRINSIC
#undef CHECK_FINGERPRINTS_OTHER

#define CHECK_FACTORY_FINGERPRINTS(symbol, library, class_name, factory_name,  \
                                   cid, fp)                                    \
  lib = Library::library();                                                    \
  func = GetFunction(lib, #class_name, #factory_name);                         \
  if (func.IsNull()) {                                                         \
    fingerprints_match = false;                                                \
    OS::PrintErr("Function not found %s.%s.%s\n", #library, #class_name,       \
                 #factory_name);                                               \
  } else {                                                                     \
    fingerprints_match =                                                       \
        func.CheckSourceFingerprint(fp) && fingerprints_match;                 \
  }

  RECOGNIZED_LIST_FACTORY_LIST(CHECK_FACTORY_FINGERPRINTS);

#undef CHECK_FACTORY_FINGERPRINTS

  if (!fingerprints_match) {
    // Private names are mangled. Mangling depends on Library::private_key_.
    // If registering a new bootstrap library, add at the end.
    FATAL(
        "FP mismatch while recognizing methods. If the behavior of "
        "these functions has changed, then changes are also needed in "
        "the VM's compiler. Otherwise the fingerprint can simply be "
        "updated in recognized_methods_list.h\n");
  }
}
#endif  // defined(DEBUG) && !defined(DART_PRECOMPILED_RUNTIME).

InstructionsPtr Instructions::New(intptr_t size,
                                  bool has_monomorphic_entry,
                                  bool should_be_aligned) {
  ASSERT(size >= 0);
  ASSERT(IsolateGroup::Current()->class_table()->At(kInstructionsCid) !=
         Class::null());
  if (size < 0 || size > kMaxElements) {
    // This should be caught before we reach here.
    FATAL("Fatal error in Instructions::New: invalid size %" Pd "\n", size);
  }
  Instructions& result = Instructions::Handle();
  {
    auto raw = Object::Allocate<Instructions>(Heap::kCode, size);
    NoSafepointScope no_safepoint;
    result = raw;
    result.SetSize(size);
    // Set this within the NoSafepointScope as well since it is contained in
    // the same bitfield as the size.
    result.SetHasMonomorphicEntry(has_monomorphic_entry);
    result.SetShouldBeAligned(should_be_aligned);
  }
  ASSERT(result.stats() == nullptr);
  return result.ptr();
}

const char* Instructions::ToCString() const {
  return "Instructions";
}

CodeStatistics* Instructions::stats() const {
#if defined(DART_PRECOMPILER)
  return reinterpret_cast<CodeStatistics*>(
      Thread::Current()->heap()->GetPeer(ptr()));
#else
  return nullptr;
#endif
}

void Instructions::set_stats(CodeStatistics* stats) const {
#if defined(DART_PRECOMPILER)
  Thread::Current()->heap()->SetPeer(ptr(), stats);
#endif
}

const char* InstructionsSection::ToCString() const {
  return "InstructionsSection";
}

void InstructionsTable::set_length(intptr_t value) const {
  StoreNonPointer(&untag()->length_, value);
}

void InstructionsTable::set_start_pc(uword value) const {
  StoreNonPointer(&untag()->start_pc_, value);
}

void InstructionsTable::set_end_pc(uword value) const {
  StoreNonPointer(&untag()->end_pc_, value);
}

void InstructionsTable::set_code_objects(const Array& value) const {
  untag()->set_code_objects(value.ptr());
}

void InstructionsTable::set_rodata(uword value) const {
  StoreNonPointer(
      &untag()->rodata_,
      reinterpret_cast<const UntaggedInstructionsTable::Data*>(value));
}

InstructionsTablePtr InstructionsTable::New(intptr_t length,
                                            uword start_pc,
                                            uword end_pc,
                                            uword rodata) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kInstructionsTableCid) !=
         Class::null());
  ASSERT(length >= 0);
  ASSERT(start_pc <= end_pc);
  auto* const zone = Thread::Current()->zone();
  const Array& code_objects =
      (length == 0) ? Object::empty_array()
                    : Array::Handle(zone, Array::New(length, Heap::kOld));
  const auto& result = InstructionsTable::Handle(
      zone, Object::Allocate<InstructionsTable>(Heap::kOld));
  result.set_code_objects(code_objects);
  result.set_length(length);
  result.set_start_pc(start_pc);
  result.set_end_pc(end_pc);
  result.set_rodata(rodata);
  return result.ptr();
}

void InstructionsTable::SetCodeAt(intptr_t index, CodePtr code) const {
  ASSERT((0 <= index) &&
         (index < Smi::Value(code_objects()->untag()->length())));
  code_objects()->untag()->set_element(index, code);
}

bool InstructionsTable::ContainsPc(InstructionsTablePtr table, uword pc) {
  return (InstructionsTable::start_pc(table) <= pc) &&
         (pc < InstructionsTable::end_pc(table));
}

uint32_t InstructionsTable::ConvertPcToOffset(InstructionsTablePtr table,
                                              uword pc) {
  ASSERT(InstructionsTable::ContainsPc(table, pc));
  const uint32_t pc_offset =
      static_cast<uint32_t>(pc - InstructionsTable::start_pc(table));
  ASSERT(InstructionsTable::start_pc(table) + pc_offset == pc);  // No overflow.
  return pc_offset;
}

intptr_t InstructionsTable::FindEntry(InstructionsTablePtr table,
                                      uword pc,
                                      intptr_t start_index /* = 0 */) {
  // This can run in the middle of GC and must not allocate handles.
  NoSafepointScope no_safepoint;
  if (!InstructionsTable::ContainsPc(table, pc)) return -1;
  const uint32_t pc_offset = InstructionsTable::ConvertPcToOffset(table, pc);

  const auto rodata = table.untag()->rodata_;
  const auto entries = rodata->entries();
  intptr_t lo = start_index;
  intptr_t hi = rodata->length - 1;
  while (lo <= hi) {
    intptr_t mid = (hi - lo + 1) / 2 + lo;
    ASSERT(mid >= lo);
    ASSERT(mid <= hi);
    if (pc_offset < entries[mid].pc_offset) {
      hi = mid - 1;
    } else if ((mid != hi) && (pc_offset >= entries[mid + 1].pc_offset)) {
      lo = mid + 1;
    } else {
      return mid;
    }
  }
  return -1;
}

const UntaggedCompressedStackMaps::Payload*
InstructionsTable::GetCanonicalStackMap(InstructionsTablePtr table) {
  const auto rodata = table.untag()->rodata_;
  return rodata->canonical_stack_map_entries_offset != 0
             ? rodata->StackMapAt(rodata->canonical_stack_map_entries_offset)
             : nullptr;
}

const UntaggedCompressedStackMaps::Payload* InstructionsTable::FindStackMap(
    InstructionsTablePtr table,
    uword pc,
    uword* start_pc) {
  // This can run in the middle of GC and must not allocate handles.
  NoSafepointScope no_safepoint;
  const intptr_t idx = FindEntry(table, pc);
  if (idx != -1) {
    const auto rodata = table.untag()->rodata_;
    const auto entries = rodata->entries();
    *start_pc = InstructionsTable::start_pc(table) + entries[idx].pc_offset;
    return rodata->StackMapAt(entries[idx].stack_map_offset);
  }
  return nullptr;
}

CodePtr InstructionsTable::FindCode(InstructionsTablePtr table, uword pc) {
  // This can run in the middle of GC and must not allocate handles.
  NoSafepointScope no_safepoint;
  if (!InstructionsTable::ContainsPc(table, pc)) return Code::null();

  const auto rodata = table.untag()->rodata_;

  const auto pc_offset = InstructionsTable::ConvertPcToOffset(table, pc);

  if (pc_offset <= rodata->entries()[rodata->first_entry_with_code].pc_offset) {
    return StubCode::UnknownDartCode().ptr();
  }

  const auto idx =
      FindEntry(table, pc, table.untag()->rodata_->first_entry_with_code);
  if (idx != -1) {
    const intptr_t code_index = idx - rodata->first_entry_with_code;
    ASSERT(code_index >= 0);
    ASSERT(code_index <
           Smi::Value(table.untag()->code_objects()->untag()->length()));
    ObjectPtr result =
        table.untag()->code_objects()->untag()->element(code_index);
    ASSERT(result->IsCode());
    // Note: can't use Code::RawCast(...) here because it allocates handles
    // in DEBUG mode.
    return static_cast<CodePtr>(result);
  }

  return Code::null();
}

uword InstructionsTable::EntryPointAt(intptr_t code_index) const {
  ASSERT(0 <= code_index);
  ASSERT(code_index < static_cast<intptr_t>(rodata()->length));
  return InstructionsTable::start_pc(this->ptr()) +
         rodata()->entries()[code_index].pc_offset;
}

const char* InstructionsTable::ToCString() const {
  return "InstructionsTable";
}

ObjectPoolPtr ObjectPool::New(intptr_t len) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kObjectPoolCid) !=
         Class::null());
  if (len < 0 || len > kMaxElements) {
    // This should be caught before we reach here.
    FATAL("Fatal error in ObjectPool::New: invalid length %" Pd "\n", len);
  }
  // We only verify the entry bits in DEBUG, so only allocate a handle there.
  DEBUG_ONLY(auto& result = ObjectPool::Handle());
  auto raw = Object::Allocate<ObjectPool>(Heap::kOld, len);
  NoSafepointScope no_safepoint;
  raw->untag()->length_ = len;
#if defined(DEBUG)
  result = raw;
  for (intptr_t i = 0; i < len; i++) {
    // Verify that InitializeObject() already set the payload as expected.
    ASSERT_EQUAL(result.PatchableAt(i), ObjectPool::Patchability::kPatchable);
    ASSERT_EQUAL(result.TypeAt(i), ObjectPool::EntryType::kImmediate);
    ASSERT_EQUAL(result.RawValueAt(i), 0);
  }
#endif
  return raw;
}

#if !defined(DART_PRECOMPILED_RUNTIME)
ObjectPoolPtr ObjectPool::NewFromBuilder(
    const compiler::ObjectPoolBuilder& builder) {
  const intptr_t len = builder.CurrentLength();
  if (len == 0) {
    return Object::empty_object_pool().ptr();
  }
  const ObjectPool& result = ObjectPool::Handle(ObjectPool::New(len));
  for (intptr_t i = 0; i < len; i++) {
    auto entry = builder.EntryAt(i);
    auto type = entry.type();
    auto patchable = entry.patchable();
    auto snapshot_behavior = entry.snapshot_behavior();
    result.SetTypeAt(i, type, patchable, snapshot_behavior);
    if (type == EntryType::kTaggedObject) {
      result.SetObjectAt(i, *entry.obj_);
    } else {
#if defined(TARGET_ARCH_IS_32_BIT)
      ASSERT(type != EntryType::kImmediate64);
#endif
      ASSERT(type != EntryType::kImmediate128);
      result.SetRawValueAt(i, entry.imm_);
    }
  }
  return result.ptr();
}

void ObjectPool::CopyInto(compiler::ObjectPoolBuilder* builder) const {
  ASSERT(builder->CurrentLength() == 0);
  for (intptr_t i = 0; i < Length(); i++) {
    auto type = TypeAt(i);
    auto patchable = PatchableAt(i);
    auto snapshot_behavior = SnapshotBehaviorAt(i);
    switch (type) {
      case compiler::ObjectPoolBuilderEntry::kTaggedObject: {
        compiler::ObjectPoolBuilderEntry entry(&Object::ZoneHandle(ObjectAt(i)),
                                               patchable, snapshot_behavior);
        builder->AddObject(entry);
        break;
      }
      case compiler::ObjectPoolBuilderEntry::kImmediate:
      case compiler::ObjectPoolBuilderEntry::kNativeFunction: {
        compiler::ObjectPoolBuilderEntry entry(RawValueAt(i), type, patchable,
                                               snapshot_behavior);
        builder->AddObject(entry);
        break;
      }
      default:
        UNREACHABLE();
    }
  }
  ASSERT(builder->CurrentLength() == Length());
}
#endif

const char* ObjectPool::ToCString() const {
  Zone* zone = Thread::Current()->zone();
  return zone->PrintToString("ObjectPool len:%" Pd, Length());
}

void ObjectPool::DebugPrint() const {
  THR_Print("ObjectPool len:%" Pd " {\n", Length());
  for (intptr_t i = 0; i < Length(); i++) {
#if defined(DART_PRECOMPILED_RUNTIME)
    intptr_t offset = ObjectPool::element_offset(i);
#else
    intptr_t offset = compiler::target::ObjectPool::element_offset(i);
#endif
#if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64)
    THR_Print("  %" Pd "(pp) ", offset);  // PP is untagged
#elif defined(TARGET_ARCH_ARM64)
    THR_Print("  [pp, #%" Pd "] ", offset);  // PP is untagged
#elif defined(TARGET_ARCH_ARM32)
    THR_Print("  [pp, #%" Pd "] ", offset - kHeapObjectTag);  // PP is tagged
#else
    THR_Print("  [pp+0x%" Px "] ", offset - kHeapObjectTag);  // PP is tagged
#endif
    if (TypeAt(i) == EntryType::kTaggedObject) {
      const Object& obj = Object::Handle(ObjectAt(i));
      THR_Print("%s (obj)\n", obj.ToCString());
    } else if (TypeAt(i) == EntryType::kNativeFunction) {
      uword pc = RawValueAt(i);
      uintptr_t start = 0;
      const char* name = NativeSymbolResolver::LookupSymbolName(pc, &start);
      const char* dso_name;
      uword dso_base;
      if (name != nullptr) {
        THR_Print("%s (native function)\n", name);
        NativeSymbolResolver::FreeSymbolName(name);
      } else if (NativeSymbolResolver::LookupSharedObject(pc, &dso_base,
                                                          &dso_name)) {
        uword dso_offset = pc - dso_base;
        THR_Print("%s+0x%" Px " (native function)\n", dso_name, dso_offset);
        NativeSymbolResolver::FreeSymbolName(dso_name);
      } else {
        THR_Print("0x%" Px " (native function)\n", pc);
      }
    } else {
      THR_Print("0x%" Px " (raw)\n", RawValueAt(i));
    }
  }
  THR_Print("}\n");
}

intptr_t PcDescriptors::Length() const {
  return untag()->length_;
}

void PcDescriptors::SetLength(intptr_t value) const {
  StoreNonPointer(&untag()->length_, value);
}

void PcDescriptors::CopyData(const void* bytes, intptr_t size) {
  NoSafepointScope no_safepoint;
  uint8_t* data = UnsafeMutableNonPointer(&untag()->data()[0]);
  // We're guaranteed these memory spaces do not overlap.
  memcpy(data, bytes, size);  // NOLINT
}

PcDescriptorsPtr PcDescriptors::New(const void* delta_encoded_data,
                                    intptr_t size) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kPcDescriptorsCid) !=
         Class::null());
  Thread* thread = Thread::Current();
  PcDescriptors& result = PcDescriptors::Handle(thread->zone());
  {
    auto raw = Object::Allocate<PcDescriptors>(Heap::kOld, size);
    NoSafepointScope no_safepoint;
    result = raw;
    result.SetLength(size);
  }
  result.CopyData(delta_encoded_data, size);
  return result.ptr();
}

PcDescriptorsPtr PcDescriptors::New(intptr_t length) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kPcDescriptorsCid) !=
         Class::null());
  Thread* thread = Thread::Current();
  PcDescriptors& result = PcDescriptors::Handle(thread->zone());
  {
    auto raw = Object::Allocate<PcDescriptors>(Heap::kOld, length);
    NoSafepointScope no_safepoint;
    result = raw;
    result.SetLength(length);
  }
  return result.ptr();
}

const char* PcDescriptors::KindAsStr(UntaggedPcDescriptors::Kind kind) {
  switch (kind) {
    case UntaggedPcDescriptors::kDeopt:
      return "deopt        ";
    case UntaggedPcDescriptors::kIcCall:
      return "ic-call";
    case UntaggedPcDescriptors::kUnoptStaticCall:
      return "unopt-call";
    case UntaggedPcDescriptors::kRuntimeCall:
      return "runtime-call";
    case UntaggedPcDescriptors::kOsrEntry:
      return "osr-entry";
    case UntaggedPcDescriptors::kRewind:
      return "rewind";
    case UntaggedPcDescriptors::kOther:
      return "other";
    case UntaggedPcDescriptors::kAnyKind:
      UNREACHABLE();
      break;
  }
  UNREACHABLE();
  return "";
}

void PcDescriptors::WriteToBuffer(BaseTextBuffer* buffer, uword base) const {
  // 4 bits per hex digit.
  const int addr_width = kBitsPerWord / 4;
  // "*" in a printf format specifier tells it to read the field width from
  // the printf argument list.
  buffer->Printf(
      "%-*s  kind           deopt-id  tok-ix        try-ix yield-idx\n",
      addr_width, "pc");
  Iterator iter(*this, UntaggedPcDescriptors::kAnyKind);
  while (iter.MoveNext()) {
    buffer->Printf("%#-*" Px "  %-13s  % 8" Pd "  %-10s  % 8" Pd "  % 8" Pd
                   "\n",
                   addr_width, base + iter.PcOffset(), KindAsStr(iter.Kind()),
                   iter.DeoptId(), iter.TokenPos().ToCString(), iter.TryIndex(),
                   iter.YieldIndex());
  }
}

const char* PcDescriptors::ToCString() const {
  if (Length() == 0) {
    return "empty PcDescriptors";
  }
  ZoneTextBuffer buffer(Thread::Current()->zone());
  WriteToBuffer(&buffer, /*base=*/0);
  return buffer.buffer();
}

// Verify assumptions (in debug mode only).
// - No two deopt descriptors have the same deoptimization id.
// - No two ic-call descriptors have the same deoptimization id (type feedback).
// A function without unique ids is marked as non-optimizable (e.g., because of
// finally blocks).
void PcDescriptors::Verify(const Function& function) const {
#if defined(DEBUG)
  // Only check ids for unoptimized code that is optimizable.
  if (!function.IsOptimizable()) {
    return;
  }
  intptr_t max_deopt_id = 0;
  Iterator max_iter(
      *this, UntaggedPcDescriptors::kDeopt | UntaggedPcDescriptors::kIcCall);
  while (max_iter.MoveNext()) {
    if (max_iter.DeoptId() > max_deopt_id) {
      max_deopt_id = max_iter.DeoptId();
    }
  }

  Zone* zone = Thread::Current()->zone();
  BitVector* deopt_ids = new (zone) BitVector(zone, max_deopt_id + 1);
  BitVector* iccall_ids = new (zone) BitVector(zone, max_deopt_id + 1);
  Iterator iter(*this,
                UntaggedPcDescriptors::kDeopt | UntaggedPcDescriptors::kIcCall);
  while (iter.MoveNext()) {
    // 'deopt_id' is set for kDeopt and kIcCall and must be unique for one kind.
    if (DeoptId::IsDeoptAfter(iter.DeoptId())) {
      // TODO(vegorov): some instructions contain multiple calls and have
      // multiple "after" targets recorded. Right now it is benign but might
      // lead to issues in the future. Fix that and enable verification.
      continue;
    }
    if (iter.Kind() == UntaggedPcDescriptors::kDeopt) {
      ASSERT(!deopt_ids->Contains(iter.DeoptId()));
      deopt_ids->Add(iter.DeoptId());
    } else {
      ASSERT(!iccall_ids->Contains(iter.DeoptId()));
      iccall_ids->Add(iter.DeoptId());
    }
  }
#endif  // DEBUG
}

void CodeSourceMap::SetLength(intptr_t value) const {
  StoreNonPointer(&untag()->length_, value);
}

CodeSourceMapPtr CodeSourceMap::New(intptr_t length) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kCodeSourceMapCid) !=
         Class::null());
  Thread* thread = Thread::Current();
  CodeSourceMap& result = CodeSourceMap::Handle(thread->zone());
  {
    auto raw = Object::Allocate<CodeSourceMap>(Heap::kOld, length);
    NoSafepointScope no_safepoint;
    result = raw;
    result.SetLength(length);
  }
  return result.ptr();
}

const char* CodeSourceMap::ToCString() const {
  return "CodeSourceMap";
}

uword CompressedStackMaps::Hash() const {
  NoSafepointScope scope;
  uint8_t* data = UnsafeMutableNonPointer(&untag()->payload()->data()[0]);
  uint8_t* end = data + payload_size();
  uint32_t hash = payload_size();
  for (uint8_t* cursor = data; cursor < end; cursor++) {
    hash = CombineHashes(hash, *cursor);
  }
  return FinalizeHash(hash, kHashBits);
}

void CompressedStackMaps::WriteToBuffer(BaseTextBuffer* buffer,
                                        uword base,
                                        const char* separator) const {
  auto it = iterator(Thread::Current());
  bool first_entry = true;
  while (it.MoveNext()) {
    if (!first_entry) {
      buffer->AddString(separator);
    }
    buffer->Printf("0x%.8" Px ": ", base + it.pc_offset());
    for (intptr_t i = 0, n = it.Length(); i < n; i++) {
      buffer->AddString(it.IsObject(i) ? "1" : "0");
    }
    first_entry = false;
  }
}

CompressedStackMaps::Iterator<CompressedStackMaps>
CompressedStackMaps::iterator(Thread* thread) const {
  return Iterator<CompressedStackMaps>(
      *this, CompressedStackMaps::Handle(
                 thread->zone(), thread->isolate_group()
                                     ->object_store()
                                     ->canonicalized_stack_map_entries()));
}

CompressedStackMapsPtr CompressedStackMaps::New(const void* payload,
                                                intptr_t size,
                                                bool is_global_table,
                                                bool uses_global_table) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kCompressedStackMapsCid) !=
         Class::null());
  // We don't currently allow both flags to be true.
  ASSERT(!is_global_table || !uses_global_table);
  // The canonical empty instance should be used instead.
  ASSERT(size != 0);

  if (!UntaggedCompressedStackMaps::SizeField::is_valid(size)) {
    FATAL(
        "Fatal error in CompressedStackMaps::New: "
        "invalid payload size %" Pu "\n",
        size);
  }

  auto& result = CompressedStackMaps::Handle();
  {
    // CompressedStackMaps data objects are associated with a code object,
    // allocate them in old generation.
    auto raw = Object::Allocate<CompressedStackMaps>(Heap::kOld, size);
    NoSafepointScope no_safepoint;
    result = raw;
    result.untag()->payload()->set_flags_and_size(
        UntaggedCompressedStackMaps::GlobalTableBit::encode(is_global_table) |
        UntaggedCompressedStackMaps::UsesTableBit::encode(uses_global_table) |
        UntaggedCompressedStackMaps::SizeField::encode(size));
    // Perform the copy under the NoSafepointScope since it uses a raw pointer
    // to the payload, and so the object should not move during the copy.
    auto cursor =
        result.UnsafeMutableNonPointer(result.untag()->payload()->data());
    memcpy(cursor, payload, size);  // NOLINT
  }

  ASSERT(!result.IsGlobalTable() || !result.UsesGlobalTable());

  return result.ptr();
}

const char* CompressedStackMaps::ToCString() const {
  ASSERT(!IsGlobalTable());
  if (payload_size() == 0) {
    return "CompressedStackMaps()";
  }
  auto const t = Thread::Current();
  ZoneTextBuffer buffer(t->zone(), 100);
  buffer.AddString("CompressedStackMaps(");
  WriteToBuffer(&buffer, /*base=*/0, ", ");
  buffer.AddString(")");
  return buffer.buffer();
}

StringPtr LocalVarDescriptors::GetName(intptr_t var_index) const {
  ASSERT(var_index < Length());
  ASSERT(Object::Handle(ptr()->untag()->name(var_index)).IsString());
  return ptr()->untag()->name(var_index);
}

void LocalVarDescriptors::SetVar(
    intptr_t var_index,
    const String& name,
    UntaggedLocalVarDescriptors::VarInfo* info) const {
  ASSERT(var_index < Length());
  ASSERT(!name.IsNull());
  ptr()->untag()->set_name(var_index, name.ptr());
  ptr()->untag()->data()[var_index] = *info;
}

void LocalVarDescriptors::GetInfo(
    intptr_t var_index,
    UntaggedLocalVarDescriptors::VarInfo* info) const {
  ASSERT(var_index < Length());
  *info = ptr()->untag()->data()[var_index];
}

static int PrintVarInfo(char* buffer,
                        int len,
                        intptr_t i,
                        const String& var_name,
                        const UntaggedLocalVarDescriptors::VarInfo& info) {
  const UntaggedLocalVarDescriptors::VarInfoKind kind = info.kind();
  const int32_t index = info.index();
  if (kind == UntaggedLocalVarDescriptors::kContextLevel) {
    return Utils::SNPrint(buffer, len,
                          "%2" Pd
                          " %-13s level=%-3d"
                          " begin=%-3d end=%d\n",
                          i, LocalVarDescriptors::KindToCString(kind), index,
                          static_cast<int>(info.begin_pos.Pos()),
                          static_cast<int>(info.end_pos.Pos()));
  } else if (kind == UntaggedLocalVarDescriptors::kContextVar) {
    return Utils::SNPrint(
        buffer, len,
        "%2" Pd
        " %-13s level=%-3d index=%-3d"
        " begin=%-3d end=%-3d name=%s\n",
        i, LocalVarDescriptors::KindToCString(kind), info.scope_id, index,
        static_cast<int>(info.begin_pos.Pos()),
        static_cast<int>(info.end_pos.Pos()), var_name.ToCString());
  } else {
    return Utils::SNPrint(
        buffer, len,
        "%2" Pd
        " %-13s scope=%-3d index=%-3d"
        " begin=%-3d end=%-3d name=%s\n",
        i, LocalVarDescriptors::KindToCString(kind), info.scope_id, index,
        static_cast<int>(info.begin_pos.Pos()),
        static_cast<int>(info.end_pos.Pos()), var_name.ToCString());
  }
}

const char* LocalVarDescriptors::ToCString() const {
  if (IsNull()) {
    return "LocalVarDescriptors: null";
  }
  if (Length() == 0) {
    return "empty LocalVarDescriptors";
  }
  intptr_t len = 1;  // Trailing '\0'.
  String& var_name = String::Handle();
  for (intptr_t i = 0; i < Length(); i++) {
    UntaggedLocalVarDescriptors::VarInfo info;
    var_name = GetName(i);
    GetInfo(i, &info);
    len += PrintVarInfo(nullptr, 0, i, var_name, info);
  }
  char* buffer = Thread::Current()->zone()->Alloc<char>(len + 1);
  buffer[0] = '\0';
  intptr_t num_chars = 0;
  for (intptr_t i = 0; i < Length(); i++) {
    UntaggedLocalVarDescriptors::VarInfo info;
    var_name = GetName(i);
    GetInfo(i, &info);
    num_chars += PrintVarInfo((buffer + num_chars), (len - num_chars), i,
                              var_name, info);
  }
  return buffer;
}

const char* LocalVarDescriptors::KindToCString(
    UntaggedLocalVarDescriptors::VarInfoKind kind) {
  switch (kind) {
    case UntaggedLocalVarDescriptors::kStackVar:
      return "StackVar";
    case UntaggedLocalVarDescriptors::kContextVar:
      return "ContextVar";
    case UntaggedLocalVarDescriptors::kContextLevel:
      return "ContextLevel";
    case UntaggedLocalVarDescriptors::kSavedCurrentContext:
      return "CurrentCtx";
    default:
      UNIMPLEMENTED();
      return nullptr;
  }
}

LocalVarDescriptorsPtr LocalVarDescriptors::New(intptr_t num_variables) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kLocalVarDescriptorsCid) !=
         Class::null());
  if (num_variables < 0 || num_variables > kMaxElements) {
    // This should be caught before we reach here.
    FATAL(
        "Fatal error in LocalVarDescriptors::New: "
        "invalid num_variables %" Pd ". Maximum is: %d\n",
        num_variables, UntaggedLocalVarDescriptors::VarInfo::kMaxIndex);
  }
  auto raw = Object::Allocate<LocalVarDescriptors>(Heap::kOld, num_variables);
  NoSafepointScope no_safepoint;
  raw->untag()->num_entries_ = num_variables;
  return raw;
}

intptr_t LocalVarDescriptors::Length() const {
  return untag()->num_entries_;
}

intptr_t ExceptionHandlers::num_entries() const {
  return untag()->num_entries();
}

bool ExceptionHandlers::has_async_handler() const {
  return UntaggedExceptionHandlers::AsyncHandlerBit::decode(
      untag()->packed_fields_);
}

void ExceptionHandlers::set_has_async_handler(bool value) const {
  StoreNonPointer(&untag()->packed_fields_,
                  UntaggedExceptionHandlers::AsyncHandlerBit::update(
                      value, untag()->packed_fields_));
}

void ExceptionHandlers::SetHandlerInfo(intptr_t try_index,
                                       intptr_t outer_try_index,
                                       uword handler_pc_offset,
                                       bool needs_stacktrace,
                                       bool has_catch_all,
                                       bool is_generated) const {
  ASSERT((try_index >= 0) && (try_index < num_entries()));
  NoSafepointScope no_safepoint;
  ExceptionHandlerInfo* info =
      UnsafeMutableNonPointer(&untag()->data()[try_index]);
  info->outer_try_index = outer_try_index;
  // Some C compilers warn about the comparison always being true when using <=
  // due to limited range of data type.
  ASSERT((handler_pc_offset == static_cast<uword>(kMaxUint32)) ||
         (handler_pc_offset < static_cast<uword>(kMaxUint32)));
  info->handler_pc_offset = handler_pc_offset;
  info->needs_stacktrace = static_cast<int8_t>(needs_stacktrace);
  info->has_catch_all = static_cast<int8_t>(has_catch_all);
  info->is_generated = static_cast<int8_t>(is_generated);
}

void ExceptionHandlers::GetHandlerInfo(intptr_t try_index,
                                       ExceptionHandlerInfo* info) const {
  ASSERT((try_index >= 0) && (try_index < num_entries()));
  ASSERT(info != nullptr);
  *info = untag()->data()[try_index];
}

uword ExceptionHandlers::HandlerPCOffset(intptr_t try_index) const {
  ASSERT((try_index >= 0) && (try_index < num_entries()));
  return untag()->data()[try_index].handler_pc_offset;
}

intptr_t ExceptionHandlers::OuterTryIndex(intptr_t try_index) const {
  ASSERT((try_index >= 0) && (try_index < num_entries()));
  return untag()->data()[try_index].outer_try_index;
}

bool ExceptionHandlers::NeedsStackTrace(intptr_t try_index) const {
  ASSERT((try_index >= 0) && (try_index < num_entries()));
  return untag()->data()[try_index].needs_stacktrace != 0;
}

bool ExceptionHandlers::IsGenerated(intptr_t try_index) const {
  ASSERT((try_index >= 0) && (try_index < num_entries()));
  return untag()->data()[try_index].is_generated != 0;
}

bool ExceptionHandlers::HasCatchAll(intptr_t try_index) const {
  ASSERT((try_index >= 0) && (try_index < num_entries()));
  return untag()->data()[try_index].has_catch_all != 0;
}

void ExceptionHandlers::SetHandledTypes(intptr_t try_index,
                                        const Array& handled_types) const {
  ASSERT((try_index >= 0) && (try_index < num_entries()));
  ASSERT(!handled_types.IsNull());
  const Array& handled_types_data =
      Array::Handle(untag()->handled_types_data());
  handled_types_data.SetAt(try_index, handled_types);
}

ArrayPtr ExceptionHandlers::GetHandledTypes(intptr_t try_index) const {
  ASSERT((try_index >= 0) && (try_index < num_entries()));
  Array& array = Array::Handle(untag()->handled_types_data());
  array ^= array.At(try_index);
  return array.ptr();
}

void ExceptionHandlers::set_handled_types_data(const Array& value) const {
  untag()->set_handled_types_data(value.ptr());
}

ExceptionHandlersPtr ExceptionHandlers::New(intptr_t num_handlers) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kExceptionHandlersCid) !=
         Class::null());
  if ((num_handlers < 0) || (num_handlers >= kMaxHandlers)) {
    FATAL(
        "Fatal error in ExceptionHandlers::New(): "
        "invalid num_handlers %" Pd "\n",
        num_handlers);
  }
  const Array& handled_types_data =
      (num_handlers == 0) ? Object::empty_array()
                          : Array::Handle(Array::New(num_handlers, Heap::kOld));
  return ExceptionHandlers::New(handled_types_data);
}

ExceptionHandlersPtr ExceptionHandlers::New(const Array& handled_types_data) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kExceptionHandlersCid) !=
         Class::null());
  const intptr_t num_handlers = handled_types_data.Length();
  if ((num_handlers < 0) || (num_handlers >= kMaxHandlers)) {
    FATAL(
        "Fatal error in ExceptionHandlers::New(): "
        "invalid num_handlers %" Pd "\n",
        num_handlers);
  }
  ExceptionHandlers& result = ExceptionHandlers::Handle();
  {
    auto raw = Object::Allocate<ExceptionHandlers>(Heap::kOld, num_handlers);
    NoSafepointScope no_safepoint;
    result = raw;
    result.untag()->packed_fields_ =
        UntaggedExceptionHandlers::NumEntriesBits::encode(num_handlers);
  }
  result.set_handled_types_data(handled_types_data);
  return result.ptr();
}

void ExceptionHandlers::WriteToBuffer(BaseTextBuffer* buffer,
                                      uword base) const {
  auto& handled_types = Array::Handle();
  auto& type = AbstractType::Handle();
  ExceptionHandlerInfo info;
  for (intptr_t i = 0; i < num_entries(); i++) {
    GetHandlerInfo(i, &info);
    handled_types = GetHandledTypes(i);
    const intptr_t num_types =
        handled_types.IsNull() ? 0 : handled_types.Length();
    buffer->Printf("%" Pd " => %#" Px "  (%" Pd " types) (outer %d)%s%s\n", i,
                   base + info.handler_pc_offset, num_types,
                   info.outer_try_index,
                   ((info.needs_stacktrace != 0) ? " (needs stack trace)" : ""),
                   ((info.is_generated != 0) ? " (generated)" : ""));
    for (int k = 0; k < num_types; k++) {
      type ^= handled_types.At(k);
      ASSERT(!type.IsNull());
      buffer->Printf("  %d. %s\n", k, type.ToCString());
    }
  }
  if (has_async_handler()) {
    buffer->AddString("<async handler>\n");
  }
}

const char* ExceptionHandlers::ToCString() const {
  if (num_entries() == 0) {
    return has_async_handler()
               ? "empty ExceptionHandlers (with <async handler>)"
               : "empty ExceptionHandlers";
  }
  ZoneTextBuffer buffer(Thread::Current()->zone());
  WriteToBuffer(&buffer, /*base=*/0);
  return buffer.buffer();
}

void SingleTargetCache::set_target(const Code& value) const {
  untag()->set_target(value.ptr());
}

const char* SingleTargetCache::ToCString() const {
  return "SingleTargetCache";
}

SingleTargetCachePtr SingleTargetCache::New() {
  return Object::Allocate<SingleTargetCache>(Heap::kOld);
}

void UnlinkedCall::set_can_patch_to_monomorphic(bool value) const {
  StoreNonPointer(&untag()->can_patch_to_monomorphic_, value);
}

uword UnlinkedCall::Hash() const {
  return String::Handle(target_name()).Hash();
}

bool UnlinkedCall::Equals(const UnlinkedCall& other) const {
  return (target_name() == other.target_name()) &&
         (arguments_descriptor() == other.arguments_descriptor()) &&
         (can_patch_to_monomorphic() == other.can_patch_to_monomorphic());
}

const char* UnlinkedCall::ToCString() const {
  return "UnlinkedCall";
}

UnlinkedCallPtr UnlinkedCall::New() {
  const auto& result =
      UnlinkedCall::Handle(Object::Allocate<UnlinkedCall>(Heap::kOld));
  result.set_can_patch_to_monomorphic(!FLAG_precompiled_mode);
  return result.ptr();
}

MonomorphicSmiableCallPtr MonomorphicSmiableCall::New(classid_t expected_cid,
                                                      const Code& target) {
  const auto& result = MonomorphicSmiableCall::Handle(
      Object::Allocate<MonomorphicSmiableCall>(Heap::kOld));
  result.StoreNonPointer(&result.untag()->expected_cid_, expected_cid);
  result.StoreNonPointer(&result.untag()->entrypoint_, target.EntryPoint());
  return result.ptr();
}

const char* MonomorphicSmiableCall::ToCString() const {
  return "MonomorphicSmiableCall";
}

const char* CallSiteData::ToCString() const {
  // CallSiteData is an abstract class.  We should never reach here.
  UNREACHABLE();
  return "CallSiteData";
}

void CallSiteData::set_target_name(const String& value) const {
  ASSERT(!value.IsNull());
  ASSERT(value.IsCanonical());
  untag()->set_target_name(value.ptr());
}

void CallSiteData::set_arguments_descriptor(const Array& value) const {
  ASSERT(!value.IsNull());
  untag()->set_args_descriptor(value.ptr());
}

#if !defined(DART_PRECOMPILED_RUNTIME)
void ICData::SetReceiversStaticType(const AbstractType& type) const {
  untag()->set_receivers_static_type(type.ptr());

  if (!type.IsNull() && type.HasTypeClass() && (NumArgsTested() == 1) &&
      type.IsInstantiated() && !type.IsFutureOrType()) {
    const Class& cls = Class::Handle(type.type_class());
    if (cls.IsGeneric()) {
      set_tracking_exactness(true);
    }
  }
}
#endif

void ICData::SetTargetAtPos(const Array& data,
                            intptr_t data_pos,
                            intptr_t num_args_tested,
                            const Function& target) {
#if !defined(DART_PRECOMPILED_RUNTIME)
  // JIT
  data.SetAt(data_pos + TargetIndexFor(num_args_tested), target);
#else
  // AOT
  ASSERT(target.HasCode());
  const Code& code = Code::Handle(target.CurrentCode());
  data.SetAt(data_pos + CodeIndexFor(num_args_tested), code);
  data.SetAt(data_pos + EntryPointIndexFor(num_args_tested), target);
#endif
}

uword ICData::Hash() const {
  return String::HashRawSymbol(target_name()) ^ deopt_id();
}

const char* ICData::ToCString() const {
  Zone* zone = Thread::Current()->zone();
  const String& name = String::Handle(zone, target_name());
  return zone->PrintToString("ICData(%s num-args: %" Pd " num-checks: %" Pd
                             " type-args-len: %" Pd ", deopt-id: %" Pd ")",
                             name.ToCString(), NumArgsTested(),
                             NumberOfChecks(), TypeArgsLen(), deopt_id());
}

FunctionPtr ICData::Owner() const {
  Object& obj = Object::Handle(untag()->owner());
  if (obj.IsNull()) {
    return Function::null();
  } else if (obj.IsFunction()) {
    return Function::Cast(obj).ptr();
  } else {
    ICData& original = ICData::Handle();
    original ^= obj.ptr();
    return original.Owner();
  }
}

ICDataPtr ICData::Original() const {
  if (IsNull()) {
    return ICData::null();
  }
  if (untag()->owner()->IsICData()) {
    return static_cast<ICDataPtr>(untag()->owner());
  }
  return this->ptr();
}

void ICData::SetOriginal(const ICData& value) const {
  ASSERT(value.IsOriginal());
  ASSERT(!value.IsNull());
  untag()->set_owner(static_cast<ObjectPtr>(value.ptr()));
}

void ICData::set_owner(const Function& value) const {
  untag()->set_owner(static_cast<ObjectPtr>(value.ptr()));
}

void ICData::set_deopt_id(intptr_t value) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  ASSERT(value <= kMaxInt32);
  StoreNonPointer(&untag()->deopt_id_, value);
#endif
}

void ICData::set_entries(const Array& value) const {
  ASSERT(!value.IsNull());
  untag()->set_entries<std::memory_order_release>(value.ptr());
}

intptr_t ICData::NumArgsTested() const {
  return untag()->state_bits_.Read<NumArgsTestedBits>();
}

void ICData::SetNumArgsTested(intptr_t value) const {
  ASSERT(Utils::IsUint(2, value));
  untag()->state_bits_.Update<NumArgsTestedBits>(value);
}

intptr_t CallSiteData::TypeArgsLen() const {
  ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor()));
  return args_desc.TypeArgsLen();
}

intptr_t CallSiteData::CountWithTypeArgs() const {
  ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor()));
  return args_desc.CountWithTypeArgs();
}

intptr_t CallSiteData::CountWithoutTypeArgs() const {
  ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor()));
  return args_desc.Count();
}

intptr_t CallSiteData::SizeWithoutTypeArgs() const {
  ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor()));
  return args_desc.Size();
}

intptr_t CallSiteData::SizeWithTypeArgs() const {
  ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor()));
  return args_desc.SizeWithTypeArgs();
}

uint32_t ICData::DeoptReasons() const {
  return untag()->state_bits_.Read<DeoptReasonBits>();
}

void ICData::SetDeoptReasons(uint32_t reasons) const {
  untag()->state_bits_.Update<DeoptReasonBits>(reasons);
}

bool ICData::HasDeoptReason(DeoptReasonId reason) const {
  ASSERT(reason <= kLastRecordedDeoptReason);
  return (DeoptReasons() & (1 << reason)) != 0;
}

void ICData::AddDeoptReason(DeoptReasonId reason) const {
  if (reason <= kLastRecordedDeoptReason) {
    untag()->state_bits_.FetchOr<DeoptReasonBits>(1 << reason);
  }
}

ICData::RebindRule ICData::rebind_rule() const {
  return RebindRule(untag()->state_bits_.Read<RebindRuleBits>());
}

void ICData::set_rebind_rule(uint32_t rebind_rule) const {
  untag()->state_bits_.Update<ICData::RebindRuleBits>(rebind_rule);
}

bool ICData::is_static_call() const {
  return rebind_rule() != kInstance;
}

void ICData::clear_state_bits() const {
  untag()->state_bits_ = 0;
}

intptr_t ICData::TestEntryLengthFor(intptr_t num_args,
                                    bool tracking_exactness) {
  return num_args + 1 /* target function*/ + 1 /* frequency */ +
         (tracking_exactness ? 1 : 0) /* exactness state */;
}

intptr_t ICData::TestEntryLength() const {
  return TestEntryLengthFor(NumArgsTested(), is_tracking_exactness());
}

intptr_t ICData::Length() const {
  return (Smi::Value(entries()->untag()->length()) / TestEntryLength());
}

intptr_t ICData::NumberOfChecks() const {
  DEBUG_ONLY(AssertInvariantsAreSatisfied());
  return Length() - 1;
}

bool ICData::NumberOfChecksIs(intptr_t n) const {
  DEBUG_ONLY(AssertInvariantsAreSatisfied());
  return NumberOfChecks() == n;
}

#if defined(DEBUG)
void ICData::AssertInvariantsAreSatisfied() const {
  // See layout and invariant of [ICData] in class comment in object.h.
  //
  // This method can be called without holding any locks, it will grab a
  // snapshot of `entries()` and do it's verification logic on that.
  auto zone = Thread::Current()->zone();
  const auto& array = Array::Handle(zone, entries());

  const intptr_t entry_length = TestEntryLength();
  const intptr_t num_checks = array.Length() / entry_length - 1;
  const intptr_t num_args = NumArgsTested();

  /// Backing store must be multiple of entry length.
  ASSERT((array.Length() % entry_length) == 0);

  /// Entries must be valid.
  for (intptr_t i = 0; i < num_checks; ++i) {
    // Should be valid entry.
    const intptr_t start = entry_length * i;
    for (intptr_t i = 0; i < num_args; ++i) {
      ASSERT(!array.At(start + i)->IsHeapObject());
      ASSERT(array.At(start + i) != smi_illegal_cid().ptr());
    }
    ASSERT(array.At(start + TargetIndexFor(num_args))->IsHeapObject());
    if (is_tracking_exactness()) {
      ASSERT(!array.At(start + ExactnessIndexFor(num_args))->IsHeapObject());
    }
  }

  /// Sentinel at end must be valid.
  const intptr_t sentinel_start = num_checks * entry_length;
  for (intptr_t i = 0; i < entry_length - 1; ++i) {
    ASSERT(array.At(sentinel_start + i) == smi_illegal_cid().ptr());
  }
  if (num_checks == 0) {
    ASSERT(array.At(sentinel_start + entry_length - 1) ==
           smi_illegal_cid().ptr());
    ASSERT(ICData::CachedEmptyICDataArray(num_args, is_tracking_exactness()) ==
           array.ptr());
  } else {
    ASSERT(array.At(sentinel_start + entry_length - 1) == ptr());
  }

  // Invariants for ICData of static calls.
  if (num_args == 0) {
    ASSERT(Length() == 2);
    ASSERT(TestEntryLength() == 2);
  }
}
#endif  // defined(DEBUG)

// Discounts any checks with usage of zero.
intptr_t ICData::NumberOfUsedChecks() const {
  const intptr_t n = NumberOfChecks();
  intptr_t count = 0;
  for (intptr_t i = 0; i < n; i++) {
    if (GetCountAt(i) > 0) {
      count++;
    }
  }
  return count;
}

void ICData::WriteSentinel(const Array& data,
                           intptr_t test_entry_length,
                           const Object& back_ref) {
  ASSERT(!data.IsNull());
  RELEASE_ASSERT(smi_illegal_cid().Value() == kIllegalCid);
  const intptr_t entry_start = data.Length() - test_entry_length;
  for (intptr_t i = 0; i < test_entry_length - 1; i++) {
    data.SetAt(entry_start + i, smi_illegal_cid());
  }
  data.SetAt(entry_start + test_entry_length - 1, back_ref);
}

#if defined(DEBUG)
// Used in asserts to verify that a check is not added twice.
bool ICData::HasCheck(const GrowableArray<intptr_t>& cids) const {
  return FindCheck(cids) != -1;
}
#endif  // DEBUG

intptr_t ICData::FindCheck(const GrowableArray<intptr_t>& cids) const {
  const intptr_t len = NumberOfChecks();
  GrowableArray<intptr_t> class_ids;
  for (intptr_t i = 0; i < len; i++) {
    GetClassIdsAt(i, &class_ids);
    bool matches = true;
    for (intptr_t k = 0; k < class_ids.length(); k++) {
      ASSERT(class_ids[k] != kIllegalCid);
      if (class_ids[k] != cids[k]) {
        matches = false;
        break;
      }
    }
    if (matches) {
      return i;
    }
  }
  return -1;
}

void ICData::TruncateTo(intptr_t num_checks,
                        const CallSiteResetter& proof_of_reload) const {
  USE(proof_of_reload);  // This method can only be called during reload.

  DEBUG_ONLY(AssertInvariantsAreSatisfied());
  ASSERT(num_checks <= NumberOfChecks());

  // Nothing to do.
  if (NumberOfChecks() == num_checks) return;

  auto thread = Thread::Current();
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  auto& array = thread->ArrayHandle();

  // If we make the ICData empty, use the pre-allocated shared backing stores.
  const intptr_t num_args = NumArgsTested();
  if (num_checks == 0) {
    array = ICData::CachedEmptyICDataArray(num_args, is_tracking_exactness());
    set_entries(array);
    return;
  }

  // Otherwise truncate array and initialize sentinel.
  // Use kSmiCid for all slots in the entry except the last, which is a backref
  // to ICData.
  const intptr_t entry_length = TestEntryLength();
  array = entries();
  array.Truncate((num_checks + 1) * entry_length);
  WriteSentinel(array, entry_length, *this);
}

void ICData::ClearCountAt(intptr_t index,
                          const CallSiteResetter& proof_of_reload) const {
  USE(proof_of_reload);  // This method can only be called during reload.

  ASSERT(index >= 0);
  ASSERT(index < NumberOfChecks());
  SetCountAt(index, 0);
}

void ICData::ClearAndSetStaticTarget(
    const Function& func,
    const CallSiteResetter& proof_of_reload) const {
  USE(proof_of_reload);  // This method can only be called during reload.

  // The final entry is always the sentinel.
  DEBUG_ONLY(AssertInvariantsAreSatisfied());

  if (IsImmutable()) return;
  if (NumberOfChecks() == 0) return;

  // Leave one entry.
  TruncateTo(/*num_checks=*/1, proof_of_reload);

  // Reinitialize the one and only entry.
  const intptr_t num_args = NumArgsTested();
  Thread* thread = Thread::Current();
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  Array& data = thread->ArrayHandle();
  data = entries();
  const Smi& object_cid = Smi::Handle(Smi::New(kObjectCid));
  for (intptr_t i = 0; i < num_args; i++) {
    data.SetAt(i, object_cid);
  }
  data.SetAt(TargetIndexFor(num_args), func);
  data.SetAt(CountIndexFor(num_args), Object::smi_zero());
}

bool ICData::ValidateInterceptor(const Function& target) const {
#if !defined(DART_PRECOMPILED_RUNTIME)
  const String& name = String::Handle(target_name());
  if (Function::IsDynamicInvocationForwarderName(name)) {
    return Function::DemangleDynamicInvocationForwarderName(name) ==
           target.name();
  }
#endif
  ObjectStore* store = IsolateGroup::Current()->object_store();
  ASSERT((target.ptr() == store->simple_instance_of_true_function()) ||
         (target.ptr() == store->simple_instance_of_false_function()));
  const String& instance_of_name = String::Handle(
      Library::PrivateCoreLibName(Symbols::_simpleInstanceOf()).ptr());
  ASSERT(target_name() == instance_of_name.ptr());
  return true;
}

void ICData::EnsureHasCheck(const GrowableArray<intptr_t>& class_ids,
                            const Function& target,
                            intptr_t count) const {
  SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());

  if (FindCheck(class_ids) != -1) return;
  AddCheckInternal(class_ids, target, count);
}

void ICData::AddCheck(const GrowableArray<intptr_t>& class_ids,
                      const Function& target,
                      intptr_t count) const {
  SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());
  AddCheckInternal(class_ids, target, count);
}

void ICData::AddCheckInternal(const GrowableArray<intptr_t>& class_ids,
                              const Function& target,
                              intptr_t count) const {
  ASSERT(
      IsolateGroup::Current()->type_feedback_mutex()->IsOwnedByCurrentThread());

  ASSERT(!is_tracking_exactness());
  ASSERT(!target.IsNull());
  ASSERT((target.name() == target_name()) || ValidateInterceptor(target));
  DEBUG_ASSERT(!HasCheck(class_ids));
  ASSERT(NumArgsTested() > 1);  // Otherwise use 'AddReceiverCheck'.
  const intptr_t num_args_tested = NumArgsTested();
  ASSERT(class_ids.length() == num_args_tested);
  const intptr_t old_num = NumberOfChecks();
  Array& data = Array::Handle(entries());

  // ICData of static calls with NumArgsTested() > 0 have initially a
  // dummy set of cids entered (see ICData::NewForStaticCall). That entry is
  // overwritten by first real type feedback data.
  if (old_num == 1 && num_args_tested == 2) {
    const bool has_dummy_entry =
        Smi::Value(Smi::RawCast(data.At(0))) == kObjectCid &&
        Smi::Value(Smi::RawCast(data.At(1))) == kObjectCid;
    if (has_dummy_entry) {
      ASSERT(target.ptr() == data.At(TargetIndexFor(num_args_tested)));
      // Replace dummy entry.
      Smi& value = Smi::Handle();
      for (intptr_t i = 0; i < NumArgsTested(); i++) {
        ASSERT(class_ids[i] != kIllegalCid);
        value = Smi::New(class_ids[i]);
        data.SetAt(i, value);
      }
      return;
    }
  }
  intptr_t index = -1;
  data = Grow(&index);
  ASSERT(!data.IsNull());
  intptr_t data_pos = index * TestEntryLength();
  Smi& value = Smi::Handle();
  for (intptr_t i = 0; i < class_ids.length(); i++) {
    // kIllegalCid is used as terminating value, do not add it.
    ASSERT(class_ids[i] != kIllegalCid);
    value = Smi::New(class_ids[i]);
    data.SetAt(data_pos + i, value);
  }
  ASSERT(!target.IsNull());
  data.SetAt(data_pos + TargetIndexFor(num_args_tested), target);
  value = Smi::New(count);
  data.SetAt(data_pos + CountIndexFor(num_args_tested), value);
  // Multithreaded access to ICData requires setting of array to be the last
  // operation.
  set_entries(data);
}

ArrayPtr ICData::Grow(intptr_t* index) const {
  DEBUG_ONLY(AssertInvariantsAreSatisfied());

  *index = NumberOfChecks();
  Array& data = Array::Handle(entries());
  const intptr_t new_len = data.Length() + TestEntryLength();
  data = Array::Grow(data, new_len, Heap::kOld);
  WriteSentinel(data, TestEntryLength(), *this);
  return data.ptr();
}

void ICData::DebugDump() const {
  const Function& owner = Function::Handle(Owner());
  THR_Print("ICData::DebugDump\n");
  THR_Print("Owner = %s [deopt=%" Pd "]\n", owner.ToCString(), deopt_id());
  THR_Print("NumArgsTested = %" Pd "\n", NumArgsTested());
  THR_Print("Length = %" Pd "\n", Length());
  THR_Print("NumberOfChecks = %" Pd "\n", NumberOfChecks());

  GrowableArray<intptr_t> class_ids;
  for (intptr_t i = 0; i < NumberOfChecks(); i++) {
    THR_Print("Check[%" Pd "]:", i);
    GetClassIdsAt(i, &class_ids);
    for (intptr_t c = 0; c < class_ids.length(); c++) {
      THR_Print(" %" Pd "", class_ids[c]);
    }
    THR_Print("--- %" Pd " hits\n", GetCountAt(i));
  }
}

void ICData::EnsureHasReceiverCheck(intptr_t receiver_class_id,
                                    const Function& target,
                                    intptr_t count,
                                    StaticTypeExactnessState exactness) const {
  SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());

  GrowableArray<intptr_t> class_ids(1);
  class_ids.Add(receiver_class_id);
  if (FindCheck(class_ids) != -1) return;

  AddReceiverCheckInternal(receiver_class_id, target, count, exactness);
}

void ICData::AddReceiverCheck(intptr_t receiver_class_id,
                              const Function& target,
                              intptr_t count,
                              StaticTypeExactnessState exactness) const {
  SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());
  AddReceiverCheckInternal(receiver_class_id, target, count, exactness);
}

void ICData::AddReceiverCheckInternal(
    intptr_t receiver_class_id,
    const Function& target,
    intptr_t count,
    StaticTypeExactnessState exactness) const {
#if defined(DEBUG)
  GrowableArray<intptr_t> class_ids(1);
  class_ids.Add(receiver_class_id);
  ASSERT(!HasCheck(class_ids));
#endif  // DEBUG
  ASSERT(!target.IsNull());
  const intptr_t kNumArgsTested = 1;
  ASSERT(NumArgsTested() == kNumArgsTested);  // Otherwise use 'AddCheck'.
  ASSERT(receiver_class_id != kIllegalCid);

  intptr_t index = -1;
  Array& data = Array::Handle(Grow(&index));
  intptr_t data_pos = index * TestEntryLength();
  if ((receiver_class_id == kSmiCid) && (data_pos > 0)) {
    ASSERT(GetReceiverClassIdAt(0) != kSmiCid);
    // Move class occupying position 0 to the data_pos.
    for (intptr_t i = 0; i < TestEntryLength(); i++) {
      data.SetAt(data_pos + i, Object::Handle(data.At(i)));
    }
    // Insert kSmiCid in position 0.
    data_pos = 0;
  }
  data.SetAt(data_pos, Smi::Handle(Smi::New(receiver_class_id)));
  SetTargetAtPos(data, data_pos, kNumArgsTested, target);

#if !defined(DART_PRECOMPILED_RUNTIME)
  data.SetAt(data_pos + CountIndexFor(kNumArgsTested),
             Smi::Handle(Smi::New(count)));
  if (is_tracking_exactness()) {
    data.SetAt(data_pos + ExactnessIndexFor(kNumArgsTested),
               Smi::Handle(Smi::New(exactness.Encode())));
  }
#endif

  // Multithreaded access to ICData requires setting of array to be the last
  // operation.
  set_entries(data);
}

StaticTypeExactnessState ICData::GetExactnessAt(intptr_t index) const {
  if (!is_tracking_exactness()) {
    return StaticTypeExactnessState::NotTracking();
  }
  Thread* thread = Thread::Current();
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  Array& data = thread->ArrayHandle();
  data = entries();
  intptr_t data_pos =
      index * TestEntryLength() + ExactnessIndexFor(NumArgsTested());
  return StaticTypeExactnessState::Decode(
      Smi::Value(Smi::RawCast(data.At(data_pos))));
}

void ICData::GetCheckAt(intptr_t index,
                        GrowableArray<intptr_t>* class_ids,
                        Function* target) const {
  ASSERT(index < NumberOfChecks());
  ASSERT(class_ids != nullptr);
  ASSERT(target != nullptr);
  class_ids->Clear();
  Thread* thread = Thread::Current();
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  Array& data = thread->ArrayHandle();
  data = entries();
  intptr_t data_pos = index * TestEntryLength();
  for (intptr_t i = 0; i < NumArgsTested(); i++) {
    class_ids->Add(Smi::Value(Smi::RawCast(data.At(data_pos + i))));
  }
  (*target) ^= data.At(data_pos + TargetIndexFor(NumArgsTested()));
}

void ICData::GetClassIdsAt(intptr_t index,
                           GrowableArray<intptr_t>* class_ids) const {
  ASSERT(index < Length());
  ASSERT(class_ids != nullptr);
  ASSERT(IsValidEntryIndex(index));
  class_ids->Clear();
  Thread* thread = Thread::Current();
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  Array& data = thread->ArrayHandle();
  data = entries();
  intptr_t data_pos = index * TestEntryLength();
  for (intptr_t i = 0; i < NumArgsTested(); i++) {
    class_ids->Add(Smi::Value(Smi::RawCast(data.At(data_pos++))));
  }
}

void ICData::GetOneClassCheckAt(intptr_t index,
                                intptr_t* class_id,
                                Function* target) const {
  ASSERT(class_id != nullptr);
  ASSERT(target != nullptr);
  ASSERT(NumArgsTested() == 1);
  Thread* thread = Thread::Current();
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  Array& data = thread->ArrayHandle();
  data = entries();
  const intptr_t data_pos = index * TestEntryLength();
  *class_id = Smi::Value(Smi::RawCast(data.At(data_pos)));
  *target ^= data.At(data_pos + TargetIndexFor(NumArgsTested()));
}

intptr_t ICData::GetCidAt(intptr_t index) const {
  ASSERT(NumArgsTested() == 1);
  Thread* thread = Thread::Current();
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  Array& data = thread->ArrayHandle();
  data = entries();
  const intptr_t data_pos = index * TestEntryLength();
  return Smi::Value(Smi::RawCast(data.At(data_pos)));
}

intptr_t ICData::GetClassIdAt(intptr_t index, intptr_t arg_nr) const {
  GrowableArray<intptr_t> class_ids;
  GetClassIdsAt(index, &class_ids);
  return class_ids[arg_nr];
}

intptr_t ICData::GetReceiverClassIdAt(intptr_t index) const {
  ASSERT(index < Length());
  ASSERT(IsValidEntryIndex(index));
  const intptr_t data_pos = index * TestEntryLength();
  NoSafepointScope no_safepoint;
  ArrayPtr raw_data = entries();
  return Smi::Value(Smi::RawCast(raw_data->untag()->element(data_pos)));
}

FunctionPtr ICData::GetTargetAt(intptr_t index) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
  return nullptr;
#else
  const intptr_t data_pos =
      index * TestEntryLength() + TargetIndexFor(NumArgsTested());
  ASSERT(Object::Handle(Array::Handle(entries()).At(data_pos)).IsFunction());

  NoSafepointScope no_safepoint;
  ArrayPtr raw_data = entries();
  return static_cast<FunctionPtr>(raw_data->untag()->element(data_pos));
#endif
}

void ICData::IncrementCountAt(intptr_t index, intptr_t value) const {
  ASSERT(0 <= value);
  ASSERT(value <= Smi::kMaxValue);
  SetCountAt(index, Utils::Minimum(GetCountAt(index) + value, Smi::kMaxValue));
}

void ICData::SetCountAt(intptr_t index, intptr_t value) const {
  ASSERT(0 <= value);
  ASSERT(value <= Smi::kMaxValue);

  Thread* thread = Thread::Current();
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  Array& data = thread->ArrayHandle();
  data = entries();
  const intptr_t data_pos =
      index * TestEntryLength() + CountIndexFor(NumArgsTested());
  data.SetAt(data_pos, Smi::Handle(Smi::New(value)));
}

intptr_t ICData::GetCountAt(intptr_t index) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
  return 0;
#else
  Thread* thread = Thread::Current();
  REUSABLE_ARRAY_HANDLESCOPE(thread);
  Array& data = thread->ArrayHandle();
  data = entries();
  const intptr_t data_pos =
      index * TestEntryLength() + CountIndexFor(NumArgsTested());
  intptr_t value = Smi::Value(Smi::RawCast(data.At(data_pos)));
  if (value >= 0) return value;

  // The counter very rarely overflows to a negative value, but if it does, we
  // would rather just reset it to zero.
  SetCountAt(index, 0);
  return 0;
#endif
}

intptr_t ICData::AggregateCount() const {
  if (IsNull()) return 0;
  const intptr_t len = NumberOfChecks();
  intptr_t count = 0;
  for (intptr_t i = 0; i < len; i++) {
    count += GetCountAt(i);
  }
  return count;
}

#if !defined(DART_PRECOMPILED_RUNTIME)
ICDataPtr ICData::AsUnaryClassChecksForArgNr(intptr_t arg_nr) const {
  ASSERT(!IsNull());
  ASSERT(NumArgsTested() > arg_nr);
  if ((arg_nr == 0) && (NumArgsTested() == 1)) {
    // Frequent case.
    return ptr();
  }
  const intptr_t kNumArgsTested = 1;
  ICData& result = ICData::Handle(ICData::NewFrom(*this, kNumArgsTested));
  const intptr_t len = NumberOfChecks();
  for (intptr_t i = 0; i < len; i++) {
    const intptr_t class_id = GetClassIdAt(i, arg_nr);
    const intptr_t count = GetCountAt(i);
    if (count == 0) {
      continue;
    }
    intptr_t duplicate_class_id = -1;
    const intptr_t result_len = result.NumberOfChecks();
    for (intptr_t k = 0; k < result_len; k++) {
      if (class_id == result.GetReceiverClassIdAt(k)) {
        duplicate_class_id = k;
        break;
      }
    }
    if (duplicate_class_id >= 0) {
      // This check is valid only when checking the receiver.
      ASSERT((arg_nr != 0) ||
             (result.GetTargetAt(duplicate_class_id) == GetTargetAt(i)));
      result.IncrementCountAt(duplicate_class_id, count);
    } else {
      // This will make sure that Smi is first if it exists.
      result.AddReceiverCheckInternal(class_id,
                                      Function::Handle(GetTargetAt(i)), count,
                                      StaticTypeExactnessState::NotTracking());
    }
  }

  return result.ptr();
}

// (cid, count) tuple used to sort ICData by count.
struct CidCount {
  CidCount(intptr_t cid_, intptr_t count_, Function* f_)
      : cid(cid_), count(count_), function(f_) {}

  static int HighestCountFirst(const CidCount* a, const CidCount* b);

  intptr_t cid;
  intptr_t count;
  Function* function;
};

int CidCount::HighestCountFirst(const CidCount* a, const CidCount* b) {
  if (a->count > b->count) {
    return -1;
  }
  return (a->count < b->count) ? 1 : 0;
}

ICDataPtr ICData::AsUnaryClassChecksSortedByCount() const {
  ASSERT(!IsNull());
  const intptr_t kNumArgsTested = 1;
  const intptr_t len = NumberOfChecks();
  if (len <= 1) {
    // No sorting needed.
    return AsUnaryClassChecks();
  }
  GrowableArray<CidCount> aggregate;
  for (intptr_t i = 0; i < len; i++) {
    const intptr_t class_id = GetClassIdAt(i, 0);
    const intptr_t count = GetCountAt(i);
    if (count == 0) {
      continue;
    }
    bool found = false;
    for (intptr_t r = 0; r < aggregate.length(); r++) {
      if (aggregate[r].cid == class_id) {
        aggregate[r].count += count;
        found = true;
        break;
      }
    }
    if (!found) {
      aggregate.Add(
          CidCount(class_id, count, &Function::ZoneHandle(GetTargetAt(i))));
    }
  }
  aggregate.Sort(CidCount::HighestCountFirst);

  ICData& result = ICData::Handle(ICData::NewFrom(*this, kNumArgsTested));
  ASSERT(result.NumberOfChecksIs(0));
  // Room for all entries and the sentinel.
  const intptr_t data_len = result.TestEntryLength() * (aggregate.length() + 1);
  // Allocate the array but do not assign it to result until we have populated
  // it with the aggregate data and the terminating sentinel.
  const Array& data = Array::Handle(Array::New(data_len, Heap::kOld));
  intptr_t pos = 0;
  for (intptr_t i = 0; i < aggregate.length(); i++) {
    data.SetAt(pos + 0, Smi::Handle(Smi::New(aggregate[i].cid)));
    data.SetAt(pos + TargetIndexFor(1), *aggregate[i].function);
    data.SetAt(pos + CountIndexFor(1),
               Smi::Handle(Smi::New(aggregate[i].count)));

    pos += result.TestEntryLength();
  }
  WriteSentinel(data, result.TestEntryLength(), result);
  result.set_entries(data);
  ASSERT(result.NumberOfChecksIs(aggregate.length()));
  return result.ptr();
}

UnlinkedCallPtr ICData::AsUnlinkedCall() const {
  ASSERT(NumArgsTested() == 1);
  ASSERT(!is_tracking_exactness());
  const UnlinkedCall& result = UnlinkedCall::Handle(UnlinkedCall::New());
  result.set_target_name(String::Handle(target_name()));
  result.set_arguments_descriptor(Array::Handle(arguments_descriptor()));
  result.set_can_patch_to_monomorphic(!FLAG_precompiled_mode ||
                                      receiver_cannot_be_smi());
  return result.ptr();
}

bool ICData::HasReceiverClassId(intptr_t class_id) const {
  ASSERT(NumArgsTested() > 0);
  const intptr_t len = NumberOfChecks();
  for (intptr_t i = 0; i < len; i++) {
    if (IsUsedAt(i)) {
      const intptr_t test_class_id = GetReceiverClassIdAt(i);
      if (test_class_id == class_id) {
        return true;
      }
    }
  }
  return false;
}
#endif

bool ICData::IsUsedAt(intptr_t i) const {
  if (GetCountAt(i) <= 0) {
    // Do not mistake unoptimized static call ICData for unused.
    // See ICData::AddTarget.
    // TODO(srdjan): Make this test more robust.
    if (NumArgsTested() > 0) {
      const intptr_t cid = GetReceiverClassIdAt(i);
      if (cid == kObjectCid) {
        return true;
      }
    }
    return false;
  }
  return true;
}

void ICData::Init() {
  for (int i = 0; i <= kCachedICDataMaxArgsTestedWithoutExactnessTracking;
       i++) {
    Roots::set_cached_icdata_array(
        kCachedICDataZeroArgTestedWithoutExactnessTrackingIdx + i,
        ICData::NewNonCachedEmptyICDataArray(i, false));
  }
  Roots::set_cached_icdata_array(kCachedICDataOneArgWithExactnessTrackingIdx,
                                 ICData::NewNonCachedEmptyICDataArray(1, true));
}

ArrayPtr ICData::NewNonCachedEmptyICDataArray(intptr_t num_args_tested,
                                              bool tracking_exactness) {
  // IC data array must be null terminated (sentinel entry).
  const intptr_t len = TestEntryLengthFor(num_args_tested, tracking_exactness);
  const Array& array = Array::Handle(Array::New(len, Heap::kOld));
  // Only empty [ICData]s are allowed to have a non-ICData backref.
  WriteSentinel(array, len, /*back_ref=*/smi_illegal_cid());
  array.MakeImmutable();
  return array.ptr();
}

ArrayPtr ICData::CachedEmptyICDataArray(intptr_t num_args_tested,
                                        bool tracking_exactness) {
  if (tracking_exactness) {
    ASSERT(num_args_tested == 1);
    return Roots::cached_icdata_array(
        kCachedICDataOneArgWithExactnessTrackingIdx);
  } else {
    ASSERT(num_args_tested >= 0);
    ASSERT(num_args_tested <=
           kCachedICDataMaxArgsTestedWithoutExactnessTracking);
    return Roots::cached_icdata_array(
        kCachedICDataZeroArgTestedWithoutExactnessTrackingIdx +
        num_args_tested);
  }
}

bool ICData::IsCachedEmptyEntry(const Array& array) {
  for (int i = 0; i < kCachedICDataArrayCount; ++i) {
    if (Roots::cached_icdata_array(i) == array.ptr()) return true;
  }
  return false;
}

// Does not initialize ICData array.
ICDataPtr ICData::NewDescriptor(Zone* zone,
                                const Function& owner,
                                const String& target_name,
                                const Array& arguments_descriptor,
                                intptr_t deopt_id,
                                intptr_t num_args_tested,
                                RebindRule rebind_rule,
                                const AbstractType& receivers_static_type) {
#if !defined(DART_PRECOMPILED_RUNTIME)
  // We should only have null owners in the precompiled runtime, if the
  // owning function for a Code object was optimized out.
  ASSERT(!owner.IsNull());
#endif
  ASSERT(!target_name.IsNull());
  ASSERT(!arguments_descriptor.IsNull());
  ASSERT(IsolateGroup::Current()->class_table()->At(kICDataCid) !=
         Class::null());
  ASSERT(num_args_tested >= 0);
  // IC data objects are long living objects, allocate them in old generation.
  const auto& result =
      ICData::Handle(zone, Object::Allocate<ICData>(Heap::kOld));
  result.set_owner(owner);
  result.set_target_name(target_name);
  result.set_arguments_descriptor(arguments_descriptor);
  NOT_IN_PRECOMPILED(result.set_deopt_id(deopt_id));
  ASSERT_EQUAL(result.untag()->state_bits_, 0);
  result.set_rebind_rule(rebind_rule);
  result.SetNumArgsTested(num_args_tested);
  NOT_IN_PRECOMPILED(result.SetReceiversStaticType(receivers_static_type));
  return result.ptr();
}

bool ICData::IsImmutable() const {
  return entries()->IsImmutableArray();
}

ICDataPtr ICData::New() {
  // IC data objects are long living objects, allocate them in old generation.
  const auto& result = ICData::Handle(Object::Allocate<ICData>(Heap::kOld));
  ASSERT_EQUAL(result.untag()->state_bits_, 0);
  result.set_deopt_id(DeoptId::kNone);
  return result.ptr();
}

ICDataPtr ICData::New(const Function& owner,
                      const String& target_name,
                      const Array& arguments_descriptor,
                      intptr_t deopt_id,
                      intptr_t num_args_tested,
                      RebindRule rebind_rule,
                      const AbstractType& receivers_static_type) {
  Zone* zone = Thread::Current()->zone();
  const ICData& result = ICData::Handle(
      zone,
      NewDescriptor(zone, owner, target_name, arguments_descriptor, deopt_id,
                    num_args_tested, rebind_rule, receivers_static_type));
  result.set_entries(Array::Handle(
      zone,
      CachedEmptyICDataArray(num_args_tested, result.is_tracking_exactness())));
  return result.ptr();
}

ICDataPtr ICData::NewWithCheck(const Function& owner,
                               const String& target_name,
                               const Array& arguments_descriptor,
                               intptr_t deopt_id,
                               intptr_t num_args_tested,
                               RebindRule rebind_rule,
                               GrowableArray<intptr_t>* cids,
                               const Function& target,
                               const AbstractType& receiver_type) {
  ASSERT((cids != nullptr) && !target.IsNull());
  ASSERT(cids->length() == num_args_tested);

  Zone* zone = Thread::Current()->zone();
  const auto& result = ICData::Handle(
      zone,
      NewDescriptor(zone, owner, target_name, arguments_descriptor, deopt_id,
                    num_args_tested, rebind_rule, receiver_type));

  const intptr_t kNumEntries = 2;  // 1 entry and a sentinel.
  const intptr_t entry_len =
      TestEntryLengthFor(num_args_tested, result.is_tracking_exactness());
  const auto& array =
      Array::Handle(zone, Array::New(kNumEntries * entry_len, Heap::kOld));

  auto& cid = Smi::Handle(zone);
  for (intptr_t i = 0; i < num_args_tested; ++i) {
    cid = Smi::New((*cids)[i]);
    array.SetAt(i, cid);
  }

  SetTargetAtPos(array, 0, num_args_tested, target);
#if !defined(DART_PRECOMPILED_RUNTIME)
  array.SetAt(CountIndexFor(num_args_tested), Object::smi_zero());
#endif
  WriteSentinel(array, entry_len, result);

  result.set_entries(array);

  return result.ptr();
}

ICDataPtr ICData::NewForStaticCall(const Function& owner,
                                   const Function& target,
                                   const Array& arguments_descriptor,
                                   intptr_t deopt_id,
                                   intptr_t num_args_tested,
                                   RebindRule rebind_rule) {
  // See `MethodRecognizer::NumArgsCheckedForStaticCall`.
  ASSERT(num_args_tested == 0 || num_args_tested == 1 || num_args_tested == 2);
  ASSERT(!target.IsNull());

  Zone* zone = Thread::Current()->zone();
  const auto& target_name = String::Handle(zone, target.name());
  GrowableArray<intptr_t> cids(num_args_tested);
  for (intptr_t i = 0; i < num_args_tested; ++i) {
    cids.Add(kObjectCid);
  }
  return ICData::NewWithCheck(owner, target_name, arguments_descriptor,
                              deopt_id, num_args_tested, rebind_rule, &cids,
                              target, Object::null_abstract_type());
}

#if !defined(DART_PRECOMPILED_RUNTIME)
ICDataPtr ICData::NewFrom(const ICData& from, intptr_t num_args_tested) {
  // See comment in [ICData::Clone] why we access the megamorphic bit first.
  const bool is_megamorphic = from.is_megamorphic();

  const ICData& result = ICData::Handle(ICData::New(
      Function::Handle(from.Owner()), String::Handle(from.target_name()),
      Array::Handle(from.arguments_descriptor()), from.deopt_id(),
      num_args_tested, from.rebind_rule(),
      AbstractType::Handle(from.receivers_static_type())));
  // Copy deoptimization reasons.
  result.SetDeoptReasons(from.DeoptReasons());
  result.set_is_megamorphic(is_megamorphic);
  return result.ptr();
}

ICDataPtr ICData::Clone(const ICData& from) {
  Zone* zone = Thread::Current()->zone();
  SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());

  // We have to check the megamorphic bit before accessing the entries of the
  // ICData to ensure all writes to the entries have been flushed and are
  // visible at this point.
  //
  // This will allow us to maintain the invariant that if the megamorphic bit is
  // set, the number of entries in the ICData have reached the limit.
  const bool is_megamorphic = from.is_megamorphic();

  const ICData& result = ICData::Handle(
      zone, ICData::NewDescriptor(
                zone, Function::Handle(zone, from.Owner()),
                String::Handle(zone, from.target_name()),
                Array::Handle(zone, from.arguments_descriptor()),
                from.deopt_id(), from.NumArgsTested(), from.rebind_rule(),
                AbstractType::Handle(zone, from.receivers_static_type())));
  // Clone entry array.
  const Array& from_array = Array::Handle(zone, from.entries());
  if (ICData::IsCachedEmptyEntry(from_array)) {
    result.set_entries(from_array);
  } else {
    const intptr_t len = from_array.Length();
    const Array& cloned_array =
        Array::Handle(zone, Array::New(len, Heap::kOld));
    Object& obj = Object::Handle(zone);
    for (intptr_t i = 0; i < len; i++) {
      obj = from_array.At(i);
      cloned_array.SetAt(i, obj);
    }
    // Update backref in our clone.
    cloned_array.SetAt(cloned_array.Length() - 1, result);
    result.set_entries(cloned_array);
  }
  // Copy deoptimization reasons.
  result.SetDeoptReasons(from.DeoptReasons());
  result.set_is_megamorphic(is_megamorphic);

  DEBUG_ONLY(result.AssertInvariantsAreSatisfied());

  return result.ptr();
}
#endif

ICDataPtr ICData::ICDataOfEntriesArray(const Array& array) {
  const auto& back_ref = Object::Handle(array.At(array.Length() - 1));
  if (back_ref.ptr() == smi_illegal_cid().ptr()) {
    ASSERT(IsCachedEmptyEntry(array));
    return ICData::null();
  }

  const auto& ic_data = ICData::Cast(back_ref);
  DEBUG_ONLY(ic_data.AssertInvariantsAreSatisfied());
  return ic_data.ptr();
}

const char* WeakSerializationReference::ToCString() const {
  return Object::Handle(target()).ToCString();
}

ObjectPtr WeakSerializationReference::New(const Object& target,
                                          const Object& replacement) {
  ASSERT(IsolateGroup::Current()->class_table()->At(
             kWeakSerializationReferenceCid) != Class::null());
  // Don't wrap any object in the VM heap, as all objects in the VM isolate
  // heap are currently serialized.
  //
  // Note that we _do_ wrap Smis if requested. Smis are serialized in the Mint
  // cluster, and so dropping them if not strongly referenced saves space in
  // the snapshot.
  // If the target is a WSR that already uses the replacement, then return it.
  if (target.IsWeakSerializationReference() &&
      WeakSerializationReference::Cast(target).replacement() ==
          replacement.ptr()) {
    return target.ptr();
  }
  const auto& result = WeakSerializationReference::Handle(
      Object::Allocate<WeakSerializationReference>(Heap::kOld));
  // Don't nest WSRs, instead just use the old WSR's target.
  result.untag()->set_target(target.IsWeakSerializationReference()
                                 ? WeakSerializationReference::Unwrap(target)
                                 : target.ptr());
  result.untag()->set_replacement(replacement.ptr());
  return result.ptr();
}

const char* WeakArray::ToCString() const {
  return Thread::Current()->zone()->PrintToString("WeakArray len:%" Pd,
                                                  Length());
}

WeakArrayPtr WeakArray::New(intptr_t length, Heap::Space space) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kWeakArrayCid) !=
         Class::null());
  if (!IsValidLength(length)) {
    // This should be caught before we reach here.
    FATAL("Fatal error in WeakArray::New: invalid len %" Pd "\n", length);
  }
  auto raw = Object::Allocate<WeakArray>(space, length);
  NoSafepointScope no_safepoint;
  raw->untag()->set_length(Smi::New(length));
  return raw;
}

#if defined(INCLUDE_IL_PRINTER)
Code::Comments& Code::Comments::New(intptr_t count) {
  Comments* comments;
  if (count < 0 || count > (kIntptrMax / kNumberOfEntries)) {
    // This should be caught before we reach here.
    FATAL("Fatal error in Code::Comments::New: invalid count %" Pd "\n", count);
  }
  if (count == 0) {
    comments = new Comments(Object::empty_array());
  } else {
    const Array& data =
        Array::Handle(Array::New(count * kNumberOfEntries, Heap::kOld));
    comments = new Comments(data);
  }
  return *comments;
}

intptr_t Code::Comments::Length() const {
  if (comments_.IsNull()) {
    return 0;
  }
  return comments_.Length() / kNumberOfEntries;
}

intptr_t Code::Comments::PCOffsetAt(intptr_t idx) const {
  return Smi::Value(
      Smi::RawCast(comments_.At(idx * kNumberOfEntries + kPCOffsetEntry)));
}

void Code::Comments::SetPCOffsetAt(intptr_t idx, intptr_t pc) {
  comments_.SetAt(idx * kNumberOfEntries + kPCOffsetEntry,
                  Smi::Handle(Smi::New(pc)));
}

const char* Code::Comments::CommentAt(intptr_t idx) const {
  string_ ^= comments_.At(idx * kNumberOfEntries + kCommentEntry);
  return string_.ToCString();
}

void Code::Comments::SetCommentAt(intptr_t idx, const String& comment) {
  comments_.SetAt(idx * kNumberOfEntries + kCommentEntry, comment);
}

Code::Comments::Comments(const Array& comments)
    : comments_(comments), string_(String::Handle()) {}
#endif  // defined(INCLUDE_IL_PRINTER)

const char* Code::EntryKindToCString(EntryKind kind) {
  switch (kind) {
    case EntryKind::kNormal:
      return "Normal";
    case EntryKind::kUnchecked:
      return "Unchecked";
    case EntryKind::kMonomorphic:
      return "Monomorphic";
    case EntryKind::kMonomorphicUnchecked:
      return "MonomorphicUnchecked";
    default:
      UNREACHABLE();
      return nullptr;
  }
}

bool Code::ParseEntryKind(const char* str, EntryKind* out) {
  if (strcmp(str, "Normal") == 0) {
    *out = EntryKind::kNormal;
    return true;
  } else if (strcmp(str, "Unchecked") == 0) {
    *out = EntryKind::kUnchecked;
    return true;
  } else if (strcmp(str, "Monomorphic") == 0) {
    *out = EntryKind::kMonomorphic;
    return true;
  } else if (strcmp(str, "MonomorphicUnchecked") == 0) {
    *out = EntryKind::kMonomorphicUnchecked;
    return true;
  }
  return false;
}

LocalVarDescriptorsPtr Code::GetLocalVarDescriptors() const {
  const LocalVarDescriptors& v = LocalVarDescriptors::Handle(var_descriptors());
  if (v.IsNull()) {
    ASSERT(!is_optimized());
    const Function& f = Function::Handle(function());
    ASSERT(!f.IsIrregexpFunction());  // Not yet implemented.
    Compiler::ComputeLocalVarDescriptors(*this);
  }
  return var_descriptors();
}

void Code::set_owner(const Object& owner) const {
#if defined(DEBUG)
  const auto& unwrapped_owner =
      Object::Handle(WeakSerializationReference::Unwrap(owner));
  ASSERT(unwrapped_owner.IsFunction() || unwrapped_owner.IsClass() ||
         unwrapped_owner.IsAbstractType());
#endif
  untag()->set_owner(owner.ptr());
}

void Code::set_state_bits(intptr_t bits) const {
  StoreNonPointer(&untag()->state_bits_, bits);
}

void Code::set_is_optimized(bool value) const {
  set_state_bits(OptimizedBit::update(value, untag()->state_bits_));
}

void Code::set_is_force_optimized(bool value) const {
  set_state_bits(ForceOptimizedBit::update(value, untag()->state_bits_));
}

void Code::set_is_alive(bool value) const {
  set_state_bits(AliveBit::update(value, untag()->state_bits_));
}

void Code::set_is_discarded(bool value) const {
  set_state_bits(DiscardedBit::update(value, untag()->state_bits_));
}

void Code::set_compressed_stackmaps(const CompressedStackMaps& maps) const {
  ASSERT(maps.IsOld());
  untag()->set_compressed_stackmaps(maps.ptr());
}

#if !defined(DART_PRECOMPILED_RUNTIME)
intptr_t Code::num_variables() const {
  ASSERT(!FLAG_precompiled_mode);
  return Smi::Value(Smi::RawCast(untag()->catch_entry()));
}
void Code::set_num_variables(intptr_t num_variables) const {
  ASSERT(!FLAG_precompiled_mode);
  untag()->set_catch_entry(Smi::New(num_variables));
}
#endif

TypedDataPtr Code::catch_entry_moves_maps() const {
  return TypedData::RawCast(untag()->catch_entry());
}
void Code::set_catch_entry_moves_maps(const TypedData& maps) const {
  untag()->set_catch_entry(maps.ptr());
}

void Code::set_deopt_info_array(const Array& array) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  ASSERT(array.IsOld());
  untag()->set_deopt_info_array(array.ptr());
#endif
}

void Code::set_static_calls_target_table(const Array& value) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  untag()->set_static_calls_target_table(value.ptr());
#endif
#if defined(DEBUG)
  // Check that the table is sorted by pc offsets.
  // FlowGraphCompiler::AddStaticCallTarget adds pc-offsets to the table while
  // emitting assembly. This guarantees that every succeeding pc-offset is
  // larger than the previously added one.
  StaticCallsTable entries(value);
  const intptr_t count = entries.Length();
  for (intptr_t i = 0; i < count - 1; ++i) {
    auto left = Smi::Value(entries[i].Get<kSCallTableKindAndOffset>());
    auto right = Smi::Value(entries[i + 1].Get<kSCallTableKindAndOffset>());
    ASSERT(OffsetField::decode(left) < OffsetField::decode(right));
  }
#endif  // DEBUG
}

ObjectPoolPtr Code::GetObjectPool() const {
#if defined(DART_PRECOMPILER) || defined(DART_PRECOMPILED_RUNTIME)
  if (FLAG_precompiled_mode) {
    return IsolateGroup::Current()->object_store()->global_object_pool();
  }
#endif
  return object_pool();
}

bool Code::HasBreakpoint() const {
#if defined(PRODUCT)
  return false;
#else
  return IsolateGroup::Current()->debugger()->HasBreakpointInCode(*this);
#endif
}

TypedDataPtr Code::GetDeoptInfoAtPc(uword pc,
                                    ICData::DeoptReasonId* deopt_reason,
                                    uint32_t* deopt_flags) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  return TypedData::null();
#else
  ASSERT(is_optimized());
  const Instructions& instrs = Instructions::Handle(instructions());
  uword code_entry = instrs.PayloadStart();
  const Array& table = Array::Handle(deopt_info_array());
  if (table.IsNull()) {
    return TypedData::null();
  }
  // Linear search for the PC offset matching the target PC.
  intptr_t length = DeoptTable::GetLength(table);
  Smi& offset = Smi::Handle();
  Smi& reason_and_flags = Smi::Handle();
  TypedData& info = TypedData::Handle();
  for (intptr_t i = 0; i < length; ++i) {
    DeoptTable::GetEntry(table, i, &offset, &info, &reason_and_flags);
    if (pc == (code_entry + offset.Value())) {
      ASSERT(!info.IsNull());
      *deopt_reason = DeoptTable::ReasonField::decode(reason_and_flags.Value());
      *deopt_flags = DeoptTable::FlagsField::decode(reason_and_flags.Value());
      return info.ptr();
    }
  }
  *deopt_reason = ICData::kDeoptUnknown;
  return TypedData::null();
#endif  // defined(DART_PRECOMPILED_RUNTIME)
}

intptr_t Code::BinarySearchInSCallTable(uword pc) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  NoSafepointScope no_safepoint;
  const Array& table = Array::Handle(untag()->static_calls_target_table());
  StaticCallsTable entries(table);
  const intptr_t pc_offset = pc - PayloadStart();
  intptr_t imin = 0;
  intptr_t imax = (table.Length() / kSCallTableEntryLength) - 1;
  while (imax >= imin) {
    const intptr_t imid = imin + (imax - imin) / 2;
    const auto offset = OffsetField::decode(
        Smi::Value(entries[imid].Get<kSCallTableKindAndOffset>()));
    if (offset < pc_offset) {
      imin = imid + 1;
    } else if (offset > pc_offset) {
      imax = imid - 1;
    } else {
      return imid;
    }
  }
#endif
  return -1;
}

FunctionPtr Code::GetStaticCallTargetFunctionAt(uword pc) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
  return Function::null();
#else
  const intptr_t i = BinarySearchInSCallTable(pc);
  if (i < 0) {
    return Function::null();
  }
  const Array& array = Array::Handle(untag()->static_calls_target_table());
  StaticCallsTable entries(array);
  return entries[i].Get<kSCallTableFunctionTarget>();
#endif
}

void Code::SetStaticCallTargetCodeAt(uword pc, const Code& code) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  const intptr_t i = BinarySearchInSCallTable(pc);
  ASSERT(i >= 0);
  const Array& array = Array::Handle(untag()->static_calls_target_table());
  StaticCallsTable entries(array);
  ASSERT(code.IsNull() ||
         (code.function() == entries[i].Get<kSCallTableFunctionTarget>()));
  return entries[i].Set<kSCallTableCodeOrTypeTarget>(code);
#endif
}

void Code::SetStubCallTargetCodeAt(uword pc, const Code& code) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  const intptr_t i = BinarySearchInSCallTable(pc);
  ASSERT(i >= 0);
  const Array& array = Array::Handle(untag()->static_calls_target_table());
  StaticCallsTable entries(array);
#if defined(DEBUG)
  if (entries[i].Get<kSCallTableFunctionTarget>() == Function::null()) {
    ASSERT(!code.IsNull() && Object::Handle(code.owner()).IsClass());
  } else {
    ASSERT(code.IsNull() ||
           (code.function() == entries[i].Get<kSCallTableFunctionTarget>()));
  }
#endif
  return entries[i].Set<kSCallTableCodeOrTypeTarget>(code);
#endif
}

void Code::Disassemble(DisassemblyFormatter* formatter) const {
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
  if (!FLAG_support_disassembler) {
    return;
  }
  const uword start = PayloadStart();
  if (formatter == nullptr) {
    Disassembler::Disassemble(start, start + Size(), *this);
  } else {
    Disassembler::Disassemble(start, start + Size(), formatter, *this);
  }
#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
}

#if defined(INCLUDE_IL_PRINTER)
#if defined(PRODUCT)
// In PRODUCT builds we don't have space in Code object to store code comments
// so we move them into malloced heap (and leak them). This functionality
// is only intended to be used in AOT compiler so leaking is fine.
class MallocCodeComments final : public CodeComments {
 public:
  explicit MallocCodeComments(const CodeComments& comments)
      : length_(comments.Length()), comments_(new Comment[comments.Length()]) {
    for (intptr_t i = 0; i < length_; i++) {
      comments_[i].pc_offset = comments.PCOffsetAt(i);
      comments_[i].comment =
          CStringUniquePtr(Utils::StrDup(comments.CommentAt(i)));
    }
  }

  intptr_t Length() const override { return length_; }

  intptr_t PCOffsetAt(intptr_t i) const override {
    return comments_[i].pc_offset;
  }

  const char* CommentAt(intptr_t i) const override {
    return comments_[i].comment.get();
  }

 private:
  struct Comment {
    intptr_t pc_offset;
    CStringUniquePtr comment;
  };

  intptr_t length_;
  std::unique_ptr<Comment[]> comments_;
};
#endif

const CodeComments& Code::comments() const {
#if defined(PRODUCT)
  auto comments =
      static_cast<CodeComments*>(Thread::Current()->heap()->GetPeer(ptr()));
  return (comments != nullptr) ? *comments : Code::Comments::New(0);
#else
  return *new Code::Comments(Array::Handle(untag()->comments()));
#endif
}

void Code::set_comments(const CodeComments& comments) const {
#if !defined(PRODUCT)
  auto& wrapper = static_cast<const Code::Comments&>(comments);
  ASSERT(wrapper.comments_.IsOld());
  untag()->set_comments(wrapper.comments_.ptr());
#else
  if (IsolateGroup::Current()->code_comments() && comments.Length() > 0) {
    Thread::Current()->heap()->SetPeer(ptr(), new MallocCodeComments(comments));
  } else {
    Thread::Current()->heap()->SetPeer(ptr(), nullptr);
  }
#endif
}
#endif  // defined(INCLUDE_IL_PRINTER)

void Code::SetPrologueOffset(intptr_t offset) const {
#if defined(PRODUCT)
  UNREACHABLE();
#else
  ASSERT(offset >= 0);
  untag()->set_return_address_metadata(Smi::New(offset));
#endif
}

intptr_t Code::GetPrologueOffset() const {
#if defined(PRODUCT)
  return -1;
#else
  const Object& object = Object::Handle(untag()->return_address_metadata());
  // In the future we may put something other than a smi in
  // |return_address_metadata_|.
  if (object.IsNull() || !object.IsSmi()) {
    return -1;
  }
  return Smi::Cast(object).Value();
#endif
}

ArrayPtr Code::inlined_id_to_function() const {
  return untag()->inlined_id_to_function();
}

void Code::set_inlined_id_to_function(const Array& value) const {
  ASSERT(value.IsOld());
  untag()->set_inlined_id_to_function(value.ptr());
}

CodePtr Code::New(intptr_t pointer_offsets_length) {
  if (pointer_offsets_length < 0 || pointer_offsets_length > kMaxElements) {
    // This should be caught before we reach here.
    FATAL("Fatal error in Code::New: invalid pointer_offsets_length %" Pd "\n",
          pointer_offsets_length);
  }
  ASSERT(IsolateGroup::Current()->class_table()->At(kCodeCid) != Class::null());
  Code& result = Code::Handle();
  {
    auto raw = Object::Allocate<Code>(Heap::kOld, pointer_offsets_length);
    NoSafepointScope no_safepoint;
    result = raw;
    ASSERT_EQUAL(result.untag()->state_bits_, 0);
    result.set_pointer_offsets_length(pointer_offsets_length);
  }
  DEBUG_ASSERT(result.compile_timestamp() == 0);
#if defined(INCLUDE_IL_PRINTER)
  result.set_comments(Comments::New(0));
#endif
  result.set_pc_descriptors(Object::empty_descriptors());
  result.set_compressed_stackmaps(Object::empty_compressed_stackmaps());
  return result.ptr();
}

#if !defined(DART_PRECOMPILED_RUNTIME)
CodePtr Code::FinalizeCodeAndNotify(const Function& function,
                                    FlowGraphCompiler* compiler,
                                    compiler::Assembler* assembler,
                                    PoolAttachment pool_attachment,
                                    bool optimized,
                                    CodeStatistics* stats) {
  auto thread = Thread::Current();
  ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());

  const auto& code = Code::Handle(
      FinalizeCode(compiler, assembler, pool_attachment, optimized, stats));
  NotifyCodeObservers(function, code, optimized);
  return code.ptr();
}

CodePtr Code::FinalizeCodeAndNotify(const char* name,
                                    FlowGraphCompiler* compiler,
                                    compiler::Assembler* assembler,
                                    PoolAttachment pool_attachment,
                                    bool optimized,
                                    CodeStatistics* stats) {
  auto thread = Thread::Current();
  ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());

  const auto& code = Code::Handle(
      FinalizeCode(compiler, assembler, pool_attachment, optimized, stats));
  NotifyCodeObservers(name, code, optimized);
  return code.ptr();
}

#if defined(DART_PRECOMPILER)
DECLARE_FLAG(charp, write_v8_snapshot_profile_to);
DECLARE_FLAG(charp, trace_precompiler_to);
#endif  // defined(DART_PRECOMPILER)

CodePtr Code::FinalizeCode(FlowGraphCompiler* compiler,
                           compiler::Assembler* assembler,
                           PoolAttachment pool_attachment,
                           bool optimized,
                           CodeStatistics* stats /* = nullptr */) {
  auto thread = Thread::Current();
  ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());

  ASSERT(assembler != nullptr);
  ObjectPool& object_pool = ObjectPool::Handle();

  if (pool_attachment == PoolAttachment::kAttachPool) {
    if (assembler->HasObjectPoolBuilder()) {
      object_pool =
          ObjectPool::NewFromBuilder(assembler->object_pool_builder());
    } else {
      object_pool = ObjectPool::empty_object_pool().ptr();
    }
  } else {
#if defined(DART_PRECOMPILER)
    if (assembler->HasObjectPoolBuilder() &&
        assembler->object_pool_builder().HasParent()) {
      // We are not going to write this pool into snapshot, but we will use
      // it to emit references from this code object to other objects in the
      // snapshot that it uses.
      object_pool =
          ObjectPool::NewFromBuilder(assembler->object_pool_builder());
    }
#endif  // defined(DART_PRECOMPILER)
  }

  // Allocate the Code and Instructions objects.  Code is allocated first
  // because a GC during allocation of the code will leave the instruction
  // pages read-only.
  intptr_t pointer_offset_count = assembler->CountPointerOffsets();
  Code& code = Code::ZoneHandle(Code::New(pointer_offset_count));
#ifdef TARGET_ARCH_IA32
  assembler->GetSelfHandle() = code.ptr();
#endif
  Instructions& instrs = Instructions::ZoneHandle(Instructions::New(
      assembler->CodeSize(), assembler->has_monomorphic_entry(),
      assembler->should_be_aligned()));

  {
    // Important: if GC is triggered at any point between Instructions::New
    // and here it would write protect instructions object that we are trying
    // to fill in.
    NoSafepointScope no_safepoint;

    // Copy the instructions into the instruction area and apply all fixups.
    // Embedded pointers are still in handles at this point.
    MemoryRegion region(reinterpret_cast<void*>(
                            Instructions::WritablePayloadStart(instrs.ptr())),
                        instrs.Size());
    assembler->FinalizeInstructions(region);

    const auto& pointer_offsets = assembler->GetPointerOffsets();
    ASSERT(pointer_offsets.length() == pointer_offset_count);
    ASSERT(code.pointer_offsets_length() == pointer_offsets.length());

    // Set pointer offsets list in Code object and resolve all handles in
    // the instruction stream to raw objects.
    for (intptr_t i = 0; i < pointer_offsets.length(); i++) {
      intptr_t offset_in_instrs = pointer_offsets[i];
      code.SetPointerOffsetAt(i, offset_in_instrs);
      uword addr = region.start() + offset_in_instrs;
      ASSERT(instrs.PayloadStart() <= addr);
      ASSERT((instrs.PayloadStart() + instrs.Size()) > addr);
      const Object* object = LoadUnaligned(reinterpret_cast<Object**>(addr));
      ASSERT(object->IsOld());
      // N.B. The pointer is embedded in the Instructions object, but visited
      // through the Code object.
      code.StorePointerUnaligned(reinterpret_cast<ObjectPtr*>(addr),
                                 object->ptr(), thread);
    }

    // Write protect instructions and, if supported by OS, use dual mapping
    // for execution.
    if (FLAG_write_protect_code) {
      // Note: when dual mapping is used we have separate RX and RW mappings.
      // RX mapping never changes protection while RW mapping flips between
      // R and RW.
      uword address = UntaggedObject::ToAddr(instrs.ptr());
      VirtualMemory::WriteProtectCode(reinterpret_cast<void*>(address),
                                      instrs.ptr()->untag()->HeapSize());
    }

    // Hook up Code and Instructions objects.
    const uword unchecked_offset = assembler->UncheckedEntryOffset();
    code.SetActiveInstructions(instrs, unchecked_offset);
    code.set_instructions(instrs);
    NOT_IN_PRECOMPILED(code.set_unchecked_offset(unchecked_offset));
    code.set_is_alive(true);

    // Set object pool in Instructions object.
    if (!object_pool.IsNull()) {
      code.set_object_pool(object_pool.ptr());
    }

#if defined(DART_PRECOMPILER)
    if (stats != nullptr) {
      stats->Finalize();
      instrs.set_stats(stats);
    }
#endif

    CPU::FlushICache(region.start(), region.size());
  }

#if defined(INCLUDE_IL_PRINTER)
  code.set_comments(CreateCommentsFrom(assembler));
#endif  // defined(INCLUDE_IL_PRINTER)

#ifndef PRODUCT
  code.set_compile_timestamp(OS::GetCurrentMonotonicMicros());
  if (assembler->prologue_offset() >= 0) {
    code.SetPrologueOffset(assembler->prologue_offset());
  } else {
    // No prologue was ever entered, optimistically assume nothing was ever
    // pushed onto the stack.
    code.SetPrologueOffset(assembler->CodeSize());
  }
#endif
  return code.ptr();
}

void Code::NotifyCodeObservers(const Code& code, bool optimized) {
#if !defined(PRODUCT)
  ASSERT(!Thread::Current()->OwnsGCSafepoint());
  if (CodeObservers::AreActive()) {
    if (code.IsFunctionCode()) {
      const auto& function = Function::Handle(code.function());
      if (!function.IsNull()) {
        return NotifyCodeObservers(function, code, optimized);
      }
    }
    NotifyCodeObservers(code.Name(), code, optimized);
  }
#endif
}

void Code::NotifyCodeObservers(const Function& function,
                               const Code& code,
                               bool optimized) {
#if !defined(PRODUCT)
  ASSERT(!function.IsNull());
  // Calling ToLibNamePrefixedQualifiedCString is very expensive,
  // try to avoid it.
  if (CodeObservers::AreActive()) {
    const char* name = function.ToLibNamePrefixedQualifiedCString();
    NotifyCodeObservers(name, code, optimized);
  }
#endif
  RegisterTsanSymbolize(code);
}

void Code::NotifyCodeObservers(const char* name,
                               const Code& code,
                               bool optimized) {
#if !defined(PRODUCT)
  ASSERT(name != nullptr);
  ASSERT(!code.IsNull());
  if (CodeObservers::AreActive()) {
    const auto& instrs = Instructions::Handle(code.instructions());
    CodeObservers::NotifyAll(name, instrs.PayloadStart(),
                             code.GetPrologueOffset(), instrs.Size(), optimized,
                             &code.comments());
  }
#endif
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

CodePtr Code::FindCode(uword pc, int64_t timestamp) {
  class SlowFindCodeVisitor : public ObjectVisitor {
   public:
    SlowFindCodeVisitor(uword pc, int64_t timestamp)
        : pc_(pc), timestamp_(timestamp), result_(Code::null()) {}

    void VisitObject(ObjectPtr obj) {
      if (!obj->IsCode()) return;
      CodePtr code = static_cast<CodePtr>(obj);
      if (Code::PayloadStartOf(code) != pc_) return;
#if !defined(PRODUCT)
      if (code->untag()->compile_timestamp_ != timestamp_) return;
#endif
      ASSERT(result_ == Code::null());
      result_ = code;
    }

    CodePtr result() const { return result_; }

   private:
    uword pc_;
    int64_t timestamp_;
    CodePtr result_;
  };

  HeapIterationScope iteration(Thread::Current());
  SlowFindCodeVisitor visitor(pc, timestamp);
  iteration.IterateOldObjectsNoImagePages(&visitor);
  return visitor.result();
}

CodePtr Code::FindCodeUnsafe(uword pc) {
  class FindCodeUnsafeVisitor : public ObjectVisitor {
   public:
    explicit FindCodeUnsafeVisitor(uword pc) : pc_(pc), result_(Code::null()) {}

    void VisitObject(ObjectPtr obj) {
      if (obj->IsCode()) {
        CodePtr code = static_cast<CodePtr>(obj);
        if (Code::ContainsInstructionAt(code, pc_)) {
          result_ = code;
        }
      }
    }

    CodePtr result() { return result_; }

   private:
    uword pc_;
    CodePtr result_;
  };

  IsolateGroup* group = IsolateGroup::Current();
  PageSpace* old_space = group->heap()->old_space();
  old_space->MakeIterable();
  FindCodeUnsafeVisitor visitor(pc);
  old_space->VisitObjectsUnsafe(&visitor);
  return visitor.result();
}

TokenPosition Code::GetTokenIndexOfPC(uword pc) const {
  uword pc_offset = pc - PayloadStart();
  const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors());
  PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kAnyKind);
  while (iter.MoveNext()) {
    if (iter.PcOffset() == pc_offset) {
      return iter.TokenPos();
    }
  }
  return TokenPosition::kNoSource;
}

uword Code::GetPcForDeoptId(intptr_t deopt_id,
                            UntaggedPcDescriptors::Kind kind) const {
  const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors());
  PcDescriptors::Iterator iter(descriptors, kind);
  while (iter.MoveNext()) {
    if (iter.DeoptId() == deopt_id) {
      uword pc_offset = iter.PcOffset();
      uword pc = PayloadStart() + pc_offset;
      ASSERT(ContainsInstructionAt(pc));
      return pc;
    }
  }
  return 0;
}

intptr_t Code::GetDeoptIdForOsr(uword pc) const {
  uword pc_offset = pc - PayloadStart();
  const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors());
  PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kOsrEntry);
  while (iter.MoveNext()) {
    if (iter.PcOffset() == pc_offset) {
      return iter.DeoptId();
    }
  }
  return DeoptId::kNone;
}

const char* Code::ToCString() const {
  return OS::SCreate(Thread::Current()->zone(), "Code(%s)",
                     QualifiedName(NameFormattingParams(
                         kScrubbedName, NameDisambiguation::kYes)));
}

uint32_t Code::Hash() const {
  // PayloadStart() is a tempting hash as Instructions are not moved by the
  // compactor, but Instructions are effectively moved between the process
  // creating an AppJIT/AOT snapshot and the process loading the snapshot.
  const Object& obj =
      Object::Handle(WeakSerializationReference::UnwrapIfTarget(owner()));
  if (obj.IsClass()) {
    return Class::Cast(obj).Hash();
  } else if (obj.IsAbstractType()) {
    return AbstractType::Cast(obj).Hash();
  } else if (obj.IsFunction()) {
    return Function::Cast(obj).Hash();
  } else {
    // E.g., VM stub.
    return 42;
  }
}

const char* Code::Name() const {
  Zone* zone = Thread::Current()->zone();
  if (IsStubCode()) {
    // Regular stub.
    const char* name = StubCode::NameOfStub(EntryPoint());
    if (name == nullptr) {
      return "[unknown stub]";  // Not yet recorded.
    }
    return OS::SCreate(zone, "[Stub] %s", name);
  }
  const auto& obj =
      Object::Handle(zone, WeakSerializationReference::UnwrapIfTarget(owner()));
  if (obj.IsClass()) {
    // Allocation stub.
    return OS::SCreate(zone, "[Stub] Allocate %s",
                       Class::Cast(obj).ScrubbedNameCString());
  } else if (obj.IsAbstractType()) {
    // Type test stub.
    return OS::SCreate(zone, "[Stub] Type Test %s",
                       AbstractType::Cast(obj).ToCString());
  } else if (obj.IsFunction()) {
    // Dart function.
    const char* opt = is_optimized() ? "[Optimized]" : "[Unoptimized]";
    const char* function_name = Function::Cast(obj).UserVisibleNameCString();
    return OS::SCreate(zone, "%s %s", opt, function_name);
  } else {
    // --no_retain_function_objects etc
    return "[unknown code]";
  }
}

const char* Code::QualifiedName(const NameFormattingParams& params) const {
  Zone* zone = Thread::Current()->zone();
  const Object& obj =
      Object::Handle(zone, WeakSerializationReference::UnwrapIfTarget(owner()));
  if (obj.IsFunction()) {
    ZoneTextBuffer printer(zone);
    printer.AddString(is_optimized() ? "[Optimized] " : "[Unoptimized] ");
    Function::Cast(obj).PrintName(params, &printer);
    return printer.buffer();
  }
  return Name();
}

bool Code::IsStubCode() const {
  // We should _not_ unwrap any possible WSRs here, as the null value is never
  // wrapped by a WSR.
  return owner() == Object::null();
}

bool Code::IsAllocationStubCode() const {
  return OwnerClassId() == kClassCid;
}

bool Code::IsTypeTestStubCode() const {
  auto const cid = OwnerClassId();
  return cid == kAbstractTypeCid || cid == kTypeCid ||
         cid == kFunctionTypeCid || cid == kRecordTypeCid ||
         cid == kTypeParameterCid;
}

bool Code::IsFunctionCode() const {
  return OwnerClassId() == kFunctionCid;
}

bool Code::IsUnknownDartCode(CodePtr code) {
  ASSERT(StubCode::UnknownDartCode().ptr() != nullptr);
  return code == StubCode::UnknownDartCode().ptr();
}

void Code::DisableDartCode() const {
  GcSafepointOperationScope safepoint(Thread::Current());
  ASSERT(IsFunctionCode());
  ASSERT(instructions() == active_instructions());
  const Code& new_code = StubCode::FixCallersTarget();
  SetActiveInstructions(Instructions::Handle(new_code.instructions()),
                        new_code.UncheckedEntryPointOffset());
}

void Code::DisableStubCode(bool is_cls_parameterized) const {
  GcSafepointOperationScope safepoint(Thread::Current());
  ASSERT(IsAllocationStubCode());
  ASSERT(instructions() == active_instructions());
  const Code& new_code = is_cls_parameterized
                             ? StubCode::FixParameterizedAllocationStubTarget()
                             : StubCode::FixAllocationStubTarget();
  SetActiveInstructions(Instructions::Handle(new_code.instructions()),
                        new_code.UncheckedEntryPointOffset());
}

void Code::InitializeCachedEntryPointsFrom(CodePtr code,
                                           InstructionsPtr instructions,
                                           uint32_t unchecked_offset) {
  NoSafepointScope _;
  const uword entry_point = Instructions::EntryPoint(instructions);
  const uword monomorphic_entry_point =
      Instructions::MonomorphicEntryPoint(instructions);
  code->untag()->entry_point_ = entry_point;
  code->untag()->monomorphic_entry_point_ = monomorphic_entry_point;
  code->untag()->unchecked_entry_point_ = entry_point + unchecked_offset;
  code->untag()->monomorphic_unchecked_entry_point_ =
      monomorphic_entry_point + unchecked_offset;
}

void Code::SetActiveInstructions(const Instructions& instructions,
                                 uint32_t unchecked_offset) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
  SetActiveInstructionsSafe(instructions, unchecked_offset);
#endif
}

void Code::SetActiveInstructionsSafe(const Instructions& instructions,
                                     uint32_t unchecked_offset) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  // RawInstructions are never allocated in New space and hence a
  // store buffer update is not needed here.
  untag()->set_active_instructions(instructions.ptr());
  Code::InitializeCachedEntryPointsFrom(ptr(), instructions.ptr(),
                                        unchecked_offset);
#endif
}

void Code::ResetActiveInstructions() const {
#if defined(DART_PRECOMPILED_RUNTIME)
  UNREACHABLE();
#else
  SetActiveInstructions(Instructions::Handle(instructions()),
                        untag()->unchecked_offset_);
#endif
}

void Code::GetInlinedFunctionsAtInstruction(
    intptr_t pc_offset,
    GrowableArray<const Function*>* functions,
    GrowableArray<TokenPosition>* token_positions) const {
  const CodeSourceMap& map = CodeSourceMap::Handle(code_source_map());
  if (map.IsNull()) {
    ASSERT(!IsFunctionCode());
    return;  // VM stub, allocation stub, or type testing stub.
  }
  const Array& id_map = Array::Handle(inlined_id_to_function());
  const Function& root = Function::Handle(function());
  CodeSourceMapReader reader(map, id_map, root);
  reader.GetInlinedFunctionsAt(pc_offset, functions, token_positions);
}

#ifndef PRODUCT
void Code::PrintJSONInlineIntervals(JSONObject* jsobj) const {
  if (!is_optimized()) {
    return;  // No inlining.
  }
  const CodeSourceMap& map = CodeSourceMap::Handle(code_source_map());
  const Array& id_map = Array::Handle(inlined_id_to_function());
  const Function& root = Function::Handle(function());
  CodeSourceMapReader reader(map, id_map, root);
  reader.PrintJSONInlineIntervals(jsobj);
}
#endif

void Code::DumpInlineIntervals() const {
  const CodeSourceMap& map = CodeSourceMap::Handle(code_source_map());
  if (map.IsNull()) {
    // Stub code.
    return;
  }
  const Array& id_map = Array::Handle(inlined_id_to_function());
  const Function& root = Function::Handle(function());
  CodeSourceMapReader reader(map, id_map, root);
  reader.DumpInlineIntervals(PayloadStart());
}

void Code::DumpSourcePositions(bool relative_addresses) const {
  const CodeSourceMap& map = CodeSourceMap::Handle(code_source_map());
  if (map.IsNull()) {
    // Stub code.
    return;
  }
  const Array& id_map = Array::Handle(inlined_id_to_function());
  const Function& root = Function::Handle(function());
  CodeSourceMapReader reader(map, id_map, root);
  reader.DumpSourcePositions(relative_addresses ? 0 : PayloadStart());
}

void Bytecode::Disassemble(DisassemblyFormatter* formatter) const {
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
#if defined(DART_DYNAMIC_MODULES)
  if (!FLAG_support_disassembler) {
    return;
  }
  uword start = PayloadStart();
  intptr_t size = Size();
  if (formatter == NULL) {
    KernelBytecodeDisassembler::Disassemble(start, start + size, *this);
  } else {
    KernelBytecodeDisassembler::Disassemble(start, start + size, formatter,
                                            *this);
  }
#endif  // defined(DART_DYNAMIC_MODULES)
#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
}

BytecodePtr Bytecode::New(uword instructions,
                          intptr_t instructions_size,
                          intptr_t instructions_offset,
                          const TypedDataBase& binary,
                          const ObjectPool& object_pool) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kBytecodeCid) !=
         Class::null());
  Bytecode& result = Bytecode::Handle();
  {
    auto raw = Object::Allocate<Bytecode>(Heap::kOld);
    NoSafepointScope no_safepoint;
    result = raw;
    result.set_instructions(instructions);
    result.set_instructions_size(instructions_size);
    result.set_object_pool(object_pool);
    result.set_pc_descriptors(Object::empty_descriptors());
    result.set_binary(binary);
    result.set_instructions_binary_offset(instructions_offset);
    result.set_code_offset(0);
    result.set_source_positions_binary_offset(0);
  }
  return result.ptr();
}

TokenPosition Bytecode::GetTokenIndexOfPC(uword return_address) const {
#if defined(DART_DYNAMIC_MODULES)
  if (!HasSourcePositions()) {
    return TokenPosition::kNoSource;
  }
  Zone* zone = Thread::Current()->zone();
  uword pc_offset = return_address - PayloadStart();
  // pc_offset could equal to bytecode size if the last instruction is Throw.
  ASSERT(pc_offset <= static_cast<uword>(Size()));
  bytecode::BytecodeSourcePositionsIterator iter(zone, *this);
  TokenPosition token_pos = TokenPosition::kNoSource;
  while (iter.MoveNext()) {
    if (pc_offset <= iter.PcOffset()) {
      break;
    }
    token_pos = iter.TokenPos();
  }
  return token_pos;
#else
  UNREACHABLE();
#endif
}

intptr_t Bytecode::GetTryIndexAtPc(uword return_address) const {
#if defined(DART_DYNAMIC_MODULES)
  intptr_t try_index = -1;
  const uword pc_offset = return_address - PayloadStart();
  const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors());
  PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kAnyKind);
  while (iter.MoveNext()) {
    // PC descriptors for try blocks in bytecode are generated in pairs,
    // marking start and end of a try block.
    // See BytecodeReaderHelper::ReadExceptionsTable for details.
    const intptr_t current_try_index = iter.TryIndex();
    const uword start_pc = iter.PcOffset();
    if (pc_offset < start_pc) {
      break;
    }
    const bool has_next = iter.MoveNext();
    ASSERT(has_next);
    const uword end_pc = iter.PcOffset();
    if (start_pc <= pc_offset && pc_offset < end_pc) {
      ASSERT(try_index < current_try_index);
      try_index = current_try_index;
    }
  }
  return try_index;
#else
  UNREACHABLE();
#endif
}

uword Bytecode::GetInstructionBefore(uword return_address) const {
#if defined(DART_DYNAMIC_MODULES)
  const uword start = PayloadStart();
  // return_address could be the end of the bytecode instructions
  // if the last instruction is Throw.
  if (return_address <= start || return_address > start + Size()) {
    return 0;
  }
  uword prev = start;
  uword current = KernelBytecode::Next(start);
  while (current < return_address) {
    prev = current;
    current = KernelBytecode::Next(prev);
  }
  // Any valid return address should be on an instruction boundary.
  return current == return_address ? prev : 0;
#else
  UNREACHABLE();
  return 0;
#endif
}

#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
LocalVarDescriptorsPtr Bytecode::GetLocalVarDescriptors() const {
#if defined(DART_DYNAMIC_MODULES)
  Zone* zone = Thread::Current()->zone();
  auto& var_descs = LocalVarDescriptors::Handle(zone, var_descriptors());
  if (var_descs.IsNull()) {
    const auto& func = Function::Handle(zone, function());
    ASSERT(!func.IsNull());
    var_descs =
        bytecode::BytecodeReader::ComputeLocalVarDescriptors(zone, func, *this);
    ASSERT(!var_descs.IsNull());
    set_var_descriptors(var_descs);
  }
  return var_descs.ptr();
#else
  UNREACHABLE();
#endif
}

TypedDataPtr Bytecode::EnsureCoverageArray(Thread* thread) const {
#if defined(DART_DYNAMIC_MODULES)
  // Should only be called for bytecode with RecordCoverage instructions.
  ASSERT(HasRecordedCoverage());
  if (coverage_array() == TypedData::null()) {
    Zone* const zone = thread->zone();
    bytecode::BytecodeRecordedCoverageIterator it(zone, *this);
    const auto& array = TypedData::Handle(
        zone, TypedData::New(kTypedDataUint32ArrayCid, 2 * it.NumEntries(),
                             Heap::kOld));
    // The coverage array has two consecutive entries for each logical
    // index: the encoded coverage position and the hit count.
    for (intptr_t i = 0; it.MoveNext(); i += 2) {
      array.SetUint32(i * kInt32Size, it.EncodedCoveragePosition());
      array.SetUint32((i + 1) * kInt32Size, 0);
    }
    SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
    if (coverage_array() == TypedData::null()) {
      untag()->set_coverage_array(array.ptr());
    }
  }
  return coverage_array();
#else
  UNREACHABLE();
#endif
}
#endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)

const char* Bytecode::ToCString() const {
  return Thread::Current()->zone()->PrintToString("Bytecode(%s)",
                                                  QualifiedName());
}

static const char* BytecodeStubName(const Bytecode& bytecode) {
  if (bytecode.ptr() == Object::implicit_getter_bytecode().ptr()) {
    return "[Bytecode Stub] VMInternal_ImplicitGetter";
  } else if (bytecode.ptr() == Object::implicit_setter_bytecode().ptr()) {
    return "[Bytecode Stub] VMInternal_ImplicitSetter";
  } else if (bytecode.ptr() ==
             Object::implicit_static_getter_bytecode().ptr()) {
    return "[Bytecode Stub] VMInternal_ImplicitStaticGetter";
  } else if (bytecode.ptr() ==
             Object::implicit_shared_static_getter_bytecode().ptr()) {
    return "[Bytecode Stub] VMInternal_ImplicitSharedStaticGetter";
  } else if (bytecode.ptr() ==
             Object::implicit_static_setter_bytecode().ptr()) {
    return "[Bytecode Stub] VMInternal_ImplicitStaticSetter";
  } else if (bytecode.ptr() ==
             Object::implicit_shared_static_setter_bytecode().ptr()) {
    return "[Bytecode Stub] VMInternal_ImplicitSharedStaticSetter";
  } else if (bytecode.ptr() ==
             Object::method_extractor_with_ita_bytecode().ptr()) {
    return "[Bytecode Stub] VMInternal_MethodExtractorWithITA";
  } else if (bytecode.ptr() ==
             Object::method_extractor_without_ita_bytecode().ptr()) {
    return "[Bytecode Stub] VMInternal_MethodExtractorWithoutITA";
  } else if (bytecode.ptr() == Object::invoke_closure_bytecode().ptr()) {
    return "[Bytecode Stub] VMInternal_InvokeClosure";
  } else if (bytecode.ptr() == Object::invoke_field_bytecode().ptr()) {
    return "[Bytecode Stub] VMInternal_InvokeField";
  }
  return "[unknown stub]";
}

const char* Bytecode::Name() const {
  Zone* zone = Thread::Current()->zone();
  const Function& fun = Function::Handle(zone, function());
  if (fun.IsNull()) {
    return BytecodeStubName(*this);
  }
  const char* function_name = fun.UserVisibleNameCString();
  return zone->PrintToString("[Bytecode] %s", function_name);
}

const char* Bytecode::QualifiedName() const {
  Zone* zone = Thread::Current()->zone();
  const Function& fun = Function::Handle(zone, function());
  if (fun.IsNull()) {
    return BytecodeStubName(*this);
  }
  const char* function_name = fun.QualifiedScrubbedNameCString();
  return zone->PrintToString("[Bytecode] %s", function_name);
}

const char* Bytecode::FullyQualifiedName() const {
  Zone* zone = Thread::Current()->zone();
  const Function& fun = Function::Handle(zone, function());
  if (fun.IsNull()) {
    return BytecodeStubName(*this);
  }
  const char* function_name = fun.ToFullyQualifiedCString();
  return zone->PrintToString("[Bytecode] %s", function_name);
}

BytecodePtr Bytecode::FindBytecode(uword pc) {
#if defined(DART_DYNAMIC_MODULES)
  class SlowFindBytecodeVisitor : public ObjectVisitor {
   public:
    explicit SlowFindBytecodeVisitor(uword pc)
        : pc_(pc), result_(Bytecode::null()) {}

    void VisitObject(ObjectPtr obj) {
      if (!obj->IsBytecode()) return;
      BytecodePtr bytecode = static_cast<BytecodePtr>(obj);
      if (PayloadStartOf(bytecode) != pc_) return;
      ASSERT(result_ == Bytecode::null());
      result_ = bytecode;
    }

    BytecodePtr result() const { return result_; }

   private:
    uword pc_;
    BytecodePtr result_;
  };

  HeapIterationScope iteration(Thread::Current());
  SlowFindBytecodeVisitor visitor(pc);
  iteration.IterateOldObjectsNoImagePages(&visitor);
  return visitor.result();
#else
  UNREACHABLE();
  return Bytecode::null();
#endif
}

void Bytecode::set_binary(const TypedDataBase& binary) const {
  ASSERT(binary.IsNull() || binary.IsExternalOrExternalView());
  untag()->set_binary(binary.ptr());
}

intptr_t Context::GetLevel() const {
  intptr_t level = 0;
  Context& parent_ctx = Context::Handle(parent());
  while (!parent_ctx.IsNull()) {
    level++;
    parent_ctx = parent_ctx.parent();
  }
  return level;
}

ContextPtr Context::New(intptr_t num_variables, Heap::Space space) {
  ASSERT(num_variables >= 0);
  ASSERT(IsolateGroup::Current()->class_table()->At(kContextCid) !=
         Class::null());

  if (!IsValidLength(num_variables)) {
    // This should be caught before we reach here.
    FATAL("Fatal error in Context::New: invalid num_variables %" Pd "\n",
          num_variables);
  }
  auto raw = Object::Allocate<Context>(space, num_variables);
  NoSafepointScope no_safepoint;
  raw->untag()->num_variables_ = num_variables;
  return raw;
}

const char* Context::ToCString() const {
  if (IsNull()) {
    return "Context: null";
  }
  Zone* zone = Thread::Current()->zone();
  const Context& parent_ctx = Context::Handle(parent());
  if (parent_ctx.IsNull()) {
    return zone->PrintToString("Context num_variables: %" Pd "",
                               num_variables());
  } else {
    const char* parent_str = parent_ctx.ToCString();
    return zone->PrintToString("Context num_variables: %" Pd " parent:{ %s }",
                               num_variables(), parent_str);
  }
}

static void IndentN(int count) {
  for (int i = 0; i < count; i++) {
    THR_Print(" ");
  }
}

void Context::Dump(int indent) const {
  if (IsNull()) {
    IndentN(indent);
    THR_Print("Context@null\n");
    return;
  }

  IndentN(indent);
  THR_Print("Context vars(%" Pd ") {\n", num_variables());
  Object& obj = Object::Handle();
  for (intptr_t i = 0; i < num_variables(); i++) {
    IndentN(indent + 2);
    obj = At(i);
    const char* s = obj.ToCString();
    if (strlen(s) > 50) {
      THR_Print("[%" Pd "] = [first 50 chars:] %.50s...\n", i, s);
    } else {
      THR_Print("[%" Pd "] = %s\n", i, s);
    }
  }

  const Context& parent_ctx = Context::Handle(parent());
  if (!parent_ctx.IsNull()) {
    parent_ctx.Dump(indent + 2);
  }
  IndentN(indent);
  THR_Print("}\n");
}

ContextScopePtr ContextScope::New(intptr_t num_variables, bool is_implicit) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kContextScopeCid) !=
         Class::null());
  if (num_variables < 0 || num_variables > kMaxElements) {
    // This should be caught before we reach here.
    FATAL("Fatal error in ContextScope::New: invalid num_variables %" Pd "\n",
          num_variables);
  }
  ContextScope& result = ContextScope::Handle();
  {
    auto raw = Object::Allocate<ContextScope>(Heap::kOld, num_variables);
    NoSafepointScope no_safepoint;
    result = raw;
    result.set_num_variables(num_variables);
  }
  result.set_is_implicit(is_implicit);
  return result.ptr();
}

TokenPosition ContextScope::TokenIndexAt(intptr_t scope_index) const {
  return TokenPosition::Deserialize(
      Smi::Value(untag()->token_pos_at(scope_index)));
}

void ContextScope::SetTokenIndexAt(intptr_t scope_index,
                                   TokenPosition token_pos) const {
  untag()->set_token_pos_at(scope_index, Smi::New(token_pos.Serialize()));
}

TokenPosition ContextScope::DeclarationTokenIndexAt(
    intptr_t scope_index) const {
  return TokenPosition::Deserialize(
      Smi::Value(untag()->declaration_token_pos_at(scope_index)));
}

void ContextScope::SetDeclarationTokenIndexAt(
    intptr_t scope_index,
    TokenPosition declaration_token_pos) const {
  untag()->set_declaration_token_pos_at(
      scope_index, Smi::New(declaration_token_pos.Serialize()));
}

StringPtr ContextScope::NameAt(intptr_t scope_index) const {
  return untag()->name_at(scope_index);
}

void ContextScope::SetNameAt(intptr_t scope_index, const String& name) const {
  untag()->set_name_at(scope_index, name.ptr());
}

void ContextScope::ClearFlagsAt(intptr_t scope_index) const {
  untag()->set_flags_at(scope_index, Smi::New(0));
}

bool ContextScope::GetFlagAt(intptr_t scope_index, intptr_t bit_index) const {
  const intptr_t mask = 1 << bit_index;
  return (Smi::Value(untag()->flags_at(scope_index)) & mask) != 0;
}

void ContextScope::SetFlagAt(intptr_t scope_index,
                             intptr_t bit_index,
                             bool value) const {
  const intptr_t mask = 1 << bit_index;
  intptr_t flags = Smi::Value(untag()->flags_at(scope_index));
  untag()->set_flags_at(scope_index,
                        Smi::New(value ? flags | mask : flags & ~mask));
}

#define DEFINE_FLAG_ACCESSORS(Name)                                            \
  bool ContextScope::Is##Name##At(intptr_t scope_index) const {                \
    return GetFlagAt(scope_index,                                              \
                     UntaggedContextScope::VariableDesc::kIs##Name);           \
  }                                                                            \
                                                                               \
  void ContextScope::SetIs##Name##At(intptr_t scope_index, bool value) const { \
    SetFlagAt(scope_index, UntaggedContextScope::VariableDesc::kIs##Name,      \
              value);                                                          \
  }

CONTEXT_SCOPE_VARIABLE_DESC_FLAG_LIST(DEFINE_FLAG_ACCESSORS)
#undef DEFINE_FLAG_ACCESSORS

intptr_t ContextScope::LateInitOffsetAt(intptr_t scope_index) const {
  return Smi::Value(untag()->late_init_offset_at(scope_index));
}

void ContextScope::SetLateInitOffsetAt(intptr_t scope_index,
                                       intptr_t late_init_offset) const {
  untag()->set_late_init_offset_at(scope_index, Smi::New(late_init_offset));
}

AbstractTypePtr ContextScope::TypeAt(intptr_t scope_index) const {
  return untag()->type_at(scope_index);
}

void ContextScope::SetTypeAt(intptr_t scope_index,
                             const AbstractType& type) const {
  untag()->set_type_at(scope_index, type.ptr());
}

intptr_t ContextScope::CidAt(intptr_t scope_index) const {
  return Smi::Value(untag()->cid_at(scope_index));
}

void ContextScope::SetCidAt(intptr_t scope_index, intptr_t cid) const {
  untag()->set_cid_at(scope_index, Smi::New(cid));
}

intptr_t ContextScope::ContextIndexAt(intptr_t scope_index) const {
  return Smi::Value(untag()->context_index_at(scope_index));
}

void ContextScope::SetContextIndexAt(intptr_t scope_index,
                                     intptr_t context_index) const {
  untag()->set_context_index_at(scope_index, Smi::New(context_index));
}

intptr_t ContextScope::ContextLevelAt(intptr_t scope_index) const {
  return Smi::Value(untag()->context_level_at(scope_index));
}

void ContextScope::SetContextLevelAt(intptr_t scope_index,
                                     intptr_t context_level) const {
  untag()->set_context_level_at(scope_index, Smi::New(context_level));
}

intptr_t ContextScope::KernelOffsetAt(intptr_t scope_index) const {
  return Smi::Value(untag()->kernel_offset_at(scope_index));
}

void ContextScope::SetKernelOffsetAt(intptr_t scope_index,
                                     intptr_t kernel_offset) const {
  untag()->set_kernel_offset_at(scope_index, Smi::New(kernel_offset));
}

const char* ContextScope::ToCString() const {
  const char* prev_cstr = "ContextScope:";
  String& name = String::Handle();
  for (int i = 0; i < num_variables(); i++) {
    name = NameAt(i);
    const char* cname = name.ToCString();
    TokenPosition pos = TokenIndexAt(i);
    intptr_t idx = ContextIndexAt(i);
    intptr_t lvl = ContextLevelAt(i);
    char* chars =
        OS::SCreate(Thread::Current()->zone(),
                    "%s\nvar %s  token-pos %s  ctx lvl %" Pd "  index %" Pd "",
                    prev_cstr, cname, pos.ToCString(), lvl, idx);
    prev_cstr = chars;
  }
  return prev_cstr;
}

SentinelPtr Sentinel::New() {
  return Object::Allocate<Sentinel>(Heap::kOld);
}

const char* Sentinel::ToCString() const {
  if (ptr() == Object::sentinel().ptr()) {
    return "sentinel";
  } else if (ptr() == Object::unknown_constant().ptr()) {
    return "unknown_constant";
  } else if (ptr() == Object::non_constant().ptr()) {
    return "non_constant";
  } else if (ptr() == Object::optimized_out().ptr()) {
    return "<optimized out>";
  }
  return "Sentinel(unknown)";
}

ArrayPtr MegamorphicCache::buckets() const {
  return untag()->buckets();
}

void MegamorphicCache::set_buckets(const Array& buckets) const {
  untag()->set_buckets(buckets.ptr());
}

// Class IDs in the table are smi-tagged, so we use a smi-tagged mask
// and target class ID to avoid untagging (on each iteration of the
// test loop) in generated code.
intptr_t MegamorphicCache::mask() const {
  return Smi::Value(untag()->mask());
}

void MegamorphicCache::set_mask(intptr_t mask) const {
  untag()->set_mask(Smi::New(mask));
}

intptr_t MegamorphicCache::filled_entry_count() const {
  return untag()->filled_entry_count_;
}

void MegamorphicCache::set_filled_entry_count(intptr_t count) const {
  StoreNonPointer(&untag()->filled_entry_count_, count);
}

MegamorphicCachePtr MegamorphicCache::New() {
  return Object::Allocate<MegamorphicCache>(Heap::kOld);
}

MegamorphicCachePtr MegamorphicCache::New(const String& target_name,
                                          const Array& arguments_descriptor) {
  auto* const zone = Thread::Current()->zone();
  const auto& result = MegamorphicCache::Handle(
      zone, Object::Allocate<MegamorphicCache>(Heap::kOld));
  const intptr_t capacity = kInitialCapacity;
  const Array& buckets =
      Array::Handle(zone, Array::New(kEntryLength * capacity, Heap::kOld));
  const Object& handler = Object::Handle(zone);
  for (intptr_t i = 0; i < capacity; ++i) {
    SetEntry(buckets, i, smi_illegal_cid(), handler);
  }
  result.set_buckets(buckets);
  result.set_mask(capacity - 1);
  result.set_target_name(target_name);
  result.set_arguments_descriptor(arguments_descriptor);
  result.set_filled_entry_count(0);
  return result.ptr();
}

void MegamorphicCache::EnsureContains(const Smi& class_id,
                                      const Object& target) const {
  SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());

  if (LookupLocked(class_id) == Object::null()) {
    InsertLocked(class_id, target);
  }

#if defined(DEBUG)
  ASSERT(LookupLocked(class_id) == target.ptr());
#endif  // define(DEBUG)
}

ObjectPtr MegamorphicCache::Lookup(const Smi& class_id) const {
  SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());
  return LookupLocked(class_id);
}

ObjectPtr MegamorphicCache::LookupLocked(const Smi& class_id) const {
  auto thread = Thread::Current();
  auto isolate_group = thread->isolate_group();
  auto zone = thread->zone();
  ASSERT(thread->IsDartMutatorThread());
  ASSERT(isolate_group->type_feedback_mutex()->IsOwnedByCurrentThread());

  const auto& backing_array = Array::Handle(zone, buckets());
  intptr_t id_mask = mask();
  intptr_t index = (class_id.Value() * kSpreadFactor) & id_mask;
  intptr_t i = index;
  do {
    const classid_t current_cid =
        Smi::Value(Smi::RawCast(GetClassId(backing_array, i)));
    if (current_cid == class_id.Value()) {
      return GetTargetFunction(backing_array, i);
    } else if (current_cid == kIllegalCid) {
      return Object::null();
    }
    i = (i + 1) & id_mask;
  } while (i != index);
  UNREACHABLE();
}

// Note this may run with Dart execution reading this cache.
void MegamorphicCache::InsertLocked(const Smi& class_id,
                                    const Object& target) const {
  auto thread = Thread::Current();
  auto zone = thread->zone();
  auto isolate_group = thread->isolate_group();
  ASSERT(isolate_group->type_feedback_mutex()->IsOwnedByCurrentThread());

  intptr_t old_capacity = mask() + 1;
  double load_limit = kLoadFactor * static_cast<double>(old_capacity);
  if (static_cast<double>(filled_entry_count() + 1) > load_limit) {
    const Array& old_buckets = Array::Handle(zone, buckets());
    intptr_t new_capacity = old_capacity * 2;
    intptr_t new_mask = new_capacity - 1;
    const Array& new_buckets =
        Array::Handle(zone, Array::New(kEntryLength * new_capacity));

    auto& target = Object::Handle(zone);
    for (intptr_t i = 0; i < new_capacity; ++i) {
      SetEntry(new_buckets, i, smi_illegal_cid(), target);
    }

    // Rehash the valid entries.
    Smi& class_id = Smi::Handle(zone);
    for (intptr_t i = 0; i < old_capacity; ++i) {
      class_id ^= GetClassId(old_buckets, i);
      if (class_id.Value() != kIllegalCid) {
        target = GetTargetFunction(old_buckets, i);
        InsertEntryLocked<std::memory_order_relaxed>(new_buckets, new_mask,
                                                     class_id, target);
      }
    }

    // Publish buckets first. Old mask with new buckets is just a spurious miss.
    untag()->set_buckets<std::memory_order_release>(new_buckets.ptr());
    untag()->set_mask<std::memory_order_release>(Smi::New(new_mask));
  }

  const Array& new_buckets = Array::Handle(zone, buckets());
  InsertEntryLocked<std::memory_order_release>(new_buckets, mask(), class_id,
                                               target);
  set_filled_entry_count(filled_entry_count() + 1);
}

template <std::memory_order order>
void MegamorphicCache::InsertEntryLocked(const Array& backing_array,
                                         intptr_t mask,
                                         const Smi& class_id,
                                         const Object& target) {
  const intptr_t start = (class_id.Value() * kSpreadFactor) & mask;
  intptr_t i = start;
  do {
    if (Smi::Value(Smi::RawCast(GetClassId(backing_array, i))) == kIllegalCid) {
      // Publish target first. Old class id with new target is just a spurious
      // miss.
      SetTargetFunction<order>(backing_array, i, target);
      SetClassId<order>(backing_array, i, class_id);
      return;
    }
    i = (i + 1) & mask;
  } while (i != start);
  UNREACHABLE();
}

const char* MegamorphicCache::ToCString() const {
  const String& name = String::Handle(target_name());
  return OS::SCreate(Thread::Current()->zone(), "MegamorphicCache(%s)",
                     name.ToCString());
}

SubtypeTestCachePtr SubtypeTestCache::New(intptr_t num_inputs) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kSubtypeTestCacheCid) !=
         Class::null());
  ASSERT(num_inputs >= 1);
  ASSERT(num_inputs <= kMaxInputs);
  // SubtypeTestCache objects are long living objects, allocate them in the
  // old generation.
  const auto& result =
      SubtypeTestCache::Handle(Object::Allocate<SubtypeTestCache>(Heap::kOld));
  ASSERT_EQUAL(result.num_occupied(), 0);
  result.untag()->num_inputs_ = num_inputs;
  result.set_cache(Object::empty_subtype_test_cache_array());
  return result.ptr();
}

ArrayPtr SubtypeTestCache::cache() const {
  return untag()->cache<std::memory_order_acquire>();
}

void SubtypeTestCache::set_cache(const Array& value) const {
  // We have to ensure that initializing stores to the array are available
  // when releasing the pointer to the array pointer.
  // => We have to use store-release here.
  untag()->set_cache<std::memory_order_release>(value.ptr());
}

void SubtypeTestCache::set_num_occupied(intptr_t value) const {
  ASSERT(Utils::IsUint(32, value));
  untag()->num_occupied_ = value;
}

intptr_t SubtypeTestCache::NumberOfChecks() const {
  ASSERT(!IsNull());
  return num_occupied();
}

intptr_t SubtypeTestCache::NumEntries(const ArrayPtr array) {
  ASSERT(array != Array::null());
  return Array::LengthOf(array) / kTestEntryLength;
}

bool SubtypeTestCache::IsHash(const ArrayPtr array) {
  ASSERT(array != Array::null());
  return Array::LengthOf(array) > kMaxLinearCacheSize;
}

intptr_t SubtypeTestCache::AddCheck(
    const Object& instance_class_id_or_signature,
    const AbstractType& destination_type,
    const TypeArguments& instance_type_arguments,
    const TypeArguments& instantiator_type_arguments,
    const TypeArguments& function_type_arguments,
    const TypeArguments& instance_parent_function_type_arguments,
    const TypeArguments& instance_delayed_type_arguments,
    const Bool& test_result) const {
  ASSERT(Thread::Current()
             ->isolate_group()
             ->subtype_test_cache_mutex()
             ->IsOwnedByCurrentThread());
  ASSERT(!test_result.IsNull());
  ASSERT(Smi::New(kRecordCid) != instance_class_id_or_signature.ptr());

  const intptr_t old_num = NumberOfChecks();
  Zone* const zone = Thread::Current()->zone();
  Array& data = Array::Handle(zone, cache());
  bool was_grown;
  data = EnsureCapacity(zone, data, old_num + 1, &was_grown);
  ASSERT(data.ptr() != Object::empty_subtype_test_cache_array().ptr());

  const auto& loc = FindKeyOrUnused(
      data, num_inputs(), instance_class_id_or_signature, destination_type,
      instance_type_arguments, instantiator_type_arguments,
      function_type_arguments, instance_parent_function_type_arguments,
      instance_delayed_type_arguments);
  SubtypeTestCacheTable entries(data);
  const auto& entry = entries[loc.entry];
  if (loc.present) {
    if (entry.Get<kTestResult>() != test_result.ptr()) {
      const auto& old_result = Bool::Handle(zone, entry.Get<kTestResult>());
      FATAL("Existing subtype test cache entry has result %s, not %s",
            old_result.ToCString(), test_result.ToCString());
    }
    return loc.entry;
  }

  // Set the used elements in the entry in reverse order, so that the instance
  // cid or signature is last, then increment the number of entries.
  entry.Set<kTestResult>(test_result);
  switch (num_inputs()) {
    case 7:
      entry.Set<kDestinationType>(destination_type);
      FALL_THROUGH;
    case 6:
      entry.Set<kInstanceDelayedFunctionTypeArguments>(
          instance_delayed_type_arguments);
      FALL_THROUGH;
    case 5:
      entry.Set<kInstanceParentFunctionTypeArguments>(
          instance_parent_function_type_arguments);
      FALL_THROUGH;
    case 4:
      entry.Set<kFunctionTypeArguments>(function_type_arguments);
      FALL_THROUGH;
    case 3:
      entry.Set<kInstantiatorTypeArguments>(instantiator_type_arguments);
      FALL_THROUGH;
    case 2:
      entry.Set<kInstanceTypeArguments>(instance_type_arguments);
      FALL_THROUGH;
    case 1:
      // If this is a new backing array, we don't need store-release barriers,
      // as no reader has access to the array until it is set as the backing
      // store (which is done with a store-release barrier).
      //
      // Otherwise, the instance cid or signature must be set last with a
      // store-release barrier, so concurrent readers can depend on a non-null
      // value meaning the rest of the entry is safe to load without barriers.
      if (was_grown) {
        entry.Set<kInstanceCidOrSignature>(instance_class_id_or_signature);
      } else {
        entry.Set<kInstanceCidOrSignature, std::memory_order_release>(
            instance_class_id_or_signature);
      }
      break;
    default:
      UNREACHABLE();
  }
  set_num_occupied(old_num + 1);
  if (was_grown) {
    set_cache(data);
  }
  return loc.entry;
}

SubtypeTestCache::KeyLocation SubtypeTestCache::FindKeyOrUnused(
    const Array& array,
    intptr_t num_inputs,
    const Object& instance_class_id_or_signature,
    const AbstractType& destination_type,
    const TypeArguments& instance_type_arguments,
    const TypeArguments& instantiator_type_arguments,
    const TypeArguments& function_type_arguments,
    const TypeArguments& instance_parent_function_type_arguments,
    const TypeArguments& instance_delayed_type_arguments) {
  // Fast case for empty STCs.
  if (array.ptr() == Object::empty_subtype_test_cache_array().ptr()) {
    return {0, false};
  }
  const bool is_hash = IsHash(array.ptr());
  SubtypeTestCacheTable table(array);
  const intptr_t num_entries = table.Length();
  // For a linear cache, start at the first entry and probe linearly. This can
  // be done because a linear cache always has at least one unoccupied entry
  // after all the occupied ones.
  intptr_t probe = 0;
  if (is_hash) {
    // For a hash-based cache, instead start at an entry determined by the hash
    // of the keys.
    //
    // If we have an instance cid, then just use that as our starting hash.
    uint32_t hash =
        instance_class_id_or_signature.IsFunctionType()
            ? FunctionType::Cast(instance_class_id_or_signature).Hash()
            : Smi::Cast(instance_class_id_or_signature).Value();
    switch (num_inputs) {
      case 7:
        hash = CombineHashes(hash, destination_type.Hash());
        FALL_THROUGH;
      case 6:
        hash = CombineHashes(hash, instance_delayed_type_arguments.Hash());
        FALL_THROUGH;
      case 5:
        hash =
            CombineHashes(hash, instance_parent_function_type_arguments.Hash());
        FALL_THROUGH;
      case 4:
        hash = CombineHashes(hash, function_type_arguments.Hash());
        FALL_THROUGH;
      case 3:
        hash = CombineHashes(hash, instantiator_type_arguments.Hash());
        FALL_THROUGH;
      case 2:
        hash = CombineHashes(hash, instance_type_arguments.Hash());
        FALL_THROUGH;
      case 1:
        break;
      default:
        UNREACHABLE();
    }
    hash = FinalizeHash(hash);
    probe = hash & (num_entries - 1);
  }
  NoSafepointScope scope;
  return FindKeyOrUnusedFromProbe(
      array.ptr(), num_inputs, probe, instance_class_id_or_signature.ptr(),
      destination_type.ptr(), instance_type_arguments.ptr(),
      instantiator_type_arguments.ptr(), function_type_arguments.ptr(),
      instance_parent_function_type_arguments.ptr(),
      instance_delayed_type_arguments.ptr());
}

static inline bool CheckSubtypeTestCacheEntry(
    ArrayPtr array,
    intptr_t num_inputs,
    intptr_t entry_start,
    ObjectPtr instance_class_id_or_signature,
    AbstractTypePtr destination_type,
    TypeArgumentsPtr instance_type_arguments,
    TypeArgumentsPtr instantiator_type_arguments,
    TypeArgumentsPtr function_type_arguments,
    TypeArgumentsPtr instance_parent_function_type_arguments,
    TypeArgumentsPtr instance_delayed_type_arguments) {
  // This function is only called when the entry slot that determines occupancy
  // is non-null. This means that the elements do not need to be load-acquired
  // here, as entries in STC backing arrays are never changed once added.
  switch (num_inputs) {
    case 7:
      if (Array::ElementAt(array,
                           entry_start + SubtypeTestCache::kDestinationType) !=
          destination_type) {
        return false;
      }
      FALL_THROUGH;
    case 6:
      if (Array::ElementAt(
              array,
              entry_start +
                  SubtypeTestCache::kInstanceDelayedFunctionTypeArguments) !=
          instance_delayed_type_arguments) {
        return false;
      }
      FALL_THROUGH;
    case 5:
      if (Array::ElementAt(
              array,
              entry_start +
                  SubtypeTestCache::kInstanceParentFunctionTypeArguments) !=
          instance_parent_function_type_arguments) {
        return false;
      }
      FALL_THROUGH;
    case 4:
      if (Array::ElementAt(
              array, entry_start + SubtypeTestCache::kFunctionTypeArguments) !=
          function_type_arguments) {
        return false;
      }
      FALL_THROUGH;
    case 3:
      if (Array::ElementAt(
              array,
              entry_start + SubtypeTestCache::kInstantiatorTypeArguments) !=
          instantiator_type_arguments) {
        return false;
      }
      FALL_THROUGH;
    case 2:
      if (Array::ElementAt(
              array, entry_start + SubtypeTestCache::kInstanceTypeArguments) !=
          instance_type_arguments) {
        return false;
      }
      FALL_THROUGH;
    case 1:
      return Array::ElementAt(
                 array,
                 entry_start + SubtypeTestCache::kInstanceCidOrSignature) ==
             instance_class_id_or_signature;
  }
  UNREACHABLE();
  return false;
}

SubtypeTestCache::KeyLocation SubtypeTestCache::FindKeyOrUnusedFromProbe(
    ArrayPtr array,
    intptr_t num_inputs,
    intptr_t probe,
    ObjectPtr instance_class_id_or_signature,
    AbstractTypePtr destination_type,
    TypeArgumentsPtr instance_type_arguments,
    TypeArgumentsPtr instantiator_type_arguments,
    TypeArgumentsPtr function_type_arguments,
    TypeArgumentsPtr instance_parent_function_type_arguments,
    TypeArgumentsPtr instance_delayed_type_arguments,
    BoolPtr* test_result) {
  // Fast case for empty STCs.
  if (array == Object::empty_subtype_test_cache_array().ptr()) {
    return {probe, false};
  }
  const bool is_hash = IsHash(array);
  const intptr_t num_entries = NumEntries(array);
  intptr_t probe_distance = 1;
  while (true) {
    intptr_t entry_start = probe * SubtypeTestCache::kTestEntryLength;
    // First check the entry slot that determines occupancy, which requires
    // load-acquire semantics.
    ObjectPtr cid_or_sig = Array::ElementAt<std::memory_order_acquire>(
        array, entry_start + SubtypeTestCache::kInstanceCidOrSignature);
    if (cid_or_sig == Object::null()) {
      // The appropriate location was found, but there's no existing entry.
      return {probe, false};
    }
    if (CheckSubtypeTestCacheEntry(
            array, num_inputs, entry_start, instance_class_id_or_signature,
            destination_type, instance_type_arguments,
            instantiator_type_arguments, function_type_arguments,
            instance_parent_function_type_arguments,
            instance_delayed_type_arguments)) {
      if (test_result != nullptr) {
        *test_result = Bool::RawCast(Array::ElementAt(
            array, entry_start + SubtypeTestCache::kTestResult));
      }
      return {probe, true};
    }
    // Advance probe by the current probing distance.
    probe = probe + probe_distance;
    if (is_hash) {
      // Wrap around if the probe goes off the end of the entries array.
      probe = probe & (num_entries - 1);
      // We had a collision, so increase the probe distance. See comment in
      // EnsureCapacityLocked for an explanation of how this hits all slots.
      probe_distance++;
    }
  }
  UNREACHABLE();
  return {probe, false};
}

ArrayPtr SubtypeTestCache::EnsureCapacity(Zone* zone,
                                          const Array& array,
                                          intptr_t new_occupied,
                                          bool* was_grown) const {
  ASSERT(new_occupied > NumberOfChecks());
  ASSERT(was_grown != nullptr);
  // How many entries are in the current array (including unoccupied entries).
  const intptr_t current_capacity = NumEntries(array.ptr());

  // Early returns for cases where no growth is needed.
  *was_grown = false;
  const bool is_linear = IsLinear(array.ptr());
  if (is_linear) {
    // We need at least one unoccupied entry in addition to the occupied ones.
    if (current_capacity > new_occupied) return array.ptr();
  } else {
    if (LoadFactor(new_occupied, current_capacity) < kMaxLoadFactor) {
      return array.ptr();
    }
  }

  // Every path from here should result in a new backing array.
  *was_grown = true;
  // Initially null for initializing unoccupied entries.
  auto& instance_cid_or_signature = Object::Handle(zone);
  if (new_occupied <= kMaxLinearCacheEntries) {
    ASSERT(is_linear);
    // Not enough room for both the new entry and at least one unoccupied
    // entry, so grow the tuple capacity of the linear cache by about 50%,
    // ensuring that space for at least one new tuple is added, capping the
    // total number of occupied entries to the max allowed.
    const intptr_t new_capacity =
        Utils::Minimum(current_capacity + (current_capacity >> 1),
                       kMaxLinearCacheEntries) +
        1;
    const intptr_t cache_size = new_capacity * kTestEntryLength;
    ASSERT(cache_size <= kMaxLinearCacheSize);
    const auto& new_data =
        Array::Handle(zone, Array::Grow(array, cache_size, Heap::kOld));
    ASSERT(!new_data.IsNull());
    // No need to adjust old entries, as they are copied over by Array::Grow.
    // Just mark any new entries as unoccupied.
    SubtypeTestCacheTable table(new_data);
    for (intptr_t i = current_capacity; i < new_capacity; i++) {
      const auto& tuple = table.At(i);
      tuple.Set<kInstanceCidOrSignature>(instance_cid_or_signature);
    }
    return new_data.ptr();
  }

  // Either we're converting a linear cache into a hash-based cache, or the
  // load factor of the hash-based cache has increased to the point where we
  // need to grow it.
  const intptr_t new_capacity =
      is_linear ? kNumInitialHashCacheEntries : 2 * current_capacity;
  // Because we use quadratic (actually triangle number) probing it is
  // important that the size is a power of two (otherwise we could fail to
  // find an empty slot).  This is described in Knuth's The Art of Computer
  // Programming Volume 2, Chapter 6.4, exercise 20 (solution in the
  // appendix, 2nd edition).
  //
  // This is also important because when we do hash probing, we take the
  // calculated hash from the inputs and then calculate (hash % capacity) to get
  // the initial probe index. To ensure this is a fast calculation in the stubs,
  // we ensure the capacity is a power of 2, which allows (hash % capacity) to
  // be calculated as (hash & (capacity - 1)).
  ASSERT(Utils::IsPowerOfTwo(new_capacity));
  ASSERT(LoadFactor(new_occupied, new_capacity) < kMaxLoadFactor);
  const intptr_t new_size = new_capacity * kTestEntryLength;
  const auto& new_data =
      Array::Handle(zone, Array::NewUninitialized(new_size, Heap::kOld));
  ASSERT(!new_data.IsNull());
  // Mark all the entries in new_data as unoccupied.
  SubtypeTestCacheTable to_table(new_data);
  for (const auto& tuple : to_table) {
    tuple.Set<kInstanceCidOrSignature>(instance_cid_or_signature);
  }
  // Finally, copy over the entries.
  auto& destination_type = AbstractType::Handle(zone);
  auto& instance_type_arguments = TypeArguments::Handle(zone);
  auto& instantiator_type_arguments = TypeArguments::Handle(zone);
  auto& function_type_arguments = TypeArguments::Handle(zone);
  auto& instance_parent_function_type_arguments = TypeArguments::Handle(zone);
  auto& instance_delayed_type_arguments = TypeArguments::Handle(zone);
  auto& test_result = Bool::Handle(zone);
  const SubtypeTestCacheTable from_table(array);
  const intptr_t used_inputs = num_inputs();
  for (intptr_t i = 0; i < current_capacity; i++) {
    const auto& from_tuple = from_table.At(i);
    // Skip unoccupied entries.
    if (from_tuple.Get<kInstanceCidOrSignature>() == Object::null()) continue;
    GetCheckFromArray(array, used_inputs, i, &instance_cid_or_signature,
                      &destination_type, &instance_type_arguments,
                      &instantiator_type_arguments, &function_type_arguments,
                      &instance_parent_function_type_arguments,
                      &instance_delayed_type_arguments, &test_result);
    // Since new_data has a different total capacity, we can't use the old
    // entry indexes, but must recalculate them.
    auto loc = FindKeyOrUnused(
        new_data, used_inputs, instance_cid_or_signature, destination_type,
        instance_type_arguments, instantiator_type_arguments,
        function_type_arguments, instance_parent_function_type_arguments,
        instance_delayed_type_arguments);
    ASSERT(!loc.present);
    const auto& to_tuple = to_table.At(loc.entry);
    to_tuple.Set<kTestResult>(test_result);
    switch (used_inputs) {
      case 7:
        to_tuple.Set<kDestinationType>(destination_type);
        FALL_THROUGH;
      case 6:
        to_tuple.Set<kInstanceDelayedFunctionTypeArguments>(
            instance_delayed_type_arguments);
        FALL_THROUGH;
      case 5:
        to_tuple.Set<kInstanceParentFunctionTypeArguments>(
            instance_parent_function_type_arguments);
        FALL_THROUGH;
      case 4:
        to_tuple.Set<kFunctionTypeArguments>(function_type_arguments);
        FALL_THROUGH;
      case 3:
        to_tuple.Set<kInstantiatorTypeArguments>(instantiator_type_arguments);
        FALL_THROUGH;
      case 2:
        to_tuple.Set<kInstanceTypeArguments>(instance_type_arguments);
        FALL_THROUGH;
      case 1:
        to_tuple.Set<kInstanceCidOrSignature>(instance_cid_or_signature);
        break;
      default:
        UNREACHABLE();
    }
  }
  return new_data.ptr();
}

void SubtypeTestCache::GetCheck(
    intptr_t ix,
    Object* instance_class_id_or_signature,
    AbstractType* destination_type,
    TypeArguments* instance_type_arguments,
    TypeArguments* instantiator_type_arguments,
    TypeArguments* function_type_arguments,
    TypeArguments* instance_parent_function_type_arguments,
    TypeArguments* instance_delayed_type_arguments,
    Bool* test_result) const {
  ASSERT(Thread::Current()
             ->isolate_group()
             ->subtype_test_cache_mutex()
             ->IsOwnedByCurrentThread());
  GetCurrentCheck(ix, instance_class_id_or_signature, destination_type,
                  instance_type_arguments, instantiator_type_arguments,
                  function_type_arguments,
                  instance_parent_function_type_arguments,
                  instance_delayed_type_arguments, test_result);
}

void SubtypeTestCache::GetCurrentCheck(
    intptr_t ix,
    Object* instance_class_id_or_signature,
    AbstractType* destination_type,
    TypeArguments* instance_type_arguments,
    TypeArguments* instantiator_type_arguments,
    TypeArguments* function_type_arguments,
    TypeArguments* instance_parent_function_type_arguments,
    TypeArguments* instance_delayed_type_arguments,
    Bool* test_result) const {
  const Array& array = Array::Handle(cache());
  GetCheckFromArray(array, num_inputs(), ix, instance_class_id_or_signature,
                    destination_type, instance_type_arguments,
                    instantiator_type_arguments, function_type_arguments,
                    instance_parent_function_type_arguments,
                    instance_delayed_type_arguments, test_result);
}

void SubtypeTestCache::GetCheckFromArray(
    const Array& array,
    intptr_t num_inputs,
    intptr_t ix,
    Object* instance_class_id_or_signature,
    AbstractType* destination_type,
    TypeArguments* instance_type_arguments,
    TypeArguments* instantiator_type_arguments,
    TypeArguments* function_type_arguments,
    TypeArguments* instance_parent_function_type_arguments,
    TypeArguments* instance_delayed_type_arguments,
    Bool* test_result) {
  ASSERT(array.ptr() != Object::empty_subtype_test_cache_array().ptr());
  SubtypeTestCacheTable entries(array);
  auto entry = entries[ix];
  // First get the field that determines occupancy. We have to do this with
  // load-acquire because some callers may not have the subtype test cache lock.
  *instance_class_id_or_signature =
      entry.Get<kInstanceCidOrSignature, std::memory_order_acquire>();
  // We should not be retrieving unoccupied entries.
  ASSERT(!instance_class_id_or_signature->IsNull());
  switch (num_inputs) {
    case 7:
      *destination_type = entry.Get<kDestinationType>();
      FALL_THROUGH;
    case 6:
      *instance_delayed_type_arguments =
          entry.Get<kInstanceDelayedFunctionTypeArguments>();
      FALL_THROUGH;
    case 5:
      *instance_parent_function_type_arguments =
          entry.Get<kInstanceParentFunctionTypeArguments>();
      FALL_THROUGH;
    case 4:
      *function_type_arguments = entry.Get<kFunctionTypeArguments>();
      FALL_THROUGH;
    case 3:
      *instantiator_type_arguments = entry.Get<kInstantiatorTypeArguments>();
      FALL_THROUGH;
    case 2:
      *instance_type_arguments = entry.Get<kInstanceTypeArguments>();
      FALL_THROUGH;
    case 1:
      break;
    default:
      UNREACHABLE();
  }
  *test_result = entry.Get<kTestResult>();
}

bool SubtypeTestCache::GetNextCheck(
    intptr_t* ix,
    Object* instance_class_id_or_signature,
    AbstractType* destination_type,
    TypeArguments* instance_type_arguments,
    TypeArguments* instantiator_type_arguments,
    TypeArguments* function_type_arguments,
    TypeArguments* instance_parent_function_type_arguments,
    TypeArguments* instance_delayed_type_arguments,
    Bool* test_result) const {
  ASSERT(ix != nullptr);
  for (intptr_t i = *ix; i < NumEntries(); i++) {
    ASSERT(Thread::Current()
               ->isolate_group()
               ->subtype_test_cache_mutex()
               ->IsOwnedByCurrentThread());
    if (IsOccupied(i)) {
      GetCurrentCheck(i, instance_class_id_or_signature, destination_type,
                      instance_type_arguments, instantiator_type_arguments,
                      function_type_arguments,
                      instance_parent_function_type_arguments,
                      instance_delayed_type_arguments, test_result);
      *ix = i + 1;
      return true;
    }
  }
  return false;
}

bool SubtypeTestCache::HasCheck(
    const Object& instance_class_id_or_signature,
    const AbstractType& destination_type,
    const TypeArguments& instance_type_arguments,
    const TypeArguments& instantiator_type_arguments,
    const TypeArguments& function_type_arguments,
    const TypeArguments& instance_parent_function_type_arguments,
    const TypeArguments& instance_delayed_type_arguments,
    intptr_t* index,
    Bool* result) const {
  const auto& data = Array::Handle(cache());
  auto loc = FindKeyOrUnused(
      data, num_inputs(), instance_class_id_or_signature, destination_type,
      instance_type_arguments, instantiator_type_arguments,
      function_type_arguments, instance_parent_function_type_arguments,
      instance_delayed_type_arguments);
  if (loc.present) {
    if (index != nullptr) {
      *index = loc.entry;
    }
    if (result != nullptr) {
      SubtypeTestCacheTable entries(data);
      const auto& entry = entries[loc.entry];
      // A positive result from FindKeyOrUnused means that load-acquire is not
      // needed, as an occupied entry never changes for a given backing array.
      *result = entry.Get<kTestResult>();
      ASSERT(!result->IsNull());
    }
  }
  return loc.present;
}

void SubtypeTestCache::WriteEntryToBuffer(Zone* zone,
                                          BaseTextBuffer* buffer,
                                          intptr_t index,
                                          const char* line_prefix) const {
  ASSERT(Thread::Current()
             ->isolate_group()
             ->subtype_test_cache_mutex()
             ->IsOwnedByCurrentThread());
  WriteCurrentEntryToBuffer(zone, buffer, index, line_prefix);
}

void SubtypeTestCache::WriteToBuffer(Zone* zone,
                                     BaseTextBuffer* buffer,
                                     const char* line_prefix) const {
  ASSERT(Thread::Current()
             ->isolate_group()
             ->subtype_test_cache_mutex()
             ->IsOwnedByCurrentThread());
  WriteToBufferUnlocked(zone, buffer, line_prefix);
}

void SubtypeTestCache::WriteCurrentEntryToBuffer(
    Zone* zone,
    BaseTextBuffer* buffer,
    intptr_t index,
    const char* line_prefix) const {
  const char* separator =
      line_prefix == nullptr ? ", " : OS::SCreate(zone, "\n%s", line_prefix);
  auto& instance_class_id_or_signature = Object::Handle(zone);
  auto& destination_type = AbstractType::Handle(zone);
  auto& instance_type_arguments = TypeArguments::Handle(zone);
  auto& instantiator_type_arguments = TypeArguments::Handle(zone);
  auto& function_type_arguments = TypeArguments::Handle(zone);
  auto& instance_parent_function_type_arguments = TypeArguments::Handle(zone);
  auto& instance_delayed_type_arguments = TypeArguments::Handle(zone);
  auto& result = Bool::Handle(zone);
  GetCurrentCheck(index, &instance_class_id_or_signature, &destination_type,
                  &instance_type_arguments, &instantiator_type_arguments,
                  &function_type_arguments,
                  &instance_parent_function_type_arguments,
                  &instance_delayed_type_arguments, &result);
  buffer->Printf(
      "%" Pd ": [ %#" Px ", %#" Px ", %#" Px ", %#" Px ", %#" Px ", %#" Px
      ", %#" Px ", %#" Px " ]",
      index, static_cast<uword>(instance_class_id_or_signature.ptr()),
      static_cast<uword>(instance_type_arguments.ptr()),
      static_cast<uword>(instantiator_type_arguments.ptr()),
      static_cast<uword>(function_type_arguments.ptr()),
      static_cast<uword>(instance_parent_function_type_arguments.ptr()),
      static_cast<uword>(instance_delayed_type_arguments.ptr()),
      static_cast<uword>(destination_type.ptr()),
      static_cast<uword>(result.ptr()));
  if (instance_class_id_or_signature.IsSmi()) {
    buffer->Printf("%sclass id: %" Pd "", separator,
                   Smi::Cast(instance_class_id_or_signature).Value());
  } else {
    buffer->Printf(
        "%ssignature: %s", separator,
        FunctionType::Cast(instance_class_id_or_signature).ToCString());
  }
  if (!instance_type_arguments.IsNull()) {
    if (instance_class_id_or_signature.IsSmi()) {
      buffer->Printf("%sinstance type arguments: %s", separator,
                     instance_type_arguments.ToCString());
    } else {
      ASSERT(instance_class_id_or_signature.IsFunctionType());
      buffer->Printf("%sclosure instantiator function type arguments: %s",
                     separator, instance_type_arguments.ToCString());
    }
  }
  if (!instantiator_type_arguments.IsNull()) {
    buffer->Printf("%sinstantiator type arguments: %s", separator,
                   instantiator_type_arguments.ToCString());
  }
  if (!function_type_arguments.IsNull()) {
    buffer->Printf("%sfunction type arguments: %s", separator,
                   function_type_arguments.ToCString());
  }
  if (!instance_parent_function_type_arguments.IsNull()) {
    buffer->Printf("%sclosure parent function type arguments: %s", separator,
                   instance_parent_function_type_arguments.ToCString());
  }
  if (!instance_delayed_type_arguments.IsNull()) {
    buffer->Printf("%sclosure delayed function type arguments: %s", separator,
                   instance_delayed_type_arguments.ToCString());
  }
  if (!destination_type.IsNull()) {
    buffer->Printf("%sdestination type: %s", separator,
                   destination_type.ToCString());
    if (!destination_type.IsInstantiated()) {
      AbstractType& test_type = AbstractType::Handle(
          zone, destination_type.InstantiateFrom(instantiator_type_arguments,
                                                 function_type_arguments,
                                                 kAllFree, Heap::kNew));
      const auto type_class_id = test_type.type_class_id();
      buffer->Printf("%sinstantiated type: %s", separator,
                     test_type.ToCString());
      buffer->Printf("%sinstantiated type class id: %d", separator,
                     type_class_id);
    }
  }
  buffer->Printf("%sresult: %s", separator, result.ToCString());
}

void SubtypeTestCache::WriteToBufferUnlocked(Zone* zone,
                                             BaseTextBuffer* buffer,
                                             const char* line_prefix) const {
  const char* separator =
      line_prefix == nullptr ? " " : OS::SCreate(zone, "\n%s", line_prefix);
  const char* internal_line_prefix =
      line_prefix == nullptr
          ? nullptr
          : OS::SCreate(zone, "%s%s", line_prefix, line_prefix);
  const intptr_t num_entries = NumEntries();
  buffer->Printf("SubtypeTestCache(%" Pd ", %" Pd "", num_inputs(),
                 num_occupied());
  for (intptr_t i = 0; i < num_entries; i++) {
    if (!IsOccupied(i)) continue;
    buffer->Printf(",%s{", separator);
    WriteCurrentEntryToBuffer(zone, buffer, i, internal_line_prefix);
    buffer->Printf(line_prefix != nullptr ? "}" : " }");
  }
  buffer->AddString(line_prefix != nullptr && num_entries != 0 ? "\n)" : ")");
}

void SubtypeTestCache::Reset() const {
  set_num_occupied(0);
  set_cache(Object::empty_subtype_test_cache_array());
}

bool SubtypeTestCache::Equals(const SubtypeTestCache& other) const {
  ASSERT(Thread::Current()
             ->isolate_group()
             ->subtype_test_cache_mutex()
             ->IsOwnedByCurrentThread());
  if (ptr() == other.ptr()) {
    return true;
  }
  if (num_inputs() != other.num_inputs()) return false;
  if (num_occupied() != other.num_occupied()) return false;
  return Array::Handle(cache()).Equals(Array::Handle(other.cache()));
}

SubtypeTestCachePtr SubtypeTestCache::Copy(Thread* thread) const {
  ASSERT(thread->isolate_group()
             ->subtype_test_cache_mutex()
             ->IsOwnedByCurrentThread());
  if (IsNull()) {
    return SubtypeTestCache::null();
  }
  Zone* const zone = thread->zone();
  // STC caches are only copied on write if there are not enough unoccupied
  // entries to store a new one, so we need to copy the array.
  const auto& result =
      SubtypeTestCache::Handle(zone, SubtypeTestCache::New(num_inputs()));
  auto& entry_cache = Array::Handle(zone, cache());
  entry_cache = entry_cache.Copy();
  result.set_cache(entry_cache);
  result.set_num_occupied(num_occupied());
  return result.ptr();
}

bool SubtypeTestCache::IsOccupied(intptr_t index) const {
  ASSERT(!IsNull());
  ASSERT(index < NumEntries());
  const intptr_t cache_index =
      index * kTestEntryLength + kInstanceCidOrSignature;
  NoSafepointScope no_safepoint;
  return cache()->untag()->element<std::memory_order_acquire>(cache_index) !=
         Object::null();
}

intptr_t SubtypeTestCache::UsedInputsForType(const AbstractType& type) {
  if (type.IsType()) {
    if (type.IsInstantiated()) return 2;
    if (type.IsInstantiated(kFunctions)) return 3;
    return 4;
  }
  // Default to all inputs except for the destination type, which must be
  // statically known, otherwise this method wouldn't be called.
  static_assert(kDestinationType == kMaxInputs - 1,
                "destination type is not last input");
  return kMaxInputs - 1;
}

const char* SubtypeTestCache::ToCString() const {
  auto const zone = Thread::Current()->zone();
  ZoneTextBuffer buffer(zone);
  WriteToBufferUnlocked(zone, &buffer);
  return buffer.buffer();
}

LoadingUnitPtr LoadingUnit::New(intptr_t id, const LoadingUnit& parent) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kLoadingUnitCid) !=
         Class::null());
  // LoadingUnit objects are long living objects, allocate them in the
  // old generation.
  auto result = Object::Allocate<LoadingUnit>(Heap::kOld);
  NoSafepointScope scope;
  ASSERT(Utils::IsInt(UntaggedLoadingUnit::IdBits::bitsize(), id));
  result->untag()->packed_fields_.Update<UntaggedLoadingUnit::IdBits>(id);
  result->untag()->set_parent(parent.ptr());
  return result;
}

void LoadingUnit::set_base_objects(const Array& value) const {
  untag()->set_base_objects(value.ptr());
}

const char* LoadingUnit::ToCString() const {
  return "LoadingUnit";
}

ObjectPtr LoadingUnit::IssueLoad() const {
  set_load_outstanding();
  return Isolate::Current()->CallDeferredLoadHandler(id());
}

ObjectPtr LoadingUnit::CompleteLoad(const String& error_message,
                                    bool transient_error) const {
  set_loaded(error_message.IsNull());

  const Library& lib = Library::Handle(Library::CoreLibrary());
  const String& sel = String::Handle(String::New("_completeLoads"));
  const Function& func = Function::Handle(lib.LookupFunctionAllowPrivate(sel));
  ASSERT(!func.IsNull());
  const Array& args = Array::Handle(Array::New(3));
  args.SetAt(0, Smi::Handle(Smi::New(id())));
  args.SetAt(1, error_message);
  args.SetAt(2, Bool::Get(transient_error));
  return DartEntry::InvokeFunction(func, args);
}

// The assignment to loading units here must match that in
// AssignLoadingUnitsCodeVisitor, which runs after compilation is done.
intptr_t LoadingUnit::LoadingUnitOf(const Function& function) {
  Thread* thread = Thread::Current();
  REUSABLE_CLASS_HANDLESCOPE(thread);
  REUSABLE_LIBRARY_HANDLESCOPE(thread);
  REUSABLE_LOADING_UNIT_HANDLESCOPE(thread);

  Class& cls = thread->ClassHandle();
  Library& lib = thread->LibraryHandle();
  LoadingUnit& unit = thread->LoadingUnitHandle();

  cls = function.Owner();
  lib = cls.library();
  unit = lib.loading_unit();
  if (unit.IsNull()) {
    FATAL("Unable to find loading unit of %s (class %s, library %s)",
          function.ToFullyQualifiedCString(), cls.ToCString(), lib.ToCString());
  }
  return unit.id();
}

intptr_t LoadingUnit::LoadingUnitOf(const Code& code) {
  if (code.IsStubCode() || code.IsTypeTestStubCode() ||
      code.IsAllocationStubCode()) {
    return LoadingUnit::kRootId;
  } else {
    Thread* thread = Thread::Current();
    REUSABLE_FUNCTION_HANDLESCOPE(thread);
    REUSABLE_CLASS_HANDLESCOPE(thread);
    REUSABLE_LIBRARY_HANDLESCOPE(thread);
    REUSABLE_LOADING_UNIT_HANDLESCOPE(thread);

    Class& cls = thread->ClassHandle();
    Library& lib = thread->LibraryHandle();
    LoadingUnit& unit = thread->LoadingUnitHandle();
    Function& func = thread->FunctionHandle();

    if (code.IsFunctionCode()) {
      func ^= code.function();
      cls = func.Owner();
      lib = cls.library();
      unit = lib.loading_unit();
      ASSERT(!unit.IsNull());
      return unit.id();
    } else {
      UNREACHABLE();
      return LoadingUnit::kIllegalId;
    }
  }
}

const char* Error::ToErrorCString() const {
  if (IsNull()) {
    return "Error: null";
  }
  UNREACHABLE();
  return "Error";
}

const char* Error::ToCString() const {
  if (IsNull()) {
    return "Error: null";
  }
  // Error is an abstract class.  We should never reach here.
  UNREACHABLE();
  return "Error";
}

ApiErrorPtr ApiError::New() {
  ASSERT(IsolateGroup::Current()->class_table()->At(kApiErrorCid) !=
         Class::null());
  return Object::Allocate<ApiError>(Heap::kOld);
}

ApiErrorPtr ApiError::New(const String& message, Heap::Space space) {
#ifndef PRODUCT
  if (FLAG_print_stacktrace_at_api_error) {
    OS::PrintErr("ApiError: %s\n", message.ToCString());
    Profiler::DumpStackTrace(false /* for_crash */);
  }
#endif  // !PRODUCT

  ASSERT(IsolateGroup::Current()->class_table()->At(kApiErrorCid) !=
         Class::null());
  const auto& result = ApiError::Handle(Object::Allocate<ApiError>(space));
  result.set_message(message);
  return result.ptr();
}

void ApiError::set_message(const String& message) const {
  untag()->set_message(message.ptr());
}

const char* ApiError::ToErrorCString() const {
  const String& msg_str = String::Handle(message());
  return msg_str.ToCString();
}

const char* ApiError::ToCString() const {
  return "ApiError";
}

LanguageErrorPtr LanguageError::New() {
  ASSERT(IsolateGroup::Current()->class_table()->At(kLanguageErrorCid) !=
         Class::null());
  return Object::Allocate<LanguageError>(Heap::kOld);
}

LanguageErrorPtr LanguageError::NewFormattedV(const Error& prev_error,
                                              const Script& script,
                                              TokenPosition token_pos,
                                              bool report_after_token,
                                              Report::Kind kind,
                                              Heap::Space space,
                                              const char* format,
                                              va_list args) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kLanguageErrorCid) !=
         Class::null());
  const auto& result =
      LanguageError::Handle(Object::Allocate<LanguageError>(space));
  result.set_previous_error(prev_error);
  result.set_script(script);
  result.set_token_pos(token_pos);
  result.set_report_after_token(report_after_token);
  result.set_kind(kind);
  result.set_message(
      String::Handle(String::NewFormattedV(format, args, space)));
  return result.ptr();
}

LanguageErrorPtr LanguageError::NewFormatted(const Error& prev_error,
                                             const Script& script,
                                             TokenPosition token_pos,
                                             bool report_after_token,
                                             Report::Kind kind,
                                             Heap::Space space,
                                             const char* format,
                                             ...) {
  va_list args;
  va_start(args, format);
  LanguageErrorPtr result = LanguageError::NewFormattedV(
      prev_error, script, token_pos, report_after_token, kind, space, format,
      args);
  NoSafepointScope no_safepoint;
  va_end(args);
  return result;
}

LanguageErrorPtr LanguageError::New(const String& formatted_message,
                                    Report::Kind kind,
                                    Heap::Space space) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kLanguageErrorCid) !=
         Class::null());
  const auto& result =
      LanguageError::Handle(Object::Allocate<LanguageError>(space));
  result.set_formatted_message(formatted_message);
  result.set_kind(kind);
  return result.ptr();
}

void LanguageError::set_previous_error(const Error& value) const {
  untag()->set_previous_error(value.ptr());
}

void LanguageError::set_script(const Script& value) const {
  untag()->set_script(value.ptr());
}

void LanguageError::set_token_pos(TokenPosition token_pos) const {
  ASSERT(!token_pos.IsClassifying());
  StoreNonPointer(&untag()->token_pos_, token_pos);
}

void LanguageError::set_report_after_token(bool value) const {
  StoreNonPointer(&untag()->report_after_token_, value);
}

void LanguageError::set_kind(uint8_t value) const {
  StoreNonPointer(&untag()->kind_, value);
}

void LanguageError::set_message(const String& value) const {
  untag()->set_message(value.ptr());
}

void LanguageError::set_formatted_message(const String& value) const {
  untag()->set_formatted_message(value.ptr());
}

StringPtr LanguageError::FormatMessage() const {
  if (formatted_message() != String::null()) {
    return formatted_message();
  }
  String& result = String::Handle(
      Report::PrependSnippet(kind(), Script::Handle(script()), token_pos(),
                             report_after_token(), String::Handle(message())));
  // Prepend previous error message.
  const Error& prev_error = Error::Handle(previous_error());
  if (!prev_error.IsNull()) {
    result = String::Concat(
        String::Handle(String::New(prev_error.ToErrorCString())), result);
  }
  set_formatted_message(result);
  return result.ptr();
}

const char* LanguageError::ToErrorCString() const {
  const String& msg_str = String::Handle(FormatMessage());
  return msg_str.ToCString();
}

const char* LanguageError::ToCString() const {
  return "LanguageError";
}

UnhandledExceptionPtr UnhandledException::New(const Instance& exception,
                                              const Instance& stacktrace,
                                              Heap::Space space) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kUnhandledExceptionCid) !=
         Class::null());
  const auto& result =
      UnhandledException::Handle(Object::Allocate<UnhandledException>(space));
  result.set_exception(exception);
  result.set_stacktrace(stacktrace);
  return result.ptr();
}

UnhandledExceptionPtr UnhandledException::New(Heap::Space space) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kUnhandledExceptionCid) !=
         Class::null());
  return Object::Allocate<UnhandledException>(space);
}

void UnhandledException::set_exception(const Instance& exception) const {
  untag()->set_exception(exception.ptr());
}

void UnhandledException::set_stacktrace(const Instance& stacktrace) const {
  untag()->set_stacktrace(stacktrace.ptr());
}

const char* UnhandledException::ToErrorCString() const {
  Thread* thread = Thread::Current();
  auto isolate_group = thread->isolate_group();
  NoReloadScope no_reload_scope(thread);
  HANDLESCOPE(thread);
  Object& strtmp = Object::Handle();
  const char* exc_str;
  if (exception() == isolate_group->object_store()->out_of_memory()) {
    exc_str = "Out of Memory";
  } else if (exception() == isolate_group->object_store()->stack_overflow()) {
    exc_str = "Stack Overflow";
  } else {
    const Instance& exc = Instance::Handle(exception());
    strtmp = DartLibraryCalls::ToString(exc);
    if (!strtmp.IsError()) {
      exc_str = strtmp.ToCString();
    } else {
      exc_str = "<Received error while converting exception to string>";
    }
  }
  const Instance& stack = Instance::Handle(stacktrace());
  const char* stack_str;
  if (stack.IsNull()) {
    stack_str = "null";
  } else if (stack.IsStackTrace()) {
    stack_str = StackTrace::Cast(stack).ToCString();
  } else {
    strtmp = DartLibraryCalls::ToString(stack);
    if (!strtmp.IsError()) {
      stack_str = strtmp.ToCString();
    } else {
      stack_str = "<Received error while converting stack trace to string>";
    }
  }
  return OS::SCreate(thread->zone(), "Unhandled exception:\n%s\n%s", exc_str,
                     stack_str);
}

const char* UnhandledException::ToCString() const {
  return "UnhandledException";
}

UnwindErrorPtr UnwindError::New(const String& message, Heap::Space space) {
  ASSERT(IsolateGroup::Current()->class_table()->At(kUnwindErrorCid) !=
         Class::null());
  const auto& result =
      UnwindError::Handle(Object::Allocate<UnwindError>(space));
  result.set_message(message);
  ASSERT_EQUAL(result.is_user_initiated(), false);
  return result.ptr();
}

void UnwindError::set_message(const String& message) const {
  untag()->set_message(message.ptr());
}

void UnwindError::set_is_user_initiated(bool value) const {
  StoreNonPointer(&untag()->is_user_initiated_, value);
}

const char* UnwindError::ToErrorCString() const {
  const String& msg_str = String::Handle(message());
  return msg_str.ToCString();
}

const char* UnwindError::ToCString() const {
  return "UnwindError";
}

ObjectPtr Instance::InvokeGetter(const String& getter_name,
                                 bool check_is_entrypoint,
                                 bool respect_reflectable) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();

  Class& klass = Class::Handle(zone, clazz());
  CHECK_ERROR(klass.EnsureIsFinalized(thread));
  const auto& inst_type_args =
      klass.NumTypeArguments() > 0
          ? TypeArguments::Handle(zone, GetTypeArguments())
          : Object::null_type_arguments();

  const String& internal_getter_name =
      String::Handle(zone, Field::GetterName(getter_name));
  Function& function = Function::Handle(
      zone,
      Resolver::ResolveDynamicAnyArgs(zone, klass, internal_getter_name,
                                      /*allow_add=*/!FLAG_precompiled_mode));

  // Check for method extraction when method extractors are not lazily created.
  if (function.IsNull() && FLAG_precompiled_mode) {
    function = Resolver::ResolveDynamicAnyArgs(zone, klass, getter_name,
                                               /*allow_add=*/false);

    if (!function.IsNull() && function.SafeToClosurize()) {
      if (check_is_entrypoint) {
        CHECK_ERROR(function.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
      }
      const Function& closure_function =
          Function::Handle(zone, function.ImplicitClosureFunction());
      return closure_function.ImplicitInstanceClosure(*this);
    }
  }

  const int kTypeArgsLen = 0;
  const int kNumArgs = 1;
  const Array& args = Array::Handle(zone, Array::New(kNumArgs));
  args.SetAt(0, *this);
  const Array& args_descriptor = Array::Handle(
      zone,
      ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(), Heap::kNew));

  return InvokeInstanceFunction(thread, *this, function, internal_getter_name,
                                args, args_descriptor, check_is_entrypoint,
                                EntryPointPragma::kGetterOnly,
                                respect_reflectable, inst_type_args);
}

ObjectPtr Instance::InvokeSetter(const String& setter_name,
                                 const Instance& value,
                                 bool check_is_entrypoint,
                                 bool respect_reflectable) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();

  const Class& klass = Class::Handle(zone, clazz());
  CHECK_ERROR(klass.EnsureIsFinalized(thread));
  const auto& inst_type_args =
      klass.NumTypeArguments() > 0
          ? TypeArguments::Handle(zone, GetTypeArguments())
          : Object::null_type_arguments();

  const String& internal_setter_name =
      String::Handle(zone, Field::SetterName(setter_name));
  const Function& setter = Function::Handle(
      zone,
      Resolver::ResolveDynamicAnyArgs(zone, klass, internal_setter_name,
                                      /*allow_add=*/!FLAG_precompiled_mode));

  const int kTypeArgsLen = 0;
  const int kNumArgs = 2;
  const Array& args = Array::Handle(zone, Array::New(kNumArgs));
  args.SetAt(0, *this);
  args.SetAt(1, value);
  const Array& args_descriptor = Array::Handle(
      zone,
      ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(), Heap::kNew));

  return InvokeInstanceFunction(thread, *this, setter, internal_setter_name,
                                args, args_descriptor, check_is_entrypoint,
                                EntryPointPragma::kSetterOnly,
                                respect_reflectable, inst_type_args);
}

ObjectPtr Instance::Invoke(const String& function_name,
                           const Array& args,
                           const Array& arg_names,
                           bool check_is_entrypoint,
                           bool respect_reflectable) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  Class& klass = Class::Handle(zone, clazz());
  CHECK_ERROR(klass.EnsureIsFinalized(thread));

  Function& function = Function::Handle(
      zone,
      Resolver::ResolveDynamicAnyArgs(zone, klass, function_name,
                                      /*allow_add=*/!FLAG_precompiled_mode));

  // We don't pass any explicit type arguments, which will be understood as
  // using dynamic for any function type arguments by lower layers.
  const int kTypeArgsLen = 0;
  const Array& args_descriptor = Array::Handle(
      zone, ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(),
                                          arg_names, Heap::kNew));

  const auto& inst_type_args =
      klass.NumTypeArguments() > 0
          ? TypeArguments::Handle(zone, GetTypeArguments())
          : Object::null_type_arguments();

  if (function.IsNull()) {
    // Didn't find a method: try to find a getter and invoke call on its result.
    const String& getter_name =
        String::Handle(zone, Field::GetterName(function_name));
    function =
        Resolver::ResolveDynamicAnyArgs(zone, klass, getter_name,
                                        /*allow_add=*/!FLAG_precompiled_mode);
    if (!function.IsNull()) {
      ASSERT(function.kind() != UntaggedFunction::kMethodExtractor);
      // Invoke the getter.
      const int kNumArgs = 1;
      const Array& getter_args = Array::Handle(zone, Array::New(kNumArgs));
      getter_args.SetAt(0, *this);
      const Array& getter_args_descriptor = Array::Handle(
          zone, ArgumentsDescriptor::NewBoxed(
                    kTypeArgsLen, getter_args.Length(), Heap::kNew));
      const Object& getter_result = Object::Handle(
          zone, InvokeInstanceFunction(thread, *this, function, getter_name,
                                       getter_args, getter_args_descriptor,
                                       check_is_entrypoint,
                                       EntryPointPragma::kGetterOnly,
                                       respect_reflectable, inst_type_args));
      if (getter_result.IsError()) {
        return getter_result.ptr();
      }
      // Replace the closure as the receiver in the arguments list.
      args.SetAt(0, getter_result);
      return DartEntry::InvokeClosure(thread, args, args_descriptor);
    }
  }

  // Found an ordinary method.
  return InvokeInstanceFunction(thread, *this, function, function_name, args,
                                args_descriptor, check_is_entrypoint,
                                EntryPointPragma::kCallOnly,
                                respect_reflectable, inst_type_args);
}

ObjectPtr Instance::HashCode() const {
  // TODO(koda): Optimize for all builtin classes and all classes
  // that do not override hashCode.
  return DartLibraryCalls::HashCode(*this);
}

// Keep in sync with AsmIntrinsifier::Object_getHash.
IntegerPtr Instance::IdentityHashCode(Thread* thread) const {
  if (IsInteger()) return Integer::Cast(*this).ptr();
  if (IsString()) {
    return Smi::New(String::Cast(*this).Hash());
  }

#if defined(HASH_IN_OBJECT_HEADER)
  intptr_t hash = Object::GetCachedHash(ptr());
#else
  intptr_t hash = thread->heap()->GetHash(ptr());
#endif
  if (hash == 0) {
    if (IsNull()) {
      hash = kNullIdentityHash;
    } else if (IsBool()) {
      hash = Bool::Cast(*this).value() ? kTrueIdentityHash : kFalseIdentityHash;
    } else if (IsDouble()) {
      double val = Double::Cast(*this).value();
      if ((val >= kMinInt64RepresentableAsDouble) &&
          (val <= kMaxInt64RepresentableAsDouble)) {
        int64_t ival = static_cast<int64_t>(val);
        if (static_cast<double>(ival) == val) {
          return Integer::New(ival);
        }
      }

      uint64_t uval = bit_cast<uint64_t>(val);
      hash = ((uval >> 32) ^ (uval)) & kSmiMax;
    } else {
      do {
        hash = thread->random()->NextUInt32() & 0x3FFFFFFF;
      } while (hash == 0);
    }

#if defined(HASH_IN_OBJECT_HEADER)
    hash = Object::SetCachedHashIfNotSet(ptr(), hash);
#else
    hash = thread->heap()->SetHashIfNotSet(ptr(), hash);
#endif
  }
  return Smi::New(hash);
}

bool Instance::CanonicalizeEquals(const Instance& other) const {
  if (this->ptr() == other.ptr()) {
    return true;  // "===".
  }

  if (other.IsNull() || (this->clazz() != other.clazz())) {
    return false;
  }

  {
    NoSafepointScope no_safepoint;
    // Raw bits compare.
    const intptr_t instance_size = SizeFromClass();
    ASSERT(instance_size != 0);
    const intptr_t other_instance_size = other.SizeFromClass();
    ASSERT(other_instance_size != 0);
    if (instance_size != other_instance_size) {
      return false;
    }
    uword this_addr = reinterpret_cast<uword>(this->untag());
    uword other_addr = reinterpret_cast<uword>(other.untag());
    for (intptr_t offset = Instance::NextFieldOffset(); offset < instance_size;
         offset += kCompressedWordSize) {
      if ((reinterpret_cast<CompressedObjectPtr*>(this_addr + offset)
               ->Decompress(untag()->heap_base())) !=
          (reinterpret_cast<CompressedObjectPtr*>(other_addr + offset)
               ->Decompress(untag()->heap_base()))) {
        return false;
      }
    }
  }
  return true;
}

bool Symbol::IsSymbolCid(Thread* thread, classid_t class_id) {
  auto object_store = thread->isolate_group()->object_store();
  return Class::GetClassId(object_store->symbol_class()) == class_id;
}

// Must be kept in sync with Symbol.hashCode in symbol_patch.dart
uint32_t Symbol::CanonicalizeHash(Thread* thread, const Instance& instance) {
  ASSERT(IsSymbolCid(thread, instance.GetClassId()));

  auto zone = thread->zone();
  auto object_store = thread->isolate_group()->object_store();

  const auto& symbol_name_field =
      Field::Handle(zone, object_store->symbol_name_field());
  ASSERT(!symbol_name_field.IsNull());

  // Keep in sync with sdk/lib/_internal/vm/lib/symbol_patch.dart.
  const auto& name =
      String::Cast(Object::Handle(zone, instance.GetField(symbol_name_field)));
  const uint32_t arbitrary_prime = 664597;
  return 0x1fffffff & (arbitrary_prime * name.CanonicalizeHash());
}

uint32_t Instance::CanonicalizeHash() const {
  if (GetClassId() == kNullCid) {
    return kNullIdentityHash;
  }
  Thread* thread = Thread::Current();
  uint32_t hash = thread->heap()->GetCanonicalHash(ptr());
  if (hash != 0) {
    return hash;
  }
  Zone* zone = thread->zone();
  const Class& cls = Class::Handle(zone, clazz());
  const bool is_symbol = Symbol::IsSymbolCid(thread, cls.id());

  NoSafepointScope no_safepoint(thread);

  if (is_symbol) {
    hash = Symbol::CanonicalizeHash(thread, *this);
  } else {
    const intptr_t class_id = cls.id();
    ASSERT(class_id != 0);
    hash = class_id;
    uword this_addr = reinterpret_cast<uword>(this->untag());
    Object& obj = Object::Handle(zone);
    Instance& instance = Instance::Handle(zone);

    const auto unboxed_fields_bitmap =
        thread->isolate_group()->class_table()->GetUnboxedFieldsMapAt(
            GetClassId());

    for (intptr_t offset = Instance::NextFieldOffset();
         offset < cls.host_next_field_offset(); offset += kCompressedWordSize) {
      if (unboxed_fields_bitmap.Get(offset / kCompressedWordSize)) {
        if (kCompressedWordSize == 8) {
          hash = CombineHashes(
              hash, *reinterpret_cast<uint32_t*>(this_addr + offset));
          hash = CombineHashes(
              hash, *reinterpret_cast<uint32_t*>(this_addr + offset + 4));
        } else {
          hash = CombineHashes(
              hash, *reinterpret_cast<uint32_t*>(this_addr + offset));
        }
      } else {
        obj = reinterpret_cast<CompressedObjectPtr*>(this_addr + offset)
                  ->Decompress(untag()->heap_base());
        if (obj.IsSentinel()) {
          hash = CombineHashes(hash, 11);
        } else {
          instance ^= obj.ptr();
          hash = CombineHashes(hash, instance.CanonicalizeHash());
        }
      }
    }
    hash = FinalizeHash(hash, String::kHashBits);
  }
  thread->heap()->SetCanonicalHash(ptr(), hash);
  return hash;
}

#if defined(DEBUG)
class CheckForPointers : public ObjectPointerVisitor {
 public:
  explicit CheckForPointers(IsolateGroup* isolate_group)
      : ObjectPointerVisitor(isolate_group), has_pointers_(false) {}

  bool has_pointers() const { return has_pointers_; }

  void VisitPointers(ObjectPtr* first, ObjectPtr* last) override {
    if (last >= first) {
      has_pointers_ = true;
    }
  }

#if defined(DART_COMPRESSED_POINTERS)
  void VisitCompressedPointers(uword heap_base,
                               CompressedObjectPtr* first,
                               CompressedObjectPtr* last) override {
    if (last >= first) {
      has_pointers_ = true;
    }
  }
#endif

 private:
  bool has_pointers_;

  DISALLOW_COPY_AND_ASSIGN(CheckForPointers);
};
#endif  // DEBUG

void Instance::CanonicalizeFieldsLocked(Thread* thread) const {
  const intptr_t class_id = GetClassId();
  if (class_id >= kNumPredefinedCids) {
    // Iterate over all fields, canonicalize numbers and strings, expect all
    // other instances to be canonical otherwise report error (return false).
    Zone* zone = thread->zone();
    Object& obj = Object::Handle(zone);
    const intptr_t instance_size = SizeFromClass();
    ASSERT(instance_size != 0);
    const auto unboxed_fields_bitmap =
        thread->isolate_group()->class_table()->GetUnboxedFieldsMapAt(class_id);
    for (intptr_t offset = Instance::NextFieldOffset(); offset < instance_size;
         offset += kCompressedWordSize) {
      if (unboxed_fields_bitmap.Get(offset / kCompressedWordSize)) {
        continue;
      }
      obj = this->FieldAddrAtOffset(offset)->Decompress(untag()->heap_base());
      if (obj.IsInstance()) {
        obj = Instance::Cast(obj).CanonicalizeLocked(thread);
        this->SetFieldAtOffset(offset, obj);
      } else {
        ASSERT(obj.IsNull() || obj.IsSentinel());
      }
    }
  } else {
#if defined(DEBUG) && !defined(DART_COMPRESSED_POINTERS)
    // Make sure that we are not missing any fields.
    IsolateGroup* group = IsolateGroup::Current();
    CheckForPointers has_pointers(group);
    this->ptr()->untag()->VisitPointersPrecise(&has_pointers);
    ASSERT(!has_pointers.has_pointers());
#endif  // DEBUG
  }
}

InstancePtr Instance::CopyShallowToOldSpace(Thread* thread) const {
  return Instance::RawCast(Object::Clone(*this, Heap::kOld));
}

InstancePtr Instance::Canonicalize(Thread* thread) const {
  SafepointMutexLocker ml(
      thread->isolate_group()->constant_canonicalization_mutex());
  return CanonicalizeLocked(thread);
}

InstancePtr Instance::CanonicalizeLocked(Thread* thread) const {
  if (!this->ptr()->IsHeapObject() || this->IsCanonical()) {
    return this->ptr();
  }
  ASSERT(!IsNull());
  CanonicalizeFieldsLocked(thread);
  Zone* zone = thread->zone();
  const Class& cls = Class::Handle(zone, this->clazz());
  Instance& result =
      Instance::Handle(zone, cls.LookupCanonicalInstance(zone, *this));
  if (!result.IsNull()) {
    return result.ptr();
  }
  if (IsNew()) {
    // Create a canonical object in old space.
    result ^= Object::Clone(*this, Heap::kOld);
  } else {
    result = this->ptr();
  }
  ASSERT(result.IsOld());
  result.SetCanonical();
  result.SetDeeplyImmutable();
  return cls.InsertCanonicalConstant(zone, result);
}

ObjectPtr Instance::GetField(const Field& field) const {
  if (field.is_unboxed()) {
    switch (field.guarded_cid()) {
      case kDoubleCid:
        return Double::New(*reinterpret_cast<double_t*>(FieldAddr(field)));
      case kFloat32x4Cid:
        return Float32x4::New(
            *reinterpret_cast<simd128_value_t*>(FieldAddr(field)));
      case kFloat64x2Cid:
        return Float64x2::New(
            *reinterpret_cast<simd128_value_t*>(FieldAddr(field)));
      default:
        return Integer::New(*reinterpret_cast<int64_t*>(FieldAddr(field)));
    }
  } else {
    return FieldAddr(field)->Decompress(untag()->heap_base());
  }
}

void Instance::SetField(const Field& field, const Object& value) const {
  if (field.is_unboxed()) {
    switch (field.guarded_cid()) {
      case kDoubleCid:
        StoreNonPointer(reinterpret_cast<double_t*>(FieldAddr(field)),
                        Double::Cast(value).value());
        break;
      case kFloat32x4Cid:
        StoreNonPointer(reinterpret_cast<simd128_value_t*>(FieldAddr(field)),
                        Float32x4::Cast(value).value());
        break;
      case kFloat64x2Cid:
        StoreNonPointer(reinterpret_cast<simd128_value_t*>(FieldAddr(field)),
                        Float64x2::Cast(value).value());
        break;
      default:
        StoreNonPointer(reinterpret_cast<int64_t*>(FieldAddr(field)),
                        Integer::Cast(value).Value());
        break;
    }
  } else {
    field.RecordStore(value);
    StoreCompressedPointer(FieldAddr(field), value.ptr());
  }
}

void Instance::SetFieldWithoutFieldGuard(const Field& field,
                                         const Object& value) const {
  if (field.is_unboxed()) {
    switch (field.guarded_cid()) {
      case kDoubleCid:
        StoreNonPointer(reinterpret_cast<double_t*>(FieldAddr(field)),
                        Double::Cast(value).value());
        break;
      case kFloat32x4Cid:
        StoreNonPointer(reinterpret_cast<simd128_value_t*>(FieldAddr(field)),
                        Float32x4::Cast(value).value());
        break;
      case kFloat64x2Cid:
        StoreNonPointer(reinterpret_cast<simd128_value_t*>(FieldAddr(field)),
                        Float64x2::Cast(value).value());
        break;
      default:
        StoreNonPointer(reinterpret_cast<int64_t*>(FieldAddr(field)),
                        Integer::Cast(value).Value());
        break;
    }
  } else {
    // Some basic validation that we are not violating guarded cid.
    RELEASE_ASSERT(!Thread::Current()->isolate_group()->use_field_guards() ||
                   field.guarded_cid() == kDynamicCid ||
                   field.guarded_cid() == value.GetClassId() ||
                   (field.is_nullable() && value.IsNull()));
    StoreCompressedPointer(FieldAddr(field), value.ptr());
  }
}

AbstractTypePtr Instance::GetType(Heap::Space space,
                                  TypeVisibility visibility) const {
  if (IsNull()) {
    return Type::NullType();
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  if (visibility == TypeVisibility::kUserVisibleType) {
    // VM hides certain internal implementation classes
    // and substitutes them with public user-visible types
    // (String, int, double, Type, List).
    if (IsString()) {
      return Type::StringType();
    } else if (IsInteger()) {
      return Type::IntType();
    } else if (IsDouble()) {
      return Type::Double();
    } else if (IsAbstractType()) {
      return Type::DartTypeType();
    } else if (IsArrayClassId(GetClassId())) {
      const auto& cls = Class::Handle(
          zone, thread->isolate_group()->object_store()->list_class());
      auto& type_arguments = TypeArguments::Handle(zone, GetTypeArguments());
      type_arguments = type_arguments.FromInstanceTypeArguments(thread, cls);
      // Assume internal VM types are properly encapsulated in the core
      // libraries and cannot appear in type arguments.
      const auto& type =
          Type::Handle(zone, Type::New(cls, type_arguments,
                                       Nullability::kNonNullable, Heap::kNew));
      type.SetIsFinalized();
      return type.Canonicalize(thread);
    }
  }
  const Class& cls = Class::Handle(zone, clazz());
  if (!cls.is_finalized()) {
    // Various predefined classes can be instantiated by the VM or
    // Dart_NewString/Integer/TypedData/... before the class is finalized.
    ASSERT(cls.is_prefinalized());
    cls.EnsureDeclarationLoaded();
  }
  if (cls.IsClosureClass()) {
    FunctionType& signature = FunctionType::Handle(
        zone, Closure::Cast(*this).GetInstantiatedSignature(zone));
    if (!signature.IsFinalized()) {
      signature.SetIsFinalized();
    }
    signature ^= signature.Canonicalize(thread);
    return signature.ptr();
  }
  if (IsRecord()) {
    ASSERT(cls.IsRecordClass());
    auto& record_type =
        RecordType::Handle(zone, Record::Cast(*this).GetRecordType(visibility));
    ASSERT(record_type.IsFinalized());
    ASSERT(record_type.IsCanonical());
    return record_type.ptr();
  }
  Type& type = Type::Handle(zone);
  if (!cls.IsGeneric()) {
    type = cls.DeclarationType();
  }
  if (type.IsNull()) {
    TypeArguments& type_arguments = TypeArguments::Handle(zone);
    const intptr_t num_type_arguments = cls.NumTypeArguments();
    if (num_type_arguments > 0) {
      type_arguments = GetTypeArguments();
      if (!type_arguments.IsNull()) {
        type_arguments = type_arguments.FromInstanceTypeArguments(thread, cls);
      }
    }
    type = Type::New(cls, type_arguments, Nullability::kNonNullable, space);
    type.SetIsFinalized();
    type ^= type.Canonicalize(thread);
  }
  return type.ptr();
}

TypeArgumentsPtr Instance::GetTypeArguments() const {
  ASSERT(!IsType());
  const Class& cls = Class::Handle(clazz());
  intptr_t field_offset = cls.host_type_arguments_field_offset();
  ASSERT(field_offset != Class::kNoTypeArguments);
  TypeArguments& type_arguments = TypeArguments::Handle();
  type_arguments ^=
      FieldAddrAtOffset(field_offset)->Decompress(untag()->heap_base());
  return type_arguments.ptr();
}

void Instance::SetTypeArguments(const TypeArguments& value) const {
  ASSERT(!IsType());
  ASSERT(value.IsNull() || value.IsCanonical());
  const Class& cls = Class::Handle(clazz());
  intptr_t field_offset = cls.host_type_arguments_field_offset();
  ASSERT(field_offset != Class::kNoTypeArguments);
  SetFieldAtOffset(field_offset, value);
}

bool Instance::IsInstanceOf(
    const AbstractType& other,
    const TypeArguments& other_instantiator_type_arguments,
    const TypeArguments& other_function_type_arguments) const {
  ASSERT(!other.IsDynamicType());
  return RuntimeTypeIsSubtypeOf(other, other_instantiator_type_arguments,
                                other_function_type_arguments);
}

// Must be kept in sync with GenerateNullIsAssignableToType in
// stub_code_compiler.cc if any changes are made.
bool Instance::NullIsAssignableTo(const AbstractType& other) {
  // "Left Null" rule: null is assignable when destination type is
  // nullable. Otherwise it is not assignable or we cannot tell
  // without instantiating type parameter.
  if (other.IsNullable()) {
    return true;
  }
  if (other.IsFutureOrType()) {
    return NullIsAssignableTo(AbstractType::Handle(other.UnwrapFutureOr()));
  }
  // Since the TAVs are not available, for non-nullable type parameters
  // this returns a conservative approximation of "not assignable" .
  return false;
}

// Must be kept in sync with GenerateNullIsAssignableToType in
// stub_code_compiler.cc if any changes are made.
bool Instance::NullIsAssignableTo(
    const AbstractType& other,
    const TypeArguments& other_instantiator_type_arguments,
    const TypeArguments& other_function_type_arguments) {
  // Do checks that don't require instantiation first.
  if (NullIsAssignableTo(other)) return true;
  if (!other.IsTypeParameter()) return false;
  const auto& type = AbstractType::Handle(other.InstantiateFrom(
      other_instantiator_type_arguments, other_function_type_arguments,
      kAllFree, Heap::kNew));
  return NullIsAssignableTo(type);
}

bool Instance::RuntimeTypeIsSubtypeOf(
    const AbstractType& other,
    const TypeArguments& other_instantiator_type_arguments,
    const TypeArguments& other_function_type_arguments) const {
  ASSERT(other.IsFinalized());
  ASSERT(ptr() != Object::sentinel().ptr());
  // Instance may not have runtimeType dynamic, void, or Never.
  if (other.IsTopTypeForSubtyping()) {
    return true;
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const Class& cls = Class::Handle(zone, clazz());
  if (cls.IsClosureClass()) {
    if (other.IsDartFunctionType() || other.IsDartClosureType() ||
        other.IsObjectType()) {
      return true;
    }
    AbstractType& instantiated_other = AbstractType::Handle(zone, other.ptr());
    if (!other.IsInstantiated()) {
      instantiated_other = other.InstantiateFrom(
          other_instantiator_type_arguments, other_function_type_arguments,
          kAllFree, Heap::kOld);
      if (instantiated_other.IsTopTypeForSubtyping() ||
          instantiated_other.IsObjectType() ||
          instantiated_other.IsDartFunctionType()) {
        return true;
      }
    }
    if (RuntimeTypeIsSubtypeOfFutureOr(zone, instantiated_other)) {
      return true;
    }
    if (!instantiated_other.IsFunctionType()) {
      return false;
    }
    const FunctionType& sig = FunctionType::Handle(
        Closure::Cast(*this).GetInstantiatedSignature(zone));
    return sig.IsSubtypeOf(FunctionType::Cast(instantiated_other), Heap::kOld);
  }
  if (cls.IsRecordClass()) {
    if (other.IsDartRecordType() || other.IsObjectType()) {
      return true;
    }
    AbstractType& instantiated_other = AbstractType::Handle(zone, other.ptr());
    if (!other.IsInstantiated()) {
      instantiated_other = other.InstantiateFrom(
          other_instantiator_type_arguments, other_function_type_arguments,
          kAllFree, Heap::kOld);
      if (instantiated_other.IsTopTypeForSubtyping() ||
          instantiated_other.IsObjectType() ||
          instantiated_other.IsDartRecordType()) {
        return true;
      }
    }
    if (RuntimeTypeIsSubtypeOfFutureOr(zone, instantiated_other)) {
      return true;
    }
    if (!instantiated_other.IsRecordType()) {
      return false;
    }
    const Record& record = Record::Cast(*this);
    const RecordType& record_type = RecordType::Cast(instantiated_other);
    if (record.shape() != record_type.shape()) {
      return false;
    }
    Instance& field_value = Instance::Handle(zone);
    AbstractType& field_type = AbstractType::Handle(zone);
    const intptr_t num_fields = record.num_fields();
    for (intptr_t i = 0; i < num_fields; ++i) {
      field_value ^= record.FieldAt(i);
      field_type = record_type.FieldTypeAt(i);
      if (!field_value.RuntimeTypeIsSubtypeOf(field_type,
                                              Object::null_type_arguments(),
                                              Object::null_type_arguments())) {
        return false;
      }
    }
    return true;
  }
  TypeArguments& type_arguments = TypeArguments::Handle(zone);
  const intptr_t num_type_arguments = cls.NumTypeArguments();
  if (num_type_arguments > 0) {
    type_arguments = GetTypeArguments();
    ASSERT(type_arguments.IsNull() || type_arguments.IsCanonical());
    // The number of type arguments in the instance must be greater or equal to
    // the number of type arguments expected by the instance class.
    // A discrepancy is allowed for closures, which borrow the type argument
    // vector of their instantiator, which may be of a subclass of the class
    // defining the closure. Truncating the vector to the correct length on
    // instantiation is unnecessary. The vector may therefore be longer.
    // Also, an optimization reuses the type argument vector of the instantiator
    // of generic instances when its layout is compatible.
    ASSERT(type_arguments.IsNull() ||
           (type_arguments.Length() >= num_type_arguments));
  }
  AbstractType& instantiated_other = AbstractType::Handle(zone, other.ptr());
  if (!other.IsInstantiated()) {
    instantiated_other = other.InstantiateFrom(
        other_instantiator_type_arguments, other_function_type_arguments,
        kAllFree, Heap::kOld);
    if (instantiated_other.IsTopTypeForSubtyping()) {
      return true;
    }
  }
  if (IsNull()) {
    if (instantiated_other.IsNullType()) {
      return true;
    }
    if (RuntimeTypeIsSubtypeOfFutureOr(zone, instantiated_other)) {
      return true;
    }
    // At this point, instantiated_other can be a function type.
    return !instantiated_other.IsNonNullable();
  }
  if (!instantiated_other.IsType()) {
    return false;
  }
  // RuntimeType of non-null instance is non-nullable, so there is no need to
  // check nullability of other type.
  return Class::IsSubtypeOf(cls, type_arguments, Nullability::kNonNullable,
                            instantiated_other, Heap::kOld);
}

bool Instance::RuntimeTypeIsSubtypeOfFutureOr(Zone* zone,
                                              const AbstractType& other) const {
  if (other.IsFutureOrType()) {
    const TypeArguments& other_type_arguments =
        TypeArguments::Handle(zone, other.arguments());
    const AbstractType& other_type_arg =
        AbstractType::Handle(zone, other_type_arguments.TypeAtNullSafe(0));
    if (other_type_arg.IsTopTypeForSubtyping()) {
      return true;
    }
    if (Class::Handle(zone, clazz()).IsFutureClass()) {
      const TypeArguments& type_arguments =
          TypeArguments::Handle(zone, GetTypeArguments());
      const AbstractType& type_arg =
          AbstractType::Handle(zone, type_arguments.TypeAtNullSafe(0));
      if (type_arg.IsSubtypeOf(other_type_arg, Heap::kOld)) {
        return true;
      }
    }
    // Retry RuntimeTypeIsSubtypeOf after unwrapping type arg of FutureOr.
    if (RuntimeTypeIsSubtypeOf(other_type_arg, Object::null_type_arguments(),
                               Object::null_type_arguments())) {
      return true;
    }
  }
  return false;
}

bool Instance::OperatorEquals(const Instance& other) const {
  // TODO(koda): Optimize for all builtin classes and all classes
  // that do not override operator==.
  return DartLibraryCalls::Equals(*this, other) == Object::bool_true().ptr();
}

bool Instance::IsIdenticalTo(const Instance& other) const {
  if (ptr() == other.ptr()) return true;
  if (IsInteger() && other.IsInteger()) {
    return Integer::Cast(*this).Equals(other);
  }
  if (IsDouble() && other.IsDouble()) {
    double other_value = Double::Cast(other).value();
    return Double::Cast(*this).BitwiseEqualsToDouble(other_value);
  }
  return false;
}

intptr_t* Instance::NativeFieldsDataAddr() const {
  ASSERT(Thread::Current()->no_safepoint_scope_depth() > 0);
  TypedDataPtr native_fields = static_cast<TypedDataPtr>(
      NativeFieldsAddr()->Decompress(untag()->heap_base()));
  if (native_fields == TypedData::null()) {
    return nullptr;
  }
  return reinterpret_cast<intptr_t*>(native_fields->untag()->data());
}

void Instance::SetNativeField(int index, intptr_t value) const {
  ASSERT(IsValidNativeIndex(index));
  Object& native_fields =
      Object::Handle(NativeFieldsAddr()->Decompress(untag()->heap_base()));
  if (native_fields.IsNull()) {
    // Allocate backing storage for the native fields.
    native_fields = TypedData::New(kIntPtrCid, NumNativeFields());
    StoreCompressedPointer(NativeFieldsAddr(), native_fields.ptr());
  }
  intptr_t byte_offset = index * sizeof(intptr_t);
  TypedData::Cast(native_fields).SetIntPtr(byte_offset, value);
}

void Instance::SetNativeFields(uint16_t num_native_fields,
                               const intptr_t* field_values) const {
  ASSERT(num_native_fields == NumNativeFields());
  ASSERT(field_values != nullptr);
  Object& native_fields =
      Object::Handle(NativeFieldsAddr()->Decompress(untag()->heap_base()));
  if (native_fields.IsNull()) {
    // Allocate backing storage for the native fields.
    native_fields = TypedData::New(kIntPtrCid, NumNativeFields());
    StoreCompressedPointer(NativeFieldsAddr(), native_fields.ptr());
  }
  for (uint16_t i = 0; i < num_native_fields; i++) {
    intptr_t byte_offset = i * sizeof(intptr_t);
    TypedData::Cast(native_fields).SetIntPtr(byte_offset, field_values[i]);
  }
}

bool Instance::IsCallable(Function* function) const {
  Class& cls = Class::Handle(clazz());
  if (cls.IsClosureClass()) {
    if (function != nullptr) {
      *function = Closure::Cast(*this).function();
    }
    return true;
  }
  // Try to resolve a "call" method.
  Zone* zone = Thread::Current()->zone();
  Function& call_function = Function::Handle(
      zone, Resolver::ResolveDynamicAnyArgs(zone, cls, Symbols::DynamicCall(),
                                            /*allow_add=*/false));
  if (call_function.IsNull()) {
    return false;
  }
  if (function != nullptr) {
    *function = call_function.ptr();
  }
  return true;
}

InstancePtr Instance::New(const Class& cls, Heap::Space space) {
  Thread* thread = Thread::Current();
  if (cls.EnsureIsAllocateFinalized(thread) != Error::null()) {
    return Instance::null();
  }
  return NewAlreadyFinalized(cls, space);
}

InstancePtr Instance::NewAlreadyFinalized(const Class& cls, Heap::Space space) {
  ASSERT(cls.is_allocate_finalized());
  intptr_t instance_size = cls.host_instance_size();
  ASSERT(instance_size > 0);
  // Initialize everything after the object header with Object::null(), since
  // this isn't a predefined class.
  const uword ptr_field_end_offset =
      instance_size - (Instance::ContainsCompressedPointers()
                           ? kCompressedWordSize
                           : kWordSize);
  return static_cast<InstancePtr>(Object::Allocate(
      cls.id(), instance_size, space, Instance::ContainsCompressedPointers(),
      from_offset<Instance>(), ptr_field_end_offset));
}

bool Instance::IsValidFieldOffset(intptr_t offset) const {
  Thread* thread = Thread::Current();
  REUSABLE_CLASS_HANDLESCOPE(thread);
  Class& cls = thread->ClassHandle();
  cls = clazz();
  return (offset >= 0 &&
          offset <= (cls.host_instance_size() - kCompressedWordSize));
}

intptr_t Instance::ElementSizeFor(intptr_t cid) {
  if (IsExternalTypedDataClassId(cid) || IsTypedDataClassId(cid) ||
      IsTypedDataViewClassId(cid) || IsUnmodifiableTypedDataViewClassId(cid)) {
    return TypedDataBase::ElementSizeInBytes(cid);
  }
  switch (cid) {
    case kArrayCid:
    case kImmutableArrayCid:
      return Array::kBytesPerElement;
    case kTypeArgumentsCid:
      return TypeArguments::ArrayTraits::kElementSize;
    case kOneByteStringCid:
      return OneByteString::kBytesPerElement;
    case kTwoByteStringCid:
      return TwoByteString::kBytesPerElement;
    default:
      UNIMPLEMENTED();
      return 0;
  }
}

intptr_t Instance::DataOffsetFor(intptr_t cid) {
  if (IsExternalTypedDataClassId(cid)) {
    // Elements start at offset 0 of the external data.
    return 0;
  }
  if (IsTypedDataClassId(cid)) {
    return TypedData::payload_offset();
  }
  switch (cid) {
    case kArrayCid:
    case kImmutableArrayCid:
      return Array::data_offset();
    case kTypeArgumentsCid:
      return TypeArguments::types_offset();
    case kOneByteStringCid:
      return OneByteString::data_offset();
    case kTwoByteStringCid:
      return TwoByteString::data_offset();
    case kRecordCid:
      return Record::field_offset(0);
    default:
      UNIMPLEMENTED();
      return Array::data_offset();
  }
}

const char* Instance::ToCString() const {
  if (IsNull()) {
    return "null";
  } else if (Thread::Current()->no_safepoint_scope_depth() > 0) {
    // Can occur when running disassembler.
    return "Instance";
  } else {
    if (IsClosure()) {
      return Closure::Cast(*this).ToCString();
    }
    // Background compiler disassembly of instructions referring to pool objects
    // calls this function and requires allocation of Type in old space.
    const AbstractType& type = AbstractType::Handle(
        GetType(Heap::kOld, TypeVisibility::kUserVisibleType));
    const String& type_name = String::Handle(type.UserVisibleName());
    return OS::SCreate(Thread::Current()->zone(), "Instance of '%s'",
                       type_name.ToCString());
  }
}

classid_t AbstractType::type_class_id() const {
  // All subclasses should implement this appropriately, so the only value that
  // should reach this implementation should be the null value.
  ASSERT(IsNull());
  // AbstractType is an abstract class.
  UNREACHABLE();
  return kIllegalCid;
}

ClassPtr AbstractType::type_class() const {
  // All subclasses should implement this appropriately, so the only value that
  // should reach this implementation should be the null value.
  ASSERT(IsNull());
  // AbstractType is an abstract class.
  UNREACHABLE();
  return Class::null();
}

TypeArgumentsPtr AbstractType::arguments() const {
  // All subclasses should implement this appropriately, so the only value that
  // should reach this implementation should be the null value.
  ASSERT(IsNull());
  // AbstractType is an abstract class.
  UNREACHABLE();
  return nullptr;
}

bool AbstractType::IsStrictlyNonNullable() const {
  // Null can be assigned to legacy and nullable types.
  if (!IsNonNullable()) {
    return false;
  }

  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();

  if (IsTypeParameter()) {
    const auto& bound =
        AbstractType::Handle(zone, TypeParameter::Cast(*this).bound());
    ASSERT(!bound.IsNull());
    return bound.IsStrictlyNonNullable();
  }
  if (IsFutureOrType()) {
    return AbstractType::Handle(zone, UnwrapFutureOr()).IsStrictlyNonNullable();
  }
  return true;
}

AbstractTypePtr AbstractType::SetInstantiatedNullability(
    const TypeParameter& type_param,
    Heap::Space space) const {
  Nullability result_nullability;
  const Nullability arg_nullability = nullability();
  const Nullability var_nullability = type_param.nullability();
  // Adjust nullability of result 'arg' instantiated from 'var'.
  // arg/var ! ?
  //  !      ! ?
  //  ?      ? ?
  if (var_nullability == Nullability::kNullable) {
    result_nullability = Nullability::kNullable;
  } else {
    // Keep arg nullability.
    return ptr();
  }
  if (arg_nullability == result_nullability) {
    return ptr();
  }
  if (IsType()) {
    return Type::Cast(*this).ToNullability(result_nullability, space);
  }
  if (IsFunctionType()) {
    return FunctionType::Cast(*this).ToNullability(result_nullability, space);
  }
  if (IsRecordType()) {
    return RecordType::Cast(*this).ToNullability(result_nullability, space);
  }
  if (IsTypeParameter()) {
    return TypeParameter::Cast(*this).ToNullability(result_nullability, space);
  }
  UNREACHABLE();
}

AbstractTypePtr AbstractType::NormalizeFutureOrType(Heap::Space space) const {
  if (IsFutureOrType()) {
    Zone* zone = Thread::Current()->zone();
    const AbstractType& unwrapped_type =
        AbstractType::Handle(zone, UnwrapFutureOr());
    const classid_t cid = unwrapped_type.type_class_id();
    if (cid == kDynamicCid || cid == kVoidCid) {
      return unwrapped_type.ptr();
    }
    if (cid == kInstanceCid) {
      if (IsNonNullable()) {
        return unwrapped_type.ptr();
      }
      ASSERT(IsNullable());
      return Type::Cast(unwrapped_type)
          .ToNullability(Nullability::kNullable, space);
    }
    if (cid == kNeverCid && unwrapped_type.IsNonNullable()) {
      ObjectStore* object_store = IsolateGroup::Current()->object_store();
      const Type& future_never_type =
          Type::Handle(zone, object_store->non_nullable_future_never_type());
      ASSERT(!future_never_type.IsNull());
      return future_never_type.ToNullability(nullability(), space);
    }
    if (cid == kNullCid) {
      ObjectStore* object_store = IsolateGroup::Current()->object_store();
      ASSERT(object_store->nullable_future_null_type() != Type::null());
      return object_store->nullable_future_null_type();
    }
    if (IsNullable() && unwrapped_type.IsNullable()) {
      return Type::Cast(*this).ToNullability(Nullability::kNonNullable, space);
    }
  }
  return ptr();
}

bool AbstractType::IsInstantiated(Genericity genericity,
                                  intptr_t num_free_fun_type_params) const {
  // All subclasses should implement this appropriately, so the only value that
  // should reach this implementation should be the null value.
  ASSERT(IsNull());
  // AbstractType is an abstract class.
  UNREACHABLE();
  return false;
}

void AbstractType::SetIsFinalized() const {
  ASSERT(!IsFinalized());
  set_type_state(IsInstantiated()
                     ? UntaggedAbstractType::kFinalizedInstantiated
                     : UntaggedAbstractType::kFinalizedUninstantiated);
}

void AbstractType::set_flags(uint32_t value) const {
  untag()->set_flags(value);
}

void AbstractType::set_type_state(UntaggedAbstractType::TypeState value) const {
  ASSERT(!IsCanonical());
  set_flags(
      UntaggedAbstractType::TypeStateBits::update(value, untag()->flags()));
}

void AbstractType::set_nullability(Nullability value) const {
  ASSERT(!IsCanonical());
  set_flags(UntaggedAbstractType::NullabilityBit::update(
      static_cast<uint8_t>(value), untag()->flags()));
}

bool AbstractType::IsEquivalent(
    const Instance& other,
    TypeEquality kind,
    FunctionTypeMapping* function_type_equivalence) const {
  // All subclasses should implement this appropriately, so the only value that
  // should reach this implementation should be the null value.
  ASSERT(IsNull());
  // AbstractType is an abstract class.
  UNREACHABLE();
  return false;
}

bool AbstractType::IsNullabilityEquivalent(Thread* thread,
                                           const AbstractType& other_type,
                                           TypeEquality kind) const {
  Nullability this_type_nullability = nullability();
  Nullability other_type_nullability = other_type.nullability();
  if (kind == TypeEquality::kInSubtypeTest) {
    if (this_type_nullability == Nullability::kNullable &&
        other_type_nullability == Nullability::kNonNullable) {
      return false;
    }
  } else {
    ASSERT((kind == TypeEquality::kSyntactical) ||
           (kind == TypeEquality::kCanonical));
    if (this_type_nullability != other_type_nullability) {
      return false;
    }
  }
  return true;
}

AbstractTypePtr AbstractType::InstantiateFrom(
    const TypeArguments& instantiator_type_arguments,
    const TypeArguments& function_type_arguments,
    intptr_t num_free_fun_type_params,
    Heap::Space space,
    FunctionTypeMapping* function_type_mapping,
    intptr_t num_parent_type_args_adjustment) const {
  // All subclasses should implement this appropriately, so the only value that
  // should reach this implementation should be the null value.
  ASSERT(IsNull());
  // AbstractType is an abstract class.
  UNREACHABLE();
  return nullptr;
}

AbstractTypePtr AbstractType::UpdateFunctionTypes(
    intptr_t num_parent_type_args_adjustment,
    intptr_t num_free_fun_type_params,
    Heap::Space space,
    FunctionTypeMapping* function_type_mapping) const {
  UNREACHABLE();
  return nullptr;
}

AbstractTypePtr AbstractType::Canonicalize(Thread* thread) const {
  // All subclasses should implement this appropriately, so the only value that
  // should reach this implementation should be the null value.
  ASSERT(IsNull());
  // AbstractType is an abstract class.
  UNREACHABLE();
  return nullptr;
}

void AbstractType::EnumerateURIs(URIs* uris) const {
  // All subclasses should implement this appropriately, so the only value that
  // should reach this implementation should be the null value.
  ASSERT(IsNull());
  // AbstractType is an abstract class.
  UNREACHABLE();
}

void AbstractType::AddURI(URIs* uris, const String& name, const String& uri) {
  ASSERT(uris != nullptr);
  const intptr_t len = uris->length();
  ASSERT((len % 3) == 0);
  bool print_uri = false;
  for (intptr_t i = 0; i < len; i += 3) {
    if (uris->At(i).Equals(name)) {
      if (uris->At(i + 1).Equals(uri)) {
        // Same name and same URI: no need to add this already listed URI.
        return;  // No state change is possible.
      } else {
        // Same name and different URI: the name is ambiguous, print both URIs.
        print_uri = true;
        uris->SetAt(i + 2, Symbols::print());
      }
    }
  }
  uris->Add(name);
  uris->Add(uri);
  if (print_uri) {
    uris->Add(Symbols::print());
  } else {
    uris->Add(Symbols::Empty());
  }
}

StringPtr AbstractType::PrintURIs(URIs* uris) {
  ASSERT(uris != nullptr);
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const intptr_t len = uris->length();
  ASSERT((len % 3) == 0);
  GrowableHandlePtrArray<const String> pieces(zone, 5 * (len / 3));
  for (intptr_t i = 0; i < len; i += 3) {
    // Only print URIs that have been marked.
    if (uris->At(i + 2).ptr() == Symbols::print().ptr()) {
      pieces.Add(Symbols::TwoSpaces());
      pieces.Add(uris->At(i));
      pieces.Add(Symbols::SpaceIsFromSpace());
      pieces.Add(uris->At(i + 1));
      pieces.Add(Symbols::NewLine());
    }
  }
  return Symbols::FromConcatAll(thread, pieces);
}

const char* AbstractType::NullabilitySuffix(
    NameVisibility name_visibility) const {
  if (IsDynamicType() || IsVoidType() || IsNullType()) {
    // Hide nullable suffix.
    return "";
  }
  // Keep in sync with Nullability enum in runtime/vm/object.h.
  switch (nullability()) {
    case Nullability::kNullable:
      return "?";
    case Nullability::kNonNullable:
      return "";
    default:
      UNREACHABLE();
  }
}

StringPtr AbstractType::Name() const {
  return Symbols::New(Thread::Current(), NameCString());
}

const char* AbstractType::NameCString() const {
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  PrintName(kInternalName, &printer);
  return printer.buffer();
}

StringPtr AbstractType::UserVisibleName() const {
  return Symbols::New(Thread::Current(), UserVisibleNameCString());
}

const char* AbstractType::UserVisibleNameCString() const {
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  PrintName(kUserVisibleName, &printer);
  return printer.buffer();
}

StringPtr AbstractType::ScrubbedName() const {
  return Symbols::New(Thread::Current(), ScrubbedNameCString());
}

const char* AbstractType::ScrubbedNameCString() const {
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  PrintName(kScrubbedName, &printer);
  return printer.buffer();
}

void AbstractType::PrintName(NameVisibility name_visibility,
                             BaseTextBuffer* printer) const {
  // All subclasses should implement this appropriately, so the only value that
  // should reach this implementation should be the null value.
  ASSERT(IsNull());
  // AbstractType is an abstract class.
  UNREACHABLE();
}

StringPtr AbstractType::ClassName() const {
  ASSERT(!IsFunctionType() && !IsRecordType());
  return Class::Handle(type_class()).Name();
}

bool AbstractType::IsNullType() const {
  return type_class_id() == kNullCid;
}

bool AbstractType::IsNeverType() const {
  return type_class_id() == kNeverCid;
}

bool AbstractType::IsSentinelType() const {
  return type_class_id() == kSentinelCid;
}

bool AbstractType::IsTopTypeForInstanceOf() const {
  const classid_t cid = type_class_id();
  if (cid == kDynamicCid || cid == kVoidCid) {
    return true;
  }
  if (cid == kInstanceCid) {  // Object type.
    return IsNullable();
  }
  if (cid == kFutureOrCid) {
    // FutureOr<T> where T is a top type behaves as a top type.
    return AbstractType::Handle(UnwrapFutureOr()).IsTopTypeForInstanceOf();
  }
  return false;
}

// Must be kept in sync with GenerateTypeIsTopTypeForSubtyping in
// stub_code_compiler.cc if any changes are made.
bool AbstractType::IsTopTypeForSubtyping() const {
  const classid_t cid = type_class_id();
  if (cid == kDynamicCid || cid == kVoidCid) {
    return true;
  }
  if (cid == kInstanceCid) {  // Object type.
    return !IsNonNullable();
  }
  if (cid == kFutureOrCid) {
    // FutureOr<T> where T is a top type behaves as a top type.
    return AbstractType::Handle(UnwrapFutureOr()).IsTopTypeForSubtyping();
  }
  return false;
}

bool AbstractType::IsIntType() const {
  return HasTypeClass() &&
         (type_class() == Type::Handle(Type::IntType()).type_class());
}

bool AbstractType::IsIntegerImplementationType() const {
  return HasTypeClass() &&
         (type_class() == IsolateGroup::Current()
                              ->object_store()
                              ->integer_implementation_class());
}

bool AbstractType::IsDoubleType() const {
  return HasTypeClass() &&
         (type_class() == Type::Handle(Type::Double()).type_class());
}

bool AbstractType::IsFloat32x4Type() const {
  // kFloat32x4Cid refers to the private class and cannot be used here.
  return HasTypeClass() &&
         (type_class() == Type::Handle(Type::Float32x4()).type_class());
}

bool AbstractType::IsFloat64x2Type() const {
  // kFloat64x2Cid refers to the private class and cannot be used here.
  return HasTypeClass() &&
         (type_class() == Type::Handle(Type::Float64x2()).type_class());
}

bool AbstractType::IsInt32x4Type() const {
  // kInt32x4Cid refers to the private class and cannot be used here.
  return HasTypeClass() &&
         (type_class() == Type::Handle(Type::Int32x4()).type_class());
}

bool AbstractType::IsStringType() const {
  return HasTypeClass() &&
         (type_class() == Type::Handle(Type::StringType()).type_class());
}

bool AbstractType::IsDartFunctionType() const {
  return HasTypeClass() &&
         (type_class() == Type::Handle(Type::DartFunctionType()).type_class());
}

bool AbstractType::IsDartClosureType() const {
  return (type_class_id() == kClosureCid);
}

bool AbstractType::IsDartRecordType() const {
  if (!HasTypeClass()) return false;
  const auto cid = type_class_id();
  return ((cid == kRecordCid) ||
          (cid == Class::Handle(
                      IsolateGroup::Current()->object_store()->record_class())
                      .id()));
}

bool AbstractType::IsFfiPointerType() const {
  return HasTypeClass() && type_class_id() == kPointerCid;
}

bool AbstractType::IsTypeClassAllowedBySpawnUri() const {
  if (!HasTypeClass()) return false;

  intptr_t cid = type_class_id();

  if (cid == kBoolCid) return true;
  if (cid == kDynamicCid) return true;
  if (cid == kInstanceCid) return true;  // Object.
  if (cid == kNeverCid) return true;
  if (cid == kNullCid) return true;
  if (cid == kVoidCid) return true;

  // These are not constant CID checks because kDoubleCid refers to _Double
  // not double, etc.
  ObjectStore* object_store = IsolateGroup::Current()->object_store();
  Type& candidate_type = Type::Handle();
  candidate_type = object_store->int_type();
  if (cid == candidate_type.type_class_id()) return true;
  candidate_type = object_store->double_type();
  if (cid == candidate_type.type_class_id()) return true;
  candidate_type = object_store->number_type();
  if (cid == candidate_type.type_class_id()) return true;
  candidate_type = object_store->string_type();
  if (cid == candidate_type.type_class_id()) return true;

  Class& candidate_cls = Class::Handle();
  candidate_cls = object_store->list_class();
  if (cid == candidate_cls.id()) return true;
  candidate_cls = object_store->map_class();
  if (cid == candidate_cls.id()) return true;
  candidate_cls = object_store->set_class();
  if (cid == candidate_cls.id()) return true;
  candidate_cls = object_store->capability_class();
  if (cid == candidate_cls.id()) return true;
  candidate_cls = object_store->send_port_class();
  if (cid == candidate_cls.id()) return true;
  candidate_cls = object_store->transferable_class();
  if (cid == candidate_cls.id()) return true;

  const auto& typed_data_lib =
      Library::Handle(object_store->typed_data_library());

#define IS_CHECK(name)                                                         \
  candidate_cls = typed_data_lib.LookupClass(Symbols::name##List());           \
  if (cid == candidate_cls.id()) {                                             \
    return true;                                                               \
  }
  DART_CLASS_LIST_TYPED_DATA(IS_CHECK)
#undef IS_CHECK

  return false;
}

AbstractTypePtr AbstractType::UnwrapFutureOr() const {
  if (!IsFutureOrType()) {
    return ptr();
  }
  if (arguments() == TypeArguments::null()) {
    return Type::dynamic_type().ptr();
  }
  Thread* thread = Thread::Current();
  REUSABLE_TYPE_ARGUMENTS_HANDLESCOPE(thread);
  TypeArguments& type_args = thread->TypeArgumentsHandle();
  type_args = arguments();
  REUSABLE_ABSTRACT_TYPE_HANDLESCOPE(thread);
  AbstractType& type_arg = thread->AbstractTypeHandle();
  type_arg = type_args.TypeAt(0);
  while (type_arg.IsFutureOrType()) {
    if (type_arg.arguments() == TypeArguments::null()) {
      return Type::dynamic_type().ptr();
    }
    type_args = type_arg.arguments();
    type_arg = type_args.TypeAt(0);
  }
  return type_arg.ptr();
}

bool AbstractType::IsSubtypeOf(
    const AbstractType& other,
    Heap::Space space,
    FunctionTypeMapping* function_type_equivalence) const {
  TRACE_TYPE_CHECKS_VERBOSE("  AbstractType::IsSubtypeOf(%s, %s)\n",
                            ToCString(), other.ToCString());
  ASSERT(IsFinalized());
  ASSERT(other.IsFinalized());
  // Reflexivity.
  if (ptr() == other.ptr()) {
    TRACE_TYPE_CHECKS_VERBOSE("   - result: true (same types)\n");
    return true;
  }
  // Right top type.
  if (other.IsTopTypeForSubtyping()) {
    TRACE_TYPE_CHECKS_VERBOSE("   - result: true (right is top)\n");
    return true;
  }
  // Left bottom type.
  // Any form of Never in weak mode maps to Null and Null is a bottom type in
  // weak mode. In strong mode, Never and Never* are bottom types. Therefore,
  // Never and Never* are bottom types regardless of weak/strong mode.
  // Note that we cannot encounter Never?, as it is normalized to Null.
  if (IsNeverType()) {
    ASSERT(!IsNullable());
    TRACE_TYPE_CHECKS_VERBOSE("   - result: true (left is Never)\n");
    return true;
  }
  // Left top type.
  if (IsDynamicType() || IsVoidType()) {
    TRACE_TYPE_CHECKS_VERBOSE("   - result: false (left is top)\n");
    return false;
  }
  // Left Null type.
  if (IsNullType()) {
    const bool result = Instance::NullIsAssignableTo(other);
    TRACE_TYPE_CHECKS_VERBOSE("   - result: %s (left is Null)\n",
                              (result ? "true" : "false"));
    return result;
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  // Type parameters cannot be handled by Class::IsSubtypeOf().
  // When comparing two uninstantiated function types, one returning type
  // parameter K, the other returning type parameter V, we cannot assume that
  // K is a subtype of V, or vice versa. We only return true if K equals V, as
  // defined by TypeParameter::Equals.
  // The same rule applies when checking the upper bound of a still
  // uninstantiated type at compile time. Returning false will defer the test
  // to run time.
  // There are however some cases that can be decided at compile time.
  // For example, with class A<K, V extends K>, new A<T, T> called from within
  // a class B<T> will never require a run time bound check, even if T is
  // uninstantiated at compile time.
  if (IsTypeParameter()) {
    const TypeParameter& type_param = TypeParameter::Cast(*this);
    if (other.IsTypeParameter()) {
      const TypeParameter& other_type_param = TypeParameter::Cast(other);
      if (type_param.IsEquivalent(other_type_param,
                                  TypeEquality::kInSubtypeTest,
                                  function_type_equivalence)) {
        TRACE_TYPE_CHECKS_VERBOSE(
            "   - result: true (equivalent type parameters)\n");
        return true;
      }
    }
    const AbstractType& bound = AbstractType::Handle(zone, type_param.bound());
    ASSERT(bound.IsFinalized());
    if (bound.IsSubtypeOf(other, space, function_type_equivalence)) {
      TRACE_TYPE_CHECKS_VERBOSE("   - result: true (bound is a subtype)\n");
      return true;
    }
    // Apply additional subtyping rules if 'other' is 'FutureOr'.
    if (IsSubtypeOfFutureOr(zone, other, space, function_type_equivalence)) {
      TRACE_TYPE_CHECKS_VERBOSE(
          "   - result: true (type parameter is a subtype of FutureOr)\n");
      return true;
    }
    TRACE_TYPE_CHECKS_VERBOSE(
        "   - result: false (left is a type parameter)\n");
    return false;
  }
  if (other.IsTypeParameter()) {
    TRACE_TYPE_CHECKS_VERBOSE(
        "   - result: false (right is a type parameter)\n");
    return false;
  }
  // Function types cannot be handled by Class::IsSubtypeOf().
  if (IsFunctionType()) {
    // Any type that can be the type of a closure is a subtype of Function or
    // non-nullable Object.
    if (other.IsObjectType() || other.IsDartFunctionType()) {
      const bool result = !IsNullable() || !other.IsNonNullable();
      TRACE_TYPE_CHECKS_VERBOSE("   - result: %s (function vs non-function)\n",
                                (result ? "true" : "false"));
      return result;
    }
    if (other.IsFunctionType()) {
      // Check for two function types.
      if (IsNullable() && other.IsNonNullable()) {
        TRACE_TYPE_CHECKS_VERBOSE(
            "   - result: false (function nullability)\n");
        return false;
      }
      const bool result = FunctionType::Cast(*this).IsSubtypeOf(
          FunctionType::Cast(other), space, function_type_equivalence);
      TRACE_TYPE_CHECKS_VERBOSE("   - result: %s (function types)\n",
                                (result ? "true" : "false"));
      return result;
    }
    // Apply additional subtyping rules if 'other' is 'FutureOr'.
    if (IsSubtypeOfFutureOr(zone, other, space, function_type_equivalence)) {
      TRACE_TYPE_CHECKS_VERBOSE(
          "   - result: true (function type is a subtype of FutureOr)\n");
      return true;
    }
    // All possible supertypes for FunctionType have been checked.
    TRACE_TYPE_CHECKS_VERBOSE("   - result: false (function type)\n");
    return false;
  } else if (other.IsFunctionType()) {
    // FunctionTypes can only be subtyped by other FunctionTypes, so don't
    // fall through to class-based type tests.
    TRACE_TYPE_CHECKS_VERBOSE(
        "   - result: false (right is a function type)\n");
    return false;
  }
  // Record types cannot be handled by Class::IsSubtypeOf().
  if (IsRecordType()) {
    if (other.IsObjectType() || other.IsDartRecordType()) {
      const bool result = !IsNullable() || !other.IsNonNullable();
      TRACE_TYPE_CHECKS_VERBOSE("   - result: %s (record vs non-record)\n",
                                (result ? "true" : "false"));
      return result;
    }
    if (other.IsRecordType()) {
      // Check for two record types.
      if (IsNullable() && other.IsNonNullable()) {
        TRACE_TYPE_CHECKS_VERBOSE("   - result: false (record nullability)\n");
        return false;
      }
      const bool result = RecordType::Cast(*this).IsSubtypeOf(
          RecordType::Cast(other), space, function_type_equivalence);
      TRACE_TYPE_CHECKS_VERBOSE("   - result: %s (record types)\n",
                                (result ? "true" : "false"));
      return result;
    }
    // Apply additional subtyping rules if 'other' is 'FutureOr'.
    if (IsSubtypeOfFutureOr(zone, other, space, function_type_equivalence)) {
      TRACE_TYPE_CHECKS_VERBOSE(
          "   - result: true (record type is a subtype of FutureOr)\n");
      return true;
    }
    // All possible supertypes for record type have been checked.
    TRACE_TYPE_CHECKS_VERBOSE("   - result: false (record type)\n");
    return false;
  } else if (other.IsRecordType()) {
    // RecordTypes can only be subtyped by other RecordTypes, so don't
    // fall through to class-based type tests.
    TRACE_TYPE_CHECKS_VERBOSE("   - result: false (right is a record type)\n");
    return false;
  }
  ASSERT(IsType());
  const Class& type_cls = Class::Handle(zone, type_class());
  const bool result = Class::IsSubtypeOf(
      type_cls,
      TypeArguments::Handle(zone, Type::Cast(*this).GetInstanceTypeArguments(
                                      thread, /*canonicalize=*/false)),
      nullability(), other, space, function_type_equivalence);
  TRACE_TYPE_CHECKS_VERBOSE("   - result: %s (class type check)\n",
                            (result ? "true" : "false"));
  return result;
}

bool AbstractType::IsSubtypeOfFutureOr(
    Zone* zone,
    const AbstractType& other,
    Heap::Space space,
    FunctionTypeMapping* function_type_equivalence) const {
  if (other.IsFutureOrType()) {
    // This function is only called with a receiver that is either a function
    // type, record type, or an uninstantiated type parameter.
    // Therefore, it cannot be of class Future and we can spare the check.
    ASSERT(IsFunctionType() || IsRecordType() || IsTypeParameter());
    const TypeArguments& other_type_arguments =
        TypeArguments::Handle(zone, other.arguments());
    const AbstractType& other_type_arg =
        AbstractType::Handle(zone, other_type_arguments.TypeAtNullSafe(0));
    if (other_type_arg.IsTopTypeForSubtyping()) {
      return true;
    }
    // Retry the IsSubtypeOf check after unwrapping type arg of FutureOr.
    if (IsSubtypeOf(other_type_arg, space, function_type_equivalence)) {
      return true;
    }
  }
  return false;
}

uword AbstractType::ComputeHash() const {
  // AbstractType is an abstract class.
  UNREACHABLE();
  return 0;
}

const char* AbstractType::ToCString() const {
  // All subclasses should implement this appropriately, so the only value that
  // should reach this implementation should be the null value.
  ASSERT(IsNull());
  return "AbstractType: null";
}

void AbstractType::SetTypeTestingStub(const Code& stub) const {
  if (stub.IsNull()) {
    InitializeTypeTestingStubNonAtomic(stub);
    return;
  }

  auto& old = Code::Handle(Thread::Current()->zone());
  while (true) {
    // We load the old TTS and it's entrypoint.
    old = untag()->type_test_stub<std::memory_order_acquire>();
    uword old_entry_point = old.IsNull() ? 0 : old.EntryPoint();

    // If we can successfully update the entrypoint of the TTS, we will
    // unconditionally also set the [Code] of the TTS.
    //
    // Any competing writer would do the same, lose the compare-exchange, loop
    // around and continue loading the old [Code] TTS and continue to lose the
    // race until we have finally also updated the [Code] TTS.
    if (untag()->type_test_stub_entry_point_.compare_exchange_strong(
            old_entry_point, stub.EntryPoint())) {
      untag()->set_type_test_stub<std::memory_order_release>(stub.ptr());
      return;
    }
  }
}

void AbstractType::InitializeTypeTestingStubNonAtomic(const Code& stub) const {
  if (stub.IsNull()) {
    // This only happens during bootstrapping when creating Type objects before
    // we have the instructions.
    ASSERT(type_class_id() == kDynamicCid || type_class_id() == kVoidCid);
    StoreNonPointer(&untag()->type_test_stub_entry_point_, 0);
    untag()->set_type_test_stub(stub.ptr());
    return;
  }

  StoreNonPointer(&untag()->type_test_stub_entry_point_, stub.EntryPoint());
  untag()->set_type_test_stub(stub.ptr());
}

TypePtr Type::NullType() {
  return IsolateGroup::Current()->object_store()->null_type();
}

TypePtr Type::DynamicType() {
  return Object::dynamic_type().ptr();
}

TypePtr Type::VoidType() {
  return Object::void_type().ptr();
}

TypePtr Type::NeverType() {
  return IsolateGroup::Current()->object_store()->never_type();
}

TypePtr Type::ObjectType() {
  return IsolateGroup::Current()->object_store()->object_type();
}

TypePtr Type::BoolType() {
  return IsolateGroup::Current()->object_store()->bool_type();
}

TypePtr Type::IntType() {
  return IsolateGroup::Current()->object_store()->int_type();
}

TypePtr Type::NullableIntType() {
  return IsolateGroup::Current()->object_store()->nullable_int_type();
}

TypePtr Type::SmiType() {
  return IsolateGroup::Current()->object_store()->smi_type();
}

TypePtr Type::MintType() {
  return IsolateGroup::Current()->object_store()->mint_type();
}

TypePtr Type::Double() {
  return IsolateGroup::Current()->object_store()->double_type();
}

TypePtr Type::NullableDouble() {
  return IsolateGroup::Current()->object_store()->nullable_double_type();
}

TypePtr Type::Float32x4() {
  return IsolateGroup::Current()->object_store()->float32x4_type();
}

TypePtr Type::Float64x2() {
  return IsolateGroup::Current()->object_store()->float64x2_type();
}

TypePtr Type::Int32x4() {
  return IsolateGroup::Current()->object_store()->int32x4_type();
}

TypePtr Type::Number() {
  return IsolateGroup::Current()->object_store()->number_type();
}

TypePtr Type::NullableNumber() {
  return IsolateGroup::Current()->object_store()->nullable_number_type();
}

TypePtr Type::StringType() {
  return IsolateGroup::Current()->object_store()->string_type();
}

TypePtr Type::ArrayType() {
  return IsolateGroup::Current()->object_store()->array_type();
}

TypePtr Type::DartFunctionType() {
  return IsolateGroup::Current()->object_store()->function_type();
}

TypePtr Type::DartTypeType() {
  return IsolateGroup::Current()->object_store()->type_type();
}

TypePtr Type::NewNonParameterizedType(const Class& type_class) {
  ASSERT(type_class.NumTypeArguments() == 0);
  if (type_class.IsNullClass()) {
    return Type::NullType();
  }
  if (type_class.IsDynamicClass()) {
    return Type::DynamicType();
  }
  if (type_class.IsVoidClass()) {
    return Type::VoidType();
  }
  // It is too early to use the class finalizer, as type_class may not be named
  // yet, so do not call DeclarationType().
  Type& type = Type::Handle(type_class.declaration_type());
  if (type.IsNull()) {
    type = Type::New(Class::Handle(type_class.ptr()),
                     Object::null_type_arguments(), Nullability::kNonNullable);
    type.SetIsFinalized();
    type ^= type.Canonicalize(Thread::Current());
    type_class.set_declaration_type(type);
  }
  ASSERT(type.IsFinalized());
  return type.ptr();
}

TypePtr Type::ToNullability(Nullability value, Heap::Space space) const {
  if (nullability() == value) {
    return ptr();
  }
  // Type parameter instantiation may request a nullability change, which should
  // be ignored for types dynamic and void. Type Null cannot be the result of
  // instantiating a non-nullable type parameter (TypeError thrown).
  const classid_t cid = type_class_id();
  if (cid == kDynamicCid || cid == kVoidCid || cid == kNullCid) {
    return ptr();
  }
  if (cid == kNeverCid && value == Nullability::kNullable) {
    // Normalize Never? to Null.
    return Type::NullType();
  }
  // Clone type and set new nullability.
  Type& type = Type::Handle();
  // Always cloning in old space and removing space parameter would not satisfy
  // currently existing requests for type instantiation in new space.
  // Load with relaxed atomics to prevent data race with updating type
  // testing stub.
  type ^= Object::Clone(*this, space, /*load_with_relaxed_atomics=*/true);
  type.set_nullability(value);
  type.SetHash(0);
  type.InitializeTypeTestingStubNonAtomic(
      Code::Handle(TypeTestingStubGenerator::DefaultCodeForType(type)));
  if (IsCanonical()) {
    // Object::Clone does not clone canonical bit.
    ASSERT(!type.IsCanonical());
    type ^= type.Canonicalize(Thread::Current());
  }
  return type.ptr();
}

FunctionTypePtr FunctionType::ToNullability(Nullability value,
                                            Heap::Space space) const {
  if (nullability() == value) {
    return ptr();
  }
  // Clone function type and set new nullability.
  FunctionType& type = FunctionType::Handle(FunctionType::Clone(*this, space));
  type.set_nullability(value);
  type.SetHash(0);
  type.InitializeTypeTestingStubNonAtomic(
      Code::Handle(TypeTestingStubGenerator::DefaultCodeForType(type)));
  if (IsCanonical()) {
    // Object::Clone does not clone canonical bit.
    ASSERT(!type.IsCanonical());
    type ^= type.Canonicalize(Thread::Current());
  }
  return type.ptr();
}

classid_t Type::type_class_id() const {
  return untag()->type_class_id();
}

ClassPtr Type::type_class() const {
  return IsolateGroup::Current()->class_table()->At(type_class_id());
}

bool Type::IsInstantiated(Genericity genericity,
                          intptr_t num_free_fun_type_params) const {
  if (type_state() == UntaggedType::kFinalizedInstantiated) {
    return true;
  }
  if ((genericity == kAny) && (num_free_fun_type_params == kAllFree) &&
      (type_state() == UntaggedType::kFinalizedUninstantiated)) {
    return false;
  }
  if (arguments() == TypeArguments::null()) {
    return true;
  }
  const TypeArguments& args = TypeArguments::Handle(arguments());
  return args.IsSubvectorInstantiated(0, args.Length(), genericity,
                                      num_free_fun_type_params);
}

AbstractTypePtr Type::InstantiateFrom(
    const TypeArguments& instantiator_type_arguments,
    const TypeArguments& function_type_arguments,
    intptr_t num_free_fun_type_params,
    Heap::Space space,
    FunctionTypeMapping* function_type_mapping,
    intptr_t num_parent_type_args_adjustment) const {
  Zone* zone = Thread::Current()->zone();
  ASSERT(IsFinalized());
  ASSERT(!IsInstantiated());
  // Note that the type class has to be resolved at this time, but not
  // necessarily finalized yet. We may be checking bounds at compile time or
  // finalizing the type argument vector of a recursive type.
  const Class& cls = Class::Handle(zone, type_class());
  TypeArguments& type_arguments = TypeArguments::Handle(zone, arguments());
  ASSERT(type_arguments.Length() == cls.NumTypeParameters());
  type_arguments = type_arguments.InstantiateFrom(
      instantiator_type_arguments, function_type_arguments,
      num_free_fun_type_params, space, function_type_mapping,
      num_parent_type_args_adjustment);
  // A returned empty_type_arguments indicates a failed instantiation in dead
  // code that must be propagated up to the caller, the optimizing compiler.
  if (type_arguments.ptr() == Object::empty_type_arguments().ptr()) {
    return Type::null();
  }
  // This uninstantiated type is not modified, as it can be instantiated
  // with different instantiators. Allocate a new instantiated version of it.
  const Type& instantiated_type =
      Type::Handle(zone, Type::New(cls, type_arguments, nullability(), space));
  instantiated_type.SetIsFinalized();
  // Canonicalization is not part of instantiation.
  return instantiated_type.NormalizeFutureOrType(space);
}

AbstractTypePtr Type::UpdateFunctionTypes(
    intptr_t num_parent_type_args_adjustment,
    intptr_t num_free_fun_type_params,
    Heap::Space space,
    FunctionTypeMapping* function_type_mapping) const {
  ASSERT(IsFinalized());
  ASSERT(num_parent_type_args_adjustment >= 0);
  if (arguments() == Object::null()) {
    return ptr();
  }
  Zone* zone = Thread::Current()->zone();
  const auto& type_args = TypeArguments::Handle(zone, arguments());
  const auto& updated_type_args = TypeArguments::Handle(
      zone, type_args.UpdateFunctionTypes(num_parent_type_args_adjustment,
                                          num_free_fun_type_params, space,
                                          function_type_mapping));
  if (type_args.ptr() == updated_type_args.ptr()) {
    return ptr();
  }
  const Class& cls = Class::Handle(zone, type_class());
  const Type& new_type = Type::Handle(
      zone, Type::New(cls, updated_type_args, nullability(), space));
  new_type.SetIsFinalized();
  return new_type.ptr();
}

// Certain built-in classes are treated as syntactically equivalent.
static classid_t NormalizeClassIdForSyntacticalTypeEquality(classid_t cid) {
  if (IsIntegerClassId(cid)) {
    return Type::Handle(Type::IntType()).type_class_id();
  } else if (IsStringClassId(cid)) {
    return Type::Handle(Type::StringType()).type_class_id();
  } else if (cid == kDoubleCid) {
    return Type::Handle(Type::Double()).type_class_id();
  } else if (IsTypeClassId(cid)) {
    return Type::Handle(Type::DartTypeType()).type_class_id();
  } else if (IsArrayClassId(cid)) {
    return Class::Handle(IsolateGroup::Current()->object_store()->list_class())
        .id();
  }
  return cid;
}

bool Type::IsEquivalent(const Instance& other,
                        TypeEquality kind,
                        FunctionTypeMapping* function_type_equivalence) const {
  ASSERT(!IsNull());
  if (ptr() == other.ptr()) {
    return true;
  }
  if (!other.IsType()) {
    return false;
  }
  const Type& other_type = Type::Cast(other);
  const classid_t type_cid = type_class_id();
  const classid_t other_type_cid = other_type.type_class_id();
  if (type_cid != other_type_cid) {
    if ((kind != TypeEquality::kSyntactical) ||
        (NormalizeClassIdForSyntacticalTypeEquality(type_cid) !=
         NormalizeClassIdForSyntacticalTypeEquality(other_type_cid))) {
      return false;
    }
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  ASSERT(
      Class::Handle(zone, type_class()).NumTypeParameters(thread) ==
      Class::Handle(zone, other_type.type_class()).NumTypeParameters(thread));

  if (!IsNullabilityEquivalent(thread, other_type, kind)) {
    return false;
  }
  if (!IsFinalized() || !other_type.IsFinalized()) {
    ASSERT(kind != TypeEquality::kCanonical);
    return false;  // Too early to decide if equal.
  }
  if (arguments() == other_type.arguments()) {
    return true;
  }
  const TypeArguments& type_args =
      TypeArguments::Handle(zone, this->arguments());
  const TypeArguments& other_type_args =
      TypeArguments::Handle(zone, other_type.arguments());
  return type_args.IsEquivalent(other_type_args, kind,
                                function_type_equivalence);
}

bool FunctionType::IsEquivalent(
    const Instance& other,
    TypeEquality kind,
    FunctionTypeMapping* function_type_equivalence) const {
  ASSERT(!IsNull());
  if (ptr() == other.ptr()) {
    return true;
  }
  if (!other.IsFunctionType()) {
    return false;
  }
  const FunctionType& other_type = FunctionType::Cast(other);
  if ((packed_parameter_counts() != other_type.packed_parameter_counts()) ||
      (packed_type_parameter_counts() !=
       other_type.packed_type_parameter_counts())) {
    // Different number of type parameters or parameters.
    return false;
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  if (!IsNullabilityEquivalent(thread, other_type, kind)) {
    return false;
  }
  if (!IsFinalized() || !other_type.IsFinalized()) {
    ASSERT(kind != TypeEquality::kCanonical);
    return false;  // Too early to decide if equal.
  }
  FunctionTypeMapping scope(zone, &function_type_equivalence, *this,
                            other_type);

  // Equal function types must have equal signature types and equal optional
  // named arguments.
  // Compare function type parameters and their bounds.
  // Check the type parameters and bounds of generic functions.
  if (!HasSameTypeParametersAndBounds(other_type, kind,
                                      function_type_equivalence)) {
    return false;
  }
  AbstractType& param_type = Type::Handle(zone);
  AbstractType& other_param_type = Type::Handle(zone);
  // Check the result type.
  param_type = result_type();
  other_param_type = other_type.result_type();
  if (!param_type.IsEquivalent(other_param_type, kind,
                               function_type_equivalence)) {
    return false;
  }
  // Check the types of all parameters.
  const intptr_t num_params = NumParameters();
  ASSERT(other_type.NumParameters() == num_params);
  for (intptr_t i = 0; i < num_params; i++) {
    param_type = ParameterTypeAt(i);
    other_param_type = other_type.ParameterTypeAt(i);
    // Use contravariant order in case we test for subtyping.
    if (!other_param_type.IsEquivalent(param_type, kind,
                                       function_type_equivalence)) {
      return false;
    }
  }
  if (HasOptionalNamedParameters()) {
    ASSERT(other_type.HasOptionalNamedParameters());  // Same packed counts.
    for (intptr_t i = num_fixed_parameters(); i < num_params; i++) {
      if (ParameterNameAt(i) != other_type.ParameterNameAt(i)) {
        return false;
      }
      if (IsRequiredAt(i) != other_type.IsRequiredAt(i)) {
        return false;
      }
    }
  }
  return true;
}

bool Type::IsDeclarationTypeOf(const Class& cls) const {
  ASSERT(type_class() == cls.ptr());
  if (cls.IsNullClass()) {
    return true;
  }
  if (cls.IsGeneric() || cls.IsClosureClass()) {
    return false;
  }
  return nullability() == Nullability::kNonNullable;
}

// Keep in sync with TypeSerializationCluster::IsInCanonicalSet.
AbstractTypePtr Type::Canonicalize(Thread* thread) const {
  Zone* zone = thread->zone();
  ASSERT(IsFinalized());
  if (IsCanonical()) {
#ifdef DEBUG
    TypeArguments& type_args = TypeArguments::Handle(zone, arguments());
    ASSERT(type_args.IsCanonical());
    ASSERT(type_args.IsOld());
#endif
    return this->ptr();
  }
  auto isolate_group = thread->isolate_group();
  const classid_t cid = type_class_id();
  if (cid == kDynamicCid) {
    ASSERT(Object::dynamic_type().IsCanonical());
    return Object::dynamic_type().ptr();
  }

  if (cid == kVoidCid) {
    ASSERT(Object::void_type().IsCanonical());
    return Object::void_type().ptr();
  }

  const Class& cls = Class::Handle(zone, type_class());

  // Fast canonical lookup/registry for simple types.
  if (IsDeclarationTypeOf(cls)) {
    ASSERT(!cls.IsNullClass() || IsNullable());
    Type& type = Type::Handle(zone, cls.declaration_type());
    if (type.IsNull()) {
      // Canonicalize the type arguments of the supertype, if any.
      TypeArguments& type_args = TypeArguments::Handle(zone, arguments());
      type_args = type_args.Canonicalize(thread);
      set_arguments(type_args);
      type = cls.declaration_type();
      // May be set while canonicalizing type args.
      if (type.IsNull()) {
        SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
        // Recheck if type exists.
        type = cls.declaration_type();
        if (type.IsNull()) {
          if (this->IsNew()) {
            type ^= Object::Clone(*this, Heap::kOld);
          } else {
            type = this->ptr();
          }
          ASSERT(type.IsOld());
          type.ComputeHash();
          type.SetCanonical();
          cls.set_declaration_type(type);
          return type.ptr();
        }
      }
    }
    ASSERT(this->Equals(type));
    ASSERT(type.IsOld());
    if (type.IsCanonical()) {
      return type.ptr();
    }
  }

  Type& type = Type::Handle(zone);
  ObjectStore* object_store = isolate_group->object_store();
  {
    SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
    CanonicalTypeSet table(zone, object_store->canonical_types());
    type ^= table.GetOrNull(CanonicalTypeKey(*this));
    ASSERT(object_store->canonical_types() == table.Release().ptr());
  }
  if (type.IsNull()) {
    // The type was not found in the table. It is not canonical yet.

    // Canonicalize the type arguments.
    TypeArguments& type_args = TypeArguments::Handle(zone, arguments());
    ASSERT(type_args.IsNull() ||
           (type_args.Length() == cls.NumTypeParameters()));
    type_args = type_args.Canonicalize(thread);
    set_arguments(type_args);
    ASSERT(type_args.IsNull() || type_args.IsOld());

    // Check to see if the type got added to canonical table as part of the
    // type arguments canonicalization.
    SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
    CanonicalTypeSet table(zone, object_store->canonical_types());
    type ^= table.GetOrNull(CanonicalTypeKey(*this));
    if (type.IsNull()) {
      // Add this type into the canonical table of types.
      if (this->IsNew()) {
        type ^= Object::Clone(*this, Heap::kOld);
      } else {
        type = this->ptr();
      }
      ASSERT(type.IsOld());
      type.SetCanonical();  // Mark object as being canonical.
      bool present = table.Insert(type);
      ASSERT(!present);
    }
    object_store->set_canonical_types(table.Release());
  }
  return type.ptr();
}

void Type::EnumerateURIs(URIs* uris) const {
  if (IsDynamicType() || IsVoidType() || IsNeverType()) {
    return;
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const Class& cls = Class::Handle(zone, type_class());
  const String& name = String::Handle(zone, cls.UserVisibleName());
  const Library& library = Library::Handle(zone, cls.library());
  const String& uri = String::Handle(zone, library.url());
  AddURI(uris, name, uri);
  const TypeArguments& type_args = TypeArguments::Handle(zone, arguments());
  type_args.EnumerateURIs(uris);
}

void Type::PrintName(NameVisibility name_visibility,
                     BaseTextBuffer* printer) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const Class& cls = Class::Handle(zone, type_class());
  const TypeParameters& params =
      TypeParameters::Handle(zone, cls.type_parameters());
  printer->AddString(cls.NameCString(name_visibility));
  const TypeArguments& args = TypeArguments::Handle(zone, arguments());
  intptr_t num_type_params = 0;
  if (cls.is_declaration_loaded()) {
    num_type_params = cls.NumTypeParameters(thread);
  } else if (!args.IsNull() || args.ptr() != params.defaults()) {
    num_type_params = args.Length();
  }
  if (num_type_params == 0) {
    // Do nothing.
  } else {
    args.PrintSubvectorName(0, num_type_params, name_visibility, printer);
  }
  printer->AddString(NullabilitySuffix(name_visibility));
  // The name is only used for type checking and debugging purposes.
  // Unless profiling data shows otherwise, it is not worth caching the name in
  // the type.
}

uword Type::ComputeHash() const {
  ASSERT(IsFinalized());
  uint32_t result = type_class_id();
  result = CombineHashes(result, static_cast<uint32_t>(nullability()));
  uint32_t type_args_hash = TypeArguments::kAllDynamicHash;
  if (arguments() != TypeArguments::null()) {
    const TypeArguments& args = TypeArguments::Handle(arguments());
    type_args_hash = args.Hash();
  }
  result = CombineHashes(result, type_args_hash);
  result = FinalizeHash(result, kHashBits);
  SetHash(result);
  return result;
}

uword FunctionType::ComputeHash() const {
  ASSERT(IsFinalized());
  uint32_t result =
      CombineHashes(packed_parameter_counts(), packed_type_parameter_counts());
  result = CombineHashes(result, static_cast<uint32_t>(nullability()));
  AbstractType& type = AbstractType::Handle();
  const intptr_t num_type_params = NumTypeParameters();
  if (num_type_params > 0) {
    const TypeParameters& type_params =
        TypeParameters::Handle(type_parameters());
    const TypeArguments& bounds = TypeArguments::Handle(type_params.bounds());
    result = CombineHashes(result, bounds.Hash());
    // Since the default arguments are ignored when comparing two generic
    // function types for type equality, the hash does not depend on them.
  }
  type = result_type();
  result = CombineHashes(result, type.Hash());
  const intptr_t num_params = NumParameters();
  for (intptr_t i = 0; i < num_params; i++) {
    type = ParameterTypeAt(i);
    result = CombineHashes(result, type.Hash());
  }
  if (HasOptionalNamedParameters()) {
    String& param_name = String::Handle();
    for (intptr_t i = num_fixed_parameters(); i < num_params; i++) {
      param_name = ParameterNameAt(i);
      result = CombineHashes(result, param_name.Hash());
    }
    // Required flag is not hashed, see comment above about legacy type.
  }
  result = FinalizeHash(result, kHashBits);
  SetHash(result);
  return result;
}

void Type::set_type_class(const Class& value) const {
  ASSERT(!value.IsNull());
  set_type_class_id(value.id());
}

void Type::set_arguments(const TypeArguments& value) const {
  ASSERT(!IsCanonical());
  ASSERT(value.IsNull() ||
         // Do not attempt to query number of type parameters
         // before class declaration is fully loaded.
         !Class::Handle(type_class()).is_declaration_loaded() ||
         // Relax assertion in order to support invalid generic types
         // created in ClosureMirror_function.
         (type_class_id() == kInstanceCid) ||
         value.Length() == Class::Handle(type_class()).NumTypeParameters());
  untag()->set_arguments(value.ptr());
}

TypeArgumentsPtr Type::GetInstanceTypeArguments(Thread* thread,
                                                bool canonicalize) const {
  Zone* zone = thread->zone();
  const auto& cls = Class::Handle(zone, type_class());
  const auto& args = TypeArguments::Handle(zone, arguments());
  return cls.GetInstanceTypeArguments(thread, args, canonicalize);
}

TypePtr Type::New(Heap::Space space) {
  return Object::Allocate<Type>(space);
}

TypePtr Type::New(const Class& clazz,
                  const TypeArguments& arguments,
                  Nullability nullability,
                  Heap::Space space) {
  Zone* Z = Thread::Current()->zone();
  const Type& result = Type::Handle(Z, Type::New(space));
  result.SetHash(0);
  result.set_flags(0);
  result.set_nullability(nullability);
  result.set_type_state(UntaggedAbstractType::kAllocated);
  result.set_type_class(clazz);
  result.set_arguments(arguments);

  result.InitializeTypeTestingStubNonAtomic(
      Code::Handle(Z, TypeTestingStubGenerator::DefaultCodeForType(result)));
  return result.ptr();
}

void Type::set_type_class_id(intptr_t id) const {
  ASSERT(Utils::IsUint(UntaggedObject::kClassIdTagSize, id));
  // We should never need a Type object for a top-level class.
  ASSERT(!ClassTable::IsTopLevelCid(id));
  ASSERT(id != kIllegalCid);
  ASSERT(!IsInternalOnlyClassId(id));
  ASSERT(id >= kInstanceCid);
  untag()->set_type_class_id(id);
}

const char* Type::ToCString() const {
  if (IsNull()) {
    return "Type: null";
  }
  Zone* zone = Thread::Current()->zone();
  ZoneTextBuffer args(zone);
  const TypeArguments& type_args = TypeArguments::Handle(zone, arguments());
  const char* args_cstr = "";
  if (!type_args.IsNull()) {
    type_args.PrintSubvectorName(0, type_args.Length(), kInternalName, &args);
    args_cstr = args.buffer();
  }
  const Class& cls = Class::Handle(zone, type_class());
  const char* class_name;
  const String& name = String::Handle(zone, cls.Name());
  class_name = name.IsNull() ? "<null>" : name.ToCString();
  const char* suffix = NullabilitySuffix(kInternalName);
  return OS::SCreate(zone, "Type: %s%s%s", class_name, args_cstr, suffix);
}

AbstractTypePtr FunctionType::Canonicalize(Thread* thread) const {
  ASSERT(IsFinalized());
  Zone* zone = thread->zone();
  if (IsCanonical()) {
#ifdef DEBUG
    // Verify that all fields are allocated in old space and are canonical.
    if (IsGeneric()) {
      const TypeParameters& type_params =
          TypeParameters::Handle(zone, type_parameters());
      ASSERT(type_params.IsOld());
      TypeArguments& type_args = TypeArguments::Handle(zone);
      type_args = type_params.bounds();
      ASSERT(type_args.IsOld());
      ASSERT(type_args.IsCanonical());
      type_args = type_params.defaults();
      ASSERT(type_args.IsOld());
      ASSERT(type_args.IsCanonical());
    }
    AbstractType& type = AbstractType::Handle(zone);
    type = result_type();
    ASSERT(type.IsOld());
    ASSERT(type.IsCanonical());
    ASSERT(Array::Handle(zone, parameter_types()).IsOld());
    ASSERT(Array::Handle(zone, named_parameter_names()).IsOld());
    const intptr_t num_params = NumParameters();
    for (intptr_t i = 0; i < num_params; i++) {
      type = ParameterTypeAt(i);
      ASSERT(type.IsOld());
      ASSERT(type.IsCanonical());
    }
#endif
    return ptr();
  }
  auto isolate_group = thread->isolate_group();
  ObjectStore* object_store = isolate_group->object_store();
  FunctionType& sig = FunctionType::Handle(zone);
  {
    SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
    CanonicalFunctionTypeSet table(zone,
                                   object_store->canonical_function_types());
    sig ^= table.GetOrNull(CanonicalFunctionTypeKey(*this));
    ASSERT(object_store->canonical_function_types() == table.Release().ptr());
  }
  if (sig.IsNull()) {
    // The function type was not found in the table. It is not canonical yet.
    // Canonicalize its type parameters and types.

    // Clone this function type to the old heap and update
    // owners of type parameters.
    FunctionType& new_sig = FunctionType::Handle(zone);
    if (this->IsNew()) {
      new_sig ^= FunctionType::Clone(*this, Heap::kOld);
    } else {
      new_sig ^= this->ptr();
    }
    ASSERT(new_sig.IsOld());

    if (new_sig.IsGeneric()) {
      const TypeParameters& type_params =
          TypeParameters::Handle(zone, new_sig.type_parameters());
      ASSERT(type_params.IsOld());
      TypeArguments& type_args = TypeArguments::Handle(zone);
      type_args = type_params.bounds();
      if (!type_args.IsCanonical()) {
        type_args = type_args.Canonicalize(thread);
        type_params.set_bounds(type_args);
      }
      type_args = type_params.defaults();
      if (!type_args.IsCanonical()) {
        type_args = type_args.Canonicalize(thread);
        type_params.set_defaults(type_args);
      }
    }
    AbstractType& type = AbstractType::Handle(zone);
    type = new_sig.result_type();
    if (!type.IsCanonical()) {
      type = type.Canonicalize(thread);
      new_sig.set_result_type(type);
    }
    ASSERT(Array::Handle(zone, new_sig.parameter_types()).IsOld());
    ASSERT(Array::Handle(zone, new_sig.named_parameter_names()).IsOld());
    const intptr_t num_params = new_sig.NumParameters();
    for (intptr_t i = 0; i < num_params; i++) {
      type = new_sig.ParameterTypeAt(i);
      if (!type.IsCanonical()) {
        type = type.Canonicalize(thread);
        new_sig.SetParameterTypeAt(i, type);
      }
    }
    // Check to see if the function type got added to canonical table
    // during canonicalization of its signature types.
    SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
    CanonicalFunctionTypeSet table(zone,
                                   object_store->canonical_function_types());
    sig ^= table.GetOrNull(CanonicalFunctionTypeKey(new_sig));
    if (sig.IsNull()) {
      // Add this function type into the canonical table of function types.
      sig = new_sig.ptr();
      ASSERT(sig.IsOld());
      sig.SetCanonical();  // Mark object as being canonical.
      bool present = table.Insert(sig);
      ASSERT(!present);
    }
    object_store->set_canonical_function_types(table.Release());
  }
  return sig.ptr();
}

void FunctionType::EnumerateURIs(URIs* uris) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  AbstractType& type = AbstractType::Handle(zone);
  const intptr_t num_params = NumParameters();
  for (intptr_t i = 0; i < num_params; i++) {
    type = ParameterTypeAt(i);
    type.EnumerateURIs(uris);
  }
  // Handle result type last, since it appears last in the user visible name.
  type = result_type();
  type.EnumerateURIs(uris);
}

void FunctionType::PrintName(NameVisibility name_visibility,
                             BaseTextBuffer* printer) const {
  const char* suffix = NullabilitySuffix(name_visibility);
  if (suffix[0] != '\0') {
    printer->AddString("(");
  }
  FunctionType::Cast(*this).Print(name_visibility, printer);
  if (suffix[0] != '\0') {
    printer->AddString(")");
    printer->AddString(suffix);
  }
}

TypeParameterPtr TypeParameter::ToNullability(Nullability value,
                                              Heap::Space space) const {
  if (nullability() == value) {
    return ptr();
  }
  // Clone type parameter and set new nullability.
  TypeParameter& type_parameter = TypeParameter::Handle();
  type_parameter ^= Object::Clone(*this, space);
  type_parameter.set_nullability(value);
  type_parameter.SetHash(0);
  type_parameter.InitializeTypeTestingStubNonAtomic(Code::Handle(
      TypeTestingStubGenerator::DefaultCodeForType(type_parameter)));
  if (IsCanonical()) {
    // Object::Clone does not clone canonical bit.
    ASSERT(!type_parameter.IsCanonical());
    ASSERT(IsFinalized());
    ASSERT(type_parameter.IsFinalized());
    type_parameter ^= type_parameter.Canonicalize(Thread::Current());
  }
  return type_parameter.ptr();
}

bool TypeParameter::IsInstantiated(Genericity genericity,
                                   intptr_t num_free_fun_type_params) const {
  // Bounds of class type parameters are ignored in the VM.
  if (IsClassTypeParameter()) {
    return genericity == kFunctions;
  }
  ASSERT(IsFunctionTypeParameter());
  return (genericity == kCurrentClass) || (index() >= num_free_fun_type_params);
}

bool TypeParameter::IsEquivalent(
    const Instance& other,
    TypeEquality kind,
    FunctionTypeMapping* function_type_equivalence) const {
  TRACE_TYPE_CHECKS_VERBOSE("  TypeParameter::IsEquivalent(%s, %s, kind %d)\n",
                            ToCString(), other.ToCString(),
                            static_cast<int>(kind));
  if (ptr() == other.ptr()) {
    TRACE_TYPE_CHECKS_VERBOSE("   - result: true (same types)\n");
    return true;
  }
  if (!other.IsTypeParameter()) {
    TRACE_TYPE_CHECKS_VERBOSE(
        "   - result: false (other is not a type parameter)\n");
    return false;
  }
  const TypeParameter& other_type_param = TypeParameter::Cast(other);
  ASSERT(IsFinalized() && other_type_param.IsFinalized());
  // Compare index, base and owner.
  if (IsFunctionTypeParameter()) {
    if (!other_type_param.IsFunctionTypeParameter()) {
      TRACE_TYPE_CHECKS_VERBOSE(
          "   - result: false (other is not a function type parameter)\n");
      return false;
    }
    if ((parameterized_function_type() !=
         other_type_param.parameterized_function_type()) &&
        ((function_type_equivalence == nullptr) ||
         !function_type_equivalence->ContainsOwnersOfTypeParameters(
             *this, other_type_param))) {
      TRACE_TYPE_CHECKS_VERBOSE(
          "   - result: false (owners are not equivalent)\n");
      return false;
    }
  } else {
    if (!other_type_param.IsClassTypeParameter()) {
      TRACE_TYPE_CHECKS_VERBOSE(
          "   - result: false (other is not a class type parameter)\n");
      return false;
    }
    if (parameterized_class_id() != other_type_param.parameterized_class_id()) {
      TRACE_TYPE_CHECKS_VERBOSE(
          "   - result: false (parameterized class id)\n");
      return false;
    }
  }
  if (base() != other_type_param.base() ||
      index() != other_type_param.index()) {
    TRACE_TYPE_CHECKS_VERBOSE("   - result: false (mismatch base/index)\n");
    return false;
  }
  if (!IsNullabilityEquivalent(Thread::Current(), other_type_param, kind)) {
    TRACE_TYPE_CHECKS_VERBOSE("   - result: false (mismatch nullability)\n");
    return false;
  }
  TRACE_TYPE_CHECKS_VERBOSE("   - result: true\n");
  return true;
}

void TypeParameter::set_owner(const Object& value) const {
  ASSERT((IsFunctionTypeParameter() && value.IsFunctionType()) ||
         (IsClassTypeParameter() && value.IsSmi()));
  untag()->set_owner(value.ptr());
}

classid_t TypeParameter::parameterized_class_id() const {
  if (IsClassTypeParameter()) {
    return Smi::Value(Smi::RawCast(untag()->owner()));
  } else {
    return kFunctionCid;
  }
}
void TypeParameter::set_parameterized_class_id(classid_t value) const {
  ASSERT(IsClassTypeParameter());
  untag()->set_owner(Smi::New(value));
}

ClassPtr TypeParameter::parameterized_class() const {
  if (IsClassTypeParameter()) {
    const classid_t cid = parameterized_class_id();
    if (cid != kIllegalCid) {
      return IsolateGroup::Current()->class_table()->At(cid);
    }
  }
  return Class::null();
}

FunctionTypePtr TypeParameter::parameterized_function_type() const {
  ASSERT(IsFunctionTypeParameter());
  return FunctionType::RawCast(untag()->owner());
}

void TypeParameter::set_base(intptr_t value) const {
  ASSERT(value >= 0);
  ASSERT(Utils::IsUint(16, value));
  StoreNonPointer(&untag()->base_, value);
}

void TypeParameter::set_index(intptr_t value) const {
  ASSERT(value >= 0);
  ASSERT(Utils::IsUint(16, value));
  StoreNonPointer(&untag()->index_, value);
}

AbstractTypePtr TypeParameter::bound() const {
  if (IsFunctionTypeParameter()) {
    const auto& owner = FunctionType::Handle(parameterized_function_type());
    const auto& type_parameters =
        TypeParameters::Handle(owner.type_parameters());
    return type_parameters.BoundAt(index() - base());
  } else {
    const auto& owner = Class::Handle(parameterized_class());
    if (owner.IsNull()) {
      return IsolateGroup::Current()->object_store()->nullable_object_type();
    }
    const auto& type_parameters =
        TypeParameters::Handle(owner.type_parameters());
    return type_parameters.BoundAt(index() - base());
  }
}

AbstractTypePtr TypeParameter::GetFromTypeArguments(
    const TypeArguments& instantiator_type_arguments,
    const TypeArguments& function_type_arguments) const {
  ASSERT(IsFinalized());
  const TypeArguments& type_args = IsFunctionTypeParameter()
                                       ? function_type_arguments
                                       : instantiator_type_arguments;
  return type_args.TypeAtNullSafe(index());
}

AbstractTypePtr TypeParameter::InstantiateFrom(
    const TypeArguments& instantiator_type_arguments,
    const TypeArguments& function_type_arguments,
    intptr_t num_free_fun_type_params,
    Heap::Space space,
    FunctionTypeMapping* function_type_mapping,
    intptr_t num_parent_type_args_adjustment) const {
  Zone* zone = Thread::Current()->zone();
  AbstractType& result = AbstractType::Handle(zone);
  bool substituted = false;
  if (IsFunctionTypeParameter()) {
    ASSERT(IsFinalized());
    if (index() >= num_free_fun_type_params) {
      // Do not instantiate the function type parameter.
      // Get a replacement from the updated function type.
      ASSERT(function_type_mapping != nullptr);
      result = function_type_mapping->MapTypeParameter(*this);
      ASSERT(TypeParameter::Cast(result).index() ==
             index() - num_free_fun_type_params);
      ASSERT(TypeParameter::Cast(result).base() ==
             base() - num_free_fun_type_params);
      ASSERT(TypeParameter::Cast(result).nullability() == nullability());
      AbstractType& upper_bound = AbstractType::Handle(zone, bound());
      if (!upper_bound.IsInstantiated()) {
        upper_bound = upper_bound.InstantiateFrom(
            instantiator_type_arguments, function_type_arguments,
            num_free_fun_type_params, space, function_type_mapping,
            num_parent_type_args_adjustment);
      }
      if (upper_bound.ptr() == Type::NeverType()) {
        // Normalize 'X extends Never' to 'Never'.
        result = Type::NeverType();
      }
    } else if (function_type_arguments.IsNull()) {
      return Type::DynamicType();
    } else {
      result = function_type_arguments.TypeAt(index());
      substituted = true;
    }
  } else {
    ASSERT(IsClassTypeParameter());
    ASSERT(IsFinalized());
    if (instantiator_type_arguments.IsNull()) {
      return Type::DynamicType();
    }
    if (instantiator_type_arguments.Length() <= index()) {
      // InstantiateFrom can be invoked from a compilation pipeline with
      // mismatching type arguments vector. This can only happen for
      // a dynamically unreachable code - which compiler can't remove
      // statically for some reason.
      // To prevent crashes we return AbstractType::null(), understood by caller
      // (see AssertAssignableInstr::Canonicalize).
      return AbstractType::null();
    }
    result = instantiator_type_arguments.TypeAt(index());
    substituted = true;
    // Instantiating a class type parameter cannot result in a
    // function type parameter.
    // Bounds of class type parameters are ignored in the VM.
  }
  result = result.SetInstantiatedNullability(*this, space);
  if (substituted && (num_parent_type_args_adjustment != 0)) {
    // This type parameter is used inside a generic function type.
    // A type being substituted can have nested function types,
    // whose number of parent function type arguments should be adjusted
    // after the substitution.
    result = result.UpdateFunctionTypes(num_parent_type_args_adjustment,
                                        kAllFree, space, function_type_mapping);
  }
  // Canonicalization is not part of instantiation.
  return result.NormalizeFutureOrType(space);
}

AbstractTypePtr TypeParameter::UpdateFunctionTypes(
    intptr_t num_parent_type_args_adjustment,
    intptr_t num_free_fun_type_params,
    Heap::Space space,
    FunctionTypeMapping* function_type_mapping) const {
  ASSERT(IsFinalized());
  ASSERT(num_parent_type_args_adjustment >= 0);
  if (IsFunctionTypeParameter() && (index() >= num_free_fun_type_params)) {
    Zone* zone = Thread::Current()->zone();
    ASSERT(function_type_mapping != nullptr);
    const auto& new_tp = TypeParameter::Handle(
        zone, function_type_mapping->MapTypeParameter(*this));
    ASSERT(new_tp.base() == base() + num_parent_type_args_adjustment);
    ASSERT(new_tp.index() == index() + num_parent_type_args_adjustment);
    ASSERT(new_tp.nullability() == nullability());
    ASSERT(new_tp.IsFinalized());
    return new_tp.ptr();
  } else {
    return ptr();
  }
}

AbstractTypePtr TypeParameter::Canonicalize(Thread* thread) const {
  ASSERT(IsFinalized());
  Zone* zone = thread->zone();
  if (IsCanonical()) {
#ifdef DEBUG
    if (IsFunctionTypeParameter()) {
      ASSERT(FunctionType::Handle(zone, parameterized_function_type()).IsOld());
    }
#endif
    return this->ptr();
  }
  auto isolate_group = thread->isolate_group();
  ObjectStore* object_store = isolate_group->object_store();
  TypeParameter& type_parameter = TypeParameter::Handle(zone);
  {
    SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
    CanonicalTypeParameterSet table(zone,
                                    object_store->canonical_type_parameters());
    type_parameter ^= table.GetOrNull(CanonicalTypeParameterKey(*this));
    if (type_parameter.IsNull()) {
      // Add this type parameter into the canonical table of type parameters.
      if (this->IsNew()) {
        type_parameter ^= Object::Clone(*this, Heap::kOld);
      } else {
        type_parameter = this->ptr();
      }
      ASSERT(type_parameter.IsOld());
      type_parameter.SetCanonical();  // Mark object as being canonical.
      bool present = table.Insert(type_parameter);
      ASSERT(!present);
    }
    object_store->set_canonical_type_parameters(table.Release());
  }
  return type_parameter.ptr();
}

void TypeParameter::PrintName(NameVisibility name_visibility,
                              BaseTextBuffer* printer) const {
  const TypeParameter& type_param = TypeParameter::Cast(*this);
  // Type parameter names are meaningless after canonicalization.
  printer->AddString(type_param.CanonicalNameCString());
  printer->AddString(NullabilitySuffix(name_visibility));
}

uword TypeParameter::ComputeHash() const {
  ASSERT(IsFinalized());
  uint32_t result = base();
  result = CombineHashes(result, index());
  result = CombineHashes(result, static_cast<uint32_t>(nullability()));
  if (IsFunctionTypeParameter()) {
    const FunctionType& func =
        FunctionType::Handle(parameterized_function_type());
    result = CombineHashes(result, func.packed_parameter_counts());
    result = CombineHashes(result, func.packed_type_parameter_counts());
  } else {
    ASSERT(IsClassTypeParameter());
    result = CombineHashes(result, parameterized_class_id());
  }
  result = FinalizeHash(result, kHashBits);
  SetHash(result);
  return result;
}

TypeParameterPtr TypeParameter::New() {
  return Object::Allocate<TypeParameter>(Heap::kOld);
}

TypeParameterPtr TypeParameter::New(const Object& owner,
                                    intptr_t base,
                                    intptr_t index,
                                    Nullability nullability) {
  ASSERT(owner.IsNull() || owner.IsClass() || owner.IsFunctionType());
  const bool is_function_type_parameter = owner.IsFunctionType();
  const uint32_t flags = UntaggedTypeParameter::IsFunctionTypeParameter::encode(
      is_function_type_parameter);
  Zone* Z = Thread::Current()->zone();
  const TypeParameter& result = TypeParameter::Handle(Z, TypeParameter::New());
  result.set_flags(flags);
  if (is_function_type_parameter) {
    result.set_owner(owner);
  } else {
    result.set_parameterized_class_id(owner.IsNull() ? kIllegalCid
                                                     : Class::Cast(owner).id());
  }
  result.set_base(base);
  result.set_index(index);
  result.SetHash(0);
  result.set_nullability(nullability);
  result.set_type_state(UntaggedAbstractType::kAllocated);

  result.InitializeTypeTestingStubNonAtomic(
      Code::Handle(Z, TypeTestingStubGenerator::DefaultCodeForType(result)));
  return result.ptr();
}

const char* TypeParameter::CanonicalNameCString(bool is_class_type_parameter,
                                                intptr_t base,
                                                intptr_t index) {
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  const char* base_fmt = is_class_type_parameter ? "C%" Pd : "F%" Pd;
  const char* index_fmt = is_class_type_parameter ? "X%" Pd : "Y%" Pd;
  if (base != 0) {
    printer.Printf(base_fmt, base);
  }
  printer.Printf(index_fmt, index - base);
  return printer.buffer();
}

const char* TypeParameter::ToCString() const {
  if (IsNull()) {
    return "TypeParameter: null";
  }
  Thread* thread = Thread::Current();
  ZoneTextBuffer printer(thread->zone());
  printer.Printf("TypeParameter: ");
  printer.AddString(CanonicalNameCString());
  printer.AddString(NullabilitySuffix(kInternalName));
  return printer.buffer();
}

const char* Number::ToCString() const {
  // Number is an interface. No instances of Number should exist.
  UNREACHABLE();
  return "Number";
}

const char* Integer::ToCString() const {
  // Integer is an interface. No instances of Integer should exist except null.
  ASSERT(IsNull());
  return "nullptr Integer";
}

IntegerPtr Integer::New(const String& str, Heap::Space space) {
  // We are not supposed to have integers represented as two byte strings.
  ASSERT(str.IsOneByteString());
  if (str.IsNull() || (str.Length() == 0)) {
    return Integer::null();
  }
  int64_t value = 0;
  const char* cstr = str.ToCString();
  if (!OS::StringToInt64(cstr, &value)) {
    // Out of range.
    return Integer::null();
  }
  return Integer::New(value, space);
}

IntegerPtr Integer::NewCanonical(const String& str) {
  // We are not supposed to have integers represented as two byte strings.
  ASSERT(str.IsOneByteString());
  int64_t value = 0;
  const char* cstr = str.ToCString();
  if (!OS::StringToInt64(cstr, &value)) {
    // Out of range.
    return Integer::null();
  }
  return NewCanonical(value);
}

IntegerPtr Integer::NewCanonical(int64_t value) {
  if (Smi::IsValid(value)) {
    return Smi::New(static_cast<intptr_t>(value));
  }
  return Mint::NewCanonical(value);
}

IntegerPtr Integer::New(int64_t value, Heap::Space space) {
  const bool is_smi = Smi::IsValid(value);
  if (is_smi) {
    return Smi::New(static_cast<intptr_t>(value));
  }
  return Mint::New(value, space);
}

IntegerPtr Integer::NewFromUint64(uint64_t value, Heap::Space space) {
  return Integer::New(static_cast<int64_t>(value), space);
}

bool Integer::IsValueInRange(uint64_t value) {
  return (value <= static_cast<uint64_t>(Mint::kMaxValue));
}

bool Integer::Equals(const Instance& other) const {
  // Integer is an abstract class.
  UNREACHABLE();
  return false;
}

int Integer::CompareWith(const Integer& other) const {
  int64_t a = Value();
  int64_t b = other.Value();
  if (a < b) {
    return -1;
  } else if (a > b) {
    return 1;
  } else {
    return 0;
  }
}

uint32_t Integer::CanonicalizeHash() const {
  return Multiply64Hash(Value());
}

const char* Integer::ToHexCString(Zone* zone) const {
  ASSERT(IsSmi() || IsMint());
  int64_t value = Value();
  if (value < 0) {
    return OS::SCreate(zone, "-0x%" PX64, -static_cast<uint64_t>(value));
  } else {
    return OS::SCreate(zone, "0x%" PX64, static_cast<uint64_t>(value));
  }
}

IntegerPtr Integer::ArithmeticOp(Token::Kind operation,
                                 const Integer& other,
                                 Heap::Space space) const {
  // In 32-bit mode, the result of any operation between two Smis will fit in a
  // 32-bit signed result, except the product of two Smis, which will be 64-bit.
  // In 64-bit mode, the result of any operation between two Smis will fit in a
  // 64-bit signed result, except the product of two Smis (see below).
  if (IsSmi() && other.IsSmi()) {
    const intptr_t left_value = Smi::Value(Smi::RawCast(ptr()));
    const intptr_t right_value = Smi::Value(Smi::RawCast(other.ptr()));
    switch (operation) {
      case Token::kADD:
        return Integer::New(left_value + right_value, space);
      case Token::kSUB:
        return Integer::New(left_value - right_value, space);
      case Token::kMUL:
        return Integer::New(
            Utils::MulWithWrapAround(static_cast<int64_t>(left_value),
                                     static_cast<int64_t>(right_value)),
            space);
      case Token::kTRUNCDIV:
        return Integer::New(left_value / right_value, space);
      case Token::kMOD: {
        const intptr_t remainder = left_value % right_value;
        if (remainder < 0) {
          if (right_value < 0) {
            return Integer::New(remainder - right_value, space);
          } else {
            return Integer::New(remainder + right_value, space);
          }
        }
        return Integer::New(remainder, space);
      }
      default:
        UNIMPLEMENTED();
    }
  }
  const int64_t left_value = Value();
  const int64_t right_value = other.Value();
  switch (operation) {
    case Token::kADD:
      return Integer::New(Utils::AddWithWrapAround(left_value, right_value),
                          space);

    case Token::kSUB:
      return Integer::New(Utils::SubWithWrapAround(left_value, right_value),
                          space);

    case Token::kMUL:
      return Integer::New(Utils::MulWithWrapAround(left_value, right_value),
                          space);

    case Token::kTRUNCDIV:
      if ((left_value == Mint::kMinValue) && (right_value == -1)) {
        // Division special case: overflow in int64_t.
        // MIN_VALUE / -1 = (MAX_VALUE + 1), which wraps around to MIN_VALUE
        return Integer::New(Mint::kMinValue, space);
      }
      return Integer::New(left_value / right_value, space);

    case Token::kMOD: {
      if ((left_value == Mint::kMinValue) && (right_value == -1)) {
        // Modulo special case: overflow in int64_t.
        // MIN_VALUE % -1 = 0 for reason given above.
        return Integer::New(0, space);
      }
      const int64_t remainder = left_value % right_value;
      if (remainder < 0) {
        if (right_value < 0) {
          return Integer::New(remainder - right_value, space);
        } else {
          return Integer::New(remainder + right_value, space);
        }
      }
      return Integer::New(remainder, space);
    }
    default:
      UNIMPLEMENTED();
      return Integer::null();
  }
}

IntegerPtr Integer::BitOp(Token::Kind kind,
                          const Integer& other,
                          Heap::Space space) const {
  if (IsSmi() && other.IsSmi()) {
    intptr_t op1_value = Smi::Value(Smi::RawCast(ptr()));
    intptr_t op2_value = Smi::Value(Smi::RawCast(other.ptr()));
    intptr_t result = 0;
    switch (kind) {
      case Token::kBIT_AND:
        result = op1_value & op2_value;
        break;
      case Token::kBIT_OR:
        result = op1_value | op2_value;
        break;
      case Token::kBIT_XOR:
        result = op1_value ^ op2_value;
        break;
      default:
        UNIMPLEMENTED();
    }
    ASSERT(Smi::IsValid(result));
    return Smi::New(result);
  } else {
    int64_t a = Value();
    int64_t b = other.Value();
    switch (kind) {
      case Token::kBIT_AND:
        return Integer::New(a & b, space);
      case Token::kBIT_OR:
        return Integer::New(a | b, space);
      case Token::kBIT_XOR:
        return Integer::New(a ^ b, space);
      default:
        UNIMPLEMENTED();
        return Integer::null();
    }
  }
}

IntegerPtr Integer::ShiftOp(Token::Kind kind,
                            const Integer& other,
                            Heap::Space space) const {
  int64_t a = Value();
  int64_t b = other.Value();
  ASSERT(b >= 0);
  switch (kind) {
    case Token::kSHL:
      return Integer::New(Utils::ShiftLeftWithTruncation(a, b), space);
    case Token::kSHR:
      return Integer::New(a >> Utils::Minimum<int64_t>(b, Mint::kBits), space);
    case Token::kUSHR:
      return Integer::New(
          (b >= kBitsPerInt64) ? 0 : static_cast<uint64_t>(a) >> b, space);
    default:
      UNIMPLEMENTED();
      return Integer::null();
  }
}

bool Smi::Equals(const Instance& other) const {
  if (other.IsNull() || !other.IsSmi()) {
    return false;
  }
  return (this->Value() == Smi::Cast(other).Value());
}

const char* Smi::ToCString() const {
  return OS::SCreate(Thread::Current()->zone(), "%" Pd "", Value());
}

ClassPtr Smi::Class() {
  return IsolateGroup::Current()->object_store()->smi_class();
}

void Mint::set_value(int64_t value) const {
  StoreNonPointer(&untag()->value_, value);
}

MintPtr Mint::New(int64_t val, Heap::Space space) {
  // Do not allocate a Mint if Smi would do.
  ASSERT(!Smi::IsValid(val));
  ASSERT(IsolateGroup::Current()->object_store()->mint_class() !=
         Class::null());
  const auto& result = Mint::Handle(Object::Allocate<Mint>(space));
  result.set_value(val);
  return result.ptr();
}

MintPtr Mint::NewCanonical(int64_t value) {
  Thread* thread = Thread::Current();
  Mint& mint = Mint::Handle(thread->zone(), Mint::New(value, Heap::kOld));
  mint ^= mint.Canonicalize(thread);
  return mint.ptr();
}

bool Mint::Equals(const Instance& other) const {
  if (this->ptr() == other.ptr()) {
    // Both handles point to the same raw instance.
    return true;
  }
  if (!other.IsMint() || other.IsNull()) {
    return false;
  }
  return Value() == Mint::Cast(other).Value();
}

const char* Mint::ToCString() const {
  return OS::SCreate(Thread::Current()->zone(), "%" Pd64 "", Value());
}

void Double::set_value(double value) const {
  StoreNonPointer(&untag()->value_, value);
}

bool Double::BitwiseEqualsToDouble(double value) const {
  intptr_t value_offset = Double::value_offset();
  void* this_addr = reinterpret_cast<void*>(
      reinterpret_cast<uword>(this->untag()) + value_offset);
  void* other_addr = reinterpret_cast<void*>(&value);
  return (memcmp(this_addr, other_addr, sizeof(value)) == 0);
}

bool Double::OperatorEquals(const Instance& other) const {
  if (this->IsNull() || other.IsNull()) {
    return (this->IsNull() && other.IsNull());
  }
  if (!other.IsDouble()) {
    return false;
  }
  return this->value() == Double::Cast(other).value();
}

bool Double::CanonicalizeEquals(const Instance& other) const {
  if (this->ptr() == other.ptr()) {
    return true;  // "===".
  }
  if (other.IsNull() || !other.IsDouble()) {
    return false;
  }
  return BitwiseEqualsToDouble(Double::Cast(other).value());
}

uint32_t Double::CanonicalizeHash() const {
  return Hash64To32(bit_cast<uint64_t>(value()));
}

DoublePtr Double::New(double d, Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->double_class() !=
         Class::null());
  const auto& result = Double::Handle(Object::Allocate<Double>(space));
  result.set_value(d);
  return result.ptr();
}

DoublePtr Double::New(const String& str, Heap::Space space) {
  double double_value;
  if (!CStringToDouble(str.ToCString(), str.Length(), &double_value)) {
    return Double::Handle().ptr();
  }
  return New(double_value, space);
}

DoublePtr Double::NewCanonical(double value) {
  Thread* thread = Thread::Current();
  Double& dbl = Double::Handle(thread->zone(), Double::New(value, Heap::kOld));
  dbl ^= dbl.Canonicalize(thread);
  return dbl.ptr();
}

DoublePtr Double::NewCanonical(const String& str) {
  double double_value;
  if (!CStringToDouble(str.ToCString(), str.Length(), &double_value)) {
    return Double::Handle().ptr();
  }
  return NewCanonical(double_value);
}

StringPtr Number::ToString(Heap::Space space) const {
  // Refactoring can avoid Zone::Alloc and strlen, but gains are insignificant.
  const char* cstr = ToCString();
  intptr_t len = strlen(cstr);
// Resulting string is ASCII ...
#ifdef DEBUG
  for (intptr_t i = 0; i < len; ++i) {
    ASSERT(static_cast<uint8_t>(cstr[i]) < 128);
  }
#endif  // DEBUG
  // ... which is a subset of Latin-1.
  return String::FromLatin1(reinterpret_cast<const uint8_t*>(cstr), len, space);
}

const char* Double::ToCString() const {
  if (isnan(value())) {
    return "NaN";
  }
  if (isinf(value())) {
    return value() < 0 ? "-Infinity" : "Infinity";
  }
  const int kBufferSize = 128;
  char* buffer = Thread::Current()->zone()->Alloc<char>(kBufferSize);
  buffer[kBufferSize - 1] = '\0';
  DoubleToCString(value(), buffer, kBufferSize);
  return buffer;
}

void StringHasher::Add(const String& str, intptr_t begin_index, intptr_t len) {
  ASSERT(begin_index >= 0);
  ASSERT(len >= 0);
  ASSERT((begin_index + len) <= str.Length());
  if (len == 0) {
    return;
  }
  if (str.IsOneByteString()) {
    NoSafepointScope no_safepoint;
    Add(OneByteString::CharAddr(str, begin_index), len);
  } else if (str.IsTwoByteString()) {
    NoSafepointScope no_safepoint;
    Add(TwoByteString::CharAddr(str, begin_index), len);
  } else {
    UNREACHABLE();
  }
}

uword String::Hash(const String& str, intptr_t begin_index, intptr_t len) {
  StringHasher hasher;
  hasher.Add(str, begin_index, len);
  return hasher.Finalize();
}

uword String::HashConcat(const String& str1, const String& str2) {
  StringHasher hasher;
  hasher.Add(str1, 0, str1.Length());
  hasher.Add(str2, 0, str2.Length());
  return hasher.Finalize();
}

uword String::Hash(StringPtr raw) {
  StringHasher hasher;
  uword length = Smi::Value(raw->untag()->length());
  if (raw->IsOneByteString()) {
    const uint8_t* data = static_cast<OneByteStringPtr>(raw)->untag()->data();
    return String::Hash(data, length);
  } else {
    const uint16_t* data = static_cast<TwoByteStringPtr>(raw)->untag()->data();
    return String::Hash(data, length);
  }
}

uword String::Hash(const char* characters, intptr_t len) {
  StringHasher hasher;
  hasher.Add(reinterpret_cast<const uint8_t*>(characters), len);
  return hasher.Finalize();
}

uword String::Hash(const uint8_t* characters, intptr_t len) {
  StringHasher hasher;
  hasher.Add(characters, len);
  return hasher.Finalize();
}

uword String::Hash(const uint16_t* characters, intptr_t len) {
  StringHasher hasher;
  hasher.Add(characters, len);
  return hasher.Finalize();
}

intptr_t String::CharSize() const {
  intptr_t class_id = ptr()->GetClassIdOfHeapObject();
  if (class_id == kOneByteStringCid) {
    return kOneByteChar;
  }
  ASSERT(class_id == kTwoByteStringCid);
  return kTwoByteChar;
}

bool String::Equals(const Instance& other) const {
  if (this->ptr() == other.ptr()) {
    // Both handles point to the same raw instance.
    return true;
  }

  if (!other.IsString()) {
    return false;
  }

  const String& other_string = String::Cast(other);
  return Equals(other_string);
}

bool String::Equals(const String& str,
                    intptr_t begin_index,
                    intptr_t len) const {
  ASSERT(begin_index >= 0);
  ASSERT((begin_index == 0) || (begin_index < str.Length()));
  ASSERT(len >= 0);
  ASSERT(len <= str.Length());
  if (len != this->Length()) {
    return false;  // Lengths don't match.
  }

  for (intptr_t i = 0; i < len; i++) {
    if (CharAt(i) != str.CharAt(begin_index + i)) {
      return false;
    }
  }

  return true;
}

bool String::Equals(const char* cstr) const {
  ASSERT(cstr != nullptr);
  CodePointIterator it(*this);
  intptr_t len = strlen(cstr);
  while (it.Next()) {
    if (*cstr == '\0') {
      // Lengths don't match.
      return false;
    }
    int32_t ch;
    intptr_t consumed =
        Utf8::Decode(reinterpret_cast<const uint8_t*>(cstr), len, &ch);
    if (consumed == 0 || it.Current() != ch) {
      return false;
    }
    cstr += consumed;
    len -= consumed;
  }
  return *cstr == '\0';
}

bool String::Equals(const uint8_t* latin1_array, intptr_t len) const {
  if (len != this->Length()) {
    // Lengths don't match.
    return false;
  }

  for (intptr_t i = 0; i < len; i++) {
    if (this->CharAt(i) != latin1_array[i]) {
      return false;
    }
  }
  return true;
}

bool String::Equals(const uint16_t* utf16_array, intptr_t len) const {
  if (len != this->Length()) {
    // Lengths don't match.
    return false;
  }

  for (intptr_t i = 0; i < len; i++) {
    if (this->CharAt(i) != LoadUnaligned(&utf16_array[i])) {
      return false;
    }
  }
  return true;
}

bool String::Equals(const int32_t* utf32_array, intptr_t len) const {
  if (len < 0) return false;
  intptr_t j = 0;
  for (intptr_t i = 0; i < len; ++i) {
    if (Utf::IsSupplementary(utf32_array[i])) {
      uint16_t encoded[2];
      Utf16::Encode(utf32_array[i], &encoded[0]);
      if (j + 1 >= Length()) return false;
      if (CharAt(j++) != encoded[0]) return false;
      if (CharAt(j++) != encoded[1]) return false;
    } else {
      if (j >= Length()) return false;
      if (CharAt(j++) != utf32_array[i]) return false;
    }
  }
  return j == Length();
}

bool String::EqualsConcat(const String& str1, const String& str2) const {
  return (Length() == str1.Length() + str2.Length()) &&
         str1.Equals(*this, 0, str1.Length()) &&
         str2.Equals(*this, str1.Length(), str2.Length());
}

intptr_t String::CompareTo(const String& other) const {
  const intptr_t this_len = this->Length();
  const intptr_t other_len = other.IsNull() ? 0 : other.Length();
  const intptr_t len = (this_len < other_len) ? this_len : other_len;
  for (intptr_t i = 0; i < len; i++) {
    uint16_t this_code_unit = this->CharAt(i);
    uint16_t other_code_unit = other.CharAt(i);
    if (this_code_unit < other_code_unit) {
      return -1;
    }
    if (this_code_unit > other_code_unit) {
      return 1;
    }
  }
  if (this_len < other_len) return -1;
  if (this_len > other_len) return 1;
  return 0;
}

bool String::StartsWith(StringPtr str, StringPtr prefix) {
  if (prefix == String::null()) return false;

  const intptr_t length = String::LengthOf(str);
  const intptr_t prefix_length = String::LengthOf(prefix);
  if (prefix_length > length) return false;

  for (intptr_t i = 0; i < prefix_length; i++) {
    if (String::CharAt(str, i) != String::CharAt(prefix, i)) {
      return false;
    }
  }
  return true;
}

bool String::EndsWith(const String& other) const {
  if (other.IsNull()) {
    return false;
  }
  const intptr_t len = this->Length();
  const intptr_t other_len = other.Length();
  const intptr_t offset = len - other_len;

  if ((other_len == 0) || (other_len > len)) {
    return false;
  }
  for (int i = offset; i < len; i++) {
    if (this->CharAt(i) != other.CharAt(i - offset)) {
      return false;
    }
  }
  return true;
}

InstancePtr String::CanonicalizeLocked(Thread* thread) const {
  if (IsCanonical()) {
    return this->ptr();
  }
  return Symbols::New(Thread::Current(), *this);
}

StringPtr String::New(const char* cstr, Heap::Space space) {
  ASSERT(cstr != nullptr);
  intptr_t array_len = strlen(cstr);
  const uint8_t* utf8_array = reinterpret_cast<const uint8_t*>(cstr);
  return String::FromUTF8(utf8_array, array_len, space);
}

StringPtr String::FromUTF8(const uint8_t* utf8_array,
                           intptr_t array_len,
                           Heap::Space space) {
  Utf8::Type type;
  intptr_t len = Utf8::CodeUnitCount(utf8_array, array_len, &type);
  if (type == Utf8::kLatin1) {
    const String& strobj = String::Handle(OneByteString::New(len, space));
    if (len > 0) {
      NoSafepointScope no_safepoint;
      if (!Utf8::DecodeToLatin1(utf8_array, array_len,
                                OneByteString::DataStart(strobj), len)) {
        Utf8::ReportInvalidByte(utf8_array, array_len, len);
        return String::null();
      }
    }
    return strobj.ptr();
  }
  ASSERT((type == Utf8::kBMP) || (type == Utf8::kSupplementary));
  const String& strobj = String::Handle(TwoByteString::New(len, space));
  NoSafepointScope no_safepoint;
  if (!Utf8::DecodeToUTF16(utf8_array, array_len,
                           TwoByteString::DataStart(strobj), len)) {
    Utf8::ReportInvalidByte(utf8_array, array_len, len);
    return String::null();
  }
  return strobj.ptr();
}

StringPtr String::FromLatin1(const uint8_t* latin1_array,
                             intptr_t array_len,
                             Heap::Space space) {
  return OneByteString::New(latin1_array, array_len, space);
}

StringPtr String::FromUTF16(const uint16_t* utf16_array,
                            intptr_t array_len,
                            Heap::Space space) {
  bool is_one_byte_string = true;
  for (intptr_t i = 0; i < array_len; ++i) {
    if (!Utf::IsLatin1(LoadUnaligned(&utf16_array[i]))) {
      is_one_byte_string = false;
      break;
    }
  }
  if (is_one_byte_string) {
    return OneByteString::New(utf16_array, array_len, space);
  }
  return TwoByteString::New(utf16_array, array_len, space);
}

StringPtr String::FromUTF32(const int32_t* utf32_array,
                            intptr_t array_len,
                            Heap::Space space) {
  bool is_one_byte_string = true;
  intptr_t utf16_len = array_len;
  for (intptr_t i = 0; i < array_len; ++i) {
    if (!Utf::IsLatin1(utf32_array[i])) {
      is_one_byte_string = false;
      if (Utf::IsSupplementary(utf32_array[i])) {
        utf16_len += 1;
      }
    }
  }
  if (is_one_byte_string) {
    return OneByteString::New(utf32_array, array_len, space);
  }
  return TwoByteString::New(utf16_len, utf32_array, array_len, space);
}

StringPtr String::New(const String& str, Heap::Space space) {
  // Currently this just creates a copy of the string in the correct space.
  // Once we have external string support, this will also create a heap copy of
  // the string if necessary. Some optimizations are possible, such as not
  // copying internal strings into the same space.
  intptr_t len = str.Length();
  String& result = String::Handle();
  intptr_t char_size = str.CharSize();
  if (char_size == kOneByteChar) {
    result = OneByteString::New(len, space);
  } else {
    ASSERT(char_size == kTwoByteChar);
    result = TwoByteString::New(len, space);
  }
  String::Copy(result, 0, str, 0, len);
  return result.ptr();
}

void String::Copy(const String& dst,
                  intptr_t dst_offset,
                  const uint8_t* characters,
                  intptr_t len) {
  ASSERT(dst_offset >= 0);
  ASSERT(len >= 0);
  ASSERT(len <= (dst.Length() - dst_offset));
  if (dst.IsOneByteString()) {
    NoSafepointScope no_safepoint;
    if (len > 0) {
      memmove(OneByteString::CharAddr(dst, dst_offset), characters, len);
    }
  } else if (dst.IsTwoByteString()) {
    for (intptr_t i = 0; i < len; ++i) {
      *TwoByteString::CharAddr(dst, i + dst_offset) = characters[i];
    }
  }
}

void String::Copy(const String& dst,
                  intptr_t dst_offset,
                  const uint16_t* utf16_array,
                  intptr_t array_len) {
  ASSERT(dst_offset >= 0);
  ASSERT(array_len >= 0);
  ASSERT(array_len <= (dst.Length() - dst_offset));
  if (dst.IsOneByteString()) {
    NoSafepointScope no_safepoint;
    for (intptr_t i = 0; i < array_len; ++i) {
      ASSERT(Utf::IsLatin1(LoadUnaligned(&utf16_array[i])));
      *OneByteString::CharAddr(dst, i + dst_offset) = utf16_array[i];
    }
  } else {
    ASSERT(dst.IsTwoByteString());
    NoSafepointScope no_safepoint;
    if (array_len > 0) {
      memmove(TwoByteString::CharAddr(dst, dst_offset), utf16_array,
              array_len * 2);
    }
  }
}

void String::Copy(const String& dst,
                  intptr_t dst_offset,
                  const String& src,
                  intptr_t src_offset,
                  intptr_t len) {
  ASSERT(dst_offset >= 0);
  ASSERT(src_offset >= 0);
  ASSERT(len >= 0);
  ASSERT(len <= (dst.Length() - dst_offset));
  ASSERT(len <= (src.Length() - src_offset));
  if (len > 0) {
    intptr_t char_size = src.CharSize();
    if (char_size == kOneByteChar) {
      ASSERT(src.IsOneByteString());
      NoSafepointScope no_safepoint;
      String::Copy(dst, dst_offset, OneByteString::CharAddr(src, src_offset),
                   len);
    } else {
      ASSERT(char_size == kTwoByteChar);
      ASSERT(src.IsTwoByteString());
      NoSafepointScope no_safepoint;
      String::Copy(dst, dst_offset, TwoByteString::CharAddr(src, src_offset),
                   len);
    }
  }
}

StringPtr String::EscapeSpecialCharacters(const String& str) {
  if (str.IsOneByteString()) {
    return OneByteString::EscapeSpecialCharacters(str);
  }
  ASSERT(str.IsTwoByteString());
  return TwoByteString::EscapeSpecialCharacters(str);
}

static bool IsPercent(int32_t c) {
  return c == '%';
}

static bool IsHexCharacter(int32_t c) {
  if (c >= '0' && c <= '9') {
    return true;
  }
  if (c >= 'A' && c <= 'F') {
    return true;
  }
  return false;
}

static bool IsURISafeCharacter(int32_t c) {
  if ((c >= '0') && (c <= '9')) {
    return true;
  }
  if ((c >= 'a') && (c <= 'z')) {
    return true;
  }
  if ((c >= 'A') && (c <= 'Z')) {
    return true;
  }
  return (c == '-') || (c == '_') || (c == '.') || (c == '~');
}

static int32_t GetHexCharacter(int32_t c) {
  ASSERT(c >= 0);
  ASSERT(c < 16);
  const char* hex = "0123456789ABCDEF";
  return hex[c];
}

static int32_t GetHexValue(int32_t c) {
  if (c >= '0' && c <= '9') {
    return c - '0';
  }
  if (c >= 'A' && c <= 'F') {
    return c - 'A' + 10;
  }
  UNREACHABLE();
  return 0;
}

static int32_t MergeHexCharacters(int32_t c1, int32_t c2) {
  return GetHexValue(c1) << 4 | GetHexValue(c2);
}

const char* String::EncodeIRI(const String& str) {
  const intptr_t len = Utf8::Length(str);
  Zone* zone = Thread::Current()->zone();
  uint8_t* utf8 = zone->Alloc<uint8_t>(len);
  str.ToUTF8(utf8, len);
  intptr_t num_escapes = 0;
  for (int i = 0; i < len; ++i) {
    uint8_t byte = utf8[i];
    if (!IsURISafeCharacter(byte)) {
      num_escapes += 2;
    }
  }
  intptr_t cstr_len = len + num_escapes + 1;
  char* cstr = zone->Alloc<char>(cstr_len);
  intptr_t index = 0;
  for (int i = 0; i < len; ++i) {
    uint8_t byte = utf8[i];
    if (!IsURISafeCharacter(byte)) {
      cstr[index++] = '%';
      cstr[index++] = GetHexCharacter(byte >> 4);
      cstr[index++] = GetHexCharacter(byte & 0xF);
    } else {
      ASSERT(byte <= 127);
      cstr[index++] = byte;
    }
  }
  cstr[index] = '\0';
  return cstr;
}

StringPtr String::DecodeIRI(const String& str) {
  CodePointIterator cpi(str);
  intptr_t num_escapes = 0;
  intptr_t len = str.Length();
  {
    CodePointIterator cpi(str);
    while (cpi.Next()) {
      int32_t code_point = cpi.Current();
      if (IsPercent(code_point)) {
        // Verify that the two characters following the % are hex digits.
        if (!cpi.Next()) {
          return String::null();
        }
        int32_t code_point = cpi.Current();
        if (!IsHexCharacter(code_point)) {
          return String::null();
        }
        if (!cpi.Next()) {
          return String::null();
        }
        code_point = cpi.Current();
        if (!IsHexCharacter(code_point)) {
          return String::null();
        }
        num_escapes += 2;
      }
    }
  }
  intptr_t utf8_len = len - num_escapes;
  ASSERT(utf8_len >= 0);
  Zone* zone = Thread::Current()->zone();
  uint8_t* utf8 = zone->Alloc<uint8_t>(utf8_len);
  {
    intptr_t index = 0;
    CodePointIterator cpi(str);
    while (cpi.Next()) {
      ASSERT(index < utf8_len);
      int32_t code_point = cpi.Current();
      if (IsPercent(code_point)) {
        cpi.Next();
        int32_t ch1 = cpi.Current();
        cpi.Next();
        int32_t ch2 = cpi.Current();
        int32_t merged = MergeHexCharacters(ch1, ch2);
        ASSERT(merged >= 0 && merged < 256);
        utf8[index] = static_cast<uint8_t>(merged);
      } else {
        ASSERT(code_point >= 0 && code_point < 256);
        utf8[index] = static_cast<uint8_t>(code_point);
      }
      index++;
    }
  }
  return FromUTF8(utf8, utf8_len);
}

StringPtr String::NewFormatted(const char* format, ...) {
  va_list args;
  va_start(args, format);
  StringPtr result = NewFormattedV(format, args);
  NoSafepointScope no_safepoint;
  va_end(args);
  return result;
}

StringPtr String::NewFormatted(Heap::Space space, const char* format, ...) {
  va_list args;
  va_start(args, format);
  StringPtr result = NewFormattedV(format, args, space);
  NoSafepointScope no_safepoint;
  va_end(args);
  return result;
}

StringPtr String::NewFormattedV(const char* format,
                                va_list args,
                                Heap::Space space) {
  va_list args_copy;
  va_copy(args_copy, args);
  intptr_t len = Utils::VSNPrint(nullptr, 0, format, args_copy);
  va_end(args_copy);

  Zone* zone = Thread::Current()->zone();
  char* buffer = zone->Alloc<char>(len + 1);
  Utils::VSNPrint(buffer, (len + 1), format, args);

  return String::New(buffer, space);
}

StringPtr String::Concat(const String& str1,
                         const String& str2,
                         Heap::Space space) {
  ASSERT(!str1.IsNull() && !str2.IsNull());
  intptr_t char_size = Utils::Maximum(str1.CharSize(), str2.CharSize());
  if (char_size == kTwoByteChar) {
    return TwoByteString::Concat(str1, str2, space);
  }
  return OneByteString::Concat(str1, str2, space);
}

StringPtr String::ConcatAll(const Array& strings, Heap::Space space) {
  return ConcatAllRange(strings, 0, strings.Length(), space);
}

StringPtr String::ConcatAllRange(const Array& strings,
                                 intptr_t start,
                                 intptr_t end,
                                 Heap::Space space) {
  ASSERT(!strings.IsNull());
  ASSERT(start >= 0);
  ASSERT(end <= strings.Length());
  intptr_t result_len = 0;
  String& str = String::Handle();
  intptr_t char_size = kOneByteChar;
  // Compute 'char_size' and 'result_len'.
  for (intptr_t i = start; i < end; i++) {
    str ^= strings.At(i);
    const intptr_t str_len = str.Length();
    if ((kMaxElements - result_len) < str_len) {
      Exceptions::ThrowOOM();
      UNREACHABLE();
    }
    result_len += str_len;
    char_size = Utils::Maximum(char_size, str.CharSize());
  }
  if (char_size == kOneByteChar) {
    return OneByteString::ConcatAll(strings, start, end, result_len, space);
  }
  ASSERT(char_size == kTwoByteChar);
  return TwoByteString::ConcatAll(strings, start, end, result_len, space);
}

StringPtr String::SubString(const String& str,
                            intptr_t begin_index,
                            Heap::Space space) {
  ASSERT(!str.IsNull());
  if (begin_index >= str.Length()) {
    return String::null();
  }
  return String::SubString(str, begin_index, (str.Length() - begin_index),
                           space);
}

StringPtr String::SubString(Thread* thread,
                            const String& str,
                            intptr_t begin_index,
                            intptr_t length,
                            Heap::Space space) {
  ASSERT(!str.IsNull());
  ASSERT(begin_index >= 0);
  ASSERT(length >= 0);
  if (begin_index <= str.Length() && length == 0) {
    return Symbols::Empty().ptr();
  }
  if (begin_index > str.Length()) {
    return String::null();
  }
  bool is_one_byte_string = true;
  intptr_t char_size = str.CharSize();
  if (char_size == kTwoByteChar) {
    for (intptr_t i = begin_index; i < begin_index + length; ++i) {
      if (!Utf::IsLatin1(str.CharAt(i))) {
        is_one_byte_string = false;
        break;
      }
    }
  }
  REUSABLE_STRING_HANDLESCOPE(thread);
  String& result = thread->StringHandle();
  if (is_one_byte_string) {
    result = OneByteString::New(length, space);
  } else {
    result = TwoByteString::New(length, space);
  }
  String::Copy(result, 0, str, begin_index, length);
  return result.ptr();
}

const char* String::ToCString() const {
  if (IsNull()) {
    return "String: null";
  }
  const intptr_t len = Utf8::Length(*this);
  Zone* zone = Thread::Current()->zone();
  uint8_t* result = zone->Alloc<uint8_t>(len + 1);
  ToUTF8(result, len);
  result[len] = 0;
  return reinterpret_cast<const char*>(result);
}

char* String::ToMallocCString() const {
  const intptr_t len = Utf8::Length(*this);
  uint8_t* result = reinterpret_cast<uint8_t*>(malloc(len + 1));
  ToUTF8(result, len);
  result[len] = 0;
  return reinterpret_cast<char*>(result);
}

void String::ToUTF8(uint8_t* utf8_array, intptr_t array_len) const {
  ASSERT(array_len >= Utf8::Length(*this));
  Utf8::Encode(*this, reinterpret_cast<char*>(utf8_array), array_len);
}

const char* String::ToCString(Thread* thread, StringPtr ptr) {
  if (ptr == nullptr) return nullptr;
  REUSABLE_STRING_HANDLESCOPE(thread);
  String& str = reused_string_handle.Handle();
  str = ptr;
  return str.ToCString();
}

static FinalizablePersistentHandle* AddFinalizer(const Object& referent,
                                                 void* peer,
                                                 Dart_HandleFinalizer callback,
                                                 intptr_t external_size) {
  ASSERT(callback != nullptr);
  FinalizablePersistentHandle* finalizable_ref =
      FinalizablePersistentHandle::New(IsolateGroup::Current(), referent, peer,
                                       callback, external_size,
                                       /*auto_delete=*/true);
  ASSERT(finalizable_ref != nullptr);
  return finalizable_ref;
}

StringPtr String::Transform(int32_t (*mapping)(int32_t ch),
                            const String& str,
                            Heap::Space space) {
  ASSERT(!str.IsNull());
  bool has_mapping = false;
  int32_t dst_max = 0;
  CodePointIterator it(str);
  while (it.Next()) {
    int32_t src = it.Current();
    int32_t dst = mapping(src);
    if (src != dst) {
      has_mapping = true;
    }
    dst_max = Utils::Maximum(dst_max, dst);
  }
  if (!has_mapping) {
    return str.ptr();
  }
  if (Utf::IsLatin1(dst_max)) {
    return OneByteString::Transform(mapping, str, space);
  }
  ASSERT(Utf::IsBmp(dst_max) || Utf::IsSupplementary(dst_max));
  return TwoByteString::Transform(mapping, str, space);
}

StringPtr String::ToUpperCase(const String& str, Heap::Space space) {
  // TODO(cshapiro): create a fast-path for OneByteString instances.
  return Transform(CaseMapping::ToUpper, str, space);
}

StringPtr String::ToLowerCase(const String& str, Heap::Space space) {
  // TODO(cshapiro): create a fast-path for OneByteString instances.
  return Transform(CaseMapping::ToLower, str, space);
}

bool String::ParseDouble(const String& str,
                         intptr_t start,
                         intptr_t end,
                         double* result) {
  ASSERT(0 <= start);
  ASSERT(start <= end);
  ASSERT(end <= str.Length());
  intptr_t length = end - start;
  NoSafepointScope no_safepoint;
  const uint8_t* startChar;
  if (str.IsOneByteString()) {
    startChar = OneByteString::CharAddr(str, start);
  } else {
    uint8_t* chars = Thread::Current()->zone()->Alloc<uint8_t>(length);
    for (intptr_t i = 0; i < length; i++) {
      int32_t ch = str.CharAt(start + i);
      if (ch < 128) {
        chars[i] = ch;
      } else {
        return false;  // Not ASCII, so definitely not valid double numeral.
      }
    }
    startChar = chars;
  }
  return CStringToDouble(reinterpret_cast<const char*>(startChar), length,
                         result);
}

// Check to see if 'str1' matches 'str2' as is or
// once the private key separator is stripped from str2.
//
// Things are made more complicated by the fact that constructors are
// added *after* the private suffix, so "foo@123.named" should match
// "foo.named".
//
// Also, the private suffix can occur more than once in the name, as in:
//
//    _ReceivePortImpl@6be832b._internal@6be832b
//
template <typename T1, typename T2>
static bool EqualsIgnoringPrivateKey(const String& str1, const String& str2) {
  intptr_t len = str1.Length();
  intptr_t str2_len = str2.Length();
  if (len == str2_len) {
    for (intptr_t i = 0; i < len; i++) {
      if (T1::CharAt(str1, i) != T2::CharAt(str2, i)) {
        return false;
      }
    }
    return true;
  }
  if (len < str2_len) {
    return false;  // No way they can match.
  }
  intptr_t pos = 0;
  intptr_t str2_pos = 0;
  while (pos < len) {
    int32_t ch = T1::CharAt(str1, pos);
    pos++;

    if ((str2_pos < str2_len) && (ch == T2::CharAt(str2, str2_pos))) {
      str2_pos++;
      continue;
    }

    if (ch == Library::kPrivateKeySeparator) {
      // Consume a private key separator if str1 has it but str2 does not.
      while ((pos < len) && (T1::CharAt(str1, pos) != '.') &&
             (T1::CharAt(str1, pos) != '&')) {
        pos++;
      }
      // Resume matching characters.
      continue;
    }

    return false;
  }

  // We have reached the end of mangled_name string.
  ASSERT(pos == len);
  return (str2_pos == str2_len);
}

#define EQUALS_IGNORING_PRIVATE_KEY(class_id, type, str1, str2)                \
  switch (class_id) {                                                          \
    case kOneByteStringCid:                                                    \
      return dart::EqualsIgnoringPrivateKey<type, OneByteString>(str1, str2);  \
    case kTwoByteStringCid:                                                    \
      return dart::EqualsIgnoringPrivateKey<type, TwoByteString>(str1, str2);  \
  }                                                                            \
  UNREACHABLE();

bool String::EqualsIgnoringPrivateKey(const String& str1, const String& str2) {
  if (str1.ptr() == str2.ptr()) {
    return true;  // Both handles point to the same raw instance.
  }
  NoSafepointScope no_safepoint;
  intptr_t str1_class_id = str1.ptr()->GetClassIdOfHeapObject();
  intptr_t str2_class_id = str2.ptr()->GetClassIdOfHeapObject();
  switch (str1_class_id) {
    case kOneByteStringCid:
      EQUALS_IGNORING_PRIVATE_KEY(str2_class_id, OneByteString, str1, str2);
      break;
    case kTwoByteStringCid:
      EQUALS_IGNORING_PRIVATE_KEY(str2_class_id, TwoByteString, str1, str2);
      break;
  }
  UNREACHABLE();
  return false;
}

bool String::CodePointIterator::Next() {
  ASSERT(index_ >= -1);
  intptr_t length = Utf16::Length(ch_);
  if (index_ < (end_ - length)) {
    index_ += length;
    ch_ = str_.CharAt(index_);
    if (Utf16::IsLeadSurrogate(ch_) && (index_ < (end_ - 1))) {
      int32_t ch2 = str_.CharAt(index_ + 1);
      if (Utf16::IsTrailSurrogate(ch2)) {
        ch_ = Utf16::Decode(ch_, ch2);
      }
    }
    return true;
  }
  index_ = end_;
  return false;
}

OneByteStringPtr OneByteString::EscapeSpecialCharacters(const String& str) {
  intptr_t len = str.Length();
  if (len > 0) {
    intptr_t num_escapes = 0;
    for (intptr_t i = 0; i < len; i++) {
      num_escapes += EscapeOverhead(CharAt(str, i));
    }
    const String& dststr =
        String::Handle(OneByteString::New(len + num_escapes, Heap::kNew));
    intptr_t index = 0;
    for (intptr_t i = 0; i < len; i++) {
      uint8_t ch = CharAt(str, i);
      if (IsSpecialCharacter(ch)) {
        SetCharAt(dststr, index, '\\');
        SetCharAt(dststr, index + 1, SpecialCharacter(ch));
        index += 2;
      } else if (IsAsciiNonprintable(ch)) {
        SetCharAt(dststr, index, '\\');
        SetCharAt(dststr, index + 1, 'x');
        SetCharAt(dststr, index + 2, GetHexCharacter(ch >> 4));
        SetCharAt(dststr, index + 3, GetHexCharacter(ch & 0xF));
        index += 4;
      } else {
        SetCharAt(dststr, index, ch);
        index += 1;
      }
    }
    return OneByteString::raw(dststr);
  }
  return OneByteString::raw(Symbols::Empty());
}

OneByteStringPtr OneByteString::New(intptr_t len, Heap::Space space) {
  if (len < 0 || len > kMaxElements) {
    // This should be caught before we reach here.
    FATAL("Fatal error in OneByteString::New: invalid len %" Pd "\n", len);
  }
  auto result = Object::Allocate<OneByteString>(space, len);
  NoSafepointScope no_safepoint;
  result->untag()->set_length(Smi::New(len));
#if !defined(HASH_IN_OBJECT_HEADER)
  result->untag()->set_hash(Smi::New(0));
#endif
  intptr_t size = OneByteString::UnroundedSize(result);
  ASSERT(size <= result->untag()->HeapSize());
  memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(result) + size), 0,
         result->untag()->HeapSize() - size);
  return result;
}

OneByteStringPtr OneByteString::New(const uint8_t* characters,
                                    intptr_t len,
                                    Heap::Space space) {
  const String& result = String::Handle(OneByteString::New(len, space));
  if (len > 0) {
    NoSafepointScope no_safepoint;
    memmove(DataStart(result), characters, len);
  }
  return OneByteString::raw(result);
}

OneByteStringPtr OneByteString::New(const uint16_t* characters,
                                    intptr_t len,
                                    Heap::Space space) {
  const String& result = String::Handle(OneByteString::New(len, space));
  NoSafepointScope no_safepoint;
  for (intptr_t i = 0; i < len; ++i) {
    ASSERT(Utf::IsLatin1(characters[i]));
    *CharAddr(result, i) = characters[i];
  }
  return OneByteString::raw(result);
}

OneByteStringPtr OneByteString::New(const int32_t* characters,
                                    intptr_t len,
                                    Heap::Space space) {
  const String& result = String::Handle(OneByteString::New(len, space));
  NoSafepointScope no_safepoint;
  for (intptr_t i = 0; i < len; ++i) {
    ASSERT(Utf::IsLatin1(characters[i]));
    *CharAddr(result, i) = characters[i];
  }
  return OneByteString::raw(result);
}

OneByteStringPtr OneByteString::New(const String& str, Heap::Space space) {
  intptr_t len = str.Length();
  const String& result = String::Handle(OneByteString::New(len, space));
  String::Copy(result, 0, str, 0, len);
  return OneByteString::raw(result);
}

OneByteStringPtr OneByteString::New(const String& other_one_byte_string,
                                    intptr_t other_start_index,
                                    intptr_t other_len,
                                    Heap::Space space) {
  const String& result = String::Handle(OneByteString::New(other_len, space));
  ASSERT(other_one_byte_string.IsOneByteString());
  if (other_len > 0) {
    NoSafepointScope no_safepoint;
    memmove(OneByteString::DataStart(result),
            OneByteString::CharAddr(other_one_byte_string, other_start_index),
            other_len);
  }
  return OneByteString::raw(result);
}

OneByteStringPtr OneByteString::New(const TypedDataBase& other_typed_data,
                                    intptr_t other_start_index,
                                    intptr_t other_len,
                                    Heap::Space space) {
  const String& result = String::Handle(OneByteString::New(other_len, space));
  ASSERT(other_typed_data.ElementSizeInBytes() == 1);
  if (other_len > 0) {
    NoSafepointScope no_safepoint;
    memmove(OneByteString::DataStart(result),
            other_typed_data.DataAddr(other_start_index), other_len);
  }
  return OneByteString::raw(result);
}

OneByteStringPtr OneByteString::Concat(const String& str1,
                                       const String& str2,
                                       Heap::Space space) {
  intptr_t len1 = str1.Length();
  intptr_t len2 = str2.Length();
  intptr_t len = len1 + len2;
  const String& result = String::Handle(OneByteString::New(len, space));
  String::Copy(result, 0, str1, 0, len1);
  String::Copy(result, len1, str2, 0, len2);
  return OneByteString::raw(result);
}

OneByteStringPtr OneByteString::ConcatAll(const Array& strings,
                                          intptr_t start,
                                          intptr_t end,
                                          intptr_t len,
                                          Heap::Space space) {
  ASSERT(!strings.IsNull());
  ASSERT(start >= 0);
  ASSERT(end <= strings.Length());
  const String& result = String::Handle(OneByteString::New(len, space));
  String& str = String::Handle();
  intptr_t pos = 0;
  for (intptr_t i = start; i < end; i++) {
    str ^= strings.At(i);
    const intptr_t str_len = str.Length();
    String::Copy(result, pos, str, 0, str_len);
    ASSERT((kMaxElements - pos) >= str_len);
    pos += str_len;
  }
  return OneByteString::raw(result);
}

OneByteStringPtr OneByteString::Transform(int32_t (*mapping)(int32_t ch),
                                          const String& str,
                                          Heap::Space space) {
  ASSERT(!str.IsNull());
  intptr_t len = str.Length();
  const String& result = String::Handle(OneByteString::New(len, space));
  NoSafepointScope no_safepoint;
  for (intptr_t i = 0; i < len; ++i) {
    int32_t ch = mapping(str.CharAt(i));
    ASSERT(Utf::IsLatin1(ch));
    *CharAddr(result, i) = ch;
  }
  return OneByteString::raw(result);
}

OneByteStringPtr OneByteString::SubStringUnchecked(const String& str,
                                                   intptr_t begin_index,
                                                   intptr_t length,
                                                   Heap::Space space) {
  ASSERT(!str.IsNull() && str.IsOneByteString());
  ASSERT(begin_index >= 0);
  ASSERT(length >= 0);
  if (begin_index <= str.Length() && length == 0) {
    return OneByteString::raw(Symbols::Empty());
  }
  ASSERT(begin_index < str.Length());
  OneByteStringPtr result = OneByteString::New(length, space);
  NoSafepointScope no_safepoint;
  if (length > 0) {
    uint8_t* dest = &result->untag()->data()[0];
    const uint8_t* src = &untag(str)->data()[begin_index];
    memmove(dest, src, length);
  }
  return result;
}

TwoByteStringPtr TwoByteString::EscapeSpecialCharacters(const String& str) {
  intptr_t len = str.Length();
  if (len > 0) {
    intptr_t num_escapes = 0;
    for (intptr_t i = 0; i < len; i++) {
      num_escapes += EscapeOverhead(CharAt(str, i));
    }
    const String& dststr =
        String::Handle(TwoByteString::New(len + num_escapes, Heap::kNew));
    intptr_t index = 0;
    for (intptr_t i = 0; i < len; i++) {
      uint16_t ch = CharAt(str, i);
      if (IsSpecialCharacter(ch)) {
        SetCharAt(dststr, index, '\\');
        SetCharAt(dststr, index + 1, SpecialCharacter(ch));
        index += 2;
      } else if (IsAsciiNonprintable(ch)) {
        SetCharAt(dststr, index, '\\');
        SetCharAt(dststr, index + 1, 'x');
        SetCharAt(dststr, index + 2, GetHexCharacter(ch >> 4));
        SetCharAt(dststr, index + 3, GetHexCharacter(ch & 0xF));
        index += 4;
      } else {
        SetCharAt(dststr, index, ch);
        index += 1;
      }
    }
    return TwoByteString::raw(dststr);
  }
  return TwoByteString::New(0, Heap::kNew);
}

TwoByteStringPtr TwoByteString::New(intptr_t len, Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->two_byte_string_class() !=
         nullptr);
  if (len < 0 || len > kMaxElements) {
    // This should be caught before we reach here.
    FATAL("Fatal error in TwoByteString::New: invalid len %" Pd "\n", len);
  }
  auto s = Object::Allocate<TwoByteString>(space, len);
  NoSafepointScope no_safepoint;
  s->untag()->set_length(Smi::New(len));
#if !defined(HASH_IN_OBJECT_HEADER)
  s->untag()->set_hash(Smi::New(0));
#endif
  intptr_t size = TwoByteString::UnroundedSize(s);
  ASSERT(size <= s->untag()->HeapSize());
  memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(s) + size), 0,
         s->untag()->HeapSize() - size);
  return s;
}

TwoByteStringPtr TwoByteString::New(const uint16_t* utf16_array,
                                    intptr_t array_len,
                                    Heap::Space space) {
  ASSERT(array_len > 0);
  const String& result = String::Handle(TwoByteString::New(array_len, space));
  {
    NoSafepointScope no_safepoint;
    memmove(reinterpret_cast<void*>(DataStart(result)),
            reinterpret_cast<const void*>(utf16_array), (array_len * 2));
  }
  return TwoByteString::raw(result);
}

TwoByteStringPtr TwoByteString::New(intptr_t utf16_len,
                                    const int32_t* utf32_array,
                                    intptr_t array_len,
                                    Heap::Space space) {
  ASSERT((array_len > 0) && (utf16_len >= array_len));
  const String& result = String::Handle(TwoByteString::New(utf16_len, space));
  {
    NoSafepointScope no_safepoint;
    intptr_t j = 0;
    for (intptr_t i = 0; i < array_len; ++i) {
      if (Utf::IsSupplementary(utf32_array[i])) {
        ASSERT(j < (utf16_len - 1));
        Utf16::Encode(utf32_array[i], CharAddr(result, j));
        j += 2;
      } else {
        ASSERT(j < utf16_len);
        *CharAddr(result, j) = utf32_array[i];
        j += 1;
      }
    }
  }
  return TwoByteString::raw(result);
}

TwoByteStringPtr TwoByteString::New(const String& str, Heap::Space space) {
  intptr_t len = str.Length();
  const String& result = String::Handle(TwoByteString::New(len, space));
  String::Copy(result, 0, str, 0, len);
  return TwoByteString::raw(result);
}

TwoByteStringPtr TwoByteString::New(const TypedDataBase& other_typed_data,
                                    intptr_t other_start_index,
                                    intptr_t other_len,
                                    Heap::Space space) {
  const String& result = String::Handle(TwoByteString::New(other_len, space));
  if (other_len > 0) {
    NoSafepointScope no_safepoint;
    memmove(TwoByteString::DataStart(result),
            other_typed_data.DataAddr(other_start_index),
            other_len * sizeof(uint16_t));
  }
  return TwoByteString::raw(result);
}

TwoByteStringPtr TwoByteString::Concat(const String& str1,
                                       const String& str2,
                                       Heap::Space space) {
  intptr_t len1 = str1.Length();
  intptr_t len2 = str2.Length();
  intptr_t len = len1 + len2;
  const String& result = String::Handle(TwoByteString::New(len, space));
  String::Copy(result, 0, str1, 0, len1);
  String::Copy(result, len1, str2, 0, len2);
  return TwoByteString::raw(result);
}

TwoByteStringPtr TwoByteString::ConcatAll(const Array& strings,
                                          intptr_t start,
                                          intptr_t end,
                                          intptr_t len,
                                          Heap::Space space) {
  ASSERT(!strings.IsNull());
  ASSERT(start >= 0);
  ASSERT(end <= strings.Length());
  const String& result = String::Handle(TwoByteString::New(len, space));
  String& str = String::Handle();
  intptr_t pos = 0;
  for (intptr_t i = start; i < end; i++) {
    str ^= strings.At(i);
    const intptr_t str_len = str.Length();
    String::Copy(result, pos, str, 0, str_len);
    ASSERT((kMaxElements - pos) >= str_len);
    pos += str_len;
  }
  return TwoByteString::raw(result);
}

TwoByteStringPtr TwoByteString::Transform(int32_t (*mapping)(int32_t ch),
                                          const String& str,
                                          Heap::Space space) {
  ASSERT(!str.IsNull());
  intptr_t len = str.Length();
  const String& result = String::Handle(TwoByteString::New(len, space));
  String::CodePointIterator it(str);
  intptr_t i = 0;
  NoSafepointScope no_safepoint;
  while (it.Next()) {
    int32_t src = it.Current();
    int32_t dst = mapping(src);
    ASSERT(dst >= 0 && dst <= 0x10FFFF);
    intptr_t len = Utf16::Length(dst);
    if (len == 1) {
      *CharAddr(result, i) = dst;
    } else {
      ASSERT(len == 2);
      Utf16::Encode(dst, CharAddr(result, i));
    }
    i += len;
  }
  return TwoByteString::raw(result);
}

const char* Bool::ToCString() const {
  return value() ? "true" : "false";
}

bool Array::CanonicalizeEquals(const Instance& other) const {
  if (this->ptr() == other.ptr()) {
    // Both handles point to the same raw instance.
    return true;
  }

  // An Array may be compared to an ImmutableArray.
  if (!other.IsArray() || other.IsNull()) {
    return false;
  }

  // First check if both arrays have the same length and elements.
  const Array& other_arr = Array::Cast(other);

  intptr_t len = this->Length();
  if (len != other_arr.Length()) {
    return false;
  }

  for (intptr_t i = 0; i < len; i++) {
    if (this->At(i) != other_arr.At(i)) {
      return false;
    }
  }

  // Now check if both arrays have the same type arguments.
  if (GetTypeArguments() == other.GetTypeArguments()) {
    return true;
  }
  const TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments());
  const TypeArguments& other_type_args =
      TypeArguments::Handle(other.GetTypeArguments());
  if (!type_args.Equals(other_type_args)) {
    return false;
  }
  return true;
}

uint32_t Array::CanonicalizeHash() const {
  intptr_t len = Length();
  if (len == 0) {
    return 1;
  }
  Thread* thread = Thread::Current();
  uint32_t hash = thread->heap()->GetCanonicalHash(ptr());
  if (hash != 0) {
    return hash;
  }
  hash = len;
  Instance& member = Instance::Handle(GetTypeArguments());
  hash = CombineHashes(hash, member.CanonicalizeHash());
  for (intptr_t i = 0; i < len; i++) {
    member ^= At(i);
    hash = CombineHashes(hash, member.CanonicalizeHash());
  }
  hash = FinalizeHash(hash, kHashBits);
  thread->heap()->SetCanonicalHash(ptr(), hash);
  return hash;
}

ArrayPtr Array::New(intptr_t len,
                    const AbstractType& element_type,
                    Heap::Space space) {
  const Array& result = Array::Handle(Array::New(len, space));
  if (!element_type.IsDynamicType()) {
    TypeArguments& type_args = TypeArguments::Handle(TypeArguments::New(1));
    type_args.SetTypeAt(0, element_type);
    type_args = type_args.Canonicalize(Thread::Current());
    result.SetTypeArguments(type_args);
  }
  return result.ptr();
}

ArrayPtr Array::NewUninitialized(intptr_t class_id,
                                 intptr_t len,
                                 Heap::Space space) {
  if (!IsValidLength(len)) {
    // This should be caught before we reach here.
    FATAL("Fatal error in Array::New: invalid len %" Pd "\n", len);
  }
  auto raw = Object::AllocateVariant<Array>(class_id, space, len);
  NoSafepointScope no_safepoint;
  raw->untag()->set_length(Smi::New(len));
  if (UseCardMarkingForAllocation(len)) {
    ASSERT(raw->IsOldObject());
    raw->untag()->SetCardRememberedBitUnsynchronized();
    Page::Of(raw)->AllocateCardTable();
  }
  return raw;
}

ArrayPtr Array::New(intptr_t class_id, intptr_t len, Heap::Space space) {
  if (!UseCardMarkingForAllocation(len)) {
    return NewUninitialized(class_id, len, space);
  }

  Thread* thread = Thread::Current();
  Array& result =
      Array::Handle(thread->zone(), NewUninitialized(class_id, len, space));
  result.SetTypeArguments(Object::null_type_arguments());
  for (intptr_t i = 0; i < len; i++) {
    result.SetAt(i, Object::null_object(), thread);
    if (((i + 1) % kSlotsPerInterruptCheck) == 0) {
      thread->CheckForSafepoint();
    }
  }
  return result.ptr();
}

ArrayPtr Array::Slice(intptr_t start,
                      intptr_t count,
                      bool with_type_argument) const {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const Array& dest = Array::Handle(zone, Array::NewUninitialized(count));
  if (with_type_argument) {
    dest.SetTypeArguments(TypeArguments::Handle(zone, GetTypeArguments()));
  } else {
    dest.SetTypeArguments(Object::null_type_arguments());
  }
  if (!UseCardMarkingForAllocation(count)) {
    NoSafepointScope no_safepoint(thread);
    for (int i = 0; i < count; i++) {
      dest.untag()->set_element(i, untag()->element(i + start), thread);
    }
  } else {
    for (int i = 0; i < count; i++) {
      dest.untag()->set_element(i, untag()->element(i + start), thread);
      if (((i + 1) % kSlotsPerInterruptCheck) == 0) {
        thread->CheckForSafepoint();
      }
    }
  }
  return dest.ptr();
}

void Array::MakeImmutable() const {
  if (IsImmutable()) return;
  ASSERT(!IsCanonical());
  untag()->SetClassId(kImmutableArrayCid);
}

const char* Array::ToCString() const {
  if (IsNull()) {
    return IsImmutable() ? "_ImmutableList nullptr" : "_List nullptr";
  }
  Zone* zone = Thread::Current()->zone();
  const char* format =
      IsImmutable() ? "_ImmutableList len:%" Pd : "_List len:%" Pd;
  return zone->PrintToString(format, Length());
}

ArrayPtr Array::Grow(const Array& source,
                     intptr_t new_length,
                     Heap::Space space) {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const Array& result =
      Array::Handle(zone, Array::NewUninitialized(new_length, space));
  intptr_t old_length = 0;
  if (!source.IsNull()) {
    old_length = source.Length();
    result.SetTypeArguments(
        TypeArguments::Handle(zone, source.GetTypeArguments()));
  } else {
    result.SetTypeArguments(Object::null_type_arguments());
  }
  ASSERT(new_length > old_length);  // Unnecessary copying of array.
  if (!UseCardMarkingForAllocation(new_length)) {
    NoSafepointScope no_safepoint(thread);
    for (intptr_t i = 0; i < old_length; i++) {
      result.untag()->set_element(i, source.untag()->element(i), thread);
    }
    for (intptr_t i = old_length; i < new_length; i++) {
      ASSERT(result.untag()->element(i) == Object::null());
    }
  } else {
    for (intptr_t i = 0; i < old_length; i++) {
      result.untag()->set_element(i, source.untag()->element(i), thread);
      if (((i + 1) % kSlotsPerInterruptCheck) == 0) {
        thread->CheckForSafepoint();
      }
    }
    for (intptr_t i = old_length; i < new_length; i++) {
      result.untag()->set_element(i, Object::null(), thread);
      if (((i + 1) % kSlotsPerInterruptCheck) == 0) {
        thread->CheckForSafepoint();
      }
    }
  }
  return result.ptr();
}

void Array::Truncate(intptr_t new_len) const {
  if (IsNull()) {
    return;
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const Array& array = Array::Handle(zone, this->ptr());

  intptr_t old_len = array.Length();
  ASSERT(new_len <= old_len);
  if (old_len == new_len) {
    return;
  }
  intptr_t old_size = Array::InstanceSize(old_len);
  intptr_t new_size = Array::InstanceSize(new_len);

  NoSafepointScope no_safepoint(thread);

  // If there is any left over space fill it with either an Array object or
  // just a plain object (depending on the amount of left over space) so
  // that it can be traversed over successfully during garbage collection.
  Object::MakeUnusedSpaceTraversable(array, old_size, new_size);

  // Update the size in the header field and length of the array object.
  // These release operations are balanced by acquire operations in the
  // concurrent sweeper.
  array.untag()
      ->tags_.Update<UntaggedObject::SizeTag, std::memory_order_release>(
          new_size);

  // Between the CAS of the header above and the SetLength below, the array is
  // temporarily in an inconsistent state. The header is considered the
  // overriding source of object size by UntaggedObject::HeapSize, but the
  // ASSERTs in UntaggedObject::HeapSizeFromClass must handle this special case.
  array.SetLengthRelease(new_len);
}

ArrayPtr Array::MakeFixedLength(const GrowableObjectArray& growable_array,
                                bool unique) {
  ASSERT(!growable_array.IsNull());
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  intptr_t used_len = growable_array.Length();
  // Get the type arguments and prepare to copy them.
  const TypeArguments& type_arguments =
      TypeArguments::Handle(growable_array.GetTypeArguments());
  if (used_len == 0) {
    if (type_arguments.IsNull() && !unique) {
      // This is a raw List (as in no type arguments), so we can return the
      // simple empty array.
      return Object::empty_array().ptr();
    }

    // The backing array may be a shared instance, or may not have correct
    // type parameters. Create a new empty array.
    Heap::Space space = thread->HasDartMutatorStack() ? Heap::kNew : Heap::kOld;
    Array& array = Array::Handle(zone, Array::New(0, space));
    array.SetTypeArguments(type_arguments);
    return array.ptr();
  }
  const Array& array = Array::Handle(zone, growable_array.data());
  ASSERT(array.IsArray());
  array.SetTypeArguments(type_arguments);

  // Null the GrowableObjectArray, we are removing its backing array.
  growable_array.SetLength(0);
  growable_array.SetData(Object::empty_array());

  // Truncate the old backing array and return it.
  array.Truncate(used_len);
  return array.ptr();
}

void Array::CanonicalizeFieldsLocked(Thread* thread) const {
  ASSERT(IsImmutable());
  intptr_t len = Length();
  if (len > 0) {
    Zone* zone = thread->zone();
    Instance& obj = Instance::Handle(zone);
    for (intptr_t i = 0; i < len; i++) {
      obj ^= At(i);
      obj = obj.CanonicalizeLocked(thread);
      this->SetAt(i, obj);
    }
  }
}

ImmutableArrayPtr ImmutableArray::New(intptr_t len, Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->immutable_array_class() !=
         Class::null());
  return static_cast<ImmutableArrayPtr>(Array::New(kClassId, len, space));
}

void GrowableObjectArray::Add(const Object& value, Heap::Space space) const {
  ASSERT(!IsNull());
  if (Length() == Capacity()) {
    // Grow from 0 to 3, and then double + 1.
    intptr_t new_capacity = (Capacity() * 2) | 3;
    if (new_capacity <= Capacity()) {
      Exceptions::ThrowOOM();
      UNREACHABLE();
    }
    Grow(new_capacity, space);
  }
  ASSERT(Length() < Capacity());
  intptr_t index = Length();
  SetLength(index + 1);
  SetAt(index, value);
}

void GrowableObjectArray::Grow(intptr_t new_capacity, Heap::Space space) const {
  ASSERT(new_capacity > Capacity());
  const Array& contents = Array::Handle(data());
  const Array& new_contents =
      Array::Handle(Array::Grow(contents, new_capacity, space));
  untag()->set_data(new_contents.ptr());
}

ObjectPtr GrowableObjectArray::RemoveLast() const {
  ASSERT(!IsNull());
  ASSERT(Length() > 0);
  intptr_t index = Length() - 1;
  const Array& contents = Array::Handle(data());
  const PassiveObject& obj = PassiveObject::Handle(contents.At(index));
  contents.SetAt(index, Object::null_object());
  SetLength(index);
  return obj.ptr();
}

GrowableObjectArrayPtr GrowableObjectArray::New(intptr_t capacity,
                                                Heap::Space space) {
  ArrayPtr raw_data = (capacity == 0) ? Object::empty_array().ptr()
                                      : Array::New(capacity, space);
  const Array& data = Array::Handle(raw_data);
  return New(data, space);
}

GrowableObjectArrayPtr GrowableObjectArray::New(const Array& array,
                                                Heap::Space space) {
  ASSERT(
      IsolateGroup::Current()->object_store()->growable_object_array_class() !=
      Class::null());
  const auto& result =
      GrowableObjectArray::Handle(Object::Allocate<GrowableObjectArray>(space));
  result.SetLength(0);
  result.SetData(array);
  return result.ptr();
}

const char* GrowableObjectArray::ToCString() const {
  if (IsNull()) {
    return "_GrowableList: null";
  }
  return OS::SCreate(Thread::Current()->zone(),
                     "Instance(length:%" Pd ") of '_GrowableList'", Length());
}

// Equivalent to Dart's operator "==" and hashCode.
class DefaultHashTraits {
 public:
  static const char* Name() { return "DefaultHashTraits"; }
  static bool ReportStats() { return false; }

  static bool IsMatch(const Object& a, const Object& b) {
    if (a.IsNull() || b.IsNull()) {
      return (a.IsNull() && b.IsNull());
    } else {
      return Instance::Cast(a).OperatorEquals(Instance::Cast(b));
    }
  }
  static uword Hash(const Object& obj) {
    if (obj.IsNull()) {
      return 0;
    }
    Thread* thread = Thread::Current();
    REUSABLE_INSTANCE_HANDLESCOPE(thread);
    Instance& hash_code = thread->InstanceHandle();
    hash_code ^= Instance::Cast(obj).HashCode();
    if (hash_code.IsInteger()) {
      return static_cast<uword>(Integer::Cast(hash_code).Value() & 0xFFFFFFFF);
    } else {
      return 0;
    }
  }
};

MapPtr Map::NewDefault(intptr_t class_id, Heap::Space space) {
  const Array& data = Array::Handle(Array::New(kInitialIndexSize, space));
  const TypedData& index = TypedData::Handle(
      TypedData::New(kTypedDataUint32ArrayCid, kInitialIndexSize, space));
  // On 32-bit, the top bits are wasted to avoid Mint allocation.
  const intptr_t kAvailableBits = (kSmiBits >= 32) ? 32 : kSmiBits;
  const intptr_t kInitialHashMask =
      (1 << (kAvailableBits - kInitialIndexBits)) - 1;
  return Map::New(class_id, data, index, kInitialHashMask, 0, 0, space);
}

MapPtr Map::New(intptr_t class_id,
                const Array& data,
                const TypedData& index,
                intptr_t hash_mask,
                intptr_t used_data,
                intptr_t deleted_keys,
                Heap::Space space) {
  ASSERT(class_id == kMapCid || class_id == kConstMapCid);
  ASSERT(IsolateGroup::Current()->object_store()->map_impl_class() !=
         Class::null());
  Map& result = Map::Handle(Map::NewUninitialized(class_id, space));
  result.set_data(data);
  result.set_index(index);
  result.set_hash_mask(hash_mask);
  result.set_used_data(used_data);
  result.set_deleted_keys(deleted_keys);
  return result.ptr();
}

MapPtr Map::NewUninitialized(intptr_t class_id, Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->map_impl_class() !=
         Class::null());
  return Object::AllocateVariant<Map>(class_id, space);
}

const char* Map::ToCString() const {
  Zone* zone = Thread::Current()->zone();
  return zone->PrintToString(
      "%s len:%" Pd, GetClassId() == kConstMapCid ? "_ConstMap" : "_Map",
      Length());
}

void LinkedHashBase::ComputeAndSetHashMask() const {
  ASSERT(IsImmutable());
  ASSERT_EQUAL(Smi::Value(deleted_keys()), 0);
  Thread* const thread = Thread::Current();
  Zone* const zone = thread->zone();

  const auto& data_array = Array::Handle(zone, data());
  const intptr_t data_length = Utils::RoundUpToPowerOfTwo(data_array.Length());
  const intptr_t index_size_mult = IsMap() ? 1 : 2;
  const intptr_t index_size = Utils::Maximum(LinkedHashBase::kInitialIndexSize,
                                             data_length * index_size_mult);
  ASSERT(Utils::IsPowerOfTwo(index_size));

  const intptr_t hash_mask = IndexSizeToHashMask(index_size);
  set_hash_mask(hash_mask);
}

bool LinkedHashBase::CanonicalizeEquals(const Instance& other) const {
  ASSERT(IsImmutable());

  if (this->ptr() == other.ptr()) {
    // Both handles point to the same raw instance.
    return true;
  }
  if (other.IsNull()) {
    return false;
  }
  if (GetClassId() != other.GetClassId()) {
    return false;
  }

  Zone* zone = Thread::Current()->zone();

  const LinkedHashBase& other_map = LinkedHashBase::Cast(other);

  if (!Smi::Handle(zone, used_data())
           .Equals(Smi::Handle(zone, other_map.used_data()))) {
    return false;
  }

  // Immutable maps and sets do not have deleted keys.
  ASSERT_EQUAL(RawSmiValue(deleted_keys()), 0);

  if (!Array::Handle(zone, data())
           .CanonicalizeEquals(Array::Handle(zone, other_map.data()))) {
    return false;
  }

  if (GetTypeArguments() == other.GetTypeArguments()) {
    return true;
  }
  const TypeArguments& type_args =
      TypeArguments::Handle(zone, GetTypeArguments());
  const TypeArguments& other_type_args =
      TypeArguments::Handle(zone, other.GetTypeArguments());
  return type_args.Equals(other_type_args);
}

uint32_t LinkedHashBase::CanonicalizeHash() const {
  ASSERT(IsImmutable());

  Thread* thread = Thread::Current();
  uint32_t hash = thread->heap()->GetCanonicalHash(ptr());
  if (hash != 0) {
    return hash;
  }

  // Immutable maps and sets do not have deleted keys.
  ASSERT_EQUAL(RawSmiValue(deleted_keys()), 0);

  Zone* zone = thread->zone();
  auto& member = Instance::Handle(zone, GetTypeArguments());
  hash = member.CanonicalizeHash();
  member = data();
  hash = CombineHashes(hash, member.CanonicalizeHash());
  member = used_data();
  hash = CombineHashes(hash, member.CanonicalizeHash());
  hash = FinalizeHash(hash, kHashBits);
  thread->heap()->SetCanonicalHash(ptr(), hash);
  return hash;
}

void LinkedHashBase::CanonicalizeFieldsLocked(Thread* thread) const {
  ASSERT(IsImmutable());

  Zone* zone = thread->zone();

  TypeArguments& type_args = TypeArguments::Handle(zone, GetTypeArguments());
  if (!type_args.IsNull()) {
    type_args = type_args.Canonicalize(thread);
    SetTypeArguments(type_args);
  }

  auto& data_array = Array::Handle(zone, data());
  data_array.MakeImmutable();
  data_array ^= data_array.CanonicalizeLocked(thread);
  set_data(data_array);

  // Ignoring index. It will be initially null, created on first use, and
  // possibly non-null here if we are rehashing.
}

ConstMapPtr ConstMap::NewDefault(Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->const_map_impl_class() !=
         Class::null());
  return static_cast<ConstMapPtr>(Map::NewDefault(kClassId, space));
}

ConstMapPtr ConstMap::NewUninitialized(Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->const_map_impl_class() !=
         Class::null());
  return static_cast<ConstMapPtr>(Map::NewUninitialized(kClassId, space));
}

SetPtr Set::New(intptr_t class_id,
                const Array& data,
                const TypedData& index,
                intptr_t hash_mask,
                intptr_t used_data,
                intptr_t deleted_keys,
                Heap::Space space) {
  ASSERT(class_id == kSetCid || class_id == kConstSetCid);
  ASSERT(IsolateGroup::Current()->object_store()->set_impl_class() !=
         Class::null());
  Set& result = Set::Handle(Set::NewUninitialized(class_id, space));
  result.set_data(data);
  result.set_index(index);
  result.set_hash_mask(hash_mask);
  result.set_used_data(used_data);
  result.set_deleted_keys(deleted_keys);
  return result.ptr();
}

SetPtr Set::NewDefault(intptr_t class_id, Heap::Space space) {
  const Array& data = Array::Handle(Array::New(kInitialIndexSize, space));
  const TypedData& index = TypedData::Handle(
      TypedData::New(kTypedDataUint32ArrayCid, kInitialIndexSize, space));
  // On 32-bit, the top bits are wasted to avoid Mint allocation.
  const intptr_t kAvailableBits = (kSmiBits >= 32) ? 32 : kSmiBits;
  const intptr_t kInitialHashMask =
      (1 << (kAvailableBits - kInitialIndexBits)) - 1;
  return Set::New(class_id, data, index, kInitialHashMask, 0, 0, space);
}

SetPtr Set::NewUninitialized(intptr_t class_id, Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->set_impl_class() !=
         Class::null());
  return Object::AllocateVariant<Set>(class_id, space);
}

ConstSetPtr ConstSet::NewDefault(Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->const_set_impl_class() !=
         Class::null());
  return static_cast<ConstSetPtr>(Set::NewDefault(kClassId, space));
}

ConstSetPtr ConstSet::NewUninitialized(Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->const_set_impl_class() !=
         Class::null());
  return static_cast<ConstSetPtr>(Set::NewUninitialized(kClassId, space));
}

const char* Set::ToCString() const {
  Zone* zone = Thread::Current()->zone();
  return zone->PrintToString(
      "%s len:%" Pd, GetClassId() == kConstSetCid ? "_ConstSet" : "_Set",
      Length());
}

const char* FutureOr::ToCString() const {
  // FutureOr is an abstract class.
  UNREACHABLE();
}

Float32x4Ptr Float32x4::New(float v0,
                            float v1,
                            float v2,
                            float v3,
                            Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->float32x4_class() !=
         Class::null());
  const auto& result = Float32x4::Handle(Object::Allocate<Float32x4>(space));
  result.set_x(v0);
  result.set_y(v1);
  result.set_z(v2);
  result.set_w(v3);
  return result.ptr();
}

Float32x4Ptr Float32x4::New(simd128_value_t value, Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->float32x4_class() !=
         Class::null());
  const auto& result = Float32x4::Handle(Object::Allocate<Float32x4>(space));
  result.set_value(value);
  return result.ptr();
}

simd128_value_t Float32x4::value() const {
  return LoadUnaligned(
      reinterpret_cast<const simd128_value_t*>(&untag()->value_));
}

void Float32x4::set_value(simd128_value_t value) const {
  StoreUnaligned(reinterpret_cast<simd128_value_t*>(&ptr()->untag()->value_),
                 value);
}

void Float32x4::set_x(float value) const {
  StoreNonPointer(&untag()->value_[0], value);
}

void Float32x4::set_y(float value) const {
  StoreNonPointer(&untag()->value_[1], value);
}

void Float32x4::set_z(float value) const {
  StoreNonPointer(&untag()->value_[2], value);
}

void Float32x4::set_w(float value) const {
  StoreNonPointer(&untag()->value_[3], value);
}

float Float32x4::x() const {
  return untag()->value_[0];
}

float Float32x4::y() const {
  return untag()->value_[1];
}

float Float32x4::z() const {
  return untag()->value_[2];
}

float Float32x4::w() const {
  return untag()->value_[3];
}

bool Float32x4::CanonicalizeEquals(const Instance& other) const {
  NoSafepointScope no_safepoint;
  return memcmp(&untag()->value_, Float32x4::Cast(other).untag()->value_,
                sizeof(simd128_value_t)) == 0;
}

uint32_t Float32x4::CanonicalizeHash() const {
  NoSafepointScope no_safepoint;
  return HashBytes(&untag()->value_, sizeof(simd128_value_t), kHashBits);
}

const char* Float32x4::ToCString() const {
  float _x = x();
  float _y = y();
  float _z = z();
  float _w = w();
  return OS::SCreate(Thread::Current()->zone(), "[%f, %f, %f, %f]", _x, _y, _z,
                     _w);
}

Int32x4Ptr Int32x4::New(int32_t v0,
                        int32_t v1,
                        int32_t v2,
                        int32_t v3,
                        Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->int32x4_class() !=
         Class::null());
  const auto& result = Int32x4::Handle(Object::Allocate<Int32x4>(space));
  result.set_x(v0);
  result.set_y(v1);
  result.set_z(v2);
  result.set_w(v3);
  return result.ptr();
}

Int32x4Ptr Int32x4::New(simd128_value_t value, Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->int32x4_class() !=
         Class::null());
  const auto& result = Int32x4::Handle(Object::Allocate<Int32x4>(space));
  result.set_value(value);
  return result.ptr();
}

void Int32x4::set_x(int32_t value) const {
  StoreNonPointer(&untag()->value_[0], value);
}

void Int32x4::set_y(int32_t value) const {
  StoreNonPointer(&untag()->value_[1], value);
}

void Int32x4::set_z(int32_t value) const {
  StoreNonPointer(&untag()->value_[2], value);
}

void Int32x4::set_w(int32_t value) const {
  StoreNonPointer(&untag()->value_[3], value);
}

int32_t Int32x4::x() const {
  return untag()->value_[0];
}

int32_t Int32x4::y() const {
  return untag()->value_[1];
}

int32_t Int32x4::z() const {
  return untag()->value_[2];
}

int32_t Int32x4::w() const {
  return untag()->value_[3];
}

simd128_value_t Int32x4::value() const {
  return LoadUnaligned(
      reinterpret_cast<const simd128_value_t*>(&untag()->value_));
}

void Int32x4::set_value(simd128_value_t value) const {
  StoreUnaligned(reinterpret_cast<simd128_value_t*>(&ptr()->untag()->value_),
                 value);
}

bool Int32x4::CanonicalizeEquals(const Instance& other) const {
  NoSafepointScope no_safepoint;
  return memcmp(&untag()->value_, Int32x4::Cast(other).untag()->value_,
                sizeof(simd128_value_t)) == 0;
}

uint32_t Int32x4::CanonicalizeHash() const {
  NoSafepointScope no_safepoint;
  return HashBytes(&untag()->value_, sizeof(simd128_value_t), kHashBits);
}

const char* Int32x4::ToCString() const {
  int32_t _x = x();
  int32_t _y = y();
  int32_t _z = z();
  int32_t _w = w();
  return OS::SCreate(Thread::Current()->zone(), "[%08x, %08x, %08x, %08x]", _x,
                     _y, _z, _w);
}

Float64x2Ptr Float64x2::New(double value0, double value1, Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->float64x2_class() !=
         Class::null());
  const auto& result = Float64x2::Handle(Object::Allocate<Float64x2>(space));
  result.set_x(value0);
  result.set_y(value1);
  return result.ptr();
}

Float64x2Ptr Float64x2::New(simd128_value_t value, Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->float64x2_class() !=
         Class::null());
  const auto& result = Float64x2::Handle(Object::Allocate<Float64x2>(space));
  result.set_value(value);
  return result.ptr();
}

double Float64x2::x() const {
  return untag()->value_[0];
}

double Float64x2::y() const {
  return untag()->value_[1];
}

void Float64x2::set_x(double x) const {
  StoreNonPointer(&untag()->value_[0], x);
}

void Float64x2::set_y(double y) const {
  StoreNonPointer(&untag()->value_[1], y);
}

simd128_value_t Float64x2::value() const {
  return simd128_value_t().readFrom(&untag()->value_[0]);
}

void Float64x2::set_value(simd128_value_t value) const {
  StoreSimd128(&untag()->value_[0], value);
}

bool Float64x2::CanonicalizeEquals(const Instance& other) const {
  NoSafepointScope no_safepoint;
  return memcmp(&untag()->value_, Float64x2::Cast(other).untag()->value_,
                sizeof(simd128_value_t)) == 0;
}

uint32_t Float64x2::CanonicalizeHash() const {
  NoSafepointScope no_safepoint;
  return HashBytes(&untag()->value_, sizeof(simd128_value_t), kHashBits);
}

const char* Float64x2::ToCString() const {
  double _x = x();
  double _y = y();
  return OS::SCreate(Thread::Current()->zone(), "[%f, %f]", _x, _y);
}

const intptr_t
    TypedDataBase::element_size_table[TypedDataBase::kNumElementSizes] = {
        1,   // kTypedDataInt8ArrayCid.
        1,   // kTypedDataUint8ArrayCid.
        1,   // kTypedDataUint8ClampedArrayCid.
        2,   // kTypedDataInt16ArrayCid.
        2,   // kTypedDataUint16ArrayCid.
        4,   // kTypedDataInt32ArrayCid.
        4,   // kTypedDataUint32ArrayCid.
        8,   // kTypedDataInt64ArrayCid.
        8,   // kTypedDataUint64ArrayCid.
        4,   // kTypedDataFloat32ArrayCid.
        8,   // kTypedDataFloat64ArrayCid.
        16,  // kTypedDataFloat32x4ArrayCid.
        16,  // kTypedDataInt32x4ArrayCid.
        16,  // kTypedDataFloat64x2ArrayCid,
};

bool TypedData::CanonicalizeEquals(const Instance& other) const {
  if (this->ptr() == other.ptr()) {
    // Both handles point to the same raw instance.
    return true;
  }

  if (!other.IsTypedData() || other.IsNull()) {
    return false;
  }

  const TypedData& other_typed_data = TypedData::Cast(other);

  if (this->ElementType() != other_typed_data.ElementType()) {
    return false;
  }

  const intptr_t len = this->LengthInBytes();
  if (len != other_typed_data.LengthInBytes()) {
    return false;
  }
  NoSafepointScope no_safepoint;
  return (len == 0) ||
         (memcmp(DataAddr(0), other_typed_data.DataAddr(0), len) == 0);
}

uint32_t TypedData::CanonicalizeHash() const {
  uint32_t hash = kEmptyContainerHash;
  const intptr_t len = LengthInBytes();
  if (len != 0) {
    auto* const thread = Thread::Current();
    hash = thread->heap()->GetCanonicalHash(ptr());
    if (hash == 0) {
      {
        NoSafepointScope no_safepoint;
        hash = HashBytes(DataAddr(0), len, kHashBits);
      }
      thread->heap()->SetCanonicalHash(ptr(), hash);
    }
  }
  return hash;
}

TypedDataPtr TypedData::New(intptr_t class_id,
                            intptr_t len,
                            Heap::Space space) {
  if (len < 0 || len > TypedData::MaxElements(class_id)) {
    FATAL("Fatal error in TypedData::New: invalid len %" Pd "\n", len);
  }
  auto raw = Object::AllocateVariant<TypedData>(
      class_id, space, len * ElementSizeInBytes(class_id));
  NoSafepointScope no_safepoint;
  raw->untag()->set_length(Smi::New(len));
  raw->untag()->RecomputeDataField();
  return raw;
}

TypedDataPtr TypedData::Grow(const TypedData& current,
                             intptr_t len,
                             Heap::Space space) {
  ASSERT(len > current.Length());
  const auto& new_td =
      TypedData::Handle(TypedData::New(current.GetClassId(), len, space));
  {
    NoSafepointScope no_safepoint_scope;
    memcpy(new_td.DataAddr(0), current.DataAddr(0), current.LengthInBytes());
  }
  return new_td.ptr();
}

const char* TypedData::ToCString() const {
  const Class& cls = Class::Handle(clazz());
  return cls.ScrubbedNameCString();
}

FinalizablePersistentHandle* ExternalTypedData::AddFinalizer(
    void* peer,
    Dart_HandleFinalizer callback,
    intptr_t external_size) const {
  return dart::AddFinalizer(*this, peer, callback, external_size);
}

ExternalTypedDataPtr ExternalTypedData::New(
    intptr_t class_id,
    uint8_t* data,
    intptr_t len,
    Heap::Space space,
    bool perform_eager_msan_initialization_check) {
  if (len < 0 || len > ExternalTypedData::MaxElements(class_id)) {
    FATAL("Fatal error in ExternalTypedData::New: invalid len %" Pd "\n", len);
  }

  if (perform_eager_msan_initialization_check) {
    // Once the TypedData is created, Dart might read this memory. Check for
    // initialization at construction to make it easier to track the source.
    MSAN_CHECK_INITIALIZED(data, len);
  }

  const auto& result = ExternalTypedData::Handle(
      Object::AllocateVariant<ExternalTypedData>(class_id, space));
  result.SetLength(len);
  result.SetData(data);
  return result.ptr();
}

ExternalTypedDataPtr ExternalTypedData::NewFinalizeWithFree(uint8_t* data,
                                                            intptr_t len) {
  ExternalTypedData& result = ExternalTypedData::Handle(ExternalTypedData::New(
      kExternalTypedDataUint8ArrayCid, data, len, Heap::kOld));
  result.AddFinalizer(
      data, [](void* isolate_callback_data, void* data) { free(data); }, len);
  return result.ptr();
}

TypedDataViewPtr TypedDataView::New(intptr_t class_id, Heap::Space space) {
  return Object::AllocateVariant<TypedDataView>(class_id, space);
}

TypedDataViewPtr TypedDataView::New(intptr_t class_id,
                                    const TypedDataBase& typed_data,
                                    intptr_t offset_in_bytes,
                                    intptr_t length,
                                    Heap::Space space) {
  auto& result = TypedDataView::Handle(TypedDataView::New(class_id, space));
  result.InitializeWith(typed_data, offset_in_bytes, length);
  return result.ptr();
}

bool TypedDataBase::IsExternalOrExternalView() const {
  if (IsExternalTypedData()) return true;
  if (IsTypedDataView()) {
    const auto& backing =
        TypedDataBase::Handle(TypedDataView::Cast(*this).typed_data());
    return backing.IsExternalTypedData();
  }
  return false;
}

TypedDataViewPtr TypedDataBase::ViewFromTo(intptr_t start,
                                           intptr_t end,
                                           Heap::Space space) const {
  const intptr_t len = end - start;
  ASSERT(0 <= len);
  ASSERT(start < Length());
  ASSERT((start + len) <= Length());

  const intptr_t cid = GetClassId();

  if (IsTypedDataView()) {
    const auto& view = TypedDataView::Cast(*this);
    const auto& td = TypedDataBase::Handle(view.typed_data());
    const intptr_t view_offset = Smi::Value(view.offset_in_bytes());
    ASSERT(IsTypedDataViewClassId(cid));
    return TypedDataView::New(cid, ExternalTypedData::Cast(td),
                              view_offset + start, len, Heap::kOld);
  } else if (IsExternalTypedData()) {
    ASSERT(IsExternalTypedDataClassId(cid));
    ASSERT(IsTypedDataViewClassId(cid - 1));
    return TypedDataView::New(cid - 1, *this, start, len, Heap::kOld);
  }
  RELEASE_ASSERT(IsTypedData());
  ASSERT(IsExternalTypedDataClassId(cid));
  ASSERT(IsTypedDataViewClassId(cid + 1));
  return TypedDataView::New(cid + 1, *this, start, len, Heap::kOld);
}

const char* TypedDataBase::ToCString() const {
  // There are no instances of UntaggedTypedDataBase.
  UNREACHABLE();
  return nullptr;
}

const char* TypedDataView::ToCString() const {
  const Class& cls = Class::Handle(clazz());
  return cls.ScrubbedNameCString();
}

const char* ExternalTypedData::ToCString() const {
  const Class& cls = Class::Handle(clazz());
  return cls.ScrubbedNameCString();
}

PointerPtr Pointer::New(uword native_address, Heap::Space space) {
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  IsolateGroup* isolate_group = thread->isolate_group();

  const auto& type_args = TypeArguments::Handle(
      zone, isolate_group->object_store()->type_argument_never());

  const Class& cls =
      Class::Handle(isolate_group->class_table()->At(kPointerCid));
  cls.EnsureIsAllocateFinalized(thread);

  const auto& result = Pointer::Handle(zone, Object::Allocate<Pointer>(space));
  result.SetTypeArguments(type_args);
  result.SetNativeAddress(native_address);

  return result.ptr();
}

const char* Pointer::ToCString() const {
  return OS::SCreate(Thread::Current()->zone(), "Pointer: address=0x%" Px,
                     NativeAddress());
}

DynamicLibraryPtr DynamicLibrary::New(void* handle,
                                      bool canBeClosed,
                                      Heap::Space space) {
  const auto& result =
      DynamicLibrary::Handle(Object::Allocate<DynamicLibrary>(space));
  ASSERT_EQUAL(result.IsClosed(), false);
  result.SetHandle(handle);
  result.SetCanBeClosed(canBeClosed);
  return result.ptr();
}

bool Pointer::IsPointer(const Instance& obj) {
  return IsFfiPointerClassId(obj.ptr()->GetClassId());
}

bool Instance::IsPointer() const {
  return Pointer::IsPointer(*this);
}

const char* DynamicLibrary::ToCString() const {
  return OS::SCreate(Thread::Current()->zone(), "DynamicLibrary: handle=0x%" Px,
                     reinterpret_cast<uintptr_t>(GetHandle()));
}

CapabilityPtr Capability::New(uint64_t id, Heap::Space space) {
  const auto& result = Capability::Handle(Object::Allocate<Capability>(space));
  result.StoreNonPointer(&result.untag()->id_, id);
  return result.ptr();
}

const char* Capability::ToCString() const {
  return "Capability";
}

ReceivePortPtr ReceivePort::New(Dart_Port id,
                                const String& debug_name,
                                Heap::Space space) {
  ASSERT(id != ILLEGAL_PORT);
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  const SendPort& send_port =
      SendPort::Handle(zone, SendPort::New(id, thread->isolate_group()->id()));
#if !defined(PRODUCT)
  const StackTrace& allocation_location_ =
      HasStack() ? GetCurrentStackTrace(0) : StackTrace::Handle();
#endif  // !defined(PRODUCT)

  const auto& result =
      ReceivePort::Handle(zone, Object::Allocate<ReceivePort>(space));
  result.untag()->set_send_port(send_port.ptr());
  result.untag()->set_bitfield(
      Smi::New(IsOpen::encode(true) | IsKeepIsolateAlive::encode(true)));
#if !defined(PRODUCT)
  result.untag()->set_debug_name(debug_name.ptr());
  result.untag()->set_allocation_location(allocation_location_.ptr());
#endif  // !defined(PRODUCT)
  return result.ptr();
}

const char* ReceivePort::ToCString() const {
  return "ReceivePort";
}

SendPortPtr SendPort::New(Dart_Port id, Heap::Space space) {
  return New(id, ILLEGAL_PORT, space);
}

SendPortPtr SendPort::New(Dart_Port id,
                          Dart_Port origin_id,
                          Heap::Space space) {
  ASSERT(id != ILLEGAL_PORT);
  const auto& result = SendPort::Handle(Object::Allocate<SendPort>(space));
  result.StoreNonPointer(&result.untag()->id_, id);
  result.StoreNonPointer(&result.untag()->origin_id_, origin_id);
  return result.ptr();
}

const char* SendPort::ToCString() const {
  return "SendPort";
}

static void TransferableTypedDataFinalizer(void* isolate_callback_data,
                                           void* peer) {
  delete (reinterpret_cast<TransferableTypedDataPeer*>(peer));
}

TransferableTypedDataPtr TransferableTypedData::New(uint8_t* data,
                                                    intptr_t length) {
  auto* const peer = new TransferableTypedDataPeer(data, length);

  Thread* thread = Thread::Current();
  const auto& result =
      TransferableTypedData::Handle(Object::Allocate<TransferableTypedData>(
          thread->heap()->SpaceForExternal(length)));
  thread->heap()->SetPeer(result.ptr(), peer);

  // Set up finalizer so it frees allocated memory if handle is
  // garbage-collected.
  FinalizablePersistentHandle* finalizable_ref =
      FinalizablePersistentHandle::New(thread->isolate_group(), result, peer,
                                       &TransferableTypedDataFinalizer, length,
                                       /*auto_delete=*/true);
  ASSERT(finalizable_ref != nullptr);
  peer->set_handle(finalizable_ref);

  return result.ptr();
}

const char* TransferableTypedData::ToCString() const {
  return "TransferableTypedData";
}

bool Closure::CanonicalizeEquals(const Instance& other) const {
  if (!other.IsClosure()) return false;

  const Closure& other_closure = Closure::Cast(other);
  return (instantiator_type_arguments() ==
          other_closure.instantiator_type_arguments()) &&
         (function_type_arguments() ==
          other_closure.function_type_arguments()) &&
         (delayed_type_arguments() == other_closure.delayed_type_arguments()) &&
         (function() == other_closure.function()) &&
         (RawContext() == other_closure.RawContext());
}

void Closure::CanonicalizeFieldsLocked(Thread* thread) const {
  TypeArguments& type_args = TypeArguments::Handle();
  type_args = instantiator_type_arguments();
  if (!type_args.IsNull()) {
    type_args = type_args.Canonicalize(thread);
    set_instantiator_type_arguments(type_args);
  }
  type_args = function_type_arguments();
  if (!type_args.IsNull()) {
    type_args = type_args.Canonicalize(thread);
    set_function_type_arguments(type_args);
  }
  type_args = delayed_type_arguments();
  if (!type_args.IsNull()) {
    type_args = type_args.Canonicalize(thread);
    set_delayed_type_arguments(type_args);
  }
  // Ignore function, context, hash.
}

const char* Closure::ToCString() const {
  auto const thread = Thread::Current();
  auto const zone = thread->zone();
  ZoneTextBuffer buffer(zone);
  buffer.AddString("Closure: ");
  const Function& fun = Function::Handle(zone, function());
  const FunctionType& sig =
      FunctionType::Handle(zone, GetInstantiatedSignature(zone));
  sig.Print(kUserVisibleName, &buffer);
  if (fun.IsImplicitClosureFunction()) {
    buffer.Printf(" from %s", fun.ToCString());
  }
  return buffer.buffer();
}

uword Closure::ComputeHash() const {
  Thread* thread = Thread::Current();
  DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame());
  Zone* zone = thread->zone();
  const Function& func = Function::Handle(zone, function());
  uint32_t result = 0;
  if (func.IsImplicitClosureFunction() || func.IsGeneric()) {
    // Combine function's hash code, delayed type arguments hash code
    // (if generic), and identityHashCode of cached receiver (if implicit
    // instance closure).
    result = static_cast<uint32_t>(func.Hash());
    if (func.IsGeneric()) {
      const TypeArguments& delayed_type_args =
          TypeArguments::Handle(zone, delayed_type_arguments());
      result = CombineHashes(result, delayed_type_args.Hash());
    }
    if (func.IsImplicitInstanceClosureFunction()) {
      const Instance& receiver =
          Instance::Handle(zone, GetImplicitClosureReceiver());
      const Integer& receiverHash =
          Integer::Handle(zone, receiver.IdentityHashCode(thread));
      result =
          CombineHashes(result, static_cast<uint32_t>(receiverHash.Value()));
    }
  } else {
    // Non-implicit closures of non-generic functions are unique,
    // so identityHashCode of closure object is good enough.
    const Integer& identityHash =
        Integer::Handle(zone, this->IdentityHashCode(thread));
    result = static_cast<uint32_t>(identityHash.Value());
  }
  return FinalizeHash(result, String::kHashBits);
}

ClosurePtr Closure::New(intptr_t length_and_flags, Heap::Space space) {
  const intptr_t num_elements =
      UntaggedClosure::LengthBits::decode(length_and_flags);
  ASSERT(num_elements >= 0);
  if (!IsValidLength(num_elements)) {
    // This should be caught before we reach here.
    FATAL("Fatal error in Closure::New: invalid num_elements %" Pd "\n",
          num_elements);
  }
  auto raw = Object::Allocate<Closure>(space, num_elements);
  NoSafepointScope no_safepoint;
  raw->untag()->set_length_and_flags(Smi::New(length_and_flags));
  raw->untag()->set_hash(Smi::New(0));
  if (UntaggedClosure::HasDelayedTypeArgumentsBit::decode(length_and_flags)) {
    raw->untag()->set_element(UntaggedClosure::kDelayedTypeArgumentsIndex,
                              Object::empty_type_arguments().ptr());
  }
  return raw;
}

ClosurePtr Closure::New(const TypeArguments& instantiator_type_arguments,
                        const TypeArguments& function_type_arguments,
                        const Function& function,
                        const Object& context,
                        Heap::Space space) {
  return Closure::New(instantiator_type_arguments, function_type_arguments,
                      Closure::HasDelayedTypeArgumentsField(function)
                          ? Object::empty_type_arguments()
                          : Object::null_type_arguments(),
                      function, context, space);
}

ClosurePtr Closure::New(const TypeArguments& instantiator_type_arguments,
                        const TypeArguments& function_type_arguments,
                        const TypeArguments& delayed_type_arguments,
                        const Function& function,
                        const Object& context,
                        Heap::Space space) {
  ASSERT(instantiator_type_arguments.IsCanonical());
  ASSERT(function_type_arguments.IsCanonical());
  ASSERT(delayed_type_arguments.IsCanonical());
  ASSERT(function.IsClosureFunction());
  ASSERT(FunctionType::Handle(function.signature()).IsCanonical());
  ASSERT(
      (function.IsImplicitInstanceClosureFunction() && context.IsInstance()) ||
      (function.IsNonImplicitClosureFunction() && context.IsContext()) ||
      context.IsNull());

  const bool has_delayed_type_args =
      Closure::HasDelayedTypeArgumentsField(function);
  const bool has_instantiator_type_args =
      Closure::HasInstantiatorTypeArgumentsField(function);
  const bool has_function_type_args =
      Closure::HasFunctionTypeArgumentsField(function);
  const intptr_t context_index = UntaggedClosure::ContextIndex(
      has_delayed_type_args, has_instantiator_type_args,
      has_function_type_args);
  const intptr_t num_elements = context_index + 1;
  const intptr_t length_and_flags = UntaggedClosure::EncodeLengthAndFlags(
      has_delayed_type_args, has_instantiator_type_args, has_function_type_args,
      num_elements);

  const auto& result = Closure::Handle(Closure::New(length_and_flags, space));
  result.set_function(function);
  if (has_delayed_type_args) {
    result.set_delayed_type_arguments(delayed_type_arguments);
  }
  if (has_instantiator_type_args) {
    result.set_instantiator_type_arguments(instantiator_type_arguments);
  }
  if (has_function_type_args) {
    result.set_function_type_arguments(function_type_arguments);
  }
  result.SetElementAt(context_index, context);
  return result.ptr();
}

FunctionTypePtr Closure::GetInstantiatedSignature(Zone* zone) const {
  const Function& fun = Function::Handle(zone, function());
  FunctionType& sig = FunctionType::Handle(zone, fun.signature());
  TypeArguments& fn_type_args =
      TypeArguments::Handle(zone, function_type_arguments());
  const TypeArguments& delayed_type_args =
      TypeArguments::Handle(zone, delayed_type_arguments());
  const TypeArguments& inst_type_args =
      TypeArguments::Handle(zone, instantiator_type_arguments());

  // We detect the case of a partial tearoff type application and substitute the
  // type arguments for the type parameters of the function.
  intptr_t num_free_params;
  if (!IsGeneric() && fun.IsGeneric()) {
    num_free_params = kCurrentAndEnclosingFree;
    fn_type_args = delayed_type_args.Prepend(
        zone, fn_type_args, sig.NumParentTypeArguments(),
        sig.NumTypeParameters() + sig.NumParentTypeArguments());
  } else {
    num_free_params = kAllFree;
  }
  if (num_free_params == kCurrentAndEnclosingFree || !sig.IsInstantiated()) {
    sig ^= sig.InstantiateFrom(inst_type_args, fn_type_args, num_free_params,
                               Heap::kOld);
  }
  return sig.ptr();
}

bool StackTrace::skip_sync_start_in_parent_stack() const {
  return untag()->skip_sync_start_in_parent_stack;
}

void StackTrace::set_skip_sync_start_in_parent_stack(bool value) const {
  StoreNonPointer(&untag()->skip_sync_start_in_parent_stack, value);
}

intptr_t StackTrace::Length() const {
  const Array& code_array = Array::Handle(untag()->code_array());
  return code_array.Length();
}

ObjectPtr StackTrace::CodeAtFrame(intptr_t frame_index) const {
  const Array& code_array = Array::Handle(untag()->code_array());
  return code_array.At(frame_index);
}

void StackTrace::SetCodeAtFrame(intptr_t frame_index,
                                const Object& code) const {
  const Array& code_array = Array::Handle(untag()->code_array());
  code_array.SetAt(frame_index, code);
}

uword StackTrace::PcOffsetAtFrame(intptr_t frame_index) const {
  const TypedData& pc_offset_array =
      TypedData::Handle(untag()->pc_offset_array());
  return pc_offset_array.GetUintPtr(frame_index * kWordSize);
}

void StackTrace::SetPcOffsetAtFrame(intptr_t frame_index,
                                    uword pc_offset) const {
  const TypedData& pc_offset_array =
      TypedData::Handle(untag()->pc_offset_array());
  pc_offset_array.SetUintPtr(frame_index * kWordSize, pc_offset);
}

void StackTrace::set_async_link(const StackTrace& async_link) const {
  untag()->set_async_link(async_link.ptr());
}

void StackTrace::set_code_array(const Array& code_array) const {
  untag()->set_code_array(code_array.ptr());
}

void StackTrace::set_pc_offset_array(const TypedData& pc_offset_array) const {
  untag()->set_pc_offset_array(pc_offset_array.ptr());
}

void StackTrace::set_expand_inlined(bool value) const {
  StoreNonPointer(&untag()->expand_inlined_, value);
}

bool StackTrace::expand_inlined() const {
  return untag()->expand_inlined_;
}

StackTracePtr StackTrace::New(const Array& code_array,
                              const TypedData& pc_offset_array,
                              Heap::Space space) {
  const auto& result = StackTrace::Handle(Object::Allocate<StackTrace>(space));
  result.set_code_array(code_array);
  result.set_pc_offset_array(pc_offset_array);
  result.set_expand_inlined(true);  // default.
  ASSERT_EQUAL(result.skip_sync_start_in_parent_stack(), false);
  return result.ptr();
}

StackTracePtr StackTrace::New(const Array& code_array,
                              const TypedData& pc_offset_array,
                              const StackTrace& async_link,
                              bool skip_sync_start_in_parent_stack,
                              Heap::Space space) {
  const auto& result = StackTrace::Handle(Object::Allocate<StackTrace>(space));
  result.set_async_link(async_link);
  result.set_code_array(code_array);
  result.set_pc_offset_array(pc_offset_array);
  result.set_expand_inlined(true);  // default.
  result.set_skip_sync_start_in_parent_stack(skip_sync_start_in_parent_stack);
  return result.ptr();
}

#if defined(DART_PRECOMPILED_RUNTIME)
static bool TryPrintNonSymbolicStackFrameBodyRelative(
    BaseTextBuffer* buffer,
    uword call_addr,
    uword instructions,
    bool vm,
    LoadingUnit* unit = nullptr) {
  const Image image(reinterpret_cast<const uint8_t*>(instructions));
  if (!image.contains(call_addr)) return false;
  if (unit != nullptr) {
    ASSERT(!unit->IsNull());
    // Add the unit ID to the stack frame, so the correct loading unit
    // information from the header can be checked.
    buffer->Printf(" unit %" Pd "", unit->id());
  }
  auto const offset = call_addr - instructions;
  // Only print the relocated address of the call when we know the saved
  // debugging information (if any) will have the same relocated address.
  // Also only print 'virt' fields for isolate addresses.
  if (!vm && image.compiled_to_shared_object()) {
    const uword relocated_section_start =
        image.instructions_relocated_address();
    buffer->Printf(" virt %" Pp "", relocated_section_start + offset);
  }
  const char* symbol = kSnapshotTextAsmSymbol;
  buffer->Printf(" %s+0x%" Px "\n", symbol, offset);
  return true;
}

// Prints the best representation(s) for the call address.
static void PrintNonSymbolicStackFrameBody(BaseTextBuffer* buffer,
                                           uword call_addr,
                                           uword isolate_instructions,
                                           uword vm_instructions,
                                           const Array& loading_units,
                                           LoadingUnit* unit) {
  if (!loading_units.IsNull()) {
    // All non-VM stack frames should include the loading unit id.
    const intptr_t unit_count = loading_units.Length();
    for (intptr_t i = LoadingUnit::kRootId; i < unit_count; i++) {
      *unit ^= loading_units.At(i);
      if (!unit->has_instructions_image()) continue;
      auto const instructions =
          reinterpret_cast<uword>(unit->instructions_image());
      if (TryPrintNonSymbolicStackFrameBodyRelative(buffer, call_addr,
                                                    instructions,
                                                    /*vm=*/false, unit)) {
        return;
      }
    }
  } else {
    if (TryPrintNonSymbolicStackFrameBodyRelative(buffer, call_addr,
                                                  isolate_instructions,
                                                  /*vm=*/false)) {
      return;
    }
  }

  // The stack trace printer should never end up here, since these are not
  // addresses within a loading unit or the VM or app isolate instructions
  // sections. Thus, make it easy to notice when looking at the stack trace.
  buffer->Printf(" <invalid Dart instruction address>\n");
}
#endif

static void PrintSymbolicStackFrameIndex(BaseTextBuffer* buffer,
                                         intptr_t frame_index) {
  buffer->Printf("#%-6" Pd "", frame_index);
}

static void PrintSymbolicStackFrameBody(BaseTextBuffer* buffer,
                                        const char* function_name,
                                        const char* url,
                                        intptr_t line = -1,
                                        intptr_t column = -1) {
  buffer->Printf(" %s (%s", function_name, url);
  if (line >= 0) {
    buffer->Printf(":%" Pd "", line);
    if (column >= 0) {
      buffer->Printf(":%" Pd "", column);
    }
  }
  buffer->Printf(")\n");
}

static void PrintSymbolicStackFrame(Zone* zone,
                                    BaseTextBuffer* buffer,
                                    const Function& function,
                                    TokenPosition token_pos_or_line,
                                    intptr_t frame_index,
                                    bool is_line = false) {
  ASSERT(!function.IsNull());
  const auto& script = Script::Handle(zone, function.script());
  const char* function_name = function.QualifiedUserVisibleNameCString();
  const char* url = script.IsNull()
                        ? "Kernel"
                        : String::Handle(zone, script.url()).ToCString();

  // If the URI starts with "data:application/dart;" this is a URI encoded
  // script so we shouldn't print the entire URI because it could be very long.
  if (strstr(url, "data:application/dart;") == url) {
    url = "<data:application/dart>";
  }

  intptr_t line = -1;
  intptr_t column = -1;
  if (is_line) {
    ASSERT(token_pos_or_line.IsNoSource() || token_pos_or_line.IsReal());
    if (token_pos_or_line.IsReal()) {
      line = token_pos_or_line.Pos();
    }
  } else {
    ASSERT(!script.IsNull());
    script.GetTokenLocation(token_pos_or_line, &line, &column);
  }
  PrintSymbolicStackFrameIndex(buffer, frame_index);
  PrintSymbolicStackFrameBody(buffer, function_name, url, line, column);
}

static bool IsVisibleAsFutureListener(const Function& function) {
  if (function.is_visible()) {
    return true;
  }

  if (function.IsImplicitClosureFunction()) {
    return function.parent_function() == Function::null() ||
           Function::is_visible(function.parent_function());
  }

  return false;
}

#if defined(DART_PRECOMPILED_RUNTIME)
static void WriteImageBuildId(BaseTextBuffer* buffer,
                              const char* prefix,
                              uword image_address) {
  const auto& build_id = OS::GetAppBuildId(image_address);
  if (build_id.data != nullptr) {
    ASSERT(build_id.len > 0);
    buffer->AddString(prefix);
    buffer->AddString("'");
    for (intptr_t i = 0; i < build_id.len; i++) {
      buffer->Printf("%2.2x", build_id.data[i]);
    }
    buffer->AddString("'");
  }
}

void WriteStackTraceHeaderLoadingUnitEntry(BaseTextBuffer* buffer,
                                           intptr_t id,
                                           uword dso_base,
                                           uword instructions) {
  buffer->Printf("loading_unit: %" Pd "", id);
  WriteImageBuildId(buffer, ", build_id: ", instructions);
  buffer->Printf(", dso_base: %" Px ", instructions: %" Px "\n", dso_base,
                 instructions);
}
#endif

const char* StackTrace::ToCString() const {
  auto const T = Thread::Current();
  auto const zone = T->zone();
  auto& stack_trace = StackTrace::Handle(zone, this->ptr());
  auto& owner = Object::Handle(zone);
  auto& function = Function::Handle(zone);
  auto& code_object = Object::Handle(zone);
  auto& code = Code::Handle(zone);

#if defined(DART_PRECOMPILED_RUNTIME)
  const Array& loading_units =
      Array::Handle(T->isolate_group()->object_store()->loading_units());
  auto* const unit =
      loading_units.IsNull() ? nullptr : &LoadingUnit::Handle(zone);
#endif

  NoSafepointScope no_allocation;
  GrowableArray<const Function*> inlined_functions;
  GrowableArray<TokenPosition> inlined_token_positions;

#if defined(DART_PRECOMPILED_RUNTIME)
  GrowableArray<void*> addresses(10);
  const bool have_footnote_callback =
      T->isolate_group()->dwarf_stack_traces() &&
      Dart::dwarf_stacktrace_footnote_callback() != nullptr;
#endif

  ZoneTextBuffer buffer(zone, 1024);

#if defined(DART_PRECOMPILED_RUNTIME)
  auto const isolate_instructions =
      reinterpret_cast<uword>(T->isolate_group()->source()->snapshot_text);
#if defined(DEBUG)
  if (!loading_units.IsNull()) {
    *unit ^= loading_units.At(LoadingUnit::kRootId);
    ASSERT(!unit->IsNull());
    ASSERT(unit->has_instructions_image());
    ASSERT(reinterpret_cast<uword>(unit->instructions_image()) ==
           isolate_instructions);
  }
#endif
  if (T->isolate_group()->dwarf_stack_traces()) {
    // This prologue imitates Android's debuggerd to make it possible to paste
    // the stack trace into ndk-stack.
    buffer.Printf(
        "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
    OSThread* thread = OSThread::Current();
    buffer.Printf("pid: %" Pd ", tid: %" Pd ", name %s\n", OS::ProcessId(),
                  OSThread::ThreadIdToIntPtr(thread->id()), thread->name());
#if defined(DART_COMPRESSED_POINTERS)
    const char kCompressedPointers[] = "yes";
#else
    const char kCompressedPointers[] = "no";
#endif
#if defined(DART_INCLUDE_SIMULATOR)
    const char kUsingSimulator[] = "yes";
#else
    const char kUsingSimulator[] = "no";
#endif
    buffer.Printf("os: %s arch: %s comp: %s sim: %s", kHostOperatingSystemName,
                  kTargetArchitectureName, kCompressedPointers,
                  kUsingSimulator);
    WriteImageBuildId(&buffer, "\nbuild_id: ", isolate_instructions);
    buffer.AddString("\n");
    if (!loading_units.IsNull()) {
      const intptr_t unit_count = loading_units.Length();
      for (intptr_t i = LoadingUnit::kRootId; i < unit_count; i++) {
        *unit ^= loading_units.At(i);
        if (!unit->has_instructions_image()) continue;
        const uword instructions =
            reinterpret_cast<uword>(unit->instructions_image());
        const uword dso_base = OS::GetAppDSOBase(instructions);
        WriteStackTraceHeaderLoadingUnitEntry(&buffer, i, dso_base,
                                              instructions);
      }
    }
    // Print the dso_base of the VM and isolate_instructions. We print both here
    // as the VM and isolate may be loaded from different snapshot images.
    const uword isolate_dso_base = OS::GetAppDSOBase(isolate_instructions);
    buffer.Printf("isolate_dso_base: %" Px "", isolate_dso_base);
    buffer.Printf(", vm_dso_base: 0\n");
    buffer.Printf("isolate_instructions: %" Px "", isolate_instructions);
    buffer.Printf(", vm_instructions: 0\n");
  }
#endif

  // Iterate through the stack frames and create C string description
  // for each frame.
  intptr_t frame_index = 0;
  uint32_t frame_skip = 0;
  // If we're already in a gap, don't print multiple gap markers.
  bool in_gap = false;
  do {
    for (intptr_t i = frame_skip; i < stack_trace.Length(); i++) {
      code_object = stack_trace.CodeAtFrame(i);
      if (code_object.IsNull()) {
        // Check for a null function, which indicates a gap in a StackOverflow
        // or OutOfMemory trace.
        if ((i < (stack_trace.Length() - 1)) &&
            (stack_trace.CodeAtFrame(i + 1) != Code::null())) {
          buffer.AddString("...\n...\n");
          // To account for gap frames.
          frame_index += stack_trace.PcOffsetAtFrame(i);
        }
        continue;
      }

      if (code_object.ptr() == StubCode::AsynchronousGapMarker().ptr()) {
        if (!in_gap) {
          buffer.AddString("<asynchronous suspension>\n");
        }
        in_gap = true;
        continue;
      }

      const uword pc_offset = stack_trace.PcOffsetAtFrame(i);

      // A visible frame ends any gap we might be in.
      in_gap = false;

#if defined(DART_DYNAMIC_MODULES)
      if (code_object.IsBytecode()) {
        const auto& bytecode = Bytecode::Cast(code_object);
        function = bytecode.function();

        if (!function.IsNull() &&
            (FLAG_show_invisible_frames || function.is_visible())) {
          auto const pos =
              bytecode.GetTokenIndexOfPC(bytecode.PayloadStart() + pc_offset);
          PrintSymbolicStackFrame(zone, &buffer, function, pos, frame_index);
          frame_index++;
        }
        continue;
      }
#endif  // defined(DART_DYNAMIC_MODULES)

      ASSERT(code_object.IsCode());
      code ^= code_object.ptr();
      ASSERT(code.IsFunctionCode());
      owner = code.owner();
      if (owner.IsFunction()) {
        function ^= owner.ptr();
      } else {
        function = Function::null();
      }
      const uword pc = code.PayloadStart() + pc_offset;

      const bool is_future_listener =
          pc_offset == StackTraceUtils::kFutureListenerPcOffset;

#if defined(DART_PRECOMPILED_RUNTIME)
      // When printing non-symbolic frames, we normally print call
      // addresses, not return addresses, by subtracting one from the PC to
      // get an address within the preceding instruction.
      //
      // The one exception is a normal closure registered as a listener on a
      // future. In this case, the returned pc_offset will be pointing to the
      // entry pooint of the function, which will be invoked when the future
      // completes. To make things more uniform stack unwinding code offets
      // pc_offset by 1 for such cases.
      const uword call_addr = pc - 1;

      if (T->isolate_group()->dwarf_stack_traces()) {
        if (have_footnote_callback) {
          addresses.Add(reinterpret_cast<void*>(call_addr));
        }

        // This output is formatted like Android's debuggerd. Note debuggerd
        // prints call addresses instead of return addresses.
        buffer.Printf("    #%02" Pd " abs %" Pp "", frame_index, call_addr);
        PrintNonSymbolicStackFrameBody(&buffer, call_addr, isolate_instructions,
                                       /*vm_instructions=*/0, loading_units,
                                       unit);
        frame_index++;
        continue;
      }

      if (function.IsNull()) {
        in_gap = false;
        // We can't print the symbolic information since the owner was not
        // retained, so instead print the static symbol + offset like the
        // non-symbolic stack traces.
        PrintSymbolicStackFrameIndex(&buffer, frame_index);
        PrintNonSymbolicStackFrameBody(&buffer, call_addr, isolate_instructions,
                                       /*vm_instructions=*/0, loading_units,
                                       unit);
        frame_index++;
        continue;
      }
#endif

      if (code.is_optimized() && stack_trace.expand_inlined() &&
          (FLAG_precompiled_mode || !is_future_listener)) {
        // Note: In AOT mode EmitFunctionEntrySourcePositionDescriptorIfNeeded
        // will take care of emitting a descriptor that would allow us to
        // symbolize stack frame with 0 offset.
        code.GetInlinedFunctionsAtReturnAddress(
            is_future_listener ? 0 : pc_offset, &inlined_functions,
            &inlined_token_positions);
        ASSERT(inlined_functions.length() >= 1);
        for (intptr_t j = inlined_functions.length() - 1; j >= 0; j--) {
          function = inlined_functions[j]->ptr();
          auto const pos = inlined_token_positions[j];
          if (is_future_listener && function.IsImplicitClosureFunction()) {
            function = function.parent_function();
          }
          if (FLAG_show_invisible_frames || function.is_visible()) {
            PrintSymbolicStackFrame(zone, &buffer, function, pos, frame_index,
                                    /*is_line=*/FLAG_precompiled_mode);
            frame_index++;
          }
        }
        continue;
      }

      if (FLAG_show_invisible_frames || function.is_visible() ||
          (is_future_listener && IsVisibleAsFutureListener(function))) {
        auto const pos = is_future_listener ? function.token_pos()
                                            : code.GetTokenIndexOfPC(pc);
        PrintSymbolicStackFrame(zone, &buffer, function, pos, frame_index);
        frame_index++;
      }
    }

    // Follow the link.
    frame_skip = stack_trace.skip_sync_start_in_parent_stack()
                     ? StackTrace::kSyncAsyncCroppedFrames
                     : 0;
    stack_trace = stack_trace.async_link();
  } while (!stack_trace.IsNull());

#if defined(DART_PRECOMPILED_RUNTIME)
  if (have_footnote_callback) {
    char* footnote = Dart::dwarf_stacktrace_footnote_callback()(
        &addresses[0], addresses.length());
    if (footnote != nullptr) {
      buffer.AddString(footnote);
      free(footnote);
    }
  }
#endif

  return buffer.buffer();
}

static void DwarfStackTracesHandler(bool value) {
  FLAG_dwarf_stack_traces_mode = value;

#if defined(PRODUCT)
  // We can safely remove function objects in precompiled snapshots if the
  // runtime will generate DWARF stack traces and we don't have runtime
  // debugging options like the VM service available.
  if (value) {
    FLAG_retain_function_objects = false;
    FLAG_retain_code_objects = false;
  }
#endif
}

DEFINE_FLAG_HANDLER(DwarfStackTracesHandler,
                    dwarf_stack_traces,
                    "Omit CodeSourceMaps in precompiled snapshots and don't "
                    "symbolize stack traces in the precompiled runtime.");

SuspendStatePtr SuspendState::New(intptr_t frame_size,
                                  const Instance& function_data,
                                  Heap::Space space) {
  ASSERT(frame_size >= 0);
  const intptr_t num_elements = frame_size + SuspendState::FrameSizeGrowthGap();
#if !defined(DART_PRECOMPILED_RUNTIME)
  // Include heap object alignment overhead into the frame capacity.
  const intptr_t instance_size = SuspendState::InstanceSize(num_elements);
  const intptr_t frame_capacity =
      instance_size - SuspendState::payload_offset();
  ASSERT(SuspendState::InstanceSize(frame_capacity) == instance_size);
  ASSERT(frame_size <= frame_capacity);
#endif
  auto raw = Object::Allocate<SuspendState>(space, num_elements);
  NoSafepointScope no_safepoint;
  ASSERT_EQUAL(raw->untag()->pc_, 0);
#if !defined(DART_PRECOMPILED_RUNTIME)
  raw->untag()->frame_capacity_ = frame_capacity;
#endif
  raw->untag()->frame_size_ = frame_size;
  raw->untag()->set_function_data(function_data.ptr());
  return raw;
}

SuspendStatePtr SuspendState::Clone(Thread* thread,
                                    const SuspendState& src,
                                    Heap::Space space) {
  ASSERT(src.pc() != 0);
  Zone* zone = thread->zone();
  const intptr_t frame_size = src.frame_size();
  const SuspendState& dst = SuspendState::Handle(
      zone,
      SuspendState::New(frame_size, Instance::Handle(zone, src.function_data()),
                        space));
  dst.set_then_callback(Closure::Handle(zone, src.then_callback()));
  dst.set_error_callback(Closure::Handle(zone, src.error_callback()));
  {
    NoSafepointScope no_safepoint;
    memmove(dst.payload(), src.payload(), frame_size);
    // Update value of :suspend_state variable in the copied frame.
    const uword fp = reinterpret_cast<uword>(dst.payload() + frame_size);
    *reinterpret_cast<ObjectPtr*>(
        LocalVarAddress(fp, runtime_frame_layout.FrameSlotForVariableIndex(
                                kSuspendStateVarIndex))) = dst.ptr();
    dst.set_pc(src.pc());
    // Trigger write barrier if needed.
    if (dst.ptr()->IsOldObject()) {
      dst.untag()->EnsureInRememberedSet(thread);
    }
    if (thread->is_marking()) {
      thread->DeferredMarkingStackAddObject(dst.ptr());
    }
  }
  return dst.ptr();
}

#if !defined(DART_PRECOMPILED_RUNTIME)
void SuspendState::set_frame_capacity(intptr_t frame_capcity) const {
  ASSERT(frame_capcity >= 0);
  StoreNonPointer(&untag()->frame_capacity_, frame_capcity);
}
#endif

void SuspendState::set_frame_size(intptr_t frame_size) const {
  ASSERT(frame_size >= 0);
  StoreNonPointer(&untag()->frame_size_, frame_size);
}

void SuspendState::set_pc(uword pc) const {
  StoreNonPointer(&untag()->pc_, pc);
}

void SuspendState::set_function_data(const Instance& function_data) const {
  untag()->set_function_data(function_data.ptr());
}

void SuspendState::set_then_callback(const Closure& then_callback) const {
  untag()->set_then_callback(then_callback.ptr());
}

void SuspendState::set_error_callback(const Closure& error_callback) const {
  untag()->set_error_callback(error_callback.ptr());
}

const char* SuspendState::ToCString() const {
  return "SuspendState";
}

CodePtr SuspendState::GetCodeObject() const {
  ASSERT(pc() != 0);
#if defined(DART_PRECOMPILED_RUNTIME)
  NoSafepointScope no_safepoint;
  CodePtr code = ReversePc::Lookup(IsolateGroup::Current(), pc(),
                                   /*is_return_address=*/true);
  ASSERT(code != Code::null());
  return code;
#else
  ObjectPtr code = *(reinterpret_cast<ObjectPtr*>(
      untag()->payload() + untag()->frame_size_ +
      runtime_frame_layout.code_from_fp * kWordSize));
  return Code::RawCast(code);
#endif  // defined(DART_PRECOMPILED_RUNTIME)
}

void RegExp::set_pattern(const String& pattern) const {
  untag()->set_pattern(pattern.ptr());
}

void RegExp::set_bytecode(bool is_one_byte,
                          bool sticky,
                          const TypedData& bytecode) const {
  if (sticky) {
    if (is_one_byte) {
      untag()->set_one_byte_sticky<std::memory_order_release>(bytecode.ptr());
    } else {
      untag()->set_two_byte_sticky<std::memory_order_release>(bytecode.ptr());
    }
  } else {
    if (is_one_byte) {
      untag()->set_one_byte<std::memory_order_release>(bytecode.ptr());
    } else {
      untag()->set_two_byte<std::memory_order_release>(bytecode.ptr());
    }
  }
}

void RegExp::set_capture_name_map(const Array& array) const {
  untag()->set_capture_name_map<std::memory_order_release>(array.ptr());
}

RegExpPtr RegExp::New(const String& pattern, RegExpFlags flags) {
  const auto& result = RegExp::Handle(Object::Allocate<RegExp>(Heap::kNew));
  result.set_pattern(pattern);
  result.set_flags(flags);
  result.set_num_bracket_expressions(-1);
  result.set_num_registers(/*is_one_byte=*/false, -1);
  result.set_num_registers(/*is_one_byte=*/true, -1);
  return result.ptr();
}

const char* FlagsToCString(RegExpFlags flags) {
  switch (flags & ~RegExpFlag::kGlobal) {
    case RegExpFlag::kIgnoreCase | RegExpFlag::kMultiline |
        RegExpFlag::kDotAll | RegExpFlag::kUnicode:
      return "imsu";
    case RegExpFlag::kIgnoreCase | RegExpFlag::kMultiline | RegExpFlag::kDotAll:
      return "ims";
    case RegExpFlag::kIgnoreCase | RegExpFlag::kMultiline |
        RegExpFlag::kUnicode:
      return "imu";
    case RegExpFlag::kIgnoreCase | RegExpFlag::kUnicode | RegExpFlag::kDotAll:
      return "ius";
    case RegExpFlag::kMultiline | RegExpFlag::kDotAll | RegExpFlag::kUnicode:
      return "msu";
    case RegExpFlag::kIgnoreCase | RegExpFlag::kMultiline:
      return "im";
    case RegExpFlag::kIgnoreCase | RegExpFlag::kDotAll:
      return "is";
    case RegExpFlag::kIgnoreCase | RegExpFlag::kUnicode:
      return "iu";
    case RegExpFlag::kMultiline | RegExpFlag::kDotAll:
      return "ms";
    case RegExpFlag::kMultiline | RegExpFlag::kUnicode:
      return "mu";
    case RegExpFlag::kDotAll | RegExpFlag::kUnicode:
      return "su";
    case RegExpFlags(RegExpFlag::kIgnoreCase):
      return "i";
    case RegExpFlags(RegExpFlag::kMultiline):
      return "m";
    case RegExpFlags(RegExpFlag::kDotAll):
      return "s";
    case RegExpFlags(RegExpFlag::kUnicode):
      return "u";
    default:
      break;
  }
  return "";
}

bool RegExp::CanonicalizeEquals(const Instance& other) const {
  if (this->ptr() == other.ptr()) {
    return true;  // "===".
  }
  if (other.IsNull() || !other.IsRegExp()) {
    return false;
  }
  const RegExp& other_js = RegExp::Cast(other);
  // Match the pattern.
  const String& str1 = String::Handle(pattern());
  const String& str2 = String::Handle(other_js.pattern());
  if (!str1.Equals(str2)) {
    return false;
  }
  // Match the flags.
  if (flags() != other_js.flags()) {
    return false;
  }
  return true;
}

uint32_t RegExp::CanonicalizeHash() const {
  // Must agree with RegExpKey::Hash.
  return CombineHashes(String::Hash(pattern()), flags());
}

const char* RegExp::ToCString() const {
  const String& str = String::Handle(pattern());
  return OS::SCreate(Thread::Current()->zone(), "RegExp: pattern=%s flags=%s",
                     str.ToCString(), FlagsToCString(flags()));
}

WeakPropertyPtr WeakProperty::New(Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->weak_property_class() !=
         Class::null());
  return Object::Allocate<WeakProperty>(space);
}

const char* WeakProperty::ToCString() const {
  return "_WeakProperty";
}

WeakReferencePtr WeakReference::New(Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->weak_reference_class() !=
         Class::null());
  return Object::Allocate<WeakReference>(space);
}
const char* WeakReference::ToCString() const {
  TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments());
  String& type_args_name = String::Handle(type_args.UserVisibleName());
  return OS::SCreate(Thread::Current()->zone(), "_WeakReference%s",
                     type_args_name.ToCString());
}

const char* FinalizerBase::ToCString() const {
  return "FinalizerBase";
}

FinalizerPtr Finalizer::New(Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->finalizer_class() !=
         Class::null());
  ASSERT(
      Class::Handle(IsolateGroup::Current()->object_store()->finalizer_class())
          .EnsureIsAllocateFinalized(Thread::Current()) == Error::null());
  return Object::Allocate<Finalizer>(space);
}

const char* Finalizer::ToCString() const {
  TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments());
  String& type_args_name = String::Handle(type_args.UserVisibleName());
  return OS::SCreate(Thread::Current()->zone(), "_FinalizerImpl%s",
                     type_args_name.ToCString());
}

NativeFinalizerPtr NativeFinalizer::New(Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->native_finalizer_class() !=
         Class::null());
  ASSERT(Class::Handle(
             IsolateGroup::Current()->object_store()->native_finalizer_class())
             .EnsureIsAllocateFinalized(Thread::Current()) == Error::null());
  return Object::Allocate<NativeFinalizer>(space);
}

// Runs the finalizer if not detached, detaches the value and set external size
// to 0.
// TODO(http://dartbug.com/47777): Can this be merged with
// RunNativeFinalizerCallback?
void NativeFinalizer::RunCallback(const FinalizerEntry& entry,
                                  const char* trace_context) const {
  Thread* const thread = Thread::Current();
  Zone* const zone = thread->zone();
  IsolateGroup* const group = thread->isolate_group();
  const intptr_t external_size = entry.external_size();
  const auto& token_object = Object::Handle(zone, entry.token());
  const auto& callback_pointer = Pointer::Handle(zone, this->callback());
  const auto callback = reinterpret_cast<NativeFinalizer::Callback>(
      callback_pointer.NativeAddress());
  if (token_object.IsFinalizerEntry()) {
    // Detached from Dart code.
    ASSERT(token_object.ptr() == entry.ptr());
    ASSERT(external_size == 0);
    if (FLAG_trace_finalizers) {
      THR_Print(
          "%s: Not running native finalizer %p callback %p, "
          "detached\n",
          trace_context, ptr()->untag(), callback);
    }
  } else {
    const auto& token = Pointer::Cast(token_object);
    void* peer = reinterpret_cast<void*>(token.NativeAddress());
    if (FLAG_trace_finalizers) {
      THR_Print(
          "%s: Running native finalizer %p callback %p "
          "with token %p\n",
          trace_context, ptr()->untag(), callback, peer);
    }
    entry.set_token(entry);
    callback(peer);
    if (external_size > 0) {
      ASSERT(!entry.value()->IsSmi());
      Heap::Space space =
          entry.value()->IsOldObject() ? Heap::kOld : Heap::kNew;
      if (FLAG_trace_finalizers) {
        THR_Print("%s: Clearing external size %" Pd " bytes in %s space\n",
                  trace_context, external_size, space == 0 ? "new" : "old");
      }
      group->heap()->FreedExternal(external_size, space);
      entry.set_external_size(0);
    }
  }
}

const char* NativeFinalizer::ToCString() const {
  const auto& pointer = Pointer::Handle(callback());
  return OS::SCreate(Thread::Current()->zone(), "_NativeFinalizer %s",
                     pointer.ToCString());
}

FinalizerEntryPtr FinalizerEntry::New(const FinalizerBase& finalizer,
                                      Heap::Space space) {
  ASSERT(IsolateGroup::Current()->object_store()->finalizer_entry_class() !=
         Class::null());
  const auto& entry =
      FinalizerEntry::Handle(Object::Allocate<FinalizerEntry>(space));
  ASSERT_EQUAL(entry.external_size(), 0);
  entry.set_finalizer(finalizer);
  return entry.ptr();
}

void FinalizerEntry::set_finalizer(const FinalizerBase& value) const {
  untag()->set_finalizer(value.ptr());
}

const char* FinalizerEntry::ToCString() const {
  return "FinalizerEntry";
}

AbstractTypePtr MirrorReference::GetAbstractTypeReferent() const {
  ASSERT(Object::Handle(referent()).IsAbstractType());
  return AbstractType::Cast(Object::Handle(referent())).ptr();
}

ClassPtr MirrorReference::GetClassReferent() const {
  ASSERT(Object::Handle(referent()).IsClass());
  return Class::Cast(Object::Handle(referent())).ptr();
}

FieldPtr MirrorReference::GetFieldReferent() const {
  ASSERT(Object::Handle(referent()).IsField());
  return Field::Cast(Object::Handle(referent())).ptr();
}

FunctionPtr MirrorReference::GetFunctionReferent() const {
  ASSERT(Object::Handle(referent()).IsFunction());
  return Function::Cast(Object::Handle(referent())).ptr();
}

FunctionTypePtr MirrorReference::GetFunctionTypeReferent() const {
  ASSERT(Object::Handle(referent()).IsFunctionType());
  return FunctionType::Cast(Object::Handle(referent())).ptr();
}

LibraryPtr MirrorReference::GetLibraryReferent() const {
  ASSERT(Object::Handle(referent()).IsLibrary());
  return Library::Cast(Object::Handle(referent())).ptr();
}

TypeParameterPtr MirrorReference::GetTypeParameterReferent() const {
  ASSERT(Object::Handle(referent()).IsTypeParameter());
  return TypeParameter::Cast(Object::Handle(referent())).ptr();
}

MirrorReferencePtr MirrorReference::New(const Object& referent,
                                        Heap::Space space) {
  const auto& result =
      MirrorReference::Handle(Object::Allocate<MirrorReference>(space));
  result.set_referent(referent);
  return result.ptr();
}

const char* MirrorReference::ToCString() const {
  return "_MirrorReference";
}

UserTagPtr UserTag::MakeActive() const {
  Thread* thread = Thread::Current();
  ASSERT(thread != nullptr);
  UserTag& old = UserTag::Handle(thread->current_tag());
  thread->set_current_tag(*this);

#if !defined(PRODUCT)
  // Notify VM service clients that the current UserTag has changed.
  if (Service::profiler_stream.enabled()) {
    Isolate* isolate = thread->scheduled_dart_mutator_isolate();
    if (isolate != nullptr) {
      ServiceEvent event(isolate, ServiceEvent::kUserTagChanged);
      String& name = String::Handle(old.label());
      event.set_previous_tag(name.ToCString());
      name ^= label();
      event.set_updated_tag(name.ToCString());
      Service::HandleEvent(&event);
    }
  }
#endif  // !defined(PRODUCT)

  return old.ptr();
}

UserTagPtr UserTag::New(Thread* thread,
                        const String& label,
                        Heap::Space space) {
  auto isolate_group = thread->isolate_group();
  // Canonicalize by name.
  {
    SafepointReadRwLocker ml(thread, isolate_group->tag_table_lock());
    ASSERT(isolate_group->object_store()->tag_table() !=
           GrowableObjectArray::null());
    UserTag& result =
        UserTag::Handle(FindTagInIsolateGroup(isolate_group, thread, label));
    if (!result.IsNull()) {
      // Tag already exists, return existing instance.
      return result.ptr();
    }
  }
  SafepointWriteRwLocker mlw(thread, isolate_group->tag_table_lock());
  UserTag& result =
      UserTag::Handle(FindTagInIsolateGroup(isolate_group, thread, label));
  if (!result.IsNull()) {
    // Tag already exists, return existing instance.
    return result.ptr();
  }
  if (TagTableIsFull(thread)) {
    const String& error = String::Handle(String::NewFormatted(
        "UserTag instance limit (%" Pd ") reached.", UserTags::kMaxUserTags));
    const Array& args = Array::Handle(Array::New(1));
    args.SetAt(0, error);
    Exceptions::ThrowByType(Exceptions::kUnsupported, args);
  }
  // No tag with label exists, create and register with thread tag table.
  result = Object::Allocate<UserTag>(space);
  result.set_label(label);
  AddTagToIsolateGroup(thread, result);
  return result.ptr();
}

UserTagPtr UserTag::DefaultTag(Thread* thread) {
  Zone* zone = thread->zone();
  if (thread->default_tag() != UserTag::null()) {
    // Already created.
    return thread->default_tag();
  }
  // Create default tag.
  const UserTag& result =
      UserTag::Handle(zone, UserTag::New(thread, Symbols::Default()));
  ASSERT(result.tag() == UserTags::kDefaultUserTag);
  thread->set_default_tag(result);
  return result.ptr();
}

UserTagPtr UserTag::FindTagInIsolateGroup(IsolateGroup* isolate_group,
                                          Thread* thread,
                                          const String& label) {
  Zone* zone = thread->zone();
  if (isolate_group->object_store()->tag_table() ==
      GrowableObjectArray::null()) {
    return UserTag::null();
  }
  const GrowableObjectArray& tag_table = GrowableObjectArray::Handle(
      zone, isolate_group->object_store()->tag_table());
  UserTag& other = UserTag::Handle(zone);
  String& tag_label = String::Handle(zone);
  for (intptr_t i = 0; i < tag_table.Length(); i++) {
    other ^= tag_table.At(i);
    ASSERT(!other.IsNull());
    tag_label = other.label();
    ASSERT(!tag_label.IsNull());
    if (tag_label.Equals(label)) {
      return other.ptr();
    }
  }
  return UserTag::null();
}

void UserTag::AddTagToIsolateGroup(Thread* thread, const UserTag& tag) {
  auto isolate_group = thread->isolate_group();
  Zone* zone = thread->zone();
  ASSERT(isolate_group->object_store()->tag_table() !=
         GrowableObjectArray::null());
  const GrowableObjectArray& tag_table = GrowableObjectArray::Handle(
      zone, isolate_group->object_store()->tag_table());
  ASSERT(!TagTableIsFull(thread));
#if defined(DEBUG)
  // Verify that no existing tag has the same tag id.
  UserTag& other = UserTag::Handle(thread->zone());
  for (intptr_t i = 0; i < tag_table.Length(); i++) {
    other ^= tag_table.At(i);
    ASSERT(!other.IsNull());
    ASSERT(tag.tag() != other.tag());
  }
#endif
  // Generate the UserTag tag id by taking the length of the isolate group's
  // tag table + kUserTagIdOffset.
  uword tag_id = tag_table.Length() + UserTags::kUserTagIdOffset;
  ASSERT(tag_id >= UserTags::kUserTagIdOffset);
  ASSERT(tag_id < (UserTags::kUserTagIdOffset + UserTags::kMaxUserTags));
  tag.set_tag(tag_id);
  tag_table.Add(tag);
}

bool UserTag::TagTableIsFull(Thread* thread) {
  auto isolate_group = thread->isolate_group();
  ASSERT(isolate_group->object_store()->tag_table() !=
         GrowableObjectArray::null());
  const GrowableObjectArray& tag_table = GrowableObjectArray::Handle(
      thread->zone(), isolate_group->object_store()->tag_table());
  ASSERT(tag_table.Length() <= UserTags::kMaxUserTags);
  return tag_table.Length() == UserTags::kMaxUserTags;
}

UserTagPtr UserTag::FindTagById(const IsolateGroup* isolate_group,
                                uword tag_id) {
  ASSERT(isolate_group != nullptr);
  auto thread = Thread::Current();
  Zone* zone = thread->zone();
  ASSERT(isolate_group->object_store()->tag_table() !=
         GrowableObjectArray::null());
  const GrowableObjectArray& tag_table = GrowableObjectArray::Handle(
      zone, isolate_group->object_store()->tag_table());
  UserTag& tag = UserTag::Handle(zone);
  for (intptr_t i = 0; i < tag_table.Length(); i++) {
    tag ^= tag_table.At(i);
    if (tag.tag() == tag_id) {
      return tag.ptr();
    }
  }
  return UserTag::null();
}

const char* UserTag::ToCString() const {
  const String& tag_label = String::Handle(label());
  return tag_label.ToCString();
}

void DumpTypeTable(Isolate* isolate) {
  OS::PrintErr("canonical types:\n");
  CanonicalTypeSet table(isolate->group()->object_store()->canonical_types());
  table.Dump();
  table.Release();
}

void DumpFunctionTypeTable(Isolate* isolate) {
  OS::PrintErr("canonical function types:\n");
  CanonicalFunctionTypeSet table(
      isolate->group()->object_store()->canonical_function_types());
  table.Dump();
  table.Release();
}

void DumpRecordTypeTable(Isolate* isolate) {
  OS::PrintErr("canonical record types:\n");
  CanonicalRecordTypeSet table(
      isolate->group()->object_store()->canonical_record_types());
  table.Dump();
  table.Release();
}

void DumpTypeParameterTable(Isolate* isolate) {
  OS::PrintErr("canonical type parameters (cloned from declarations):\n");
  CanonicalTypeParameterSet table(
      isolate->group()->object_store()->canonical_type_parameters());
  table.Dump();
  table.Release();
}

void DumpTypeArgumentsTable(Isolate* isolate) {
  OS::PrintErr("canonical type arguments:\n");
  CanonicalTypeArgumentsSet table(
      isolate->group()->object_store()->canonical_type_arguments());
  table.Dump();
  table.Release();
}

EntryPointPragma FindEntryPointPragma(IsolateGroup* IG,
                                      const Array& metadata,
                                      Field* reusable_field_handle,
                                      Object* pragma) {
  for (intptr_t i = 0; i < metadata.Length(); i++) {
    *pragma = metadata.At(i);
    if (pragma->clazz() != IG->object_store()->pragma_class()) {
      continue;
    }
    *reusable_field_handle = IG->object_store()->pragma_name();
    const auto pragma_name =
        Instance::Cast(*pragma).GetField(*reusable_field_handle);

    if (pragma_name == Symbols::dyn_module_can_be_used_as_type().ptr()) {
      return EntryPointPragma::kTypeOnly;
    }

    if ((pragma_name != Symbols::vm_entry_point().ptr()) &&
        (pragma_name != Symbols::dyn_module_callable().ptr()) &&
        (pragma_name != Symbols::dyn_module_implicitly_callable().ptr()) &&
        (pragma_name != Symbols::dyn_module_extendable().ptr()) &&
        (pragma_name != Symbols::dyn_module_dynamically_callable().ptr()) &&
        (pragma_name !=
         Symbols::dyn_module_implicitly_dynamically_callable().ptr())) {
      continue;
    }
    *reusable_field_handle = IG->object_store()->pragma_options();
    *pragma = Instance::Cast(*pragma).GetField(*reusable_field_handle);
    if (pragma->ptr() == Bool::null() || pragma->ptr() == Bool::True().ptr()) {
      return EntryPointPragma::kAlways;
      break;
    }
    if (pragma->ptr() == Symbols::get().ptr()) {
      return EntryPointPragma::kGetterOnly;
    }
    if (pragma->ptr() == Symbols::set().ptr()) {
      return EntryPointPragma::kSetterOnly;
    }
    if (pragma->ptr() == Symbols::call().ptr()) {
      return EntryPointPragma::kCallOnly;
    }
  }
  return EntryPointPragma::kNever;
}

#if !defined(DART_PRECOMPILED_RUNTIME)
// Note: see also [NeedsDynamicInvocationForwarder] which ensures that we
// never land in a function which expects parameters in registers from a
// dynamic call site.
intptr_t Function::MaxNumberOfParametersInRegisters(Zone* zone) const {
#if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64) ||                  \
    defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_RISCV32) ||                \
    defined(TARGET_ARCH_RISCV64)
  if (!FLAG_precompiled_mode) {
    return 0;
  }

  if (!FLAG_use_register_cc) {
    return 0;
  }

  if (IsGeneric()) {
    return 0;
  }

  switch (kind()) {
    case UntaggedFunction::kClosureFunction:
    case UntaggedFunction::kImplicitClosureFunction:
    case UntaggedFunction::kNoSuchMethodDispatcher:
    case UntaggedFunction::kInvokeFieldDispatcher:
    case UntaggedFunction::kDynamicInvocationForwarder:
    case UntaggedFunction::kMethodExtractor:
    case UntaggedFunction::kFfiTrampoline:
    case UntaggedFunction::kFieldInitializer:
    case UntaggedFunction::kIrregexpFunction:
      return 0;

    default:
      break;
  }

  const auto unboxing_metadata = kernel::UnboxingInfoMetadataOf(*this, zone);
  if (unboxing_metadata != nullptr &&
      unboxing_metadata->must_use_stack_calling_convention) {
    return 0;
  }

  // Getters and setters have fixed signatures.
  switch (kind()) {
    case UntaggedFunction::kGetterFunction:
    case UntaggedFunction::kImplicitGetter:
    case UntaggedFunction::kSetterFunction:
    case UntaggedFunction::kImplicitSetter:
      return num_fixed_parameters();

    default:
      break;
  }

  if (unboxing_metadata != nullptr &&
      unboxing_metadata->has_overrides_with_less_direct_parameters) {
    // Receiver (`this`) can always be passed in the register because it is
    // never an optional or named parameter.
    return unboxing_metadata->unboxed_args_info.length() + 1;
  }

  return num_fixed_parameters();
#endif
  return 0;
}
#endif  // !defined(DART_PRECOMPILED_RUNTIME)

ErrorPtr Function::VerifyEntryPoint(EntryPointPragma pragma) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  const Library& lib = Library::Handle();
#else
  const Class& cls = Class::Handle(Owner());
  const Library& lib = Library::Handle(cls.library());
#endif
  return dart::VerifyEntryPoint(lib, *this, *this, pragma);
}

ErrorPtr Field::VerifyEntryPoint(EntryPointPragma pragma) const {
#if defined(DART_PRECOMPILED_RUNTIME)
  const Library& lib = Library::Handle();
#else
  const Class& cls = Class::Handle(Owner());
  const Library& lib = Library::Handle(cls.library());
#endif
  return dart::VerifyEntryPoint(lib, *this, *this, pragma);
}

ErrorPtr Class::VerifyEntryPoint() const {
#if defined(DART_PRECOMPILED_RUNTIME)
  const Library& lib = Library::Handle();
#else
  const Library& lib = Library::Handle(library());
  if (lib.IsNull()) return Error::null();
#endif
  return dart::VerifyEntryPoint(lib, *this, *this, EntryPointPragma::kAlways);
}

AbstractTypePtr RecordType::FieldTypeAt(intptr_t index) const {
  const Array& field_types = Array::Handle(untag()->field_types());
  return AbstractType::RawCast(field_types.At(index));
}

void RecordType::SetFieldTypeAt(intptr_t index,
                                const AbstractType& value) const {
  ASSERT(!value.IsNull());
  const Array& field_types = Array::Handle(untag()->field_types());
  field_types.SetAt(index, value);
}

void RecordType::set_field_types(const Array& value) const {
  ASSERT(!value.IsNull());
  untag()->set_field_types(value.ptr());
}

void RecordType::set_shape(RecordShape shape) const {
  untag()->set_shape(shape.AsSmi());
}

ArrayPtr RecordType::GetFieldNames(Thread* thread) const {
  return shape().GetFieldNames(thread);
}

void RecordType::Print(NameVisibility name_visibility,
                       BaseTextBuffer* printer) const {
  if (IsNull()) {
    printer->AddString("null");
    return;
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  AbstractType& type = AbstractType::Handle(zone);
  String& name = String::Handle(zone);
  const intptr_t num_fields = NumFields();
  const Array& field_names = Array::Handle(zone, GetFieldNames(thread));
  const intptr_t num_positional_fields = num_fields - field_names.Length();
  printer->AddString("(");
  for (intptr_t i = 0; i < num_fields; ++i) {
    if (i != 0) {
      printer->AddString(", ");
    }
    if (i == num_positional_fields) {
      printer->AddString("{");
    }
    type = FieldTypeAt(i);
    type.PrintName(name_visibility, printer);
    if (i >= num_positional_fields) {
      printer->AddString(" ");
      name ^= field_names.At(i - num_positional_fields);
      printer->AddString(name.ToCString());
    }
  }
  if (num_positional_fields < num_fields) {
    printer->AddString("}");
  }
  printer->AddString(")");
  printer->AddString(NullabilitySuffix(name_visibility));
}

const char* RecordType::ToCString() const {
  Zone* zone = Thread::Current()->zone();
  ZoneTextBuffer printer(zone);
  Print(kInternalName, &printer);
  return printer.buffer();
}

bool RecordType::IsInstantiated(Genericity genericity,
                                intptr_t num_free_fun_type_params) const {
  AbstractType& type = AbstractType::Handle();
  const intptr_t num_fields = NumFields();
  for (intptr_t i = 0; i < num_fields; ++i) {
    type = FieldTypeAt(i);
    if (!type.IsInstantiated(genericity, num_free_fun_type_params)) {
      return false;
    }
  }
  return true;
}

RecordTypePtr RecordType::New(Heap::Space space) {
  return Object::Allocate<RecordType>(space);
}

RecordTypePtr RecordType::New(RecordShape shape,
                              const Array& field_types,
                              Nullability nullability,
                              Heap::Space space) {
  Zone* Z = Thread::Current()->zone();
  const RecordType& result = RecordType::Handle(Z, RecordType::New(space));
  result.set_shape(shape);
  result.set_field_types(field_types);
  result.SetHash(0);
  result.set_flags(0);
  result.set_nullability(nullability);
  result.set_type_state(UntaggedAbstractType::kAllocated);
  result.InitializeTypeTestingStubNonAtomic(
      Code::Handle(Z, TypeTestingStubGenerator::DefaultCodeForType(result)));
  return result.ptr();
}

RecordTypePtr RecordType::ToNullability(Nullability value,
                                        Heap::Space space) const {
  if (nullability() == value) {
    return ptr();
  }
  // Clone record type and set new nullability.
  // Always cloning in old space and removing space parameter would not satisfy
  // currently existing requests for type instantiation in new space.
  Thread* T = Thread::Current();
  Zone* Z = T->zone();
  AbstractType& type = RecordType::Handle(
      Z,
      RecordType::New(shape(), Array::Handle(Z, field_types()), value, space));
  if (IsFinalized()) {
    type.SetIsFinalized();
    if (IsCanonical()) {
      type ^= type.Canonicalize(T);
    }
  }
  return RecordType::Cast(type).ptr();
}

bool RecordType::IsEquivalent(
    const Instance& other,
    TypeEquality kind,
    FunctionTypeMapping* function_type_equivalence) const {
  ASSERT(!IsNull());
  if (ptr() == other.ptr()) {
    return true;
  }
  if (!other.IsRecordType()) {
    return false;
  }
  const RecordType& other_type = RecordType::Cast(other);
  // Equal record types must have the same shape
  // (number of fields and named fields).
  if (shape() != other_type.shape()) {
    return false;
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  if (!IsNullabilityEquivalent(thread, other_type, kind)) {
    return false;
  }
  // Equal record types must have equal field types.
  AbstractType& field_type = Type::Handle(zone);
  AbstractType& other_field_type = Type::Handle(zone);
  const intptr_t num_fields = NumFields();
  for (intptr_t i = 0; i < num_fields; ++i) {
    field_type = FieldTypeAt(i);
    other_field_type = other_type.FieldTypeAt(i);
    if (!field_type.IsEquivalent(other_field_type, kind,
                                 function_type_equivalence)) {
      return false;
    }
  }
  return true;
}

uword RecordType::ComputeHash() const {
  ASSERT(IsFinalized());
  uint32_t result = 0;
  result = CombineHashes(result, static_cast<uint32_t>(nullability()));
  result = CombineHashes(result, static_cast<uint32_t>(shape().AsInt()));
  AbstractType& type = AbstractType::Handle();
  const intptr_t num_fields = NumFields();
  for (intptr_t i = 0; i < num_fields; ++i) {
    type = FieldTypeAt(i);
    result = CombineHashes(result, type.Hash());
  }
  result = FinalizeHash(result, kHashBits);
  SetHash(result);
  return result;
}

AbstractTypePtr RecordType::Canonicalize(Thread* thread) const {
  ASSERT(IsFinalized());
  Zone* zone = thread->zone();
  AbstractType& type = AbstractType::Handle(zone);
  if (IsCanonical()) {
#ifdef DEBUG
    // Verify that all fields are allocated in old space and are canonical.
    ASSERT(Array::Handle(zone, field_types()).IsOld());
    const intptr_t num_fields = NumFields();
    for (intptr_t i = 0; i < num_fields; ++i) {
      type = FieldTypeAt(i);
      ASSERT(type.IsOld());
      ASSERT(type.IsCanonical());
    }
#endif
    return ptr();
  }
  auto isolate_group = thread->isolate_group();
  ObjectStore* object_store = isolate_group->object_store();
  RecordType& rec = RecordType::Handle(zone);
  {
    SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
    CanonicalRecordTypeSet table(zone, object_store->canonical_record_types());
    rec ^= table.GetOrNull(CanonicalRecordTypeKey(*this));
    ASSERT(object_store->canonical_record_types() == table.Release().ptr());
  }
  if (rec.IsNull()) {
    ASSERT(Array::Handle(zone, field_types()).IsOld());
    const intptr_t num_fields = NumFields();
    for (intptr_t i = 0; i < num_fields; ++i) {
      type = FieldTypeAt(i);
      if (!type.IsCanonical()) {
        type = type.Canonicalize(thread);
        SetFieldTypeAt(i, type);
      }
    }
    // Check to see if the record type got added to canonical table as part
    // of the canonicalization of its signature types.
    SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
    CanonicalRecordTypeSet table(zone, object_store->canonical_record_types());
    rec ^= table.GetOrNull(CanonicalRecordTypeKey(*this));
    if (rec.IsNull()) {
      // Add this record type into the canonical table of record types.
      if (this->IsNew()) {
        rec ^= Object::Clone(*this, Heap::kOld);
      } else {
        rec = this->ptr();
      }
      ASSERT(rec.IsOld());
      rec.SetCanonical();  // Mark object as being canonical.
      bool present = table.Insert(rec);
      ASSERT(!present);
    }
    object_store->set_canonical_record_types(table.Release());
  }
  return rec.ptr();
}

void RecordType::EnumerateURIs(URIs* uris) const {
  AbstractType& type = AbstractType::Handle();
  const intptr_t num_fields = NumFields();
  for (intptr_t i = 0; i < num_fields; ++i) {
    type = FieldTypeAt(i);
    type.EnumerateURIs(uris);
  }
}

void RecordType::PrintName(NameVisibility name_visibility,
                           BaseTextBuffer* printer) const {
  RecordType::Cast(*this).Print(name_visibility, printer);
}

AbstractTypePtr RecordType::InstantiateFrom(
    const TypeArguments& instantiator_type_arguments,
    const TypeArguments& function_type_arguments,
    intptr_t num_free_fun_type_params,
    Heap::Space space,
    FunctionTypeMapping* function_type_mapping,
    intptr_t num_parent_type_args_adjustment) const {
  ASSERT(IsFinalized());
  Zone* zone = Thread::Current()->zone();

  const intptr_t num_fields = NumFields();
  const Array& old_field_types = Array::Handle(zone, field_types());
  const Array& new_field_types =
      Array::Handle(zone, Array::New(num_fields, space));
  AbstractType& type = AbstractType::Handle(zone);
  for (intptr_t i = 0; i < num_fields; ++i) {
    type ^= old_field_types.At(i);
    if (!type.IsInstantiated()) {
      type = type.InstantiateFrom(
          instantiator_type_arguments, function_type_arguments,
          num_free_fun_type_params, space, function_type_mapping,
          num_parent_type_args_adjustment);
      // A returned null type indicates a failed instantiation in dead code that
      // must be propagated up to the caller, the optimizing compiler.
      if (type.IsNull()) {
        return RecordType::null();
      }
    }
    new_field_types.SetAt(i, type);
  }

  const auto& rec = RecordType::Handle(
      zone, RecordType::New(shape(), new_field_types, nullability(), space));

  rec.SetIsFinalized();

  // Canonicalization is not part of instantiation.
  return rec.ptr();
}

AbstractTypePtr RecordType::UpdateFunctionTypes(
    intptr_t num_parent_type_args_adjustment,
    intptr_t num_free_fun_type_params,
    Heap::Space space,
    FunctionTypeMapping* function_type_mapping) const {
  ASSERT(IsFinalized());
  ASSERT(num_parent_type_args_adjustment >= 0);
  Zone* zone = Thread::Current()->zone();
  const auto& types = Array::Handle(zone, field_types());
  Array* updated_types = nullptr;
  auto& type = AbstractType::Handle(zone);
  auto& updated = AbstractType::Handle(zone);
  for (intptr_t i = 0, n = NumFields(); i < n; ++i) {
    type ^= types.At(i);
    updated = type.UpdateFunctionTypes(num_parent_type_args_adjustment,
                                       num_free_fun_type_params, space,
                                       function_type_mapping);
    if (type.ptr() != updated.ptr()) {
      if (updated_types == nullptr) {
        updated_types = &Array::Handle(zone, Array::New(n, space));
        for (intptr_t j = 0; j < i; ++j) {
          type ^= types.At(j);
          updated_types->SetAt(j, type);
        }
      }
    }
    if (updated_types != nullptr) {
      updated_types->SetAt(i, updated);
    }
  }
  if (updated_types == nullptr) {
    return ptr();
  }
  const auto& new_rt = RecordType::Handle(
      zone, RecordType::New(shape(), *updated_types, nullability(), space));
  new_rt.SetIsFinalized();
  return new_rt.ptr();
}

bool RecordType::IsSubtypeOf(
    const RecordType& other,
    Heap::Space space,
    FunctionTypeMapping* function_type_equivalence) const {
  if (ptr() == other.ptr()) {
    return true;
  }
  ASSERT(IsFinalized());
  ASSERT(other.IsFinalized());
  const intptr_t num_fields = NumFields();
  if (shape() != other.shape()) {
    // Different number of fields or different named fields.
    return false;
  }
  Thread* const thread = Thread::Current();
  if (!IsNullabilityEquivalent(thread, other, TypeEquality::kInSubtypeTest)) {
    return false;
  }
  // Check subtyping of record field types.
  Zone* const zone = thread->zone();
  AbstractType& field_type = Type::Handle(zone);
  AbstractType& other_field_type = Type::Handle(zone);
  for (intptr_t i = 0; i < num_fields; ++i) {
    field_type = FieldTypeAt(i);
    other_field_type = other.FieldTypeAt(i);
    if (!field_type.IsSubtypeOf(other_field_type, space,
                                function_type_equivalence)) {
      return false;
    }
  }
  return true;
}

RecordPtr Record::New(RecordShape shape, Heap::Space space) {
  const intptr_t num_fields = shape.num_fields();
  ASSERT(num_fields >= 0);
  auto raw = Object::Allocate<Record>(space, num_fields);
  NoSafepointScope no_safepoint;
  raw->untag()->set_shape(shape.AsSmi());
  return raw;
}

const char* Record::ToCString() const {
  if (IsNull()) {
    return "Record: null";
  }
  Thread* thread = Thread::Current();
  Zone* zone = thread->zone();
  ZoneTextBuffer printer(zone);
  const intptr_t num_fields = this->num_fields();
  const Array& field_names = Array::Handle(zone, GetFieldNames(thread));
  const intptr_t num_positional_fields = num_fields - field_names.Length();
  Object& obj = Object::Handle(zone);
  printer.AddString("Record (");
  for (intptr_t i = 0; i < num_fields; ++i) {
    if (i != 0) {
      printer.AddString(", ");
    }
    if (i >= num_positional_fields) {
      obj = field_names.At(i - num_positional_fields);
      printer.AddString(obj.ToCString());
      printer.AddString(": ");
    }
    obj = FieldAt(i);
    printer.AddString(obj.ToCString());
  }
  printer.AddString(")");
  return printer.buffer();
}

bool Record::CanonicalizeEquals(const Instance& other) const {
  if (this->ptr() == other.ptr()) {
    return true;
  }

  if (!other.IsRecord() || other.IsNull()) {
    return false;
  }

  const Record& other_rec = Record::Cast(other);
  if (shape() != other_rec.shape()) {
    return false;
  }

  const intptr_t num_fields = this->num_fields();
  for (intptr_t i = 0; i < num_fields; ++i) {
    if (this->FieldAt(i) != other_rec.FieldAt(i)) {
      return false;
    }
  }
  return true;
}

uint32_t Record::CanonicalizeHash() const {
  Thread* thread = Thread::Current();
  uint32_t hash = thread->heap()->GetCanonicalHash(ptr());
  if (hash != 0) {
    return hash;
  }
  hash = shape().AsInt();
  Instance& element = Instance::Handle();
  const intptr_t num_fields = this->num_fields();
  for (intptr_t i = 0; i < num_fields; ++i) {
    element ^= FieldAt(i);
    hash = CombineHashes(hash, element.CanonicalizeHash());
  }
  hash = FinalizeHash(hash, kHashBits);
  thread->heap()->SetCanonicalHash(ptr(), hash);
  return hash;
}

void Record::CanonicalizeFieldsLocked(Thread* thread) const {
  Zone* zone = thread->zone();
  Instance& obj = Instance::Handle(zone);
  const intptr_t num_fields = this->num_fields();
  for (intptr_t i = 0; i < num_fields; ++i) {
    obj ^= FieldAt(i);
    obj = obj.CanonicalizeLocked(thread);
    SetFieldAt(i, obj);
  }
}

RecordTypePtr Record::GetRecordType(TypeVisibility visibility) const {
  Zone* const zone = Thread::Current()->zone();
  const intptr_t num_fields = this->num_fields();
  const Array& field_types =
      Array::Handle(zone, Array::New(num_fields, Heap::kOld));
  Instance& obj = Instance::Handle(zone);
  AbstractType& type = AbstractType::Handle(zone);
  for (intptr_t i = 0; i < num_fields; ++i) {
    obj ^= FieldAt(i);
    type = obj.GetType(Heap::kNew, visibility);
    field_types.SetAt(i, type);
  }
  type = RecordType::New(shape(), field_types, Nullability::kNonNullable);
  type = ClassFinalizer::FinalizeType(type);
  return RecordType::Cast(type).ptr();
}

intptr_t Record::GetPositionalFieldIndexFromFieldName(
    const String& field_name) {
  if (field_name.IsOneByteString() && field_name.Length() >= 1 &&
      field_name.CharAt(0) == '$') {
    int64_t value = 0;
    const char* cstr = field_name.ToCString();
    if (OS::StringToInt64(cstr + 1 /* skip '$' */, &value)) {
      if (value >= 1 && value < kMaxElements) {
        return static_cast<intptr_t>(value - 1);
      }
    }
  }
  return -1;
}

intptr_t Record::GetFieldIndexByName(Thread* thread,
                                     const String& field_name) const {
  ASSERT(field_name.IsSymbol());
  const intptr_t field_index =
      Record::GetPositionalFieldIndexFromFieldName(field_name);
  const Array& field_names = Array::Handle(GetFieldNames(thread));
  const intptr_t num_positional_fields = num_fields() - field_names.Length();
  if ((field_index >= 0) && (field_index < num_positional_fields)) {
    return field_index;
  } else {
    for (intptr_t i = 0, n = field_names.Length(); i < n; ++i) {
      if (field_names.At(i) == field_name.ptr()) {
        return num_positional_fields + i;
      }
    }
  }
  return -1;
}

class RecordFieldNamesMapTraits {
 public:
  static const char* Name() { return "RecordFieldNamesMapTraits"; }
  static bool ReportStats() { return false; }

  static bool IsMatch(const Object& a, const Object& b) {
    return Array::Cast(a).CanonicalizeEquals(Array::Cast(b));
  }

  static uword Hash(const Object& key) {
    return Array::Cast(key).CanonicalizeHash();
  }

  static ObjectPtr NewKey(const Array& arr) { return arr.ptr(); }
};
typedef UnorderedHashMap<RecordFieldNamesMapTraits> RecordFieldNamesMap;

RecordShape RecordShape::Register(Thread* thread,
                                  intptr_t num_fields,
                                  const Array& field_names) {
  ASSERT(!field_names.IsNull());
  ASSERT(field_names.IsImmutable());
  ASSERT(field_names.ptr() == Object::empty_array().ptr() ||
         field_names.Length() > 0);

  Zone* zone = thread->zone();
  IsolateGroup* isolate_group = thread->isolate_group();
  ObjectStore* object_store = isolate_group->object_store();

  if (object_store->record_field_names() == Array::null()) {
    // First-time initialization.
    SafepointWriteRwLocker ml(thread, isolate_group->program_lock());
    if (object_store->record_field_names() == Array::null()) {
      // Reserve record field names index 0 for records without named fields.
      RecordFieldNamesMap map(
          HashTables::New<RecordFieldNamesMap>(16, Heap::kOld));
      map.InsertOrGetValue(Object::empty_array(),
                           Smi::Handle(zone, Smi::New(0)));
      ASSERT(map.NumOccupied() == 1);
      object_store->set_record_field_names_map(map.Release());
      const auto& table = Array::Handle(zone, Array::New(16));
      table.SetAt(0, Object::empty_array());
      object_store->set_record_field_names(table);
    }
  }

#if defined(DART_PRECOMPILER)
  const intptr_t kMaxNumFields = compiler::target::RecordShape::kMaxNumFields;
  const intptr_t kMaxFieldNamesIndex =
      compiler::target::RecordShape::kMaxFieldNamesIndex;
#else
  const intptr_t kMaxNumFields = RecordShape::kMaxNumFields;
  const intptr_t kMaxFieldNamesIndex = RecordShape::kMaxFieldNamesIndex;
#endif

  if (num_fields > kMaxNumFields) {
    FATAL("Too many record fields");
  }
  if (field_names.ptr() == Object::empty_array().ptr()) {
    return RecordShape::ForUnnamed(num_fields);
  }

  {
    SafepointReadRwLocker ml(thread, isolate_group->program_lock());
    RecordFieldNamesMap map(object_store->record_field_names_map());
    Smi& index = Smi::Handle(zone);
    index ^= map.GetOrNull(field_names);
    ASSERT(map.Release().ptr() == object_store->record_field_names_map());
    if (!index.IsNull()) {
      return RecordShape(num_fields, index.Value());
    }
  }

  SafepointWriteRwLocker ml(thread, isolate_group->program_lock());
  RecordFieldNamesMap map(object_store->record_field_names_map());
  const intptr_t new_index = map.NumOccupied();
  if (new_index > kMaxFieldNamesIndex) {
    FATAL("Too many record shapes");
  }

  const intptr_t index = Smi::Value(Smi::RawCast(map.InsertOrGetValue(
      field_names, Smi::Handle(zone, Smi::New(new_index)))));
  ASSERT(index > 0);

  if (index == new_index) {
    ASSERT(map.NumOccupied() == (new_index + 1));
    Array& table = Array::Handle(zone, object_store->record_field_names());
    intptr_t capacity = table.Length();
    if (index >= table.Length()) {
      capacity = capacity + (capacity >> 2);
      table = Array::Grow(table, capacity);
      object_store->set_record_field_names(table);
    }
    table.SetAt(index, field_names);
  } else {
    ASSERT(index < new_index);
  }
  object_store->set_record_field_names_map(map.Release());

  const RecordShape shape(num_fields, index);
  ASSERT(Array::Handle(shape.GetFieldNames(thread))
             .CanonicalizeEquals(field_names));
  ASSERT(shape.num_fields() == num_fields);
  return shape;
}

ArrayPtr RecordShape::GetFieldNames(Thread* thread) const {
  ObjectStore* object_store = thread->isolate_group()->object_store();
  Array& table =
      Array::Handle(thread->zone(), object_store->record_field_names());
  ASSERT(!table.IsNull());
  return Array::RawCast(table.At(field_names_index()));
}

}  // namespace dart
