text: // 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.
  
  #ifndef RUNTIME_VM_OBJECT_H_
  #define RUNTIME_VM_OBJECT_H_
  
  #if defined(SHOULD_NOT_INCLUDE_RUNTIME)
  #error "Should not include runtime"
  #endif
  
  #include <limits>
  #include <tuple>
  
  #include "include/dart_api.h"
  #include "platform/assert.h"
  #include "platform/atomic.h"
  #include "platform/thread_sanitizer.h"
  #include "platform/utils.h"
  #include "vm/bitmap.h"
  #include "vm/code_comments.h"
  #include "vm/code_entry_kind.h"
  #include "vm/compiler/assembler/object_pool_builder.h"
  #include "vm/compiler/method_recognizer.h"
  #include "vm/compiler/runtime_api.h"
  #include "vm/dart.h"
  #include "vm/flags.h"
  #include "vm/globals.h"
  #include "vm/growable_array.h"
  #include "vm/handles.h"
  #include "vm/heap/heap.h"
  #include "vm/isolate.h"
  #include "vm/json_stream.h"
  #include "vm/os.h"
  #include "vm/raw_object.h"
  #include "vm/report.h"
  #include "vm/static_type_exactness_state.h"
  #include "vm/tags.h"
  #include "vm/thread.h"
  #include "vm/token_position.h"
  
  namespace dart {
  
  // Forward declarations.
  namespace compiler {
  class Assembler;
  }
  
  namespace kernel {
  class Program;
  class TreeNode;
  }  // namespace kernel
  
  #define DEFINE_FORWARD_DECLARATION(clazz) class clazz;
  CLASS_LIST(DEFINE_FORWARD_DECLARATION)
  #undef DEFINE_FORWARD_DECLARATION
  class Api;
  class ArgumentsDescriptor;
  class Closure;
  class Code;
  class DeoptInstr;
  class DisassemblyFormatter;
  class FinalizablePersistentHandle;
  class FlowGraphCompiler;
  class HierarchyInfo;
  class LocalScope;
  class CallSiteResetter;
  class CodeStatistics;
  class IsolateGroupReloadContext;
  class ObjectGraphCopier;
  
  #define REUSABLE_FORWARD_DECLARATION(name) class Reusable##name##HandleScope;
  REUSABLE_HANDLE_LIST(REUSABLE_FORWARD_DECLARATION)
  #undef REUSABLE_FORWARD_DECLARATION
  
  class Symbols;
  class BaseTextBuffer;
  
  #if defined(DEBUG)
  #define CHECK_HANDLE() CheckHandle();
  #else
  #define CHECK_HANDLE()
  #endif
  
  // For AllStatic classes like OneByteString. Checks that
  // ContainsCompressedPointers() returns the same value for AllStatic class and
  // class used for handles.
  #define ALLSTATIC_CONTAINS_COMPRESSED_IMPLEMENTATION(object, handle)           \
    static_assert(std::is_base_of<dart::handle##Ptr, dart::object##Ptr>::value,  \
                  #object "Ptr must be a subtype of " #handle "Ptr");            \
    static_assert(dart::handle::ContainsCompressedPointers() ==                  \
                      dart::Untagged##object::kContainsCompressedPointers,       \
                  "Pointer compression in Untagged" #object                      \
                  " must match pointer compression in Untagged" #handle);        \
    static constexpr bool ContainsCompressedPointers() {                         \
      return dart::Untagged##object::kContainsCompressedPointers;                \
    }
  
  #define BASE_OBJECT_IMPLEMENTATION(object, super)                              \
   public: /* NOLINT */                                                          \
    using UntaggedObjectType = dart::Untagged##object;                           \
    using ObjectPtrType = dart::object##Ptr;                                     \
    static_assert(!dart::super::ContainsCompressedPointers() ||                  \
                      UntaggedObjectType::kContainsCompressedPointers,           \
                  "Untagged" #object                                             \
                  " must have compressed pointers, as supertype Untagged" #super \
                  " has compressed pointers");                                   \
    static constexpr bool ContainsCompressedPointers() {                         \
      return UntaggedObjectType::kContainsCompressedPointers;                    \
    }                                                                            \
    object##Ptr ptr() const { return static_cast<object##Ptr>(ptr_); }           \
    bool Is##object() const { return true; }                                     \
    DART_NOINLINE static object& Handle() {                                      \
      return static_cast<object&>(                                               \
          HandleImpl(Thread::Current()->zone(), object::null(), kClassId));      \
    }                                                                            \
    DART_NOINLINE static object& Handle(Zone* zone) {                            \
      return static_cast<object&>(HandleImpl(zone, object::null(), kClassId));   \
    }                                                                            \
    DART_NOINLINE static object& Handle(object##Ptr ptr) {                       \
      return static_cast<object&>(                                               \
          HandleImpl(Thread::Current()->zone(), ptr, kClassId));                 \
    }                                                                            \
    DART_NOINLINE static object& Handle(Zone* zone, object##Ptr ptr) {           \
      return static_cast<object&>(HandleImpl(zone, ptr, kClassId));              \
    }                                                                            \
    DART_NOINLINE static object& ZoneHandle() {                                  \
      return static_cast<object&>(                                               \
          ZoneHandleImpl(Thread::Current()->zone(), object::null(), kClassId));  \
    }                                                                            \
    DART_NOINLINE static object& ZoneHandle(Zone* zone) {                        \
      return static_cast<object&>(                                               \
          ZoneHandleImpl(zone, object::null(), kClassId));                       \
    }                                                                            \
    DART_NOINLINE static object& ZoneHandle(object##Ptr ptr) {                   \
      return static_cast<object&>(                                               \
          ZoneHandleImpl(Thread::Current()->zone(), ptr, kClassId));             \
    }                                                                            \
    DART_NOINLINE static object& ZoneHandle(Zone* zone, object##Ptr ptr) {       \
      return static_cast<object&>(ZoneHandleImpl(zone, ptr, kClassId));          \
    }                                                                            \
    static object* ReadOnlyHandle() {                                            \
      return static_cast<object*>(ReadOnlyHandleImpl(kClassId));                 \
    }                                                                            \
    DART_NOINLINE static object& CheckedHandle(Zone* zone, ObjectPtr ptr) {      \
      object* obj = reinterpret_cast<object*>(VMHandles::AllocateHandle(zone));  \
      initializeHandle(obj, ptr);                                                \
      if (!obj->Is##object()) {                                                  \
        FATAL2("Handle check failed: saw %s expected %s", obj->ToCString(),      \
               #object);                                                         \
      }                                                                          \
      return *obj;                                                               \
    }                                                                            \
    DART_NOINLINE static object& CheckedZoneHandle(Zone* zone, ObjectPtr ptr) {  \
      object* obj =                                                              \
          reinterpret_cast<object*>(VMHandles::AllocateZoneHandle(zone));        \
      initializeHandle(obj, ptr);                                                \
      if (!obj->Is##object()) {                                                  \
        FATAL2("Handle check failed: saw %s expected %s", obj->ToCString(),      \
               #object);                                                         \
      }                                                                          \
      return *obj;                                                               \
    }                                                                            \
    DART_NOINLINE static object& CheckedZoneHandle(ObjectPtr ptr) {              \
      return CheckedZoneHandle(Thread::Current()->zone(), ptr);                  \
    }                                                                            \
    /* T::Cast cannot be applied to a null Object, because the object vtable */  \
    /* is not setup for type T, although some methods are supposed to work   */  \
    /* with null, for example Instance::Equals().                            */  \
    static const object& Cast(const Object& obj) {                               \
      ASSERT(obj.Is##object());                                                  \
      return reinterpret_cast<const object&>(obj);                               \
    }                                                                            \
    static object##Ptr RawCast(ObjectPtr raw) {                                  \
      ASSERT(Object::Handle(raw).IsNull() || Object::Handle(raw).Is##object());  \
      return static_cast<object##Ptr>(raw);                                      \
    }                                                                            \
    static object##Ptr null() {                                                  \
      return static_cast<object##Ptr>(Object::null());                           \
    }                                                                            \
    virtual const char* ToCString() const;                                       \
    static const ClassId kClassId = k##object##Cid;                              \
                                                                                 \
   private: /* NOLINT */                                                         \
    /* Initialize the handle based on the ptr in the presence of null. */        \
    static void initializeHandle(object* obj, ObjectPtr ptr) {                   \
      obj->SetPtr(ptr, kClassId);                                                \
    }                                                                            \
    /* Disallow allocation, copy constructors and override super assignment. */  \
   public: /* NOLINT */                                                          \
    void operator delete(void* pointer) { UNREACHABLE(); }                       \
                                                                                 \
   private: /* NOLINT */                                                         \
    void* operator new(size_t size);                                             \
    object(const object& value) = delete;                                        \
    void operator=(super##Ptr value) = delete;                                   \
    void operator=(const object& value) = delete;                                \
    void operator=(const super& value) = delete;
  
  // Conditionally include object_service.cc functionality in the vtable to avoid
  // link errors like the following:
  //
  // object.o:(.rodata._ZTVN4....E[_ZTVN4...E]+0x278):
  // undefined reference to
  // `dart::Instance::PrintSharedInstanceJSON(dart::JSONObject*, bool) const'.
  //
  #ifndef PRODUCT
  #define OBJECT_SERVICE_SUPPORT(object)                                         \
   protected: /* NOLINT */                                                       \
    /* Object is printed as JSON into stream. If ref is true only a header */    \
    /* with an object id is printed. If ref is false the object is fully   */    \
    /* printed.                                                            */    \
    virtual void PrintJSONImpl(JSONStream* stream, bool ref) const;              \
    virtual const char* JSONType() const { return "" #object; }
  #else
  #define OBJECT_SERVICE_SUPPORT(object) protected: /* NOLINT */
  #endif                                            // !PRODUCT
  
  #define SNAPSHOT_SUPPORT(object)                                               \
    friend class object##MessageSerializationCluster;                            \
    friend class object##MessageDeserializationCluster;
  
  #define OBJECT_IMPLEMENTATION(object, super)                                   \
   public: /* NOLINT */                                                          \
    DART_NOINLINE void operator=(object##Ptr value) {                            \
      initializeHandle(this, value);                                             \
    }                                                                            \
    DART_NOINLINE void operator^=(ObjectPtr value) {                             \
      initializeHandle(this, value);                                             \
      ASSERT(IsNull() || Is##object());                                          \
    }                                                                            \
                                                                                 \
   protected: /* NOLINT */                                                       \
    object() : super() {}                                                        \
    BASE_OBJECT_IMPLEMENTATION(object, super)                                    \
    OBJECT_SERVICE_SUPPORT(object)                                               \
    friend class Object;
  
  #define HEAP_OBJECT_IMPLEMENTATION(object, super)                              \
    OBJECT_IMPLEMENTATION(object, super);                                        \
    Untagged##object* untag() const {                                            \
      ASSERT(ptr() != null());                                                   \
      return const_cast<Untagged##object*>(ptr()->untag());                      \
    }                                                                            \
    SNAPSHOT_SUPPORT(object)                                                     \
    friend class StackFrame;                                                     \
    friend class Thread;
  
  // This macro is used to denote types that do not have a sub-type.
  #define FINAL_HEAP_OBJECT_IMPLEMENTATION_HELPER(object, rettype, super)        \
   public: /* NOLINT */                                                          \
    void operator=(object##Ptr value) {                                          \
      ptr_ = value;                                                              \
      CHECK_HANDLE();                                                            \
    }                                                                            \
    void operator^=(ObjectPtr value) {                                           \
      ptr_ = value;                                                              \
      CHECK_HANDLE();                                                            \
    }                                                                            \
                                                                                 \
   private: /* NOLINT */                                                         \
    object() : super() {}                                                        \
    BASE_OBJECT_IMPLEMENTATION(object, super)                                    \
    OBJECT_SERVICE_SUPPORT(object)                                               \
    Untagged##object* untag() const {                                            \
      ASSERT(ptr() != null());                                                   \
      return const_cast<Untagged##object*>(ptr()->untag());                      \
    }                                                                            \
    static intptr_t NextFieldOffset() { return -kWordSize; }                     \
    SNAPSHOT_SUPPORT(rettype)                                                    \
    friend class Object;                                                         \
    friend class StackFrame;                                                     \
    friend class Thread;
  
  #define FINAL_HEAP_OBJECT_IMPLEMENTATION(object, super)                        \
    FINAL_HEAP_OBJECT_IMPLEMENTATION_HELPER(object, object, super)
  
  #define MINT_OBJECT_IMPLEMENTATION(object, rettype, super)                     \
    FINAL_HEAP_OBJECT_IMPLEMENTATION_HELPER(object, rettype, super)
  
  // In precompiled runtime, there is no access to runtime_api.cc since host
  // and target are the same. In those cases, the namespace dart is used to refer
  // to the target namespace
  #if defined(DART_PRECOMPILED_RUNTIME)
  namespace RTN = dart;
  #else
  namespace RTN = dart::compiler::target;
  #endif  //  defined(DART_PRECOMPILED_RUNTIME)
  
  class Object {
   public:
    using UntaggedObjectType = UntaggedObject;
    using ObjectPtrType = ObjectPtr;
  
    // We use 30 bits for the hash code so hashes in a snapshot taken on a
    // 64-bit architecture stay in Smi range when loaded on a 32-bit
    // architecture.
    static const intptr_t kHashBits = 30;
  
    static ObjectPtr RawCast(ObjectPtr obj) { return obj; }
  
    virtual ~Object() {}
  
    static constexpr bool ContainsCompressedPointers() {
      return UntaggedObject::kContainsCompressedPointers;
    }
    ObjectPtr ptr() const { return ptr_; }
    void operator=(ObjectPtr value) { initializeHandle(this, value); }
  
    bool IsCanonical() const { return ptr()->untag()->IsCanonical(); }
    void SetCanonical() const { ptr()->untag()->SetCanonical(); }
    void ClearCanonical() const { ptr()->untag()->ClearCanonical(); }
    intptr_t GetClassId() const {
      return !ptr()->IsHeapObject() ? static_cast<intptr_t>(kSmiCid)
                                    : ptr()->untag()->GetClassId();
    }
    inline ClassPtr clazz() const;
    static intptr_t tags_offset() { return OFFSET_OF(UntaggedObject, tags_); }
  
  // Class testers.
  #define DEFINE_CLASS_TESTER(clazz)                                             \
    virtual bool Is##clazz() const { return false; }
    CLASS_LIST_FOR_HANDLES(DEFINE_CLASS_TESTER);
  #undef DEFINE_CLASS_TESTER
  
    bool IsNull() const { return ptr_ == null_; }
  
    // Matches Object.toString on instances (except String::ToCString, bug 20583).
    virtual const char* ToCString() const {
      if (IsNull()) {
        return "null";
      } else {
        return "Object";
      }
    }
  
  #ifndef PRODUCT
    void PrintJSON(JSONStream* stream, bool ref = true) const;
    virtual void PrintJSONImpl(JSONStream* stream, bool ref) const;
    virtual const char* JSONType() const { return IsNull() ? "null" : "Object"; }
  #endif
  
    // Returns the name that is used to identify an object in the
    // namespace dictionary.
    // Object::DictionaryName() returns String::null(). Only subclasses
    // of Object that need to be entered in the library and library prefix
    // namespaces need to provide an implementation.
    virtual StringPtr DictionaryName() const;
  
    bool IsNew() const { return ptr()->IsNewObject(); }
    bool IsOld() const { return ptr()->IsOldObject(); }
  #if defined(DEBUG)
    bool InVMIsolateHeap() const;
  #else
    bool InVMIsolateHeap() const { return ptr()->untag()->InVMIsolateHeap(); }
  #endif  // DEBUG
  
    // Print the object on stdout for debugging.
    void Print() const;
  
    bool IsZoneHandle() const {
      return VMHandles::IsZoneHandle(reinterpret_cast<uword>(this));
    }
  
    bool IsReadOnlyHandle() const;
  
    bool IsNotTemporaryScopedHandle() const;
  
    static Object& Handle(Zone* zone, ObjectPtr ptr) {
      Object* obj = reinterpret_cast<Object*>(VMHandles::AllocateHandle(zone));
      initializeHandle(obj, ptr);
      return *obj;
    }
    static Object* ReadOnlyHandle() {
      Object* obj = reinterpret_cast<Object*>(Dart::AllocateReadOnlyHandle());
      initializeHandle(obj, Object::null());
      return obj;
    }
  
    static Object& Handle() { return Handle(Thread::Current()->zone(), null_); }
  
    static Object& Handle(Zone* zone) { return Handle(zone, null_); }
  
    static Object& Handle(ObjectPtr ptr) {
      return Handle(Thread::Current()->zone(), ptr);
    }
  
    static Object& ZoneHandle(Zone* zone, ObjectPtr ptr) {
      Object* obj =
          reinterpret_cast<Object*>(VMHandles::AllocateZoneHandle(zone));
      initializeHandle(obj, ptr);
      return *obj;
    }
  
    static Object& ZoneHandle(Zone* zone) { return ZoneHandle(zone, null_); }
  
    static Object& ZoneHandle() {
      return ZoneHandle(Thread::Current()->zone(), null_);
    }
  
    static Object& ZoneHandle(ObjectPtr ptr) {
      return ZoneHandle(Thread::Current()->zone(), ptr);
    }
  
    static ObjectPtr null() { return null_; }
  
  #if defined(HASH_IN_OBJECT_HEADER)
    static uint32_t GetCachedHash(const ObjectPtr obj) {
      return obj->untag()->GetHeaderHash();
    }
  
    static uint32_t SetCachedHashIfNotSet(ObjectPtr obj, uint32_t hash) {
      return obj->untag()->SetHeaderHashIfNotSet(hash);
    }
  #endif
  
    // The list below enumerates read-only handles for singleton
    // objects that are shared between the different isolates.
    //
    // - sentinel is a value that cannot be produced by Dart code. It can be used
    // to mark special values, for example to distinguish "uninitialized" fields.
    // - transition_sentinel is a value marking that we are transitioning from
    // sentinel, e.g., computing a field value. Used to detect circular
    // initialization.
    // - unknown_constant and non_constant are optimizing compiler's constant
    // propagation constants.
  #define SHARED_READONLY_HANDLES_LIST(V)                                        \
    V(Object, null_object)                                                       \
    V(Class, null_class)                                                         \
    V(Array, null_array)                                                         \
    V(String, null_string)                                                       \
    V(Instance, null_instance)                                                   \
    V(Function, null_function)                                                   \
    V(FunctionType, null_function_type)                                          \
    V(TypeArguments, null_type_arguments)                                        \
    V(CompressedStackMaps, null_compressed_stackmaps)                            \
    V(TypeArguments, empty_type_arguments)                                       \
    V(Array, empty_array)                                                        \
    V(Array, zero_array)                                                         \
    V(ContextScope, empty_context_scope)                                         \
    V(ObjectPool, empty_object_pool)                                             \
    V(CompressedStackMaps, empty_compressed_stackmaps)                           \
    V(PcDescriptors, empty_descriptors)                                          \
    V(LocalVarDescriptors, empty_var_descriptors)                                \
    V(ExceptionHandlers, empty_exception_handlers)                               \
    V(Array, extractor_parameter_types)                                          \
    V(Array, extractor_parameter_names)                                          \
    V(Sentinel, sentinel)                                                        \
    V(Sentinel, transition_sentinel)                                             \
    V(Sentinel, unknown_constant)                                                \
    V(Sentinel, non_constant)                                                    \
    V(Bool, bool_true)                                                           \
    V(Bool, bool_false)                                                          \
    V(Smi, smi_illegal_cid)                                                      \
    V(Smi, smi_zero)                                                             \
    V(ApiError, typed_data_acquire_error)                                        \
    V(LanguageError, snapshot_writer_error)                                      \
    V(LanguageError, branch_offset_error)                                        \
    V(LanguageError, speculative_inlining_error)                                 \
    V(LanguageError, background_compilation_error)                               \
    V(LanguageError, out_of_memory_error)                                        \
    V(Array, vm_isolate_snapshot_object_table)                                   \
    V(Type, dynamic_type)                                                        \
    V(Type, void_type)                                                           \
    V(AbstractType, null_abstract_type)
  
  #define DEFINE_SHARED_READONLY_HANDLE_GETTER(Type, name)                       \
    static const Type& name() {                                                  \
      ASSERT(name##_ != nullptr);                                                \
      return *name##_;                                                           \
    }
    SHARED_READONLY_HANDLES_LIST(DEFINE_SHARED_READONLY_HANDLE_GETTER)
  #undef DEFINE_SHARED_READONLY_HANDLE_GETTER
  
    static void set_vm_isolate_snapshot_object_table(const Array& table);
  
    static ClassPtr class_class() { return class_class_; }
    static ClassPtr dynamic_class() { return dynamic_class_; }
    static ClassPtr void_class() { return void_class_; }
    static ClassPtr type_parameters_class() { return type_parameters_class_; }
    static ClassPtr type_arguments_class() { return type_arguments_class_; }
    static ClassPtr patch_class_class() { return patch_class_class_; }
    static ClassPtr function_class() { return function_class_; }
    static ClassPtr closure_data_class() { return closure_data_class_; }
    static ClassPtr ffi_trampoline_data_class() {
      return ffi_trampoline_data_class_;
    }
    static ClassPtr field_class() { return field_class_; }
    static ClassPtr script_class() { return script_class_; }
    static ClassPtr library_class() { return library_class_; }
    static ClassPtr namespace_class() { return namespace_class_; }
    static ClassPtr kernel_program_info_class() {
      return kernel_program_info_class_;
    }
    static ClassPtr code_class() { return code_class_; }
    static ClassPtr instructions_class() { return instructions_class_; }
    static ClassPtr instructions_section_class() {
      return instructions_section_class_;
    }
    static ClassPtr instructions_table_class() {
      return instructions_table_class_;
    }
    static ClassPtr object_pool_class() { return object_pool_class_; }
    static ClassPtr pc_descriptors_class() { return pc_descriptors_class_; }
    static ClassPtr code_source_map_class() { return code_source_map_class_; }
    static ClassPtr compressed_stackmaps_class() {
      return compressed_stackmaps_class_;
    }
    static ClassPtr var_descriptors_class() { return var_descriptors_class_; }
    static ClassPtr exception_handlers_class() {
      return exception_handlers_class_;
    }
    static ClassPtr deopt_info_class() { return deopt_info_class_; }
    static ClassPtr context_class() { return context_class_; }
    static ClassPtr context_scope_class() { return context_scope_class_; }
    static ClassPtr sentinel_class() { return sentinel_class_; }
    static ClassPtr api_error_class() { return api_error_class_; }
    static ClassPtr language_error_class() { return language_error_class_; }
    static ClassPtr unhandled_exception_class() {
      return unhandled_exception_class_;
    }
    static ClassPtr unwind_error_class() { return unwind_error_class_; }
    static ClassPtr singletargetcache_class() { return singletargetcache_class_; }
    static ClassPtr unlinkedcall_class() { return unlinkedcall_class_; }
    static ClassPtr monomorphicsmiablecall_class() {
      return monomorphicsmiablecall_class_;
    }
    static ClassPtr icdata_class() { return icdata_class_; }
    static ClassPtr megamorphic_cache_class() { return megamorphic_cache_class_; }
    static ClassPtr subtypetestcache_class() { return subtypetestcache_class_; }
    static ClassPtr loadingunit_class() { return loadingunit_class_; }
    static ClassPtr weak_serialization_reference_class() {
      return weak_serialization_reference_class_;
    }
  
    // Initialize the VM isolate.
    static void InitNullAndBool(IsolateGroup* isolate_group);
    static void Init(IsolateGroup* isolate_group);
    static void InitVtables();
    static void FinishInit(IsolateGroup* isolate_group);
    static void FinalizeVMIsolate(IsolateGroup* isolate_group);
    static void FinalizeReadOnlyObject(ObjectPtr object);
  
    static void Cleanup();
  
    // Initialize a new isolate either from a Kernel IR, from source, or from a
    // snapshot.
    static ErrorPtr Init(IsolateGroup* isolate_group,
                         const uint8_t* kernel_buffer,
                         intptr_t kernel_buffer_size);
  
    static void MakeUnusedSpaceTraversable(const Object& obj,
                                           intptr_t original_size,
                                           intptr_t used_size);
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedObject));
    }
  
    template <class FakeObject>
    static void VerifyBuiltinVtable(intptr_t cid) {
      FakeObject fake;
      if (cid >= kNumPredefinedCids) {
        cid = kInstanceCid;
      }
      ASSERT(builtin_vtables_[cid] == fake.vtable());
    }
    static void VerifyBuiltinVtables();
  
    static const ClassId kClassId = kObjectCid;
  
    // Different kinds of name visibility.
    enum NameVisibility {
      // Internal names are the true names of classes, fields,
      // etc. inside the vm.  These names include privacy suffixes,
      // getter prefixes, and trailing dots on unnamed constructors.
      //
      // The names of core implementation classes (like _OneByteString)
      // are preserved as well.
      //
      // e.g.
      //   private getter             -> get:foo@6be832b
      //   private constructor        -> _MyClass@6b3832b.
      //   private named constructor  -> _MyClass@6b3832b.named
      //   core impl class name shown -> _OneByteString
      kInternalName = 0,
  
      // Scrubbed names drop privacy suffixes, getter prefixes, and
      // trailing dots on unnamed constructors.  These names are used in
      // the vm service.
      //
      // e.g.
      //   get:foo@6be832b        -> foo
      //   _MyClass@6b3832b.      -> _MyClass
      //   _MyClass@6b3832b.named -> _MyClass.named
      //   _OneByteString         -> _OneByteString (not remapped)
      kScrubbedName,
  
      // User visible names are appropriate for reporting type errors
      // directly to programmers.  The names have been scrubbed and
      // the names of core implementation classes are remapped to their
      // public interface names.
      //
      // e.g.
      //   get:foo@6be832b        -> foo
      //   _MyClass@6b3832b.      -> _MyClass
      //   _MyClass@6b3832b.named -> _MyClass.named
      //   _OneByteString         -> String (remapped)
      kUserVisibleName
    };
  
    // Sometimes simple formating might produce the same name for two different
    // entities, for example we might inject a synthetic forwarder into the
    // class which has the same name as an already existing function, or
    // two different types can be formatted as X<T> because T has different
    // meaning (refers to a different type parameter) in these two types.
    // Such ambiguity might be acceptable in some contexts but not in others, so
    // some formatting methods have two modes - one which tries to be more
    // user friendly, and another one which tries to avoid name conflicts by
    // emitting longer and less user friendly names.
    enum class NameDisambiguation {
      kYes,
      kNo,
    };
  
   protected:
    friend ObjectPtr AllocateObject(intptr_t, intptr_t);
  
    // Used for extracting the C++ vtable during bringup.
    Object() : ptr_(null_) {}
  
    uword raw_value() const { return static_cast<uword>(ptr()); }
  
    inline void SetPtr(ObjectPtr value, intptr_t default_cid);
    void CheckHandle() const;
    DART_NOINLINE static Object& HandleImpl(Zone* zone,
                                            ObjectPtr ptr,
                                            intptr_t default_cid) {
      Object* obj = reinterpret_cast<Object*>(VMHandles::AllocateHandle(zone));
      obj->SetPtr(ptr, default_cid);
      return *obj;
    }
    DART_NOINLINE static Object& ZoneHandleImpl(Zone* zone,
                                                ObjectPtr ptr,
                                                intptr_t default_cid) {
      Object* obj =
          reinterpret_cast<Object*>(VMHandles::AllocateZoneHandle(zone));
      obj->SetPtr(ptr, default_cid);
      return *obj;
    }
    DART_NOINLINE static Object* ReadOnlyHandleImpl(intptr_t cid) {
      Object* obj = reinterpret_cast<Object*>(Dart::AllocateReadOnlyHandle());
      obj->SetPtr(Object::null(), cid);
      return obj;
    }
  
    cpp_vtable vtable() const { return bit_copy<cpp_vtable>(*this); }
    void set_vtable(cpp_vtable value) { *vtable_address() = value; }
  
    static ObjectPtr Allocate(intptr_t cls_id,
                              intptr_t size,
                              Heap::Space space,
                              bool compressed);
  
    static intptr_t RoundedAllocationSize(intptr_t size) {
      return Utils::RoundUp(size, kObjectAlignment);
    }
  
    bool Contains(uword addr) const { return ptr()->untag()->Contains(addr); }
  
    // Start of field mutator guards.
    //
    // All writes to heap objects should ultimately pass through one of the
    // methods below or their counterparts in RawObject, to ensure that the
    // write barrier is correctly applied.
  
    template <typename type, std::memory_order order = std::memory_order_relaxed>
    type LoadPointer(type const* addr) const {
      return ptr()->untag()->LoadPointer<type, order>(addr);
    }
  
    template <typename type, std::memory_order order = std::memory_order_relaxed>
    void StorePointer(type const* addr, type value) const {
      ptr()->untag()->StorePointer<type, order>(addr, value);
    }
    template <typename type,
              typename compressed_type,
              std::memory_order order = std::memory_order_relaxed>
    void StoreCompressedPointer(compressed_type const* addr, type value) const {
      ptr()->untag()->StoreCompressedPointer<type, compressed_type, order>(addr,
                                                                           value);
    }
  
    // Use for storing into an explicitly Smi-typed field of an object
    // (i.e., both the previous and new value are Smis).
    void StoreSmi(SmiPtr const* addr, SmiPtr value) const {
      ptr()->untag()->StoreSmi(addr, value);
    }
  
    template <typename FieldType>
    void StoreSimd128(const FieldType* addr, simd128_value_t value) const {
      ASSERT(Contains(reinterpret_cast<uword>(addr)));
      value.writeTo(const_cast<FieldType*>(addr));
    }
  
    template <typename FieldType>
    FieldType LoadNonPointer(const FieldType* addr) const {
      return *const_cast<FieldType*>(addr);
    }
  
    template <typename FieldType, std::memory_order order>
    FieldType LoadNonPointer(const FieldType* addr) const {
      return reinterpret_cast<std::atomic<FieldType>*>(
                 const_cast<FieldType*>(addr))
          ->load(order);
    }
  
    // Needs two template arguments to allow assigning enums to fixed-size ints.
    template <typename FieldType, typename ValueType>
    void StoreNonPointer(const FieldType* addr, ValueType value) const {
      // Can't use Contains, as it uses tags_, which is set through this method.
      ASSERT(reinterpret_cast<uword>(addr) >= UntaggedObject::ToAddr(ptr()));
      *const_cast<FieldType*>(addr) = value;
    }
  
    template <typename FieldType, typename ValueType, std::memory_order order>
    void StoreNonPointer(const FieldType* addr, ValueType value) const {
      // Can't use Contains, as it uses tags_, which is set through this method.
      ASSERT(reinterpret_cast<uword>(addr) >= UntaggedObject::ToAddr(ptr()));
      reinterpret_cast<std::atomic<FieldType>*>(const_cast<FieldType*>(addr))
          ->store(value, order);
    }
  
    // Provides non-const access to non-pointer fields within the object. Such
    // access does not need a write barrier, but it is *not* GC-safe, since the
    // object might move, hence must be fully contained within a NoSafepointScope.
    template <typename FieldType>
    FieldType* UnsafeMutableNonPointer(const FieldType* addr) const {
      // Allow pointers at the end of variable-length data, and disallow pointers
      // within the header word.
      ASSERT(Contains(reinterpret_cast<uword>(addr) - 1) &&
             Contains(reinterpret_cast<uword>(addr) - kWordSize));
      // At least check that there is a NoSafepointScope and hope it's big enough.
      ASSERT(Thread::Current()->no_safepoint_scope_depth() > 0);
      return const_cast<FieldType*>(addr);
    }
  
  // Fail at link time if StoreNonPointer or UnsafeMutableNonPointer is
  // instantiated with an object pointer type.
  #define STORE_NON_POINTER_ILLEGAL_TYPE(type)                                   \
    template <typename ValueType>                                                \
    void StoreNonPointer(type##Ptr const* addr, ValueType value) const {         \
      UnimplementedMethod();                                                     \
    }                                                                            \
    type##Ptr* UnsafeMutableNonPointer(type##Ptr const* addr) const {            \
      UnimplementedMethod();                                                     \
      return NULL;                                                               \
    }
  
    CLASS_LIST(STORE_NON_POINTER_ILLEGAL_TYPE);
    void UnimplementedMethod() const;
  #undef STORE_NON_POINTER_ILLEGAL_TYPE
  
    // Allocate an object and copy the body of 'orig'.
    static ObjectPtr Clone(const Object& orig,
                           Heap::Space space,
                           bool load_with_relaxed_atomics = false);
  
    // End of field mutator guards.
  
    ObjectPtr ptr_;  // The raw object reference.
  
   protected:
    void AddCommonObjectProperties(JSONObject* jsobj,
                                   const char* protocol_type,
                                   bool ref) const;
  
   private:
    static intptr_t NextFieldOffset() {
      // Indicates this class cannot be extended by dart code.
      return -kWordSize;
    }
  
    static void InitializeObject(uword address,
                                 intptr_t id,
                                 intptr_t size,
                                 bool compressed);
  
    static void RegisterClass(const Class& cls,
                              const String& name,
                              const Library& lib);
    static void RegisterPrivateClass(const Class& cls,
                                     const String& name,
                                     const Library& lib);
  
    /* Initialize the handle based on the ptr in the presence of null. */
    static void initializeHandle(Object* obj, ObjectPtr ptr) {
      obj->SetPtr(ptr, kObjectCid);
    }
  
    cpp_vtable* vtable_address() const {
      uword vtable_addr = reinterpret_cast<uword>(this);
      return reinterpret_cast<cpp_vtable*>(vtable_addr);
    }
  
    static cpp_vtable builtin_vtables_[kNumPredefinedCids];
  
    // The static values below are singletons shared between the different
    // isolates. They are all allocated in the non-GC'd Dart::vm_isolate_.
    static ObjectPtr null_;
    static BoolPtr true_;
    static BoolPtr false_;
  
    static ClassPtr class_class_;            // Class of the Class vm object.
    static ClassPtr dynamic_class_;          // Class of the 'dynamic' type.
    static ClassPtr void_class_;             // Class of the 'void' type.
    static ClassPtr type_parameters_class_;  // Class of TypeParameters vm object.
    static ClassPtr type_arguments_class_;   // Class of TypeArguments vm object.
    static ClassPtr patch_class_class_;      // Class of the PatchClass vm object.
    static ClassPtr function_class_;         // Class of the Function vm object.
    static ClassPtr closure_data_class_;     // Class of ClosureData vm obj.
    static ClassPtr ffi_trampoline_data_class_;  // Class of FfiTrampolineData
                                                 // vm obj.
    static ClassPtr field_class_;                // Class of the Field vm object.
    static ClassPtr script_class_;               // Class of the Script vm object.
    static ClassPtr library_class_;    // Class of the Library vm object.
    static ClassPtr namespace_class_;  // Class of Namespace vm object.
    static ClassPtr kernel_program_info_class_;  // Class of KernelProgramInfo vm
                                                 // object.
    static ClassPtr code_class_;                 // Class of the Code vm object.
  
    static ClassPtr instructions_class_;  // Class of the Instructions vm object.
    static ClassPtr instructions_section_class_;  // Class of InstructionsSection.
    static ClassPtr instructions_table_class_;    // Class of InstructionsTable.
    static ClassPtr object_pool_class_;      // Class of the ObjectPool vm object.
    static ClassPtr pc_descriptors_class_;   // Class of PcDescriptors vm object.
    static ClassPtr code_source_map_class_;  // Class of CodeSourceMap vm object.
    static ClassPtr compressed_stackmaps_class_;  // Class of CompressedStackMaps.
    static ClassPtr var_descriptors_class_;       // Class of LocalVarDescriptors.
    static ClassPtr exception_handlers_class_;    // Class of ExceptionHandlers.
    static ClassPtr deopt_info_class_;            // Class of DeoptInfo.
    static ClassPtr context_class_;            // Class of the Context vm object.
    static ClassPtr context_scope_class_;      // Class of ContextScope vm object.
    static ClassPtr sentinel_class_;           // Class of Sentinel vm object.
    static ClassPtr singletargetcache_class_;  // Class of SingleTargetCache.
    static ClassPtr unlinkedcall_class_;       // Class of UnlinkedCall.
    static ClassPtr
        monomorphicsmiablecall_class_;         // Class of MonomorphicSmiableCall.
    static ClassPtr icdata_class_;             // Class of ICData.
    static ClassPtr megamorphic_cache_class_;  // Class of MegamorphiCache.
    static ClassPtr subtypetestcache_class_;   // Class of SubtypeTestCache.
    static ClassPtr loadingunit_class_;        // Class of LoadingUnit.
    static ClassPtr api_error_class_;          // Class of ApiError.
    static ClassPtr language_error_class_;     // Class of LanguageError.
    static ClassPtr unhandled_exception_class_;  // Class of UnhandledException.
    static ClassPtr unwind_error_class_;         // Class of UnwindError.
    // Class of WeakSerializationReference.
    static ClassPtr weak_serialization_reference_class_;
  
  #define DECLARE_SHARED_READONLY_HANDLE(Type, name) static Type* name##_;
    SHARED_READONLY_HANDLES_LIST(DECLARE_SHARED_READONLY_HANDLE)
  #undef DECLARE_SHARED_READONLY_HANDLE
  
    friend void ClassTable::Register(const Class& cls);
    friend void UntaggedObject::Validate(IsolateGroup* isolate_group) const;
    friend class Closure;
    friend class InstanceDeserializationCluster;
    friend class ObjectGraphCopier;  // For Object::InitializeObject
    friend class Simd128MessageDeserializationCluster;
    friend class OneByteString;
    friend class TwoByteString;
    friend class ExternalOneByteString;
    friend class ExternalTwoByteString;
    friend class Thread;
  
  #define REUSABLE_FRIEND_DECLARATION(name)                                      \
    friend class Reusable##name##HandleScope;
    REUSABLE_HANDLE_LIST(REUSABLE_FRIEND_DECLARATION)
  #undef REUSABLE_FRIEND_DECLARATION
  
    DISALLOW_ALLOCATION();
    DISALLOW_COPY_AND_ASSIGN(Object);
  };
  
  #if defined(DART_PRECOMPILER)
  #define PRECOMPILER_WSR_FIELD_DECLARATION(Type, Name)                          \
    Type##Ptr Name() const;                                                      \
    void set_##Name(const Object& value) const {                                 \
      untag()->set_##Name(value.ptr());                                          \
    }
  #else
  #define PRECOMPILER_WSR_FIELD_DECLARATION(Type, Name)                          \
    Type##Ptr Name() const { return untag()->Name(); }                           \
    void set_##Name(const Type& value) const;
  #endif
  
  class PassiveObject : public Object {
   public:
    void operator=(ObjectPtr value) { ptr_ = value; }
    void operator^=(ObjectPtr value) { ptr_ = value; }
  
    static PassiveObject& Handle(Zone* zone, ObjectPtr ptr) {
      PassiveObject* obj =
          reinterpret_cast<PassiveObject*>(VMHandles::AllocateHandle(zone));
      obj->ptr_ = ptr;
      obj->set_vtable(0);
      return *obj;
    }
    static PassiveObject& Handle(ObjectPtr ptr) {
      return Handle(Thread::Current()->zone(), ptr);
    }
    static PassiveObject& Handle() {
      return Handle(Thread::Current()->zone(), Object::null());
    }
    static PassiveObject& Handle(Zone* zone) {
      return Handle(zone, Object::null());
    }
    static PassiveObject& ZoneHandle(Zone* zone, ObjectPtr ptr) {
      PassiveObject* obj =
          reinterpret_cast<PassiveObject*>(VMHandles::AllocateZoneHandle(zone));
      obj->ptr_ = ptr;
      obj->set_vtable(0);
      return *obj;
    }
    static PassiveObject& ZoneHandle(ObjectPtr ptr) {
      return ZoneHandle(Thread::Current()->zone(), ptr);
    }
    static PassiveObject& ZoneHandle() {
      return ZoneHandle(Thread::Current()->zone(), Object::null());
    }
    static PassiveObject& ZoneHandle(Zone* zone) {
      return ZoneHandle(zone, Object::null());
    }
  
   private:
    PassiveObject() : Object() {}
    DISALLOW_ALLOCATION();
    DISALLOW_COPY_AND_ASSIGN(PassiveObject);
  };
  
  typedef ZoneGrowableHandlePtrArray<const AbstractType> Trail;
  typedef ZoneGrowableHandlePtrArray<const AbstractType>* TrailPtr;
  
  // A URIs array contains triplets of strings.
  // The first string in the triplet is a type name (usually a class).
  // The second string in the triplet is the URI of the type.
  // The third string in the triplet is "print" if the triplet should be printed.
  typedef ZoneGrowableHandlePtrArray<const String> URIs;
  
  enum class Nullability : uint8_t {
    kNullable = 0,
    kNonNullable = 1,
    kLegacy = 2,
    // Adjust kNullabilityBitSize in app_snapshot.cc if adding new values.
  };
  
  // Equality kind between types.
  enum class TypeEquality {
    kCanonical = 0,
    kSyntactical = 1,
    kInSubtypeTest = 2,
  };
  
  // The NNBDMode reflects the opted-in status of libraries.
  // Note that the weak or strong checking mode is not reflected in NNBDMode.
  enum class NNBDMode {
    // Status of the library:
    kLegacyLib = 0,   // Library is legacy.
    kOptedInLib = 1,  // Library is opted-in.
  };
  
  // The NNBDCompiledMode reflects the mode in which constants of the library were
  // compiled by CFE.
  enum class NNBDCompiledMode {
    kWeak = 0,
    kStrong = 1,
    kAgnostic = 2,
    kInvalid = 3,
  };
  
  class Class : public Object {
   public:
    enum InvocationDispatcherEntry {
      kInvocationDispatcherName,
      kInvocationDispatcherArgsDesc,
      kInvocationDispatcherFunction,
      kInvocationDispatcherEntrySize,
    };
  
    bool HasCompressedPointers() const;
    intptr_t host_instance_size() const {
      ASSERT(is_finalized() || is_prefinalized());
      return (untag()->host_instance_size_in_words_ * kCompressedWordSize);
    }
    intptr_t target_instance_size() const {
      ASSERT(is_finalized() || is_prefinalized());
  #if defined(DART_PRECOMPILER)
      return (untag()->target_instance_size_in_words_ *
              compiler::target::kCompressedWordSize);
  #else
      return host_instance_size();
  #endif  // defined(DART_PRECOMPILER)
    }
    static intptr_t host_instance_size(ClassPtr clazz) {
      return (clazz->untag()->host_instance_size_in_words_ * kCompressedWordSize);
    }
    static intptr_t target_instance_size(ClassPtr clazz) {
  #if defined(DART_PRECOMPILER)
      return (clazz->untag()->target_instance_size_in_words_ *
              compiler::target::kCompressedWordSize);
  #else
      return host_instance_size(clazz);
  #endif  // defined(DART_PRECOMPILER)
    }
    void set_instance_size(intptr_t host_value_in_bytes,
                           intptr_t target_value_in_bytes) const {
      ASSERT(kCompressedWordSize != 0);
      set_instance_size_in_words(
          host_value_in_bytes / kCompressedWordSize,
          target_value_in_bytes / compiler::target::kCompressedWordSize);
    }
    void set_instance_size_in_words(intptr_t host_value,
                                    intptr_t target_value) const {
      ASSERT(
          Utils::IsAligned((host_value * kCompressedWordSize), kObjectAlignment));
      StoreNonPointer(&untag()->host_instance_size_in_words_, host_value);
  #if defined(DART_PRECOMPILER)
      ASSERT(
          Utils::IsAligned((target_value * compiler::target::kCompressedWordSize),
                           compiler::target::kObjectAlignment));
      StoreNonPointer(&untag()->target_instance_size_in_words_, target_value);
  #else
      // Could be different only during cross-compilation.
      ASSERT_EQUAL(host_value, target_value);
  #endif  // defined(DART_PRECOMPILER)
    }
  
    intptr_t host_next_field_offset() const {
      return untag()->host_next_field_offset_in_words_ * kCompressedWordSize;
    }
    intptr_t target_next_field_offset() const {
  #if defined(DART_PRECOMPILER)
      return untag()->target_next_field_offset_in_words_ *
             compiler::target::kCompressedWordSize;
  #else
      return host_next_field_offset();
  #endif  // defined(DART_PRECOMPILER)
    }
    void set_next_field_offset(intptr_t host_value_in_bytes,
                               intptr_t target_value_in_bytes) const {
      set_next_field_offset_in_words(
          host_value_in_bytes / kCompressedWordSize,
          target_value_in_bytes / compiler::target::kCompressedWordSize);
    }
    void set_next_field_offset_in_words(intptr_t host_value,
                                        intptr_t target_value) const {
      // Assert that the next field offset is either negative (ie, this object
      // can't be extended by dart code), or rounds up to the kObjectAligned
      // instance size.
      ASSERT((host_value < 0) ||
             ((host_value <= untag()->host_instance_size_in_words_) &&
              (host_value + (kObjectAlignment / kCompressedWordSize) >
               untag()->host_instance_size_in_words_)));
      StoreNonPointer(&untag()->host_next_field_offset_in_words_, host_value);
  #if defined(DART_PRECOMPILER)
      ASSERT((target_value < 0) ||
             ((target_value <= untag()->target_instance_size_in_words_) &&
              (target_value + (compiler::target::kObjectAlignment /
                               compiler::target::kCompressedWordSize) >
               untag()->target_instance_size_in_words_)));
      StoreNonPointer(&untag()->target_next_field_offset_in_words_, target_value);
  #else
      // Could be different only during cross-compilation.
      ASSERT_EQUAL(host_value, target_value);
  #endif  // defined(DART_PRECOMPILER)
    }
  
    static bool is_valid_id(intptr_t value) {
      return UntaggedObject::ClassIdTag::is_valid(value);
    }
    intptr_t id() const { return untag()->id_; }
    void set_id(intptr_t value) const {
      ASSERT(value >= 0 && value < std::numeric_limits<classid_t>::max());
      StoreNonPointer(&untag()->id_, value);
    }
    static intptr_t id_offset() { return OFFSET_OF(UntaggedClass, id_); }
    static intptr_t num_type_arguments_offset() {
      return OFFSET_OF(UntaggedClass, num_type_arguments_);
    }
  
    StringPtr Name() const;
    StringPtr ScrubbedName() const;
    const char* ScrubbedNameCString() const;
    StringPtr UserVisibleName() const;
    const char* UserVisibleNameCString() const;
  
    const char* NameCString(NameVisibility name_visibility) const;
  
    // The mixin for this class if one exists. Otherwise, returns a raw pointer
    // to this class.
    ClassPtr Mixin() const;
  
    // The NNBD mode of the library declaring this class.
    NNBDMode nnbd_mode() const;
  
    bool IsInFullSnapshot() const;
  
    virtual StringPtr DictionaryName() const { return Name(); }
  
    ScriptPtr script() const { return untag()->script(); }
    void set_script(const Script& value) const;
  
    TokenPosition token_pos() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return TokenPosition::kNoSource;
  #else
      return untag()->token_pos_;
  #endif  // defined(DART_PRECOMPILED_RUNTIME)
    }
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    void set_token_pos(TokenPosition value) const;
  #endif  // !defined(DART_PRECOMPILED_RUNTIME)
  
    TokenPosition end_token_pos() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return TokenPosition::kNoSource;
  #else
      return untag()->end_token_pos_;
  #endif  // defined(DART_PRECOMPILED_RUNTIME)
    }
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    void set_end_token_pos(TokenPosition value) const;
  #endif  // !defined(DART_PRECOMPILED_RUNTIME)
  
    int32_t SourceFingerprint() const;
  
    // Return the Type with type parameters declared by this class filled in with
    // dynamic and type parameters declared in superclasses filled in as declared
    // in superclass clauses.
    AbstractTypePtr RareType() const;
  
    // Return the Type whose arguments are the type parameters declared by this
    // class preceded by the type arguments declared for superclasses, etc.
    // e.g. given
    // class B<T, S>
    // class C<R> extends B<R, int>
    // C.DeclarationType() --> C [R, int, R]
    // The declaration type's nullability is either legacy or non-nullable when
    // the non-nullable experiment is enabled.
    TypePtr DeclarationType() const;
  
    static intptr_t declaration_type_offset() {
      return OFFSET_OF(UntaggedClass, declaration_type_);
    }
  
    LibraryPtr library() const { return untag()->library(); }
    void set_library(const Library& value) const;
  
    // The formal type parameters and their bounds (no defaults), are specified as
    // an object of type TypeParameters.
    TypeParametersPtr type_parameters() const {
      ASSERT(is_declaration_loaded());
      return untag()->type_parameters();
    }
    void set_type_parameters(const TypeParameters& value) const;
    intptr_t NumTypeParameters(Thread* thread) const;
    intptr_t NumTypeParameters() const {
      return NumTypeParameters(Thread::Current());
    }
  
    // Return the type parameter declared at index.
    TypeParameterPtr TypeParameterAt(
        intptr_t index,
        Nullability nullability = Nullability::kNonNullable) const;
  
    // The type argument vector is flattened and includes the type arguments of
    // the super class.
    intptr_t NumTypeArguments() const;
  
    // Return true if this class declares type parameters.
    bool IsGeneric() const {
      // If the declaration is not loaded, fall back onto NumTypeParameters.
      if (!is_declaration_loaded()) {
        return NumTypeParameters(Thread::Current()) > 0;
      }
      return type_parameters() != Object::null();
    }
  
    // Returns a canonicalized vector of the type parameters instantiated
    // to bounds. If non-generic, the empty type arguments vector is returned.
    TypeArgumentsPtr InstantiateToBounds(Thread* thread) const;
  
    // If this class is parameterized, each instance has a type_arguments field.
    static const intptr_t kNoTypeArguments = -1;
    intptr_t host_type_arguments_field_offset() const {
      ASSERT(is_type_finalized() || is_prefinalized());
      if (untag()->host_type_arguments_field_offset_in_words_ ==
          kNoTypeArguments) {
        return kNoTypeArguments;
      }
      return untag()->host_type_arguments_field_offset_in_words_ *
             kCompressedWordSize;
    }
    intptr_t target_type_arguments_field_offset() const {
  #if defined(DART_PRECOMPILER)
      ASSERT(is_type_finalized() || is_prefinalized());
      if (untag()->target_type_arguments_field_offset_in_words_ ==
          compiler::target::Class::kNoTypeArguments) {
        return compiler::target::Class::kNoTypeArguments;
      }
      return untag()->target_type_arguments_field_offset_in_words_ *
             compiler::target::kCompressedWordSize;
  #else
      return host_type_arguments_field_offset();
  #endif  // defined(DART_PRECOMPILER)
    }
    void set_type_arguments_field_offset(intptr_t host_value_in_bytes,
                                         intptr_t target_value_in_bytes) const {
      intptr_t host_value, target_value;
      if (host_value_in_bytes == kNoTypeArguments ||
          target_value_in_bytes == RTN::Class::kNoTypeArguments) {
        ASSERT(host_value_in_bytes == kNoTypeArguments &&
               target_value_in_bytes == RTN::Class::kNoTypeArguments);
        host_value = kNoTypeArguments;
        target_value = RTN::Class::kNoTypeArguments;
      } else {
        ASSERT(kCompressedWordSize != 0 && compiler::target::kCompressedWordSize);
        host_value = host_value_in_bytes / kCompressedWordSize;
        target_value =
            target_value_in_bytes / compiler::target::kCompressedWordSize;
      }
      set_type_arguments_field_offset_in_words(host_value, target_value);
    }
    void set_type_arguments_field_offset_in_words(intptr_t host_value,
                                                  intptr_t target_value) const {
      StoreNonPointer(&untag()->host_type_arguments_field_offset_in_words_,
                      host_value);
  #if defined(DART_PRECOMPILER)
      StoreNonPointer(&untag()->target_type_arguments_field_offset_in_words_,
                      target_value);
  #else
      // Could be different only during cross-compilation.
      ASSERT_EQUAL(host_value, target_value);
  #endif  // defined(DART_PRECOMPILER)
    }
    static intptr_t host_type_arguments_field_offset_in_words_offset() {
      return OFFSET_OF(UntaggedClass, host_type_arguments_field_offset_in_words_);
    }
  
    // The super type of this class, Object type if not explicitly specified.
    AbstractTypePtr super_type() const {
      ASSERT(is_declaration_loaded());
      return untag()->super_type();
    }
    void set_super_type(const AbstractType& value) const;
    static intptr_t super_type_offset() {
      return OFFSET_OF(UntaggedClass, super_type_);
    }
  
    // Asserts that the class of the super type has been resolved.
    // |original_classes| only has an effect when reloading. If true and we
    // are reloading, it will prefer the original classes to the replacement
    // classes.
    ClassPtr SuperClass(bool original_classes = false) const;
  
    // Interfaces is an array of Types.
    ArrayPtr interfaces() const {
      ASSERT(is_declaration_loaded());
      return untag()->interfaces();
    }
    void set_interfaces(const Array& value) const;
  
    // Returns whether a path from [this] to [cls] can be found, where the first
    // element is a direct supertype of [this], each following element is a direct
    // supertype of the previous element and the final element has [cls] as its
    // type class. If [this] and [cls] are the same class, then the path is empty.
    //
    // If [path] is not nullptr, then the elements of the path are added to it.
    // This path can then be used to compute type arguments of [cls] given type
    // arguments for an instance of [this].
    //
    // Note: There may be multiple paths to [cls], but the result of applying each
    // path must be equal to the other results.
    bool FindInstantiationOf(Zone* zone,
                             const Class& cls,
                             GrowableArray<const AbstractType*>* path,
                             bool consider_only_super_classes = false) const;
    bool FindInstantiationOf(Zone* zone,
                             const Class& cls,
                             bool consider_only_super_classes = false) const {
      return FindInstantiationOf(zone, cls, /*path=*/nullptr,
                                 consider_only_super_classes);
    }
  
    // Returns whether a path from [this] to [type] can be found, where the first
    // element is a direct supertype of [this], each following element is a direct
    // supertype of the previous element and the final element has the same type
    // class as [type]. If [this] is the type class of [type], then the path is
    // empty.
    //
    // If [path] is not nullptr, then the elements of the path are added to it.
    // This path can then be used to compute type arguments of [type]'s type
    // class given type arguments for an instance of [this].
    //
    // Note: There may be multiple paths to [type]'s type class, but the result of
    // applying each path must be equal to the other results.
    bool FindInstantiationOf(Zone* zone,
                             const Type& type,
                             GrowableArray<const AbstractType*>* path,
                             bool consider_only_super_classes = false) const;
    bool FindInstantiationOf(Zone* zone,
                             const Type& type,
                             bool consider_only_super_classes = false) const {
      return FindInstantiationOf(zone, type, /*path=*/nullptr,
                                 consider_only_super_classes);
    }
  
    // If [this] is a subtype of a type with type class [cls], then this
    // returns [cls]<X_0, ..., X_n>, where n is the number of type arguments for
    // [cls] and where each type argument X_k is either instantiated or has free
    // class type parameters corresponding to the type parameters of [this].
    // Thus, given an instance of [this], the result can be instantiated
    // with the instance type arguments to get the type of the instance.
    //
    // If [this] is not a subtype of a type with type class [cls], returns null.
    TypePtr GetInstantiationOf(Zone* zone, const Class& cls) const;
  
    // If [this] is a subtype of [type], then this returns [cls]<X_0, ..., X_n>,
    // where [cls] is the type class of [type], n is the number of type arguments
    // for [cls], and where each type argument X_k is either instantiated or has
    // free class type parameters corresponding to the type parameters of [this].
    // Thus, given an instance of [this], the result can be instantiated with the
    // instance type arguments to get the type of the instance.
    //
    // If [this] is not a subtype of a type with type class [cls], returns null.
    TypePtr GetInstantiationOf(Zone* zone, const Type& type) const;
  
  #if !defined(PRODUCT) || !defined(DART_PRECOMPILED_RUNTIME)
    // Returns the list of classes directly implementing this class.
    GrowableObjectArrayPtr direct_implementors() const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
      return untag()->direct_implementors();
    }
    GrowableObjectArrayPtr direct_implementors_unsafe() const {
      return untag()->direct_implementors();
    }
  #endif  // !defined(PRODUCT) || !defined(DART_PRECOMPILED_RUNTIME)
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    void set_direct_implementors(const GrowableObjectArray& implementors) const;
    void AddDirectImplementor(const Class& subclass, bool is_mixin) const;
  #endif  // !defined(DART_PRECOMPILED_RUNTIME)
  
  #if !defined(PRODUCT) || !defined(DART_PRECOMPILED_RUNTIME)
    // Returns the list of classes having this class as direct superclass.
    GrowableObjectArrayPtr direct_subclasses() const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
      return direct_subclasses_unsafe();
    }
    GrowableObjectArrayPtr direct_subclasses_unsafe() const {
      return untag()->direct_subclasses();
    }
  #endif  // !defined(PRODUCT) || !defined(DART_PRECOMPILED_RUNTIME)
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    void set_direct_subclasses(const GrowableObjectArray& subclasses) const;
    void AddDirectSubclass(const Class& subclass) const;
  #endif  // !defined(DART_PRECOMPILED_RUNTIME)
  
    // Check if this class represents the class of null.
    bool IsNullClass() const { return id() == kNullCid; }
  
    // Check if this class represents the 'dynamic' class.
    bool IsDynamicClass() const { return id() == kDynamicCid; }
  
    // Check if this class represents the 'void' class.
    bool IsVoidClass() const { return id() == kVoidCid; }
  
    // Check if this class represents the 'Never' class.
    bool IsNeverClass() const { return id() == kNeverCid; }
  
    // Check if this class represents the 'Object' class.
    bool IsObjectClass() const { return id() == kInstanceCid; }
  
    // Check if this class represents the 'Function' class.
    bool IsDartFunctionClass() const;
  
    // Check if this class represents the 'Future' class.
    bool IsFutureClass() const;
  
    // Check if this class represents the 'FutureOr' class.
    bool IsFutureOrClass() const { return id() == kFutureOrCid; }
  
    // Check if this class represents the 'Closure' class.
    bool IsClosureClass() const { return id() == kClosureCid; }
    static bool IsClosureClass(ClassPtr cls) {
      return GetClassId(cls) == kClosureCid;
    }
  
    static bool IsInFullSnapshot(ClassPtr cls) {
      NoSafepointScope no_safepoint;
      return UntaggedLibrary::InFullSnapshotBit::decode(
          cls->untag()->library()->untag()->flags_);
    }
  
    static intptr_t GetClassId(ClassPtr cls) {
      NoSafepointScope no_safepoint;
      return cls->untag()->id_;
    }
  
    // Returns true if the type specified by cls, type_arguments, and nullability
    // is a subtype of the other type.
    static bool IsSubtypeOf(const Class& cls,
                            const TypeArguments& type_arguments,
                            Nullability nullability,
                            const AbstractType& other,
                            Heap::Space space,
                            TrailPtr trail = nullptr);
  
    // Check if this is the top level class.
    bool IsTopLevel() const;
  
    bool IsPrivate() const;
  
    DART_WARN_UNUSED_RESULT
    ErrorPtr VerifyEntryPoint() const;
  
    // Returns an array of instance and static fields defined by this class.
    ArrayPtr fields() const {
      // We rely on the fact that any loads from the array are dependent loads
      // and avoid the load-acquire barrier here.
      return untag()->fields();
    }
    void SetFields(const Array& value) const;
    void AddField(const Field& field) const;
    void AddFields(const GrowableArray<const Field*>& fields) const;
  
    // If this is a dart:internal.ClassID class, then inject our own const
    // fields. Returns true if synthetic fields are injected and regular
    // field declarations should be ignored.
    bool InjectCIDFields() const;
  
    // Returns an array of all instance fields of this class and its superclasses
    // indexed by offset in words.
    // |original_classes| only has an effect when reloading. If true and we
    // are reloading, it will prefer the original classes to the replacement
    // classes.
    ArrayPtr OffsetToFieldMap(bool original_classes = false) const;
  
    // Returns true if non-static fields are defined.
    bool HasInstanceFields() const;
  
    ArrayPtr current_functions() const {
      // We rely on the fact that any loads from the array are dependent loads
      // and avoid the load-acquire barrier here.
      return untag()->functions();
    }
    ArrayPtr functions() const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
      return current_functions();
    }
    void SetFunctions(const Array& value) const;
    void AddFunction(const Function& function) const;
    FunctionPtr FunctionFromIndex(intptr_t idx) const;
    intptr_t FindImplicitClosureFunctionIndex(const Function& needle) const;
    FunctionPtr ImplicitClosureFunctionFromIndex(intptr_t idx) const;
  
    FunctionPtr LookupFunctionReadLocked(const String& name) const;
    FunctionPtr LookupDynamicFunctionUnsafe(const String& name) const;
  
    FunctionPtr LookupDynamicFunctionAllowPrivate(const String& name) const;
    FunctionPtr LookupStaticFunction(const String& name) const;
    FunctionPtr LookupStaticFunctionAllowPrivate(const String& name) const;
    FunctionPtr LookupConstructor(const String& name) const;
    FunctionPtr LookupConstructorAllowPrivate(const String& name) const;
    FunctionPtr LookupFactory(const String& name) const;
    FunctionPtr LookupFactoryAllowPrivate(const String& name) const;
    FunctionPtr LookupFunctionAllowPrivate(const String& name) const;
    FunctionPtr LookupGetterFunction(const String& name) const;
    FunctionPtr LookupSetterFunction(const String& name) const;
    FieldPtr LookupInstanceField(const String& name) const;
    FieldPtr LookupStaticField(const String& name) const;
    FieldPtr LookupField(const String& name) const;
    FieldPtr LookupFieldAllowPrivate(const String& name,
                                     bool instance_only = false) const;
    FieldPtr LookupInstanceFieldAllowPrivate(const String& name) const;
    FieldPtr LookupStaticFieldAllowPrivate(const String& name) const;
  
    DoublePtr LookupCanonicalDouble(Zone* zone, double value) const;
    MintPtr LookupCanonicalMint(Zone* zone, int64_t value) const;
  
    // The methods above are more efficient than this generic one.
    InstancePtr LookupCanonicalInstance(Zone* zone, const Instance& value) const;
  
    InstancePtr InsertCanonicalConstant(Zone* zone,
                                        const Instance& constant) const;
    void InsertCanonicalDouble(Zone* zone, const Double& constant) const;
    void InsertCanonicalMint(Zone* zone, const Mint& constant) const;
  
    void RehashConstants(Zone* zone) const;
  
    bool RequireCanonicalTypeErasureOfConstants(Zone* zone) const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedClass));
    }
  
    bool is_implemented() const { return ImplementedBit::decode(state_bits()); }
    void set_is_implemented() const;
    void set_is_implemented_unsafe() const;
  
    bool is_abstract() const { return AbstractBit::decode(state_bits()); }
    void set_is_abstract() const;
  
    UntaggedClass::ClassLoadingState class_loading_state() const {
      return ClassLoadingBits::decode(state_bits());
    }
  
    bool is_declaration_loaded() const {
      return class_loading_state() >= UntaggedClass::kDeclarationLoaded;
    }
    void set_is_declaration_loaded() const;
    void set_is_declaration_loaded_unsafe() const;
  
    bool is_type_finalized() const {
      return class_loading_state() >= UntaggedClass::kTypeFinalized;
    }
    void set_is_type_finalized() const;
  
    bool is_synthesized_class() const {
      return SynthesizedClassBit::decode(state_bits());
    }
    void set_is_synthesized_class() const;
    void set_is_synthesized_class_unsafe() const;
  
    bool is_enum_class() const { return EnumBit::decode(state_bits()); }
    void set_is_enum_class() const;
  
    bool is_finalized() const {
      return ClassFinalizedBits::decode(state_bits()) ==
                 UntaggedClass::kFinalized ||
             ClassFinalizedBits::decode(state_bits()) ==
                 UntaggedClass::kAllocateFinalized;
    }
    void set_is_finalized() const;
    void set_is_finalized_unsafe() const;
  
    bool is_allocate_finalized() const {
      return ClassFinalizedBits::decode(state_bits()) ==
             UntaggedClass::kAllocateFinalized;
    }
    void set_is_allocate_finalized() const;
  
    bool is_prefinalized() const {
      return ClassFinalizedBits::decode(state_bits()) ==
             UntaggedClass::kPreFinalized;
    }
  
    void set_is_prefinalized() const;
  
    bool is_const() const { return ConstBit::decode(state_bits()); }
    void set_is_const() const;
  
    // Tests if this is a mixin application class which was desugared
    // to a normal class by kernel mixin transformation
    // (pkg/kernel/lib/transformations/mixin_full_resolution.dart).
    //
    // In such case, its mixed-in type was pulled into the end of
    // interfaces list.
    bool is_transformed_mixin_application() const {
      return TransformedMixinApplicationBit::decode(state_bits());
    }
    void set_is_transformed_mixin_application() const;
  
    bool is_fields_marked_nullable() const {
      return FieldsMarkedNullableBit::decode(state_bits());
    }
    void set_is_fields_marked_nullable() const;
  
    bool is_allocated() const { return IsAllocatedBit::decode(state_bits()); }
    void set_is_allocated(bool value) const;
    void set_is_allocated_unsafe(bool value) const;
  
    bool is_loaded() const { return IsLoadedBit::decode(state_bits()); }
    void set_is_loaded(bool value) const;
  
    uint16_t num_native_fields() const { return untag()->num_native_fields_; }
    void set_num_native_fields(uint16_t value) const {
      StoreNonPointer(&untag()->num_native_fields_, value);
    }
    static uint16_t NumNativeFieldsOf(ClassPtr clazz) {
      return clazz->untag()->num_native_fields_;
    }
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    CodePtr allocation_stub() const { return untag()->allocation_stub(); }
    void set_allocation_stub(const Code& value) const;
  #endif  // !defined(DART_PRECOMPILED_RUNTIME)
  
    intptr_t kernel_offset() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return 0;
  #else
      return untag()->kernel_offset_;
  #endif
    }
  
    void set_kernel_offset(intptr_t value) const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
  #else
      ASSERT(value >= 0);
      StoreNonPointer(&untag()->kernel_offset_, value);
  #endif
    }
  
    void DisableAllocationStub() const;
  
    ArrayPtr constants() const;
    void set_constants(const Array& value) const;
  
    intptr_t FindInvocationDispatcherFunctionIndex(const Function& needle) const;
    FunctionPtr InvocationDispatcherFunctionFromIndex(intptr_t idx) const;
  
    FunctionPtr GetInvocationDispatcher(const String& target_name,
                                        const Array& args_desc,
                                        UntaggedFunction::Kind kind,
                                        bool create_if_absent) const;
  
    void Finalize() const;
  
    ObjectPtr Invoke(const String& selector,
                     const Array& arguments,
                     const Array& argument_names,
                     bool respect_reflectable = true,
                     bool check_is_entrypoint = false) const;
    ObjectPtr InvokeGetter(const String& selector,
                           bool throw_nsm_if_absent,
                           bool respect_reflectable = true,
                           bool check_is_entrypoint = false) const;
    ObjectPtr InvokeSetter(const String& selector,
                           const Instance& argument,
                           bool respect_reflectable = true,
                           bool check_is_entrypoint = false) const;
  
    // Evaluate the given expression as if it appeared in a static method of this
    // class and return the resulting value, or an error object if evaluating the
    // expression fails. The method has the formal (type) parameters given in
    // (type_)param_names, and is invoked with the (type)argument values given in
    // (type_)param_values.
    ObjectPtr EvaluateCompiledExpression(
        const ExternalTypedData& kernel_buffer,
        const Array& type_definitions,
        const Array& param_values,
        const TypeArguments& type_param_values) const;
  
    // Load class declaration (super type, interfaces, type parameters and
    // number of type arguments) if it is not loaded yet.
    void EnsureDeclarationLoaded() const;
  
    ErrorPtr EnsureIsFinalized(Thread* thread) const;
    ErrorPtr EnsureIsAllocateFinalized(Thread* thread) const;
  
    // Allocate a class used for VM internal objects.
    template <class FakeObject, class TargetFakeObject>
    static ClassPtr New(IsolateGroup* isolate_group, bool register_class = true);
  
    // Allocate instance classes.
    static ClassPtr New(const Library& lib,
                        const String& name,
                        const Script& script,
                        TokenPosition token_pos,
                        bool register_class = true);
    static ClassPtr NewNativeWrapper(const Library& library,
                                     const String& name,
                                     int num_fields);
  
    // Allocate the raw string classes.
    static ClassPtr NewStringClass(intptr_t class_id,
                                   IsolateGroup* isolate_group);
  
    // Allocate the raw TypedData classes.
    static ClassPtr NewTypedDataClass(intptr_t class_id,
                                      IsolateGroup* isolate_group);
  
    // Allocate the raw TypedDataView/ByteDataView classes.
    static ClassPtr NewTypedDataViewClass(intptr_t class_id,
                                          IsolateGroup* isolate_group);
  
    // Allocate the raw ExternalTypedData classes.
    static ClassPtr NewExternalTypedDataClass(intptr_t class_id,
                                              IsolateGroup* isolate);
  
    // Allocate the raw Pointer classes.
    static ClassPtr NewPointerClass(intptr_t class_id,
                                    IsolateGroup* isolate_group);
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    // Register code that has used CHA for optimization.
    // TODO(srdjan): Also register kind of CHA optimization (e.g.: leaf class,
    // leaf method, ...).
    void RegisterCHACode(const Code& code);
  
    void DisableCHAOptimizedCode(const Class& subclass);
  
    void DisableAllCHAOptimizedCode();
  
    void DisableCHAImplementorUsers() { DisableAllCHAOptimizedCode(); }
  
    // Return the list of code objects that were compiled using CHA of this class.
    // These code objects will be invalidated if new subclasses of this class
    // are finalized.
    ArrayPtr dependent_code() const;
    void set_dependent_code(const Array& array) const;
  #endif  // !defined(DART_PRECOMPILED_RUNTIME)
  
    bool TraceAllocation(IsolateGroup* isolate_group) const;
    void SetTraceAllocation(bool trace_allocation) const;
  
    void ReplaceEnum(ProgramReloadContext* reload_context,
                     const Class& old_enum) const;
    void CopyStaticFieldValues(ProgramReloadContext* reload_context,
                               const Class& old_cls) const;
    void PatchFieldsAndFunctions() const;
    void MigrateImplicitStaticClosures(ProgramReloadContext* context,
                                       const Class& new_cls) const;
    void CopyCanonicalConstants(const Class& old_cls) const;
    void CopyDeclarationType(const Class& old_cls) const;
    void CheckReload(const Class& replacement,
                     ProgramReloadContext* context) const;
  
    void AddInvocationDispatcher(const String& target_name,
                                 const Array& args_desc,
                                 const Function& dispatcher) const;
  
    static int32_t host_instance_size_in_words(const ClassPtr cls) {
      return cls->untag()->host_instance_size_in_words_;
    }
  
    static int32_t target_instance_size_in_words(const ClassPtr cls) {
  #if defined(DART_PRECOMPILER)
      return cls->untag()->target_instance_size_in_words_;
  #else
      return host_instance_size_in_words(cls);
  #endif  // defined(DART_PRECOMPILER)
    }
  
    static int32_t host_next_field_offset_in_words(const ClassPtr cls) {
      return cls->untag()->host_next_field_offset_in_words_;
    }
  
    static int32_t target_next_field_offset_in_words(const ClassPtr cls) {
  #if defined(DART_PRECOMPILER)
      return cls->untag()->target_next_field_offset_in_words_;
  #else
      return host_next_field_offset_in_words(cls);
  #endif  // defined(DART_PRECOMPILER)
    }
  
    static int32_t host_type_arguments_field_offset_in_words(const ClassPtr cls) {
      return cls->untag()->host_type_arguments_field_offset_in_words_;
    }
  
    static int32_t target_type_arguments_field_offset_in_words(
        const ClassPtr cls) {
  #if defined(DART_PRECOMPILER)
      return cls->untag()->target_type_arguments_field_offset_in_words_;
  #else
      return host_type_arguments_field_offset_in_words(cls);
  #endif  // defined(DART_PRECOMPILER)
    }
  
   private:
    TypePtr declaration_type() const {
      return untag()->declaration_type<std::memory_order_acquire>();
    }
  
    // Caches the declaration type of this class.
    void set_declaration_type(const Type& type) const;
  
    bool CanReloadFinalized(const Class& replacement,
                            ProgramReloadContext* context) const;
    bool CanReloadPreFinalized(const Class& replacement,
                               ProgramReloadContext* context) const;
  
    // Tells whether instances need morphing for reload.
    bool RequiresInstanceMorphing(const Class& replacement) const;
  
    template <class FakeInstance, class TargetFakeInstance>
    static ClassPtr NewCommon(intptr_t index);
  
    enum MemberKind {
      kAny = 0,
      kStatic,
      kInstance,
      kInstanceAllowAbstract,
      kConstructor,
      kFactory,
    };
    enum StateBits {
      kConstBit = 0,
      kImplementedBit = 1,
      kClassFinalizedPos = 2,
      kClassFinalizedSize = 2,
      kClassLoadingPos = kClassFinalizedPos + kClassFinalizedSize,  // = 4
      kClassLoadingSize = 2,
      kAbstractBit = kClassLoadingPos + kClassLoadingSize,  // = 6
      kSynthesizedClassBit,
      kMixinAppAliasBit,
      kMixinTypeAppliedBit,
      kFieldsMarkedNullableBit,
      kEnumBit,
      kTransformedMixinApplicationBit,
      kIsAllocatedBit,
      kIsLoadedBit,
      kHasPragmaBit,
    };
    class ConstBit : public BitField<uint32_t, bool, kConstBit, 1> {};
    class ImplementedBit : public BitField<uint32_t, bool, kImplementedBit, 1> {};
    class ClassFinalizedBits : public BitField<uint32_t,
                                               UntaggedClass::ClassFinalizedState,
                                               kClassFinalizedPos,
                                               kClassFinalizedSize> {};
    class ClassLoadingBits : public BitField<uint32_t,
                                             UntaggedClass::ClassLoadingState,
                                             kClassLoadingPos,
                                             kClassLoadingSize> {};
    class AbstractBit : public BitField<uint32_t, bool, kAbstractBit, 1> {};
    class SynthesizedClassBit
        : public BitField<uint32_t, bool, kSynthesizedClassBit, 1> {};
    class FieldsMarkedNullableBit
        : public BitField<uint32_t, bool, kFieldsMarkedNullableBit, 1> {};
    class EnumBit : public BitField<uint32_t, bool, kEnumBit, 1> {};
    class TransformedMixinApplicationBit
        : public BitField<uint32_t, bool, kTransformedMixinApplicationBit, 1> {};
    class IsAllocatedBit : public BitField<uint32_t, bool, kIsAllocatedBit, 1> {};
    class IsLoadedBit : public BitField<uint32_t, bool, kIsLoadedBit, 1> {};
    class HasPragmaBit : public BitField<uint32_t, bool, kHasPragmaBit, 1> {};
  
    void set_name(const String& value) const;
    void set_user_name(const String& value) const;
    const char* GenerateUserVisibleName() const;
    void set_state_bits(intptr_t bits) const;
  
    FunctionPtr CreateInvocationDispatcher(const String& target_name,
                                           const Array& args_desc,
                                           UntaggedFunction::Kind kind) const;
  
    // Returns the bitmap of unboxed fields
    UnboxedFieldBitmap CalculateFieldOffsets() const;
  
    // functions_hash_table is in use iff there are at least this many functions.
    static const intptr_t kFunctionLookupHashTreshold = 16;
  
    // Initial value for the cached number of type arguments.
    static const intptr_t kUnknownNumTypeArguments = -1;
  
    int16_t num_type_arguments() const {
      return LoadNonPointer<int16_t, std::memory_order_relaxed>(
          &untag()->num_type_arguments_);
    }
  
    uint32_t state_bits() const {
      // Ensure any following load instructions do not get performed before this
      // one.
      return LoadNonPointer<uint32_t, std::memory_order_acquire>(
          &untag()->state_bits_);
    }
  
   public:
    void set_num_type_arguments(intptr_t value) const;
    void set_num_type_arguments_unsafe(intptr_t value) const;
  
    bool has_pragma() const { return HasPragmaBit::decode(state_bits()); }
    void set_has_pragma(bool has_pragma) const;
  
   private:
    void set_functions(const Array& value) const;
    void set_fields(const Array& value) const;
    void set_invocation_dispatcher_cache(const Array& cache) const;
  
    ArrayPtr invocation_dispatcher_cache() const;
  
    // Calculates number of type arguments of this class.
    // This includes type arguments of a superclass and takes overlapping
    // of type arguments into account.
    intptr_t ComputeNumTypeArguments() const;
  
    // Assigns empty array to all raw class array fields.
    void InitEmptyFields();
  
    static FunctionPtr CheckFunctionType(const Function& func, MemberKind kind);
    FunctionPtr LookupFunctionReadLocked(const String& name,
                                         MemberKind kind) const;
    FunctionPtr LookupFunctionAllowPrivate(const String& name,
                                           MemberKind kind) const;
    FieldPtr LookupField(const String& name, MemberKind kind) const;
  
    FunctionPtr LookupAccessorFunction(const char* prefix,
                                       intptr_t prefix_length,
                                       const String& name) const;
  
    // Allocate an instance class which has a VM implementation.
    template <class FakeInstance, class TargetFakeInstance>
    static ClassPtr New(intptr_t id,
                        IsolateGroup* isolate_group,
                        bool register_class = true,
                        bool is_abstract = false);
  
    // Helper that calls 'Class::New<Instance>(kIllegalCid)'.
    static ClassPtr NewInstanceClass();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Class, Object);
    friend class AbstractType;
    friend class Instance;
    friend class Object;
    friend class Type;
    friend class Intrinsifier;
    friend class ProgramWalker;
    friend class Precompiler;
    friend class ClassFinalizer;
  };
  
  // Classification of type genericity according to type parameter owners.
  enum Genericity {
    kAny,           // Consider type params of current class and functions.
    kCurrentClass,  // Consider type params of current class only.
    kFunctions,     // Consider type params of current and parent functions.
  };
  
  class PatchClass : public Object {
   public:
    ClassPtr patched_class() const { return untag()->patched_class(); }
    ClassPtr origin_class() const { return untag()->origin_class(); }
    ScriptPtr script() const { return untag()->script(); }
    ExternalTypedDataPtr library_kernel_data() const {
      return untag()->library_kernel_data();
    }
    void set_library_kernel_data(const ExternalTypedData& data) const;
  
    intptr_t library_kernel_offset() const {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      return untag()->library_kernel_offset_;
  #else
      return -1;
  #endif
    }
    void set_library_kernel_offset(intptr_t offset) const {
      NOT_IN_PRECOMPILED(
          StoreNonPointer(&untag()->library_kernel_offset_, offset));
    }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedPatchClass));
    }
    static bool IsInFullSnapshot(PatchClassPtr cls) {
      NoSafepointScope no_safepoint;
      return Class::IsInFullSnapshot(cls->untag()->patched_class());
    }
  
    static PatchClassPtr New(const Class& patched_class,
                             const Class& origin_class);
  
    static PatchClassPtr New(const Class& patched_class, const Script& source);
  
   private:
    void set_patched_class(const Class& value) const;
    void set_origin_class(const Class& value) const;
    void set_script(const Script& value) const;
  
    static PatchClassPtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(PatchClass, Object);
    friend class Class;
  };
  
  class SingleTargetCache : public Object {
   public:
    CodePtr target() const { return untag()->target(); }
    void set_target(const Code& target) const;
    static intptr_t target_offset() {
      return OFFSET_OF(UntaggedSingleTargetCache, target_);
    }
  
  #define DEFINE_NON_POINTER_FIELD_ACCESSORS(type, name)                         \
    type name() const { return untag()->name##_; }                               \
    void set_##name(type value) const {                                          \
      StoreNonPointer(&untag()->name##_, value);                                 \
    }                                                                            \
    static intptr_t name##_offset() {                                            \
      return OFFSET_OF(UntaggedSingleTargetCache, name##_);                      \
    }
  
    DEFINE_NON_POINTER_FIELD_ACCESSORS(uword, entry_point);
    DEFINE_NON_POINTER_FIELD_ACCESSORS(intptr_t, lower_limit);
    DEFINE_NON_POINTER_FIELD_ACCESSORS(intptr_t, upper_limit);
  #undef DEFINE_NON_POINTER_FIELD_ACCESSORS
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedSingleTargetCache));
    }
  
    static SingleTargetCachePtr New();
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(SingleTargetCache, Object);
    friend class Class;
  };
  
  class MonomorphicSmiableCall : public Object {
   public:
    classid_t expected_cid() const { return untag()->expected_cid_; }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedMonomorphicSmiableCall));
    }
  
    static MonomorphicSmiableCallPtr New(classid_t expected_cid,
                                         const Code& target);
  
    static intptr_t expected_cid_offset() {
      return OFFSET_OF(UntaggedMonomorphicSmiableCall, expected_cid_);
    }
  
    static intptr_t entrypoint_offset() {
      return OFFSET_OF(UntaggedMonomorphicSmiableCall, entrypoint_);
    }
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(MonomorphicSmiableCall, Object);
    friend class Class;
  };
  
  class CallSiteData : public Object {
   public:
    StringPtr target_name() const { return untag()->target_name(); }
    ArrayPtr arguments_descriptor() const { return untag()->args_descriptor(); }
  
    intptr_t TypeArgsLen() const;
  
    intptr_t CountWithTypeArgs() const;
  
    intptr_t CountWithoutTypeArgs() const;
  
    intptr_t SizeWithoutTypeArgs() const;
  
    intptr_t SizeWithTypeArgs() const;
  
    static intptr_t target_name_offset() {
      return OFFSET_OF(UntaggedCallSiteData, target_name_);
    }
  
    static intptr_t arguments_descriptor_offset() {
      return OFFSET_OF(UntaggedCallSiteData, args_descriptor_);
    }
  
   private:
    void set_target_name(const String& value) const;
    void set_arguments_descriptor(const Array& value) const;
  
    HEAP_OBJECT_IMPLEMENTATION(CallSiteData, Object)
  
    friend class ICData;
    friend class MegamorphicCache;
  };
  
  class UnlinkedCall : public CallSiteData {
   public:
    bool can_patch_to_monomorphic() const {
      return untag()->can_patch_to_monomorphic_;
    }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedUnlinkedCall));
    }
  
    uword Hash() const;
    bool Equals(const UnlinkedCall& other) const;
  
    static UnlinkedCallPtr New();
  
   private:
    friend class ICData;  // For set_*() methods.
  
    void set_can_patch_to_monomorphic(bool value) const;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(UnlinkedCall, CallSiteData);
    friend class Class;
  };
  
  // Object holding information about an IC: test classes and their
  // corresponding targets. The owner of the ICData can be either the function
  // or the original ICData object. In case of background compilation we
  // copy the ICData in a child object, thus freezing it during background
  // compilation. Code may contain only original ICData objects.
  class ICData : public CallSiteData {
   public:
    FunctionPtr Owner() const;
  
    ICDataPtr Original() const;
  
    void SetOriginal(const ICData& value) const;
  
    bool IsOriginal() const { return Original() == this->ptr(); }
  
    intptr_t NumArgsTested() const;
  
    intptr_t deopt_id() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
      return -1;
  #else
      return untag()->deopt_id_;
  #endif
    }
  
    bool IsImmutable() const;
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    AbstractTypePtr receivers_static_type() const {
      return untag()->receivers_static_type();
    }
    bool is_tracking_exactness() const {
      return untag()->state_bits_.Read<TrackingExactnessBit>();
    }
  #else
    bool is_tracking_exactness() const { return false; }
  #endif
  
  // Note: only deopts with reasons before Unknown in this list are recorded in
  // the ICData. All other reasons are used purely for informational messages
  // printed during deoptimization itself.
  #define DEOPT_REASONS(V)                                                       \
    V(BinarySmiOp)                                                               \
    V(BinaryInt64Op)                                                             \
    V(DoubleToSmi)                                                               \
    V(CheckSmi)                                                                  \
    V(CheckClass)                                                                \
    V(Unknown)                                                                   \
    V(PolymorphicInstanceCallTestFail)                                           \
    V(UnaryInt64Op)                                                              \
    V(BinaryDoubleOp)                                                            \
    V(UnaryOp)                                                                   \
    V(UnboxInteger)                                                              \
    V(Unbox)                                                                     \
    V(CheckArrayBound)                                                           \
    V(AtCall)                                                                    \
    V(GuardField)                                                                \
    V(TestCids)                                                                  \
    V(NumReasons)
  
    enum DeoptReasonId {
  #define DEFINE_ENUM_LIST(name) kDeopt##name,
      DEOPT_REASONS(DEFINE_ENUM_LIST)
  #undef DEFINE_ENUM_LIST
    };
  
    static const intptr_t kLastRecordedDeoptReason = kDeoptUnknown - 1;
  
    enum DeoptFlags {
      // Deoptimization is caused by an optimistically hoisted instruction.
      kHoisted = 1 << 0,
  
      // Deoptimization is caused by an optimistically generalized bounds check.
      kGeneralized = 1 << 1
    };
  
    bool HasDeoptReasons() const { return DeoptReasons() != 0; }
    uint32_t DeoptReasons() const;
    void SetDeoptReasons(uint32_t reasons) const;
  
    bool HasDeoptReason(ICData::DeoptReasonId reason) const;
    void AddDeoptReason(ICData::DeoptReasonId reason) const;
  
    // Call site classification that is helpful for hot-reload. Call sites with
    // different `RebindRule` have to be rebound differently.
  #define FOR_EACH_REBIND_RULE(V)                                                \
    V(Instance)                                                                  \
    V(NoRebind)                                                                  \
    V(NSMDispatch)                                                               \
    V(Optimized)                                                                 \
    V(Static)                                                                    \
    V(Super)
  
    enum RebindRule {
  #define REBIND_ENUM_DEF(name) k##name,
      FOR_EACH_REBIND_RULE(REBIND_ENUM_DEF)
  #undef REBIND_ENUM_DEF
          kNumRebindRules,
    };
    static const char* RebindRuleToCString(RebindRule r);
    static bool ParseRebindRule(const char* str, RebindRule* out);
    RebindRule rebind_rule() const;
  
    void set_is_megamorphic(bool value) const {
      untag()->state_bits_.UpdateBool<MegamorphicBit, std::memory_order_release>(
          value);
    }
  
    // The length of the array. This includes all sentinel entries including
    // the final one.
    intptr_t Length() const;
  
    // Takes O(result) time!
    intptr_t NumberOfChecks() const;
  
    // Discounts any checks with usage of zero.
    // Takes O(result)) time!
    intptr_t NumberOfUsedChecks() const;
  
    // Takes O(n) time!
    bool NumberOfChecksIs(intptr_t n) const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedICData));
    }
  
    static intptr_t state_bits_offset() {
      return OFFSET_OF(UntaggedICData, state_bits_);
    }
  
    static intptr_t NumArgsTestedShift() { return kNumArgsTestedPos; }
  
    static intptr_t NumArgsTestedMask() {
      return ((1 << kNumArgsTestedSize) - 1) << kNumArgsTestedPos;
    }
  
    static intptr_t entries_offset() {
      return OFFSET_OF(UntaggedICData, entries_);
    }
  
    static intptr_t owner_offset() { return OFFSET_OF(UntaggedICData, owner_); }
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    static intptr_t receivers_static_type_offset() {
      return OFFSET_OF(UntaggedICData, receivers_static_type_);
    }
  #endif
  
    // Replaces entry |index| with the sentinel.
    // NOTE: Can only be called during reload.
    void WriteSentinelAt(intptr_t index,
                         const CallSiteResetter& proof_of_reload) const;
  
    // Clears the count for entry |index|.
    // NOTE: Can only be called during reload.
    void ClearCountAt(intptr_t index,
                      const CallSiteResetter& proof_of_reload) const;
  
    // Clear all entries with the sentinel value and reset the first entry
    // with the dummy target entry.
    // NOTE: Can only be called during reload.
    void ClearAndSetStaticTarget(const Function& func,
                                 const CallSiteResetter& proof_of_reload) const;
  
    void DebugDump() const;
  
    // Adding checks.
  
    // Ensures there is a check for [class_ids].
    //
    // Calls [AddCheck] iff there is no existing check. Ensures test (and
    // potential update) will be performed under exclusive lock to guard against
    // multiple threads trying to add the same check.
    void EnsureHasCheck(const GrowableArray<intptr_t>& class_ids,
                        const Function& target,
                        intptr_t count = 1) const;
  
    // Adds one more class test to ICData. Length of 'classes' must be equal to
    // the number of arguments tested. Use only for num_args_tested > 1.
    void AddCheck(const GrowableArray<intptr_t>& class_ids,
                  const Function& target,
                  intptr_t count = 1) const;
  
    StaticTypeExactnessState GetExactnessAt(intptr_t count) const;
  
    // Ensures there is a receiver check for [receiver_class_id].
    //
    // Calls [AddCheckReceiverCheck] iff there is no existing check. Ensures
    // test (and potential update) will be performed under exclusive lock to
    // guard against multiple threads trying to add the same check.
    void EnsureHasReceiverCheck(
        intptr_t receiver_class_id,
        const Function& target,
        intptr_t count = 1,
        StaticTypeExactnessState exactness =
            StaticTypeExactnessState::NotTracking()) const;
  
    // Adds sorted so that Smi is the first class-id. Use only for
    // num_args_tested == 1.
    void AddReceiverCheck(intptr_t receiver_class_id,
                          const Function& target,
                          intptr_t count = 1,
                          StaticTypeExactnessState exactness =
                              StaticTypeExactnessState::NotTracking()) const;
  
    // Retrieving checks.
  
    void GetCheckAt(intptr_t index,
                    GrowableArray<intptr_t>* class_ids,
                    Function* target) const;
    void GetClassIdsAt(intptr_t index, GrowableArray<intptr_t>* class_ids) const;
  
    // Only for 'num_args_checked == 1'.
    void GetOneClassCheckAt(intptr_t index,
                            intptr_t* class_id,
                            Function* target) const;
    // Only for 'num_args_checked == 1'.
    intptr_t GetCidAt(intptr_t index) const;
  
    intptr_t GetReceiverClassIdAt(intptr_t index) const;
    intptr_t GetClassIdAt(intptr_t index, intptr_t arg_nr) const;
  
    FunctionPtr GetTargetAt(intptr_t index) const;
  
    void IncrementCountAt(intptr_t index, intptr_t value) const;
    void SetCountAt(intptr_t index, intptr_t value) const;
    intptr_t GetCountAt(intptr_t index) const;
    intptr_t AggregateCount() const;
  
    // Returns this->untag() if num_args_tested == 1 and arg_nr == 1, otherwise
    // returns a new ICData object containing only unique arg_nr checks.
    // Returns only used entries.
    ICDataPtr AsUnaryClassChecksForArgNr(intptr_t arg_nr) const;
    ICDataPtr AsUnaryClassChecks() const { return AsUnaryClassChecksForArgNr(0); }
  
    // Returns ICData with aggregated receiver count, sorted by highest count.
    // Smi not first!! (the convention for ICData used in code generation is that
    // Smi check is first)
    // Used for printing and optimizations.
    ICDataPtr AsUnaryClassChecksSortedByCount() const;
  
    UnlinkedCallPtr AsUnlinkedCall() const;
  
    bool HasReceiverClassId(intptr_t class_id) const;
  
    // Note: passing non-null receiver_type enables exactness tracking for
    // the receiver type. Receiver type is expected to be a fully
    // instantiated generic (but not a FutureOr).
    // See StaticTypeExactnessState for more information.
    static ICDataPtr 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& receiver_type = Object::null_abstract_type());
  
    // Similar to [New] makes the ICData have an initial (cids, target) entry.
    static ICDataPtr 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 = Object::null_abstract_type());
  
    static ICDataPtr NewForStaticCall(const Function& owner,
                                      const Function& target,
                                      const Array& arguments_descriptor,
                                      intptr_t deopt_id,
                                      intptr_t num_args_tested,
                                      RebindRule rebind_rule);
  
    static ICDataPtr NewFrom(const ICData& from, intptr_t num_args_tested);
  
    // Generates a new ICData with descriptor and data array copied (deep clone).
    static ICDataPtr Clone(const ICData& from);
  
    // Gets the [ICData] from the [ICData::entries_] array (which stores a back
    // ref).
    //
    // May return `null` if the [ICData] is empty.
    static ICDataPtr ICDataOfEntriesArray(const Array& array);
  
    static intptr_t TestEntryLengthFor(intptr_t num_args,
                                       bool tracking_exactness);
  
    static intptr_t CountIndexFor(intptr_t num_args) { return num_args; }
    static intptr_t EntryPointIndexFor(intptr_t num_args) { return num_args; }
  
    static intptr_t TargetIndexFor(intptr_t num_args) { return num_args + 1; }
    static intptr_t CodeIndexFor(intptr_t num_args) { return num_args + 1; }
  
    static intptr_t ExactnessIndexFor(intptr_t num_args) { return num_args + 2; }
  
    bool IsUsedAt(intptr_t i) const;
  
    void PrintToJSONArray(const JSONArray& jsarray,
                          TokenPosition token_pos) const;
  
    // Initialize the preallocated empty ICData entry arrays.
    static void Init();
  
    // Clear the preallocated empty ICData entry arrays.
    static void Cleanup();
  
    // We cache ICData with 0, 1, 2 arguments tested without exactness
    // tracking and with 1 argument tested with exactness tracking.
    enum {
      kCachedICDataZeroArgTestedWithoutExactnessTrackingIdx = 0,
      kCachedICDataMaxArgsTestedWithoutExactnessTracking = 2,
      kCachedICDataOneArgWithExactnessTrackingIdx =
          kCachedICDataZeroArgTestedWithoutExactnessTrackingIdx +
          kCachedICDataMaxArgsTestedWithoutExactnessTracking + 1,
      kCachedICDataArrayCount = kCachedICDataOneArgWithExactnessTrackingIdx + 1,
    };
  
    bool is_static_call() const;
  
    intptr_t FindCheck(const GrowableArray<intptr_t>& cids) const;
  
    ArrayPtr entries() const {
      return untag()->entries<std::memory_order_acquire>();
    }
  
    bool receiver_cannot_be_smi() const {
      return untag()->state_bits_.Read<ReceiverCannotBeSmiBit>();
    }
  
    void set_receiver_cannot_be_smi(bool value) const {
      untag()->state_bits_.UpdateBool<ReceiverCannotBeSmiBit>(value);
    }
  
    uword Hash() const;
  
   private:
    static ICDataPtr New();
  
    // Grows the array and also sets the argument to the index that should be used
    // for the new entry.
    ArrayPtr Grow(intptr_t* index) const;
  
    void set_deopt_id(intptr_t value) const;
    void set_entries(const Array& value) const;
    void set_owner(const Function& value) const;
    void set_rebind_rule(uint32_t rebind_rule) const;
    void clear_state_bits() const;
    void set_tracking_exactness(bool value) const {
      untag()->state_bits_.UpdateBool<TrackingExactnessBit>(value);
    }
  
    // Does entry |index| contain the sentinel value?
    bool IsSentinelAt(intptr_t index) const;
    void SetNumArgsTested(intptr_t value) const;
    void SetReceiversStaticType(const AbstractType& type) const;
  
    static void SetTargetAtPos(const Array& data,
                               intptr_t data_pos,
                               intptr_t num_args_tested,
                               const Function& target);
    void AddCheckInternal(const GrowableArray<intptr_t>& class_ids,
                          const Function& target,
                          intptr_t count) const;
    void AddReceiverCheckInternal(intptr_t receiver_class_id,
                                  const Function& target,
                                  intptr_t count,
                                  StaticTypeExactnessState exactness) const;
  
    // This bit is set when a call site becomes megamorphic and starts using a
    // MegamorphicCache instead of ICData. It means that the entries in the
    // ICData are incomplete and the MegamorphicCache needs to also be consulted
    // to list the call site's observed receiver classes and targets.
    // In the compiler, this should only be read once by CallTargets to avoid the
    // compiler seeing an unstable set of feedback.
    bool is_megamorphic() const {
      // Ensure any following load instructions do not get performed before this
      // one.
      return untag()
          ->state_bits_.Read<MegamorphicBit, std::memory_order_acquire>();
    }
  
    bool ValidateInterceptor(const Function& target) const;
  
    enum {
      kNumArgsTestedPos = 0,
      kNumArgsTestedSize = 2,
      kTrackingExactnessPos = kNumArgsTestedPos + kNumArgsTestedSize,
      kTrackingExactnessSize = 1,
      kDeoptReasonPos = kTrackingExactnessPos + kTrackingExactnessSize,
      kDeoptReasonSize = kLastRecordedDeoptReason + 1,
      kRebindRulePos = kDeoptReasonPos + kDeoptReasonSize,
      kRebindRuleSize = 3,
      kMegamorphicPos = kRebindRulePos + kRebindRuleSize,
      kMegamorphicSize = 1,
      kReceiverCannotBeSmiPos = kMegamorphicPos + kMegamorphicSize,
      kReceiverCannotBeSmiSize = 1,
    };
  
    COMPILE_ASSERT(kReceiverCannotBeSmiPos + kReceiverCannotBeSmiSize <=
                   sizeof(UntaggedICData::state_bits_) * kBitsPerWord);
    COMPILE_ASSERT(kNumRebindRules <= (1 << kRebindRuleSize));
  
    class NumArgsTestedBits : public BitField<uint32_t,
                                              uint32_t,
                                              kNumArgsTestedPos,
                                              kNumArgsTestedSize> {};
    class TrackingExactnessBit : public BitField<uint32_t,
                                                 bool,
                                                 kTrackingExactnessPos,
                                                 kTrackingExactnessSize> {};
    class DeoptReasonBits : public BitField<uint32_t,
                                            uint32_t,
                                            ICData::kDeoptReasonPos,
                                            ICData::kDeoptReasonSize> {};
    class RebindRuleBits : public BitField<uint32_t,
                                           uint32_t,
                                           ICData::kRebindRulePos,
                                           ICData::kRebindRuleSize> {};
    class MegamorphicBit
        : public BitField<uint32_t, bool, kMegamorphicPos, kMegamorphicSize> {};
  
    class ReceiverCannotBeSmiBit : public BitField<uint32_t,
                                                   bool,
                                                   kReceiverCannotBeSmiPos,
                                                   kReceiverCannotBeSmiSize> {};
  
  #if defined(DEBUG)
    // Used in asserts to verify that a check is not added twice.
    bool HasCheck(const GrowableArray<intptr_t>& cids) const;
  #endif  // DEBUG
  
    intptr_t TestEntryLength() const;
    static ArrayPtr NewNonCachedEmptyICDataArray(intptr_t num_args_tested,
                                                 bool tracking_exactness);
    static ArrayPtr CachedEmptyICDataArray(intptr_t num_args_tested,
                                           bool tracking_exactness);
    static ICDataPtr 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& receiver_type);
  
    static void WriteSentinel(const Array& data,
                              intptr_t test_entry_length,
                              const Object& back_ref);
  
    // A cache of VM heap allocated preinitialized empty ic data entry arrays.
    static ArrayPtr cached_icdata_arrays_[kCachedICDataArrayCount];
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(ICData, CallSiteData);
    friend class CallSiteResetter;
    friend class CallTargets;
    friend class Class;
    friend class VMDeserializationRoots;
    friend class ICDataTestTask;
    friend class VMSerializationRoots;
  };
  
  // Often used constants for number of free function type parameters.
  enum {
    kNoneFree = 0,
  
    // 'kCurrentAndEnclosingFree' is used when partially applying a signature
    // function to a set of type arguments. It indicates that the set of type
    // parameters declared by the current function and enclosing functions should
    // be considered free, and the current function type parameters should be
    // substituted as well.
    //
    // For instance, if the signature "<T>(T, R) => T" is instantiated with
    // function type arguments [int, String] and kCurrentAndEnclosingFree is
    // supplied, the result of the instantiation will be "(String, int) => int".
    kCurrentAndEnclosingFree = kMaxInt32 - 1,
  
    // Only parameters declared by enclosing functions are free.
    kAllFree = kMaxInt32,
  };
  
  // Formatting configuration for Function::PrintName.
  struct NameFormattingParams {
    Object::NameVisibility name_visibility;
    bool disambiguate_names;
  
    // By default function name includes the name of the enclosing class if any.
    // However in some contexts this information is redundant and class name
    // is already known. In this case setting |include_class_name| to false
    // allows you to exclude this information from the formatted name.
    bool include_class_name = true;
  
    // By default function name includes the name of the enclosing function if
    // any. However in some contexts this information is redundant and
    // the name of the enclosing function is already known. In this case
    // setting |include_parent_name| to false allows to exclude this information
    // from the formatted name.
    bool include_parent_name = true;
  
    NameFormattingParams(Object::NameVisibility visibility,
                         Object::NameDisambiguation name_disambiguation =
                             Object::NameDisambiguation::kNo)
        : name_visibility(visibility),
          disambiguate_names(name_disambiguation ==
                             Object::NameDisambiguation::kYes) {}
  
    static NameFormattingParams DisambiguatedWithoutClassName(
        Object::NameVisibility visibility) {
      NameFormattingParams params(visibility, Object::NameDisambiguation::kYes);
      params.include_class_name = false;
      return params;
    }
  
    static NameFormattingParams DisambiguatedUnqualified(
        Object::NameVisibility visibility) {
      NameFormattingParams params(visibility, Object::NameDisambiguation::kYes);
      params.include_class_name = false;
      params.include_parent_name = false;
      return params;
    }
  };
  
  class Function : public Object {
   public:
    StringPtr name() const { return untag()->name(); }
    StringPtr UserVisibleName() const;  // Same as scrubbed name.
    const char* UserVisibleNameCString() const;
  
    const char* NameCString(NameVisibility name_visibility) const;
  
    void PrintName(const NameFormattingParams& params,
                   BaseTextBuffer* printer) const;
    StringPtr QualifiedScrubbedName() const;
    StringPtr QualifiedUserVisibleName() const;
    const char* QualifiedUserVisibleNameCString() const;
  
    virtual StringPtr DictionaryName() const { return name(); }
  
    StringPtr GetSource() const;
  
    // Set the "C signature" for an FFI trampoline.
    // Can only be used on FFI trampolines.
    void SetFfiCSignature(const FunctionType& sig) const;
  
    // Retrieves the "C signature" for an FFI trampoline.
    // Can only be used on FFI trampolines.
    FunctionTypePtr FfiCSignature() const;
  
    bool FfiCSignatureContainsHandles() const;
    bool FfiCSignatureReturnsStruct() const;
  
    // Can only be called on FFI trampolines.
    // -1 for Dart -> native calls.
    int32_t FfiCallbackId() const;
  
    // Can only be called on FFI trampolines.
    void SetFfiCallbackId(int32_t value) const;
  
    // Can only be called on FFI trampolines.
    bool FfiIsLeaf() const;
  
    // Can only be called on FFI trampolines.
    void SetFfiIsLeaf(bool is_leaf) const;
  
    // Can only be called on FFI trampolines.
    // Null for Dart -> native calls.
    FunctionPtr FfiCallbackTarget() const;
  
    // Can only be called on FFI trampolines.
    void SetFfiCallbackTarget(const Function& target) const;
  
    // Can only be called on FFI trampolines.
    // Null for Dart -> native calls.
    InstancePtr FfiCallbackExceptionalReturn() const;
  
    // Can only be called on FFI trampolines.
    void SetFfiCallbackExceptionalReturn(const Instance& value) const;
  
    // Return the signature of this function.
    PRECOMPILER_WSR_FIELD_DECLARATION(FunctionType, signature);
    void SetSignature(const FunctionType& value) const;
    static intptr_t signature_offset() {
      return OFFSET_OF(UntaggedFunction, signature_);
    }
  
    // Build a string of the form '<T>(T, {B b, C c}) => R' representing the
    // internal signature of the given function. In this example, T is a type
    // parameter of this function and R is a type parameter of class C, the owner
    // of the function. B and C are not type parameters.
    StringPtr InternalSignature() const;
  
    // Build a string of the form '<T>(T, {B b, C c}) => R' representing the
    // user visible signature of the given function. In this example, T is a type
    // parameter of this function and R is a type parameter of class C, the owner
    // of the function. B and C are not type parameters.
    // Implicit parameters are hidden.
    StringPtr UserVisibleSignature() const;
  
    // Returns true if the signature of this function is instantiated, i.e. if it
    // does not involve generic parameter types or generic result type.
    // Note that function type parameters declared by this function do not make
    // its signature uninstantiated, only type parameters declared by parent
    // generic functions or class type parameters.
    bool HasInstantiatedSignature(Genericity genericity = kAny,
                                  intptr_t num_free_fun_type_params = kAllFree,
                                  TrailPtr trail = nullptr) const;
  
    bool IsPrivate() const;
  
    ClassPtr Owner() const;
    void set_owner(const Object& value) const;
    ClassPtr origin() const;
    ScriptPtr script() const;
    ObjectPtr RawOwner() const { return untag()->owner(); }
  
    // The NNBD mode of the library declaring this function.
    // TODO(alexmarkov): nnbd_mode() doesn't work for mixins.
    // It should be either removed or fixed.
    NNBDMode nnbd_mode() const { return Class::Handle(origin()).nnbd_mode(); }
  
    RegExpPtr regexp() const;
    intptr_t string_specialization_cid() const;
    bool is_sticky_specialization() const;
    void SetRegExpData(const RegExp& regexp,
                       intptr_t string_specialization_cid,
                       bool sticky) const;
  
    StringPtr native_name() const;
    void set_native_name(const String& name) const;
  
    AbstractTypePtr result_type() const {
      return signature()->untag()->result_type();
    }
  
    // The parameters, starting with NumImplicitParameters() parameters which are
    // only visible to the VM, but not to Dart users.
    // Note that type checks exclude implicit parameters.
    AbstractTypePtr ParameterTypeAt(intptr_t index) const;
    ArrayPtr parameter_types() const {
      return signature()->untag()->parameter_types();
    }
  
    // Outside of the AOT runtime, functions store the names for their positional
    // parameters, and delegate storage of the names for named parameters to
    // their signature. These methods handle fetching the name from and
    // setting the name to the correct location.
    StringPtr ParameterNameAt(intptr_t index) const;
    // Only valid for positional parameter indexes, as this should be called
    // explicitly on the signature for named parameters.
    void SetParameterNameAt(intptr_t index, const String& value) const;
    // Creates an appropriately sized array in the function to hold positional
    // parameter names, using the positional parameter count in the signature.
    // Uses same default space as Function::New.
    void CreateNameArray(Heap::Space space = Heap::kOld) const;
  
    // Delegates to the signature, which stores the named parameter flags.
    bool IsRequiredAt(intptr_t index) const;
  
    // The formal type parameters, their bounds, and defaults, are specified as an
    // object of type TypeParameters stored in the signature.
    TypeParametersPtr type_parameters() const {
      return signature()->untag()->type_parameters();
    }
  
    // Returns the number of local type arguments for this function.
    intptr_t NumTypeParameters() const;
    // Return the cumulative number of type arguments in all parent functions.
    intptr_t NumParentTypeArguments() const;
    // Return the cumulative number of type arguments for this function, including
    // type arguments for all parent functions.
    intptr_t NumTypeArguments() const;
    // Return whether this function declares local type arguments.
    bool IsGeneric() const;
    // Returns whether any parent function of this function is generic.
    bool HasGenericParent() const { return NumParentTypeArguments() > 0; }
  
    // Return the type parameter declared at index.
    TypeParameterPtr TypeParameterAt(
        intptr_t index,
        Nullability nullability = Nullability::kNonNullable) const;
  
    // Not thread-safe; must be called in the main thread.
    // Sets function's code and code's function.
    void InstallOptimizedCode(const Code& code) const;
    void AttachCode(const Code& value) const;
    void SetInstructions(const Code& value) const;
    void SetInstructionsSafe(const Code& value) const;
    void ClearCode() const;
    void ClearCodeSafe() const;
  
    // Disables optimized code and switches to unoptimized code.
    void SwitchToUnoptimizedCode() const;
  
    // Ensures that the function has code. If there is no code it compiles the
    // unoptimized version of the code.  If the code contains errors, it calls
    // Exceptions::PropagateError and does not return.  Normally returns the
    // current code, whether it is optimized or unoptimized.
    CodePtr EnsureHasCode() const;
  
    // Disables optimized code and switches to unoptimized code (or the lazy
    // compilation stub).
    void SwitchToLazyCompiledUnoptimizedCode() const;
  
    // Compiles unoptimized code (if necessary) and attaches it to the function.
    void EnsureHasCompiledUnoptimizedCode() const;
  
    // Return the most recently compiled and installed code for this function.
    // It is not the only Code object that points to this function.
    CodePtr CurrentCode() const { return CurrentCodeOf(ptr()); }
  
    bool SafeToClosurize() const;
  
    static CodePtr CurrentCodeOf(const FunctionPtr function) {
      return function->untag()->code();
    }
  
    CodePtr unoptimized_code() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return static_cast<CodePtr>(Object::null());
  #else
      return untag()->unoptimized_code();
  #endif
    }
    void set_unoptimized_code(const Code& value) const;
    bool HasCode() const;
    static bool HasCode(FunctionPtr function);
  
    static intptr_t code_offset() { return OFFSET_OF(UntaggedFunction, code_); }
  
    uword entry_point() const { return untag()->entry_point_; }
  
    static intptr_t entry_point_offset(
        CodeEntryKind entry_kind = CodeEntryKind::kNormal) {
      switch (entry_kind) {
        case CodeEntryKind::kNormal:
          return OFFSET_OF(UntaggedFunction, entry_point_);
        case CodeEntryKind::kUnchecked:
          return OFFSET_OF(UntaggedFunction, unchecked_entry_point_);
        default:
          UNREACHABLE();
      }
    }
  
    static intptr_t unchecked_entry_point_offset() {
      return OFFSET_OF(UntaggedFunction, unchecked_entry_point_);
    }
  
    virtual uword Hash() const;
  
    // Returns true if there is at least one debugger breakpoint
    // set in this function.
    bool HasBreakpoint() const;
  
    ContextScopePtr context_scope() const;
    void set_context_scope(const ContextScope& value) const;
  
    // Enclosing function of this local function.
    FunctionPtr parent_function() const;
  
    using DefaultTypeArgumentsKind =
        UntaggedClosureData::DefaultTypeArgumentsKind;
  
    // Returns a canonicalized vector of the type parameters instantiated
    // to bounds. If non-generic, the empty type arguments vector is returned.
    TypeArgumentsPtr InstantiateToBounds(
        Thread* thread,
        DefaultTypeArgumentsKind* kind_out = nullptr) const;
  
    // Only usable for closure functions.
    DefaultTypeArgumentsKind default_type_arguments_kind() const;
    void set_default_type_arguments_kind(DefaultTypeArgumentsKind value) const;
  
    // Enclosing outermost function of this local function.
    FunctionPtr GetOutermostFunction() const;
  
    void set_extracted_method_closure(const Function& function) const;
    FunctionPtr extracted_method_closure() const;
  
    void set_saved_args_desc(const Array& array) const;
    ArrayPtr saved_args_desc() const;
  
    bool HasSavedArgumentsDescriptor() const {
      return IsInvokeFieldDispatcher() || IsNoSuchMethodDispatcher();
    }
  
    void set_accessor_field(const Field& value) const;
    FieldPtr accessor_field() const;
  
    bool IsRegularFunction() const {
      return kind() == UntaggedFunction::kRegularFunction;
    }
  
    bool IsMethodExtractor() const {
      return kind() == UntaggedFunction::kMethodExtractor;
    }
  
    bool IsNoSuchMethodDispatcher() const {
      return kind() == UntaggedFunction::kNoSuchMethodDispatcher;
    }
  
    bool IsInvokeFieldDispatcher() const {
      return kind() == UntaggedFunction::kInvokeFieldDispatcher;
    }
  
    bool IsDynamicInvokeFieldDispatcher() const {
      return IsInvokeFieldDispatcher() &&
             IsDynamicInvocationForwarderName(name());
    }
  
    // Performs all the checks that don't require the current thread first, to
    // avoid retrieving it unless they all pass. If you have a handle on the
    // current thread, call the version that takes one instead.
    bool IsDynamicClosureCallDispatcher() const {
      if (!IsDynamicInvokeFieldDispatcher()) return false;
      return IsDynamicClosureCallDispatcher(Thread::Current());
    }
    bool IsDynamicClosureCallDispatcher(Thread* thread) const;
  
    bool IsDynamicInvocationForwarder() const {
      return kind() == UntaggedFunction::kDynamicInvocationForwarder;
    }
  
    bool IsImplicitGetterOrSetter() const {
      return kind() == UntaggedFunction::kImplicitGetter ||
             kind() == UntaggedFunction::kImplicitSetter ||
             kind() == UntaggedFunction::kImplicitStaticGetter;
    }
  
    // Returns true iff an implicit closure function has been created
    // for this function.
    bool HasImplicitClosureFunction() const {
      return implicit_closure_function() != null();
    }
  
    // Returns the closure function implicitly created for this function.  If none
    // exists yet, create one and remember it.  Implicit closure functions are
    // used in VM Closure instances that represent results of tear-off operations.
    FunctionPtr ImplicitClosureFunction() const;
    void DropUncompiledImplicitClosureFunction() const;
  
    // Return the closure implicitly created for this function.
    // If none exists yet, create one and remember it.
    ClosurePtr ImplicitStaticClosure() const;
  
    ClosurePtr ImplicitInstanceClosure(const Instance& receiver) const;
  
    // Returns the target of the implicit closure or null if the target is now
    // invalid (e.g., mismatched argument shapes after a reload).
    FunctionPtr ImplicitClosureTarget(Zone* zone) const;
  
    intptr_t ComputeClosureHash() const;
  
    FunctionPtr ForwardingTarget() const;
    void SetForwardingTarget(const Function& target) const;
  
    UntaggedFunction::Kind kind() const {
      return untag()->kind_tag_.Read<KindBits>();
    }
  
    UntaggedFunction::AsyncModifier modifier() const {
      return untag()->kind_tag_.Read<ModifierBits>();
    }
  
    static const char* KindToCString(UntaggedFunction::Kind kind);
  
    bool IsConstructor() const {
      return kind() == UntaggedFunction::kConstructor;
    }
    bool IsGenerativeConstructor() const {
      return IsConstructor() && !is_static();
    }
    bool IsImplicitConstructor() const;
    bool IsFactory() const { return IsConstructor() && is_static(); }
  
    bool HasThisParameter() const {
      return IsDynamicFunction(/*allow_abstract=*/true) ||
             IsGenerativeConstructor() || (IsFieldInitializer() && !is_static());
    }
  
    bool IsDynamicFunction(bool allow_abstract = false) const {
      if (is_static() || (!allow_abstract && is_abstract())) {
        return false;
      }
      switch (kind()) {
        case UntaggedFunction::kRegularFunction:
        case UntaggedFunction::kGetterFunction:
        case UntaggedFunction::kSetterFunction:
        case UntaggedFunction::kImplicitGetter:
        case UntaggedFunction::kImplicitSetter:
        case UntaggedFunction::kMethodExtractor:
        case UntaggedFunction::kNoSuchMethodDispatcher:
        case UntaggedFunction::kInvokeFieldDispatcher:
        case UntaggedFunction::kDynamicInvocationForwarder:
          return true;
        case UntaggedFunction::kClosureFunction:
        case UntaggedFunction::kImplicitClosureFunction:
        case UntaggedFunction::kConstructor:
        case UntaggedFunction::kImplicitStaticGetter:
        case UntaggedFunction::kFieldInitializer:
        case UntaggedFunction::kIrregexpFunction:
          return false;
        default:
          UNREACHABLE();
          return false;
      }
    }
    bool IsStaticFunction() const {
      if (!is_static()) {
        return false;
      }
      switch (kind()) {
        case UntaggedFunction::kRegularFunction:
        case UntaggedFunction::kGetterFunction:
        case UntaggedFunction::kSetterFunction:
        case UntaggedFunction::kImplicitGetter:
        case UntaggedFunction::kImplicitSetter:
        case UntaggedFunction::kImplicitStaticGetter:
        case UntaggedFunction::kFieldInitializer:
        case UntaggedFunction::kIrregexpFunction:
          return true;
        case UntaggedFunction::kClosureFunction:
        case UntaggedFunction::kImplicitClosureFunction:
        case UntaggedFunction::kConstructor:
        case UntaggedFunction::kMethodExtractor:
        case UntaggedFunction::kNoSuchMethodDispatcher:
        case UntaggedFunction::kInvokeFieldDispatcher:
        case UntaggedFunction::kDynamicInvocationForwarder:
          return false;
        default:
          UNREACHABLE();
          return false;
      }
    }
  
    bool NeedsTypeArgumentTypeChecks() const {
      return !(is_static() || (kind() == UntaggedFunction::kConstructor));
    }
  
    bool NeedsArgumentTypeChecks() const {
      return !(is_static() || (kind() == UntaggedFunction::kConstructor));
    }
  
    bool NeedsMonomorphicCheckedEntry(Zone* zone) const;
    bool HasDynamicCallers(Zone* zone) const;
    bool PrologueNeedsArgumentsDescriptor() const;
  
    bool MayHaveUncheckedEntryPoint() const;
  
    TokenPosition token_pos() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return TokenPosition::kNoSource;
  #else
      return untag()->token_pos_;
  #endif
    }
    void set_token_pos(TokenPosition value) const;
  
    TokenPosition end_token_pos() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return TokenPosition::kNoSource;
  #else
      return untag()->end_token_pos_;
  #endif
    }
    void set_end_token_pos(TokenPosition value) const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
  #else
      StoreNonPointer(&untag()->end_token_pos_, value);
  #endif
    }
  
    // Returns the size of the source for this function.
    intptr_t SourceSize() const;
  
    uint32_t packed_fields() const { return untag()->packed_fields_; }
    void set_packed_fields(uint32_t packed_fields) const;
    static intptr_t packed_fields_offset() {
      return OFFSET_OF(UntaggedFunction, packed_fields_);
    }
  
    // Returns the number of required positional parameters.
    intptr_t num_fixed_parameters() const;
    // Returns the number of optional parameters, whether positional or named.
    bool HasOptionalParameters() const;
    // Returns whether the function has optional named parameters.
    bool HasOptionalNamedParameters() const;
    // Returns whether the fuction has required named parameters.
    bool HasRequiredNamedParameters() const;
    // Returns whether the function has optional positional parameters.
    bool HasOptionalPositionalParameters() const;
    // Returns the number of optional parameters, or 0 if none.
    intptr_t NumOptionalParameters() const;
    // Returns the number of optional positional parameters, or 0 if none.
    intptr_t NumOptionalPositionalParameters() const;
    // Returns the number of optional named parameters, or 0 if none.
    intptr_t NumOptionalNamedParameters() const;
    // Returns the total number of both required and optional parameters.
    intptr_t NumParameters() const;
    // Returns the number of implicit parameters, e.g., this for instance methods.
    intptr_t NumImplicitParameters() const;
  
  #if defined(DART_PRECOMPILED_RUNTIME)
  #define DEFINE_GETTERS_AND_SETTERS(return_type, type, name)                    \
    static intptr_t name##_offset() {                                            \
      UNREACHABLE();                                                             \
      return 0;                                                                  \
    }                                                                            \
    return_type name() const { return 0; }                                       \
                                                                                 \
    void set_##name(type value) const { UNREACHABLE(); }
  #else
  #define DEFINE_GETTERS_AND_SETTERS(return_type, type, name)                    \
    static intptr_t name##_offset() {                                            \
      return OFFSET_OF(UntaggedFunction, name##_);                               \
    }                                                                            \
    return_type name() const {                                                   \
      return LoadNonPointer<type, std::memory_order_relaxed>(&untag()->name##_); \
    }                                                                            \
                                                                                 \
    void set_##name(type value) const {                                          \
      StoreNonPointer<type, type, std::memory_order_relaxed>(&untag()->name##_,  \
                                                             value);             \
    }
  #endif
  
    JIT_FUNCTION_COUNTERS(DEFINE_GETTERS_AND_SETTERS)
  
  #undef DEFINE_GETTERS_AND_SETTERS
  
    intptr_t kernel_offset() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return 0;
  #else
      return untag()->kernel_offset_;
  #endif
    }
  
    void set_kernel_offset(intptr_t value) const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
  #else
      ASSERT(value >= 0);
      StoreNonPointer(&untag()->kernel_offset_, value);
  #endif
    }
  
    void InheritKernelOffsetFrom(const Function& src) const;
    void InheritKernelOffsetFrom(const Field& src) const;
  
    static const intptr_t kMaxInstructionCount = (1 << 16) - 1;
  
    void SetOptimizedInstructionCountClamped(uintptr_t value) const {
      if (value > kMaxInstructionCount) value = kMaxInstructionCount;
      set_optimized_instruction_count(value);
    }
  
    void SetOptimizedCallSiteCountClamped(uintptr_t value) const {
      if (value > kMaxInstructionCount) value = kMaxInstructionCount;
      set_optimized_call_site_count(value);
    }
  
    void SetKernelDataAndScript(const Script& script,
                                const ExternalTypedData& data,
                                intptr_t offset) const;
  
    intptr_t KernelDataProgramOffset() const;
  
    ExternalTypedDataPtr KernelData() const;
  
    bool IsOptimizable() const;
    void SetIsOptimizable(bool value) const;
  
    // Whether this function must be optimized immediately and cannot be compiled
    // with the unoptimizing compiler. Such a function must be sure to not
    // deoptimize, since we won't generate deoptimization info or register
    // dependencies. It will be compiled into optimized code immediately when it's
    // run.
    bool ForceOptimize() const {
      return IsFfiFromAddress() || IsFfiGetAddress() || IsFfiLoad() ||
             IsFfiStore() || IsFfiTrampoline() || IsFfiAsExternalTypedData() ||
             IsTypedDataViewFactory() || IsUtf8Scan() || IsGetNativeField();
    }
  
    bool CanBeInlined() const;
  
    MethodRecognizer::Kind recognized_kind() const {
      return untag()->kind_tag_.Read<RecognizedBits>();
    }
    void set_recognized_kind(MethodRecognizer::Kind value) const;
  
    bool IsRecognized() const {
      return recognized_kind() != MethodRecognizer::kUnknown;
    }
  
    bool HasOptimizedCode() const;
  
    // Returns true if the argument counts are valid for calling this function.
    // Otherwise, it returns false and the reason (if error_message is not NULL).
    bool AreValidArgumentCounts(intptr_t num_type_arguments,
                                intptr_t num_arguments,
                                intptr_t num_named_arguments,
                                String* error_message) const;
  
    // Returns a TypeError if the provided arguments don't match the function
    // parameter types, null otherwise. Assumes AreValidArguments is called first.
    //
    // If the function has a non-null receiver in the arguments, the instantiator
    // type arguments are retrieved from the receiver, otherwise the null type
    // arguments vector is used.
    //
    // If the function is generic, the appropriate function type arguments are
    // retrieved either from the arguments array or the receiver (if a closure).
    // If no function type arguments are available in either location, the bounds
    // of the function type parameters are instantiated and used as the function
    // type arguments.
    //
    // The local function type arguments (_not_ parent function type arguments)
    // are also checked against the bounds of the corresponding parameters to
    // ensure they are appropriate subtypes if the function is generic.
    ObjectPtr DoArgumentTypesMatch(const Array& args,
                                   const ArgumentsDescriptor& arg_names) const;
  
    // Returns a TypeError if the provided arguments don't match the function
    // parameter types, null otherwise. Assumes AreValidArguments is called first.
    //
    // If the function is generic, the appropriate function type arguments are
    // retrieved either from the arguments array or the receiver (if a closure).
    // If no function type arguments are available in either location, the bounds
    // of the function type parameters are instantiated and used as the function
    // type arguments.
    //
    // The local function type arguments (_not_ parent function type arguments)
    // are also checked against the bounds of the corresponding parameters to
    // ensure they are appropriate subtypes if the function is generic.
    ObjectPtr DoArgumentTypesMatch(
        const Array& args,
        const ArgumentsDescriptor& arg_names,
        const TypeArguments& instantiator_type_args) const;
  
    // Returns a TypeError if the provided arguments don't match the function
    // parameter types, null otherwise. Assumes AreValidArguments is called first.
    //
    // The local function type arguments (_not_ parent function type arguments)
    // are also checked against the bounds of the corresponding parameters to
    // ensure they are appropriate subtypes if the function is generic.
    ObjectPtr DoArgumentTypesMatch(const Array& args,
                                   const ArgumentsDescriptor& arg_names,
                                   const TypeArguments& instantiator_type_args,
                                   const TypeArguments& function_type_args) const;
  
    // Returns true if the type argument count, total argument count and the names
    // of optional arguments are valid for calling this function.
    // Otherwise, it returns false and the reason (if error_message is not NULL).
    bool AreValidArguments(intptr_t num_type_arguments,
                           intptr_t num_arguments,
                           const Array& argument_names,
                           String* error_message) const;
    bool AreValidArguments(const ArgumentsDescriptor& args_desc,
                           String* error_message) const;
  
    // Fully qualified name uniquely identifying the function under gdb and during
    // ast printing. The special ':' character, if present, is replaced by '_'.
    const char* ToFullyQualifiedCString() const;
  
    const char* ToLibNamePrefixedQualifiedCString() const;
  
    const char* ToQualifiedCString() const;
  
    static constexpr intptr_t maximum_unboxed_parameter_count() {
      // Subtracts one that represents the return value
      return UntaggedFunction::UnboxedParameterBitmap::kCapacity - 1;
    }
  
    void reset_unboxed_parameters_and_return() const {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      StoreNonPointer(&untag()->unboxed_parameters_info_,
                      UntaggedFunction::UnboxedParameterBitmap());
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
    }
  
    void set_unboxed_integer_parameter_at(intptr_t index) const {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(index >= 0 && index < maximum_unboxed_parameter_count());
      index++;  // position 0 is reserved for the return value
      const_cast<UntaggedFunction::UnboxedParameterBitmap*>(
          &untag()->unboxed_parameters_info_)
          ->SetUnboxedInteger(index);
  #else
      UNREACHABLE();
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
    }
  
    void set_unboxed_double_parameter_at(intptr_t index) const {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(index >= 0 && index < maximum_unboxed_parameter_count());
      index++;  // position 0 is reserved for the return value
      const_cast<UntaggedFunction::UnboxedParameterBitmap*>(
          &untag()->unboxed_parameters_info_)
          ->SetUnboxedDouble(index);
  
  #else
      UNREACHABLE();
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
    }
  
    void set_unboxed_integer_return() const {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      const_cast<UntaggedFunction::UnboxedParameterBitmap*>(
          &untag()->unboxed_parameters_info_)
          ->SetUnboxedInteger(0);
  #else
      UNREACHABLE();
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
    }
  
    void set_unboxed_double_return() const {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      const_cast<UntaggedFunction::UnboxedParameterBitmap*>(
          &untag()->unboxed_parameters_info_)
          ->SetUnboxedDouble(0);
  
  #else
      UNREACHABLE();
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
    }
  
    bool is_unboxed_parameter_at(intptr_t index) const {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(index >= 0);
      index++;  // position 0 is reserved for the return value
      return untag()->unboxed_parameters_info_.IsUnboxed(index);
  #else
      return false;
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
    }
  
    bool is_unboxed_integer_parameter_at(intptr_t index) const {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(index >= 0);
      index++;  // position 0 is reserved for the return value
      return untag()->unboxed_parameters_info_.IsUnboxedInteger(index);
  #else
      return false;
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
    }
  
    bool is_unboxed_double_parameter_at(intptr_t index) const {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      ASSERT(index >= 0);
      index++;  // position 0 is reserved for the return value
      return untag()->unboxed_parameters_info_.IsUnboxedDouble(index);
  #else
      return false;
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
    }
  
    bool has_unboxed_return() const {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      return untag()->unboxed_parameters_info_.IsUnboxed(0);
  #else
      return false;
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
    }
  
    bool has_unboxed_integer_return() const {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      return untag()->unboxed_parameters_info_.IsUnboxedInteger(0);
  #else
      return false;
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
    }
  
    bool has_unboxed_double_return() const {
  #if !defined(DART_PRECOMPILED_RUNTIME)
      return untag()->unboxed_parameters_info_.IsUnboxedDouble(0);
  #else
      return false;
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
    }
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    bool HasUnboxedParameters() const {
      return untag()->unboxed_parameters_info_.HasUnboxedParameters();
    }
    bool HasUnboxedReturnValue() const {
      return untag()->unboxed_parameters_info_.HasUnboxedReturnValue();
    }
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
  
    bool IsDispatcherOrImplicitAccessor() const {
      switch (kind()) {
        case UntaggedFunction::kImplicitGetter:
        case UntaggedFunction::kImplicitSetter:
        case UntaggedFunction::kImplicitStaticGetter:
        case UntaggedFunction::kNoSuchMethodDispatcher:
        case UntaggedFunction::kInvokeFieldDispatcher:
        case UntaggedFunction::kDynamicInvocationForwarder:
          return true;
        default:
          return false;
      }
    }
  
    // Returns true if this function represents an explicit getter function.
    bool IsGetterFunction() const {
      return kind() == UntaggedFunction::kGetterFunction;
    }
  
    // Returns true if this function represents an implicit getter function.
    bool IsImplicitGetterFunction() const {
      return kind() == UntaggedFunction::kImplicitGetter;
    }
  
    // Returns true if this function represents an implicit static getter
    // function.
    bool IsImplicitStaticGetterFunction() const {
      return kind() == UntaggedFunction::kImplicitStaticGetter;
    }
  
    // Returns true if this function represents an explicit setter function.
    bool IsSetterFunction() const {
      return kind() == UntaggedFunction::kSetterFunction;
    }
  
    // Returns true if this function represents an implicit setter function.
    bool IsImplicitSetterFunction() const {
      return kind() == UntaggedFunction::kImplicitSetter;
    }
  
    // Returns true if this function represents an initializer for a static or
    // instance field. The function returns the initial value and the caller is
    // responsible for setting the field.
    bool IsFieldInitializer() const {
      return kind() == UntaggedFunction::kFieldInitializer;
    }
  
    // Returns true if this function represents a (possibly implicit) closure
    // function.
    bool IsClosureFunction() const {
      UntaggedFunction::Kind k = kind();
      return (k == UntaggedFunction::kClosureFunction) ||
             (k == UntaggedFunction::kImplicitClosureFunction);
    }
  
    // Returns true if this function represents a generated irregexp function.
    bool IsIrregexpFunction() const {
      return kind() == UntaggedFunction::kIrregexpFunction;
    }
  
    // Returns true if this function represents an implicit closure function.
    bool IsImplicitClosureFunction() const {
      return kind() == UntaggedFunction::kImplicitClosureFunction;
    }
  
    // Returns true if this function represents a non implicit closure function.
    bool IsNonImplicitClosureFunction() const {
      return IsClosureFunction() && !IsImplicitClosureFunction();
    }
  
    // Returns true if this function represents an implicit static closure
    // function.
    bool IsImplicitStaticClosureFunction() const {
      return IsImplicitClosureFunction() && is_static();
    }
    static bool IsImplicitStaticClosureFunction(FunctionPtr func);
  
    // Returns true if this function represents an implicit instance closure
    // function.
    bool IsImplicitInstanceClosureFunction() const {
      return IsImplicitClosureFunction() && !is_static();
    }
  
    // Returns true if this function has a parent function.
    bool HasParent() const { return parent_function() != Function::null(); }
  
    // Returns true if this function is a local function.
    bool IsLocalFunction() const {
      return !IsImplicitClosureFunction() && HasParent();
    }
  
    // Returns true if this function represents an ffi trampoline.
    bool IsFfiTrampoline() const {
      return kind() == UntaggedFunction::kFfiTrampoline;
    }
    static bool IsFfiTrampoline(FunctionPtr function) {
      NoSafepointScope no_safepoint;
      return function->untag()->kind_tag_.Read<KindBits>() ==
             UntaggedFunction::kFfiTrampoline;
    }
  
    bool IsFfiLoad() const {
      const auto kind = recognized_kind();
      return MethodRecognizer::kFfiLoadInt8 <= kind &&
             kind <= MethodRecognizer::kFfiLoadPointer;
    }
  
    bool IsFfiStore() const {
      const auto kind = recognized_kind();
      return MethodRecognizer::kFfiStoreInt8 <= kind &&
             kind <= MethodRecognizer::kFfiStorePointer;
    }
  
    bool IsFfiFromAddress() const {
      const auto kind = recognized_kind();
      return kind == MethodRecognizer::kFfiFromAddress;
    }
  
    bool IsFfiGetAddress() const {
      const auto kind = recognized_kind();
      return kind == MethodRecognizer::kFfiGetAddress;
    }
  
    bool IsFfiAsExternalTypedData() const {
      const auto kind = recognized_kind();
      return MethodRecognizer::kFfiAsExternalTypedDataInt8 <= kind &&
             kind <= MethodRecognizer::kFfiAsExternalTypedDataDouble;
    }
  
    bool IsGetNativeField() const {
      const auto kind = recognized_kind();
      return kind == MethodRecognizer::kGetNativeField;
    }
  
    bool IsUtf8Scan() const {
      const auto kind = recognized_kind();
      return kind == MethodRecognizer::kUtf8DecoderScan;
    }
  
    // Recognise async functions like:
    //   user_func async {
    //     // ...
    //   }
    bool IsAsyncFunction() const {
      return modifier() == UntaggedFunction::kAsync;
    }
  
    // Recognise synthetic sync-yielding functions like the inner-most:
    //   user_func /* was async */ {
    //      :async_op(..) yielding {
    //        // ...
    //      }
    //   }
    bool IsAsyncClosure() const {
      return is_generated_body() &&
             Function::Handle(parent_function()).IsAsyncFunction();
    }
  
    // Recognise sync* functions like:
    //   user_func sync* {
    //     // ...
    //   }
    bool IsSyncGenerator() const {
      return modifier() == UntaggedFunction::kSyncGen;
    }
  
    // Recognise synthetic :sync_op_gen()s like:
    //   user_func /* was sync* */ {
    //     :sync_op_gen() {
    //        // ...
    //      }
    //   }
    bool IsSyncGenClosureMaker() const {
      return is_generated_body() &&
             Function::Handle(parent_function()).IsSyncGenerator();
    }
  
    // Recognise async* functions like:
    //   user_func async* {
    //     // ...
    //   }
    bool IsAsyncGenerator() const {
      return modifier() == UntaggedFunction::kAsyncGen;
    }
  
    // Recognise synthetic sync-yielding functions like the inner-most:
    //   user_func /* originally async* */ {
    //      :async_op(..) yielding {
    //        // ...
    //      }
    //   }
    bool IsAsyncGenClosure() const {
      return is_generated_body() &&
             Function::Handle(parent_function()).IsAsyncGenerator();
    }
  
    bool IsAsyncOrGenerator() const {
      return modifier() != UntaggedFunction::kNoModifier;
    }
  
    // Recognise synthetic sync-yielding functions like the inner-most:
    //   user_func /* was sync* */ {
    //     :sync_op_gen() {
    //        :sync_op(..) yielding {
    //          // ...
    //        }
    //      }
    //   }
    bool IsSyncGenClosure() const {
      return is_generated_body() &&
             Function::Handle(parent_function()).IsSyncGenClosureMaker();
    }
  
    bool IsTypedDataViewFactory() const {
      if (is_native() && kind() == UntaggedFunction::kConstructor) {
        // This is a native factory constructor.
        const Class& klass = Class::Handle(Owner());
        return IsTypedDataViewClassId(klass.id());
      }
      return false;
    }
  
    DART_WARN_UNUSED_RESULT
    ErrorPtr VerifyCallEntryPoint() const;
  
    DART_WARN_UNUSED_RESULT
    ErrorPtr VerifyClosurizedEntryPoint() const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedFunction));
    }
  
    static FunctionPtr 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 = Heap::kOld);
  
    // Allocates a new Function object representing a closure function
    // with given kind - kClosureFunction or kImplicitClosureFunction.
    static FunctionPtr NewClosureFunctionWithKind(UntaggedFunction::Kind kind,
                                                  const String& name,
                                                  const Function& parent,
                                                  bool is_static,
                                                  TokenPosition token_pos,
                                                  const Object& owner);
  
    // Allocates a new Function object representing a closure function.
    static FunctionPtr NewClosureFunction(const String& name,
                                          const Function& parent,
                                          TokenPosition token_pos);
  
    // Allocates a new Function object representing an implicit closure function.
    static FunctionPtr NewImplicitClosureFunction(const String& name,
                                                  const Function& parent,
                                                  TokenPosition token_pos);
  
    FunctionPtr CreateMethodExtractor(const String& getter_name) const;
    FunctionPtr GetMethodExtractor(const String& getter_name) const;
  
    static bool IsDynamicInvocationForwarderName(const String& name);
    static bool IsDynamicInvocationForwarderName(StringPtr name);
  
    static StringPtr DemangleDynamicInvocationForwarderName(const String& name);
  
    static StringPtr CreateDynamicInvocationForwarderName(const String& name);
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    FunctionPtr CreateDynamicInvocationForwarder(
        const String& mangled_name) const;
  
    FunctionPtr GetDynamicInvocationForwarder(const String& mangled_name,
                                              bool allow_add = true) const;
  #endif
  
    // Slow function, use in asserts to track changes in important library
    // functions.
    int32_t SourceFingerprint() const;
  
    // Return false and report an error if the fingerprint does not match.
    bool CheckSourceFingerprint(int32_t fp, const char* kind = nullptr) const;
  
    // Works with map [deopt-id] -> ICData.
    void SaveICDataMap(
        const ZoneGrowableArray<const ICData*>& deopt_id_to_ic_data,
        const Array& edge_counters_array,
        const Array& coverage_array) const;
    // Uses 'ic_data_array' to populate the table 'deopt_id_to_ic_data'. Clone
    // ic_data (array and descriptor) if 'clone_ic_data' is true.
    void RestoreICDataMap(ZoneGrowableArray<const ICData*>* deopt_id_to_ic_data,
                          bool clone_ic_data) const;
  
    // ic_data_array attached to the function stores edge counters in the
    // first element, coverage data array in the second element and the rest
    // are ICData objects.
    struct ICDataArrayIndices {
      static constexpr intptr_t kEdgeCounters = 0;
      static constexpr intptr_t kCoverageData = 1;
      static constexpr intptr_t kFirstICData = 2;
    };
  
    ArrayPtr ic_data_array() const;
    void ClearICDataArray() const;
    ICDataPtr FindICData(intptr_t deopt_id) const;
  
    // Coverage data array is a list of pairs:
    //   element 2 * i + 0 is token position
    //   element 2 * i + 1 is coverage hit (zero meaning code was not hit)
    ArrayPtr GetCoverageArray() const;
  
    // Outputs this function's service ID to the provided JSON object.
    void AddFunctionServiceId(const JSONObject& obj) const;
  
    // Sets deopt reason in all ICData-s with given deopt_id.
    void SetDeoptReasonForAll(intptr_t deopt_id, ICData::DeoptReasonId reason);
  
    void set_modifier(UntaggedFunction::AsyncModifier value) const;
  
  // 'WasCompiled' is true if the function was compiled once in this
  // VM instantiation. It is independent from presence of type feedback
  // (ic_data_array) and code, which may be loaded from a snapshot.
  // 'WasExecuted' is true if the usage counter has ever been positive.
  // 'ProhibitsHoistingCheckClass' is true if this function deoptimized before on
  // a hoisted check class instruction.
  // 'ProhibitsBoundsCheckGeneralization' is true if this function deoptimized
  // before on a generalized bounds check.
  #define STATE_BITS_LIST(V)                                                     \
    V(WasCompiled)                                                               \
    V(WasExecutedBit)                                                            \
    V(ProhibitsHoistingCheckClass)                                               \
    V(ProhibitsBoundsCheckGeneralization)
  
    enum StateBits {
  #define DECLARE_FLAG_POS(Name) k##Name##Pos,
      STATE_BITS_LIST(DECLARE_FLAG_POS)
  #undef DECLARE_FLAG_POS
    };
  #define DEFINE_FLAG_BIT(Name)                                                  \
    class Name##Bit : public BitField<uint8_t, bool, k##Name##Pos, 1> {};
    STATE_BITS_LIST(DEFINE_FLAG_BIT)
  #undef DEFINE_FLAG_BIT
  
  #define DEFINE_FLAG_ACCESSORS(Name)                                            \
    void Set##Name(bool value) const {                                           \
      set_state_bits(Name##Bit::update(value, state_bits()));                    \
    }                                                                            \
    bool Name() const { return Name##Bit::decode(state_bits()); }
    STATE_BITS_LIST(DEFINE_FLAG_ACCESSORS)
  #undef DEFINE_FLAG_ACCESSORS
  
    void SetUsageCounter(intptr_t value) const {
      if (usage_counter() > 0) {
        SetWasExecuted(true);
      }
      set_usage_counter(value);
    }
  
    bool WasExecuted() const { return (usage_counter() > 0) || WasExecutedBit(); }
  
    void SetWasExecuted(bool value) const { SetWasExecutedBit(value); }
  
    static intptr_t data_offset() { return OFFSET_OF(UntaggedFunction, data_); }
  
    static intptr_t kind_tag_offset() {
      return OFFSET_OF(UntaggedFunction, kind_tag_);
    }
  
    // static: Considered during class-side or top-level resolution rather than
    //         instance-side resolution.
    // const: Valid target of a const constructor call.
    // abstract: Skipped during instance-side resolution.
    // reflectable: Enumerated by mirrors, invocable by mirrors. False for private
    //              functions of dart: libraries.
    // debuggable: Valid location of a breakpoint. Synthetic code is not
    //             debuggable.
    // visible: Frame is included in stack traces. Synthetic code such as
    //          dispatchers is not visible. Synthetic code that can trigger
    //          exceptions such as the outer async functions that create Futures
    //          is visible.
    // instrinsic: Has a hand-written assembly prologue.
    // inlinable: Candidate for inlining. False for functions with features we
    //            don't support during inlining (e.g., optional parameters),
    //            functions which are too big, etc.
    // native: Bridge to C/C++ code.
    // external: Just a declaration that expects to be defined in another patch
    //           file.
    // generated_body: Has a generated body.
    // polymorphic_target: A polymorphic method.
    // has_pragma: Has a @pragma decoration.
    // no_such_method_forwarder: A stub method that just calls noSuchMethod.
  
  // Bits that are set when function is created, don't have to worry about
  // concurrent updates.
  #define FOR_EACH_FUNCTION_KIND_BIT(V)                                          \
    V(Static, is_static)                                                         \
    V(Const, is_const)                                                           \
    V(Abstract, is_abstract)                                                     \
    V(Reflectable, is_reflectable)                                               \
    V(Visible, is_visible)                                                       \
    V(Debuggable, is_debuggable)                                                 \
    V(Intrinsic, is_intrinsic)                                                   \
    V(Native, is_native)                                                         \
    V(External, is_external)                                                     \
    V(GeneratedBody, is_generated_body)                                          \
    V(PolymorphicTarget, is_polymorphic_target)                                  \
    V(HasPragma, has_pragma)                                                     \
    V(IsSynthetic, is_synthetic)                                                 \
    V(IsExtensionMember, is_extension_member)
  // Bit that is updated after function is constructed, has to be updated in
  // concurrent-safe manner.
  #define FOR_EACH_FUNCTION_VOLATILE_KIND_BIT(V) V(Inlinable, is_inlinable)
  
  #define DEFINE_ACCESSORS(name, accessor_name)                                  \
    void set_##accessor_name(bool value) const {                                 \
      untag()->kind_tag_.UpdateUnsynchronized<name##Bit>(value);                 \
    }                                                                            \
    bool accessor_name() const { return untag()->kind_tag_.Read<name##Bit>(); }
    FOR_EACH_FUNCTION_KIND_BIT(DEFINE_ACCESSORS)
  #undef DEFINE_ACCESSORS
  
  #define DEFINE_ACCESSORS(name, accessor_name)                                  \
    void set_##accessor_name(bool value) const {                                 \
      untag()->kind_tag_.UpdateBool<name##Bit>(value);                           \
    }                                                                            \
    bool accessor_name() const { return untag()->kind_tag_.Read<name##Bit>(); }
    FOR_EACH_FUNCTION_VOLATILE_KIND_BIT(DEFINE_ACCESSORS)
  #undef DEFINE_ACCESSORS
  
    // optimizable: Candidate for going through the optimizing compiler. False for
    //              some functions known to be execute infrequently and functions
    //              which have been de-optimized too many times.
    bool is_optimizable() const {
      return untag()->packed_fields_.Read<UntaggedFunction::PackedOptimizable>();
    }
    void set_is_optimizable(bool value) const {
      untag()->packed_fields_.UpdateBool<UntaggedFunction::PackedOptimizable>(
          value);
    }
  
    enum KindTagBits {
      kKindTagPos = 0,
      kKindTagSize = 5,
      kRecognizedTagPos = kKindTagPos + kKindTagSize,
      kRecognizedTagSize = 9,
      kModifierPos = kRecognizedTagPos + kRecognizedTagSize,
      kModifierSize = 2,
      kLastModifierBitPos = kModifierPos + (kModifierSize - 1),
  // Single bit sized fields start here.
  #define DECLARE_BIT(name, _) k##name##Bit,
      FOR_EACH_FUNCTION_KIND_BIT(DECLARE_BIT)
          FOR_EACH_FUNCTION_VOLATILE_KIND_BIT(DECLARE_BIT)
  #undef DECLARE_BIT
              kNumTagBits
    };
  
    COMPILE_ASSERT(MethodRecognizer::kNumRecognizedMethods <
                   (1 << kRecognizedTagSize));
    COMPILE_ASSERT(kNumTagBits <=
                   (kBitsPerByte *
                    sizeof(decltype(UntaggedFunction::kind_tag_))));
  
    class KindBits : public BitField<uint32_t,
                                     UntaggedFunction::Kind,
                                     kKindTagPos,
                                     kKindTagSize> {};
  
    class RecognizedBits : public BitField<uint32_t,
                                           MethodRecognizer::Kind,
                                           kRecognizedTagPos,
                                           kRecognizedTagSize> {};
    class ModifierBits : public BitField<uint32_t,
                                         UntaggedFunction::AsyncModifier,
                                         kModifierPos,
                                         kModifierSize> {};
  
  #define DEFINE_BIT(name, _)                                                    \
    class name##Bit : public BitField<uint32_t, bool, k##name##Bit, 1> {};
    FOR_EACH_FUNCTION_KIND_BIT(DEFINE_BIT)
    FOR_EACH_FUNCTION_VOLATILE_KIND_BIT(DEFINE_BIT)
  #undef DEFINE_BIT
  
   private:
    // Given the provided defaults type arguments, determines which
    // DefaultTypeArgumentsKind applies.
    DefaultTypeArgumentsKind DefaultTypeArgumentsKindFor(
        const TypeArguments& defaults) const;
  
    void set_ic_data_array(const Array& value) const;
    void set_name(const String& value) const;
    void set_kind(UntaggedFunction::Kind value) const;
    void set_parent_function(const Function& value) const;
    FunctionPtr implicit_closure_function() const;
    void set_implicit_closure_function(const Function& value) const;
    ClosurePtr implicit_static_closure() const;
    void set_implicit_static_closure(const Closure& closure) const;
    ScriptPtr eval_script() const;
    void set_eval_script(const Script& value) const;
    void set_num_optional_parameters(intptr_t value) const;  // Encoded value.
    void set_kind_tag(uint32_t value) const;
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    ArrayPtr positional_parameter_names() const {
      return untag()->positional_parameter_names();
    }
    void set_positional_parameter_names(const Array& value) const;
  #endif
  
    ObjectPtr data() const { return untag()->data<std::memory_order_acquire>(); }
    void set_data(const Object& value) const;
  
    static FunctionPtr New(Heap::Space space = Heap::kOld);
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Function, Object);
    friend class Class;
    friend class Parser;  // For set_eval_script.
    // UntaggedFunction::VisitFunctionPointers accesses the private constructor of
    // Function.
    friend class UntaggedFunction;
    friend class ClassFinalizer;  // To reset parent_function.
    friend class Type;            // To adjust parent_function.
    friend class Precompiler;     // To access closure data.
    friend class ProgramVisitor;  // For set_parameter_types/names.
  };
  
  class ClosureData : public Object {
   public:
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedClosureData));
    }
  
    static intptr_t default_type_arguments_kind_offset() {
      return OFFSET_OF(UntaggedClosureData, default_type_arguments_kind_);
    }
  
    using DefaultTypeArgumentsKind =
        UntaggedClosureData::DefaultTypeArgumentsKind;
  
   private:
    ContextScopePtr context_scope() const { return untag()->context_scope(); }
    void set_context_scope(const ContextScope& value) const;
  
    // Enclosing function of this local function.
    PRECOMPILER_WSR_FIELD_DECLARATION(Function, parent_function)
  
    ClosurePtr implicit_static_closure() const {
      return untag()->closure<std::memory_order_acquire>();
    }
    void set_implicit_static_closure(const Closure& closure) const;
  
    DefaultTypeArgumentsKind default_type_arguments_kind() const;
    void set_default_type_arguments_kind(DefaultTypeArgumentsKind value) const;
  
    static ClosureDataPtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(ClosureData, Object);
    friend class Class;
    friend class Function;
    friend class Precompiler;  // To wrap parent functions in WSRs.
  };
  
  enum class EntryPointPragma {
    kAlways,
    kNever,
    kGetterOnly,
    kSetterOnly,
    kCallOnly
  };
  
  class FfiTrampolineData : public Object {
   public:
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedFfiTrampolineData));
    }
  
   private:
    FunctionTypePtr c_signature() const { return untag()->c_signature(); }
    void set_c_signature(const FunctionType& value) const;
  
    FunctionPtr callback_target() const { return untag()->callback_target(); }
    void set_callback_target(const Function& value) const;
  
    InstancePtr callback_exceptional_return() const {
      return untag()->callback_exceptional_return();
    }
    void set_callback_exceptional_return(const Instance& value) const;
  
    int32_t callback_id() const { return untag()->callback_id_; }
    void set_callback_id(int32_t value) const;
  
    bool is_leaf() const { return untag()->is_leaf_; }
    void set_is_leaf(bool value) const;
  
    static FfiTrampolineDataPtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(FfiTrampolineData, Object);
    friend class Class;
    friend class Function;
  };
  
  class Field : public Object {
   public:
    // The field that this field was cloned from, or this field itself if it isn't
    // a clone. The purpose of cloning is that the fields the background compiler
    // sees are consistent.
    FieldPtr Original() const;
  
    // Set the original field that this field was cloned from.
    void SetOriginal(const Field& value) const;
  
    // Returns whether this field is an original or a clone.
    bool IsOriginal() const {
      if (IsNull()) {
        return true;
      }
      NoSafepointScope no_safepoint;
      return !untag()->owner()->IsField();
    }
  
    // Mark previously unboxed field boxed. Only operates on clones, updates
    // original as well as this clone.
    void DisableFieldUnboxing() const;
    // Returns a field cloned from 'this'. 'this' is set as the
    // original field of result.
    FieldPtr CloneFromOriginal() const;
  
    StringPtr name() const { return untag()->name(); }
    StringPtr UserVisibleName() const;  // Same as scrubbed name.
    const char* UserVisibleNameCString() const;
    virtual StringPtr DictionaryName() const { return name(); }
  
    uint16_t kind_bits() const {
      return LoadNonPointer<uint16_t, std::memory_order_acquire>(
          &untag()->kind_bits_);
    }
  
    bool is_static() const { return StaticBit::decode(kind_bits()); }
    bool is_instance() const { return !is_static(); }
    bool is_final() const { return FinalBit::decode(kind_bits()); }
    bool is_const() const { return ConstBit::decode(kind_bits()); }
    bool is_late() const { return IsLateBit::decode(kind_bits()); }
    bool is_extension_member() const {
      return IsExtensionMemberBit::decode(kind_bits());
    }
    bool needs_load_guard() const {
      return NeedsLoadGuardBit::decode(kind_bits());
    }
    bool is_reflectable() const { return ReflectableBit::decode(kind_bits()); }
    void set_is_reflectable(bool value) const {
      ASSERT(IsOriginal());
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(ReflectableBit::update(value, untag()->kind_bits_));
    }
    bool is_double_initialized() const {
      return DoubleInitializedBit::decode(kind_bits());
    }
    // Called in parser after allocating field, immutable property otherwise.
    // Marks fields that are initialized with a simple double constant.
    void set_is_double_initialized_unsafe(bool value) const {
      ASSERT(IsOriginal());
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(DoubleInitializedBit::update(value, untag()->kind_bits_));
    }
  
    void set_is_double_initialized(bool value) const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
      set_is_double_initialized_unsafe(value);
    }
  
    bool initializer_changed_after_initialization() const {
      return InitializerChangedAfterInitializatonBit::decode(kind_bits());
    }
    void set_initializer_changed_after_initialization(bool value) const {
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(InitializerChangedAfterInitializatonBit::update(
          value, untag()->kind_bits_));
    }
  
    bool has_pragma() const { return HasPragmaBit::decode(kind_bits()); }
    void set_has_pragma(bool value) const {
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(HasPragmaBit::update(value, untag()->kind_bits_));
    }
  
    bool is_covariant() const { return CovariantBit::decode(kind_bits()); }
    void set_is_covariant(bool value) const {
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(CovariantBit::update(value, untag()->kind_bits_));
    }
  
    bool is_generic_covariant_impl() const {
      return GenericCovariantImplBit::decode(kind_bits());
    }
    void set_is_generic_covariant_impl(bool value) const {
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(GenericCovariantImplBit::update(value, untag()->kind_bits_));
    }
  
    intptr_t kernel_offset() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return 0;
  #else
      return untag()->kernel_offset_;
  #endif
    }
  
    void set_kernel_offset(intptr_t value) const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
  #else
      ASSERT(value >= 0);
      StoreNonPointer(&untag()->kernel_offset_, value);
  #endif
    }
  
    void InheritKernelOffsetFrom(const Field& src) const;
  
    ExternalTypedDataPtr KernelData() const;
  
    intptr_t KernelDataProgramOffset() const;
  
    // Called during class finalization.
    inline void SetOffset(intptr_t host_offset_in_bytes,
                          intptr_t target_offset_in_bytes) const;
  
    inline intptr_t HostOffset() const;
    static intptr_t host_offset_or_field_id_offset() {
      return OFFSET_OF(UntaggedField, host_offset_or_field_id_);
    }
  
    inline intptr_t TargetOffset() const;
    static inline intptr_t TargetOffsetOf(FieldPtr field);
  
    ObjectPtr StaticConstFieldValue() const;
    void SetStaticConstFieldValue(const Instance& value,
                                  bool assert_initializing_store = true) const;
  
    inline ObjectPtr StaticValue() const;
    void SetStaticValue(const Object& value) const;
  
    inline intptr_t field_id() const;
    inline void set_field_id(intptr_t field_id) const;
    inline void set_field_id_unsafe(intptr_t field_id) const;
  
    ClassPtr Owner() const;
    ClassPtr Origin() const;  // Either mixin class, or same as owner().
    ScriptPtr Script() const;
    ObjectPtr RawOwner() const;
  
    AbstractTypePtr type() const { return untag()->type(); }
    // Used by class finalizer, otherwise initialized in constructor.
    void SetFieldType(const AbstractType& value) const;
    void SetFieldTypeSafe(const AbstractType& value) const;
  
    DART_WARN_UNUSED_RESULT
    ErrorPtr VerifyEntryPoint(EntryPointPragma kind) const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedField));
    }
  
    static FieldPtr 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);
  
    static FieldPtr NewTopLevel(const String& name,
                                bool is_final,
                                bool is_const,
                                bool is_late,
                                const Object& owner,
                                TokenPosition token_pos,
                                TokenPosition end_token_pos);
  
    // Allocate new field object, clone values from this field. The
    // original is specified.
    FieldPtr Clone(const Field& original) const;
  
    static intptr_t kind_bits_offset() {
      return OFFSET_OF(UntaggedField, kind_bits_);
    }
  
    TokenPosition token_pos() const { return untag()->token_pos_; }
    TokenPosition end_token_pos() const { return untag()->end_token_pos_; }
  
    int32_t SourceFingerprint() const;
  
    StringPtr InitializingExpression() const;
  
    bool has_nontrivial_initializer() const {
      return HasNontrivialInitializerBit::decode(kind_bits());
    }
    // Called by parser after allocating field.
    void set_has_nontrivial_initializer_unsafe(
        bool has_nontrivial_initializer) const {
      ASSERT(IsOriginal());
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(HasNontrivialInitializerBit::update(
          has_nontrivial_initializer, untag()->kind_bits_));
    }
    void set_has_nontrivial_initializer(bool has_nontrivial_initializer) const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
      set_has_nontrivial_initializer_unsafe(has_nontrivial_initializer);
    }
  
    bool has_initializer() const {
      return HasInitializerBit::decode(kind_bits());
    }
    // Called by parser after allocating field.
    void set_has_initializer_unsafe(bool has_initializer) const {
      ASSERT(IsOriginal());
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(
          HasInitializerBit::update(has_initializer, untag()->kind_bits_));
    }
    void set_has_initializer(bool has_initializer) const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
      set_has_initializer_unsafe(has_initializer);
    }
  
    bool has_trivial_initializer() const {
      return has_initializer() && !has_nontrivial_initializer();
    }
  
    bool is_non_nullable_integer() const {
      return IsNonNullableIntBit::decode(kind_bits());
    }
  
    void set_is_non_nullable_integer(bool is_non_nullable_integer) const {
      ASSERT(Thread::Current()->IsMutatorThread());
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(IsNonNullableIntBit::update(is_non_nullable_integer,
                                                untag()->kind_bits_));
    }
  
    StaticTypeExactnessState static_type_exactness_state() const {
      return StaticTypeExactnessState::Decode(
          LoadNonPointer<int8_t, std::memory_order_relaxed>(
              &untag()->static_type_exactness_state_));
    }
  
    void set_static_type_exactness_state(StaticTypeExactnessState state) const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
      set_static_type_exactness_state_unsafe(state);
    }
  
    void set_static_type_exactness_state_unsafe(
        StaticTypeExactnessState state) const {
      StoreNonPointer<int8_t, int8_t, std::memory_order_relaxed>(
          &untag()->static_type_exactness_state_, state.Encode());
    }
  
    static intptr_t static_type_exactness_state_offset() {
      return OFFSET_OF(UntaggedField, static_type_exactness_state_);
    }
  
    // Return class id that any non-null value read from this field is guaranteed
    // to have or kDynamicCid if such class id is not known.
    // Stores to this field must update this information hence the name.
    intptr_t guarded_cid() const;
  
    void set_guarded_cid(intptr_t cid) const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
      set_guarded_cid_unsafe(cid);
    }
    void set_guarded_cid_unsafe(intptr_t cid) const {
      StoreNonPointer<ClassIdTagType, ClassIdTagType, std::memory_order_relaxed>(
          &untag()->guarded_cid_, cid);
    }
    static intptr_t guarded_cid_offset() {
      return OFFSET_OF(UntaggedField, guarded_cid_);
    }
    // Return the list length that any list stored in this field is guaranteed
    // to have. If length is kUnknownFixedLength the length has not
    // been determined. If length is kNoFixedLength this field has multiple
    // list lengths associated with it and cannot be predicted.
    intptr_t guarded_list_length() const;
    void set_guarded_list_length_unsafe(intptr_t list_length) const;
    void set_guarded_list_length(intptr_t list_length) const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
      set_guarded_list_length_unsafe(list_length);
    }
    static intptr_t guarded_list_length_offset() {
      return OFFSET_OF(UntaggedField, guarded_list_length_);
    }
    intptr_t guarded_list_length_in_object_offset() const;
    void set_guarded_list_length_in_object_offset_unsafe(intptr_t offset) const;
    void set_guarded_list_length_in_object_offset(intptr_t offset) const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
      set_guarded_list_length_in_object_offset_unsafe(offset);
    }
    static intptr_t guarded_list_length_in_object_offset_offset() {
      return OFFSET_OF(UntaggedField, guarded_list_length_in_object_offset_);
    }
  
    bool needs_length_check() const {
      const bool r = guarded_list_length() >= Field::kUnknownFixedLength;
      ASSERT(!r || is_final());
      return r;
    }
  
    bool NeedsSetter() const;
    bool NeedsGetter() const;
  
    bool NeedsInitializationCheckOnLoad() const {
      return needs_load_guard() || (is_late() && !has_trivial_initializer());
    }
  
    const char* GuardedPropertiesAsCString() const;
  
    intptr_t UnboxedFieldCid() const { return guarded_cid(); }
  
    bool is_unboxing_candidate() const {
      return UnboxingCandidateBit::decode(kind_bits());
    }
  
    // Default 'true', set to false once optimizing compiler determines it should
    // be boxed.
    void set_is_unboxing_candidate_unsafe(bool b) const {
      set_kind_bits(UnboxingCandidateBit::update(b, untag()->kind_bits_));
    }
  
    void set_is_unboxing_candidate(bool b) const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
      set_is_unboxing_candidate_unsafe(b);
    }
  
    enum {
      kUnknownLengthOffset = -1,
      kUnknownFixedLength = -1,
      kNoFixedLength = -2,
    };
    void set_is_late(bool value) const {
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(IsLateBit::update(value, untag()->kind_bits_));
    }
    void set_is_extension_member(bool value) const {
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(IsExtensionMemberBit::update(value, untag()->kind_bits_));
    }
    void set_needs_load_guard(bool value) const {
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(NeedsLoadGuardBit::update(value, untag()->kind_bits_));
    }
    // Returns false if any value read from this field is guaranteed to be
    // not null.
    // Internally we is_nullable_ field contains either kNullCid (nullable) or
    // kIllegalCid (non-nullable) instead of boolean. This is done to simplify
    // guarding sequence in the generated code.
    bool is_nullable() const;
    void set_is_nullable(bool val) const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
      set_is_nullable_unsafe(val);
    }
    bool is_nullable_unsafe() const {
      return LoadNonPointer<ClassIdTagType, std::memory_order_relaxed>(
                 &untag()->is_nullable_) == kNullCid;
    }
    void set_is_nullable_unsafe(bool val) const {
      StoreNonPointer<ClassIdTagType, ClassIdTagType, std::memory_order_relaxed>(
          &untag()->is_nullable_, val ? kNullCid : kIllegalCid);
    }
    static intptr_t is_nullable_offset() {
      return OFFSET_OF(UntaggedField, is_nullable_);
    }
  
    // Record store of the given value into this field. May trigger
    // deoptimization of dependent optimized code.
    void RecordStore(const Object& value) const;
  
    void InitializeGuardedListLengthInObjectOffset(bool unsafe = false) const;
  
    // Return the list of optimized code objects that were optimized under
    // assumptions about guarded class id and nullability of this field.
    // These code objects must be deoptimized when field's properties change.
    // Code objects are held weakly via an indirection through WeakProperty.
    ArrayPtr dependent_code() const;
    void set_dependent_code(const Array& array) const;
  
    // Add the given code object to the list of dependent ones.
    void RegisterDependentCode(const Code& code) const;
  
    // Deoptimize all dependent code objects.
    void DeoptimizeDependentCode() const;
  
    // Used by background compiler to check consistency of field copy with its
    // original.
    bool IsConsistentWith(const Field& field) const;
  
    bool IsUninitialized() const;
  
    // Run initializer and set field value.
    DART_WARN_UNUSED_RESULT ErrorPtr
    InitializeInstance(const Instance& instance) const;
    DART_WARN_UNUSED_RESULT ErrorPtr InitializeStatic() const;
  
    // Run initializer only.
    DART_WARN_UNUSED_RESULT ObjectPtr EvaluateInitializer() const;
  
    FunctionPtr EnsureInitializerFunction() const;
    FunctionPtr InitializerFunction() const {
      return untag()->initializer_function<std::memory_order_acquire>();
    }
    void SetInitializerFunction(const Function& initializer) const;
    bool HasInitializerFunction() const;
    static intptr_t initializer_function_offset() {
      return OFFSET_OF(UntaggedField, initializer_function_);
    }
  
    // For static fields only. Constructs a closure that gets/sets the
    // field value.
    InstancePtr GetterClosure() const;
    InstancePtr SetterClosure() const;
    InstancePtr AccessorClosure(bool make_setter) const;
  
    // Constructs getter and setter names for fields and vice versa.
    static StringPtr GetterName(const String& field_name);
    static StringPtr GetterSymbol(const String& field_name);
    // Returns String::null() if getter symbol does not exist.
    static StringPtr LookupGetterSymbol(const String& field_name);
    static StringPtr SetterName(const String& field_name);
    static StringPtr SetterSymbol(const String& field_name);
    // Returns String::null() if setter symbol does not exist.
    static StringPtr LookupSetterSymbol(const String& field_name);
    static StringPtr NameFromGetter(const String& getter_name);
    static StringPtr NameFromSetter(const String& setter_name);
    static StringPtr NameFromInit(const String& init_name);
    static bool IsGetterName(const String& function_name);
    static bool IsSetterName(const String& function_name);
    static bool IsInitName(const String& function_name);
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    SubtypeTestCachePtr type_test_cache() const {
      return untag()->type_test_cache();
    }
    void set_type_test_cache(const SubtypeTestCache& cache) const;
  #endif
  
    // Unboxed fields require exclusive ownership of the box.
    // Ensure this by cloning the box if necessary.
    const Object* CloneForUnboxed(const Object& value) const;
  
   private:
    static void 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);
    friend class StoreInstanceFieldInstr;  // Generated code access to bit field.
  
    enum {
      kConstBit = 0,
      kStaticBit,
      kFinalBit,
      kHasNontrivialInitializerBit,
      kUnboxingCandidateBit,
      kReflectableBit,
      kDoubleInitializedBit,
      kInitializerChangedAfterInitializatonBit,
      kHasPragmaBit,
      kCovariantBit,
      kGenericCovariantImplBit,
      kIsLateBit,
      kIsExtensionMemberBit,
      kNeedsLoadGuardBit,
      kHasInitializerBit,
      kIsNonNullableIntBit,
    };
    class ConstBit : public BitField<uint16_t, bool, kConstBit, 1> {};
    class StaticBit : public BitField<uint16_t, bool, kStaticBit, 1> {};
    class FinalBit : public BitField<uint16_t, bool, kFinalBit, 1> {};
    class HasNontrivialInitializerBit
        : public BitField<uint16_t, bool, kHasNontrivialInitializerBit, 1> {};
    class UnboxingCandidateBit
        : public BitField<uint16_t, bool, kUnboxingCandidateBit, 1> {};
    class ReflectableBit : public BitField<uint16_t, bool, kReflectableBit, 1> {};
    class DoubleInitializedBit
        : public BitField<uint16_t, bool, kDoubleInitializedBit, 1> {};
    class InitializerChangedAfterInitializatonBit
        : public BitField<uint16_t,
                          bool,
                          kInitializerChangedAfterInitializatonBit,
                          1> {};
    class HasPragmaBit : public BitField<uint16_t, bool, kHasPragmaBit, 1> {};
    class CovariantBit : public BitField<uint16_t, bool, kCovariantBit, 1> {};
    class GenericCovariantImplBit
        : public BitField<uint16_t, bool, kGenericCovariantImplBit, 1> {};
    class IsLateBit : public BitField<uint16_t, bool, kIsLateBit, 1> {};
    class IsExtensionMemberBit
        : public BitField<uint16_t, bool, kIsExtensionMemberBit, 1> {};
    class NeedsLoadGuardBit
        : public BitField<uint16_t, bool, kNeedsLoadGuardBit, 1> {};
    class HasInitializerBit
        : public BitField<uint16_t, bool, kHasInitializerBit, 1> {};
    class IsNonNullableIntBit
        : public BitField<uint16_t, bool, kIsNonNullableIntBit, 1> {};
  
    // Update guarded cid and guarded length for this field. Returns true, if
    // deoptimization of dependent code is required.
    bool UpdateGuardedCidAndLength(const Object& value) const;
  
    // Update guarded exactness state for this field. Returns true, if
    // deoptimization of dependent code is required.
    // Assumes that guarded cid was already updated.
    bool UpdateGuardedExactnessState(const Object& value) const;
  
    // Force this field's guard to be dynamic and deoptimize dependent code.
    void ForceDynamicGuardedCidAndLength() const;
  
    void set_name(const String& value) const;
    void set_is_static(bool is_static) const {
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(StaticBit::update(is_static, untag()->kind_bits_));
    }
    void set_is_final(bool is_final) const {
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(FinalBit::update(is_final, untag()->kind_bits_));
    }
    void set_is_const(bool value) const {
      // TODO(36097): Once concurrent access is possible ensure updates are safe.
      set_kind_bits(ConstBit::update(value, untag()->kind_bits_));
    }
    void set_owner(const Object& value) const { untag()->set_owner(value.ptr()); }
    void set_token_pos(TokenPosition token_pos) const {
      StoreNonPointer(&untag()->token_pos_, token_pos);
    }
    void set_end_token_pos(TokenPosition token_pos) const {
      StoreNonPointer(&untag()->end_token_pos_, token_pos);
    }
    void set_kind_bits(uint16_t value) const {
      StoreNonPointer<uint16_t, uint16_t, std::memory_order_release>(
          &untag()->kind_bits_, value);
    }
  
    static FieldPtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Field, Object);
    friend class Class;
    friend class UntaggedField;
    friend class FieldSerializationCluster;
    friend class FieldDeserializationCluster;
  };
  
  class Script : public Object {
   public:
    StringPtr url() const { return untag()->url(); }
    void set_url(const String& value) const;
  
    // The actual url which was loaded from disk, if provided by the embedder.
    StringPtr resolved_url() const;
    bool HasSource() const;
    StringPtr Source() const;
    bool IsPartOfDartColonLibrary() const;
  
    void LookupSourceAndLineStarts(Zone* zone) const;
    GrowableObjectArrayPtr GenerateLineNumberArray() const;
  
    intptr_t line_offset() const { return 0; }
    intptr_t col_offset() const { return 0; }
    // Returns the max real token position for this script, or kNoSource
    // if there is no line starts information.
    TokenPosition MaxPosition() const;
  
    // The load time in milliseconds since epoch.
    int64_t load_timestamp() const { return untag()->load_timestamp_; }
  
    KernelProgramInfoPtr kernel_program_info() const {
      return untag()->kernel_program_info();
    }
    void set_kernel_program_info(const KernelProgramInfo& info) const;
  
    intptr_t kernel_script_index() const { return untag()->kernel_script_index_; }
    void set_kernel_script_index(const intptr_t kernel_script_index) const;
  
    TypedDataPtr kernel_string_offsets() const;
  
    TypedDataPtr line_starts() const;
  
  #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
    ExternalTypedDataPtr constant_coverage() const;
  
    void set_constant_coverage(const ExternalTypedData& value) const;
  #endif  // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
  
    void set_line_starts(const TypedData& value) const;
  
    void set_debug_positions(const Array& value) const;
  
    LibraryPtr FindLibrary() const;
    StringPtr GetLine(intptr_t line_number, Heap::Space space = Heap::kNew) const;
    StringPtr GetSnippet(intptr_t from_line,
                         intptr_t from_column,
                         intptr_t to_line,
                         intptr_t to_column) const;
  
    // For real token positions when line starts are available, returns whether or
    // not a GetTokenLocation call would succeed. Returns true for non-real token
    // positions or if there is no line starts information.
    bool IsValidTokenPosition(TokenPosition token_pos) const;
  
    // Returns whether a line and column could be computed for the given token
    // position and, if so, sets *line and *column (if not nullptr).
    bool GetTokenLocation(const TokenPosition& token_pos,
                          intptr_t* line,
                          intptr_t* column = nullptr) const;
  
    // Returns the length of the token at the given position. If the length cannot
    // be determined, returns a negative value.
    intptr_t GetTokenLength(const TokenPosition& token_pos) const;
  
    // Returns whether any tokens were found for the given line. When found,
    // *first_token_index and *last_token_index are set to the first and
    // last token on the line, respectively.
    bool TokenRangeAtLine(intptr_t line_number,
                          TokenPosition* first_token_index,
                          TokenPosition* last_token_index) const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedScript));
    }
  
    static ScriptPtr New(const String& url, const String& source);
  
    static ScriptPtr New(const String& url,
                         const String& resolved_url,
                         const String& source);
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    void LoadSourceFromKernel(const uint8_t* kernel_buffer,
                              intptr_t kernel_buffer_len) const;
    bool IsLazyLookupSourceAndLineStarts() const;
  #endif  // !defined(DART_PRECOMPILED_RUNTIME)
  
   private:
  #if !defined(DART_PRECOMPILED_RUNTIME)
    bool HasCachedMaxPosition() const;
  
    void SetLazyLookupSourceAndLineStarts(bool value) const;
    void SetHasCachedMaxPosition(bool value) const;
    void SetCachedMaxPosition(intptr_t value) const;
  #endif  // !defined(DART_PRECOMPILED_RUNTIME)
  
    void set_resolved_url(const String& value) const;
    void set_source(const String& value) const;
    void set_load_timestamp(int64_t value) const;
    ArrayPtr debug_positions() const;
  
    static ScriptPtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Script, Object);
    friend class Class;
    friend class Precompiler;
  };
  
  class DictionaryIterator : public ValueObject {
   public:
    explicit DictionaryIterator(const Library& library);
  
    bool HasNext() const { return next_ix_ < size_; }
  
    // Returns next non-null raw object.
    ObjectPtr GetNext();
  
   private:
    void MoveToNextObject();
  
    const Array& array_;
    const int size_;  // Number of elements to iterate over.
    int next_ix_;     // Index of next element.
  
    friend class ClassDictionaryIterator;
    DISALLOW_COPY_AND_ASSIGN(DictionaryIterator);
  };
  
  class ClassDictionaryIterator : public DictionaryIterator {
   public:
    enum IterationKind {
      // TODO(hausner): fix call sites that use kIteratePrivate. There is only
      // one top-level class per library left, not an array to iterate over.
      kIteratePrivate,
      kNoIteratePrivate
    };
  
    ClassDictionaryIterator(const Library& library,
                            IterationKind kind = kNoIteratePrivate);
  
    bool HasNext() const {
      return (next_ix_ < size_) || !toplevel_class_.IsNull();
    }
  
    // Returns a non-null raw class.
    ClassPtr GetNextClass();
  
   private:
    void MoveToNextClass();
  
    Class& toplevel_class_;
  
    DISALLOW_COPY_AND_ASSIGN(ClassDictionaryIterator);
  };
  
  class Library : public Object {
   public:
    StringPtr name() const { return untag()->name(); }
    void SetName(const String& name) const;
  
    StringPtr url() const { return untag()->url(); }
    StringPtr private_key() const { return untag()->private_key(); }
    bool LoadNotStarted() const {
      return untag()->load_state_ == UntaggedLibrary::kAllocated;
    }
    bool LoadRequested() const {
      return untag()->load_state_ == UntaggedLibrary::kLoadRequested;
    }
    bool LoadInProgress() const {
      return untag()->load_state_ == UntaggedLibrary::kLoadInProgress;
    }
    void SetLoadRequested() const;
    void SetLoadInProgress() const;
    bool Loaded() const {
      return untag()->load_state_ == UntaggedLibrary::kLoaded;
    }
    void SetLoaded() const;
  
    LoadingUnitPtr loading_unit() const { return untag()->loading_unit(); }
    void set_loading_unit(const LoadingUnit& value) const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedLibrary));
    }
  
    static LibraryPtr New(const String& url);
  
    ObjectPtr Invoke(const String& selector,
                     const Array& arguments,
                     const Array& argument_names,
                     bool respect_reflectable = true,
                     bool check_is_entrypoint = false) const;
    ObjectPtr InvokeGetter(const String& selector,
                           bool throw_nsm_if_absent,
                           bool respect_reflectable = true,
                           bool check_is_entrypoint = false) const;
    ObjectPtr InvokeSetter(const String& selector,
                           const Instance& argument,
                           bool respect_reflectable = true,
                           bool check_is_entrypoint = false) const;
  
    // Evaluate the given expression as if it appeared in an top-level method of
    // this library and return the resulting value, or an error object if
    // evaluating the expression fails. The method has the formal (type)
    // parameters given in (type_)param_names, and is invoked with the (type)
    // argument values given in (type_)param_values.
    ObjectPtr EvaluateCompiledExpression(
        const ExternalTypedData& kernel_buffer,
        const Array& type_definitions,
        const Array& param_values,
        const TypeArguments& type_param_values) const;
  
    // Library scope name dictionary.
    //
    // TODO(turnidge): The Lookup functions are not consistent in how
    // they deal with private names.  Go through and make them a bit
    // more regular.
    void AddClass(const Class& cls) const;
    void AddObject(const Object& obj, const String& name) const;
    ObjectPtr LookupReExport(const String& name,
                             ZoneGrowableArray<intptr_t>* visited = NULL) const;
    ObjectPtr LookupObjectAllowPrivate(const String& name) const;
    ObjectPtr LookupLocalOrReExportObject(const String& name) const;
    ObjectPtr LookupImportedObject(const String& name) const;
    ClassPtr LookupClass(const String& name) const;
    ClassPtr LookupClassAllowPrivate(const String& name) const;
    ClassPtr SlowLookupClassAllowMultiPartPrivate(const String& name) const;
    ClassPtr LookupLocalClass(const String& name) const;
    FieldPtr LookupFieldAllowPrivate(const String& name) const;
    FieldPtr LookupLocalField(const String& name) const;
    FunctionPtr LookupFunctionAllowPrivate(const String& name) const;
    FunctionPtr LookupLocalFunction(const String& name) const;
    LibraryPrefixPtr LookupLocalLibraryPrefix(const String& name) const;
  
    // Look up a Script based on a url. If 'useResolvedUri' is not provided or is
    // false, 'url' should have a 'dart:' scheme for Dart core libraries,
    // a 'package:' scheme for packages, and 'file:' scheme otherwise.
    //
    // If 'useResolvedUri' is true, 'url' should have a 'org-dartlang-sdk:' scheme
    // for Dart core libraries and a 'file:' scheme otherwise.
    ScriptPtr LookupScript(const String& url, bool useResolvedUri = false) const;
    ArrayPtr LoadedScripts() const;
  
    // Resolve name in the scope of this library. First check the cache
    // of already resolved names for this library. Then look in the
    // local dictionary for the unmangled name N, the getter name get:N
    // and setter name set:N.
    // If the local dictionary contains no entry for these names,
    // look in the scopes of all libraries that are imported
    // without a library prefix.
    ObjectPtr ResolveName(const String& name) const;
  
    void AddAnonymousClass(const Class& cls) const;
  
    void AddExport(const Namespace& ns) const;
  
    void AddMetadata(const Object& declaration, intptr_t kernel_offset) const;
    ObjectPtr GetMetadata(const Object& declaration) const;
  
    // Tries to finds a @pragma annotation on [object].
    //
    // If successful returns `true`. If an error happens during constant
    // evaluation, returns `false.
    //
    // If [only_core] is true, then the annotations on the object will only
    // be inspected if it is part of a core library.
    //
    // If [multiple] is true, then sets [options] to an GrowableObjectArray
    // containing all results and [options] may not be nullptr.
    //
    // WARNING: If the isolate received an [UnwindError] this function will not
    // return and rather unwinds until the enclosing setjmp() handler.
    static bool FindPragma(Thread* T,
                           bool only_core,
                           const Object& object,
                           const String& pragma_name,
                           bool multiple = false,
                           Object* options = nullptr);
  
    ClassPtr toplevel_class() const { return untag()->toplevel_class(); }
    void set_toplevel_class(const Class& value) const;
  
    GrowableObjectArrayPtr used_scripts() const {
      return untag()->used_scripts();
    }
  
    // Library imports.
    ArrayPtr imports() const { return untag()->imports(); }
    ArrayPtr exports() const { return untag()->exports(); }
    void AddImport(const Namespace& ns) const;
    intptr_t num_imports() const { return untag()->num_imports_; }
    NamespacePtr ImportAt(intptr_t index) const;
    LibraryPtr ImportLibraryAt(intptr_t index) const;
  
    ArrayPtr dependencies() const { return untag()->dependencies(); }
    void set_dependencies(const Array& deps) const;
  
    void DropDependenciesAndCaches() const;
  
    // Resolving native methods for script loaded in the library.
    Dart_NativeEntryResolver native_entry_resolver() const {
      return LoadNonPointer<Dart_NativeEntryResolver, std::memory_order_relaxed>(
          &untag()->native_entry_resolver_);
    }
    void set_native_entry_resolver(Dart_NativeEntryResolver value) const {
      StoreNonPointer<Dart_NativeEntryResolver, Dart_NativeEntryResolver,
                      std::memory_order_relaxed>(&untag()->native_entry_resolver_,
                                                 value);
    }
    Dart_NativeEntrySymbol native_entry_symbol_resolver() const {
      return LoadNonPointer<Dart_NativeEntrySymbol, std::memory_order_relaxed>(
          &untag()->native_entry_symbol_resolver_);
    }
    void set_native_entry_symbol_resolver(
        Dart_NativeEntrySymbol native_symbol_resolver) const {
      StoreNonPointer<Dart_NativeEntrySymbol, Dart_NativeEntrySymbol,
                      std::memory_order_relaxed>(
          &untag()->native_entry_symbol_resolver_, native_symbol_resolver);
    }
  
    // Resolver for FFI native function pointers.
    Dart_FfiNativeResolver ffi_native_resolver() const {
      return LoadNonPointer<Dart_FfiNativeResolver, std::memory_order_relaxed>(
          &untag()->ffi_native_resolver_);
    }
    void set_ffi_native_resolver(Dart_FfiNativeResolver value) const {
      StoreNonPointer<Dart_FfiNativeResolver, Dart_FfiNativeResolver,
                      std::memory_order_relaxed>(&untag()->ffi_native_resolver_,
                                                 value);
    }
  
    bool is_in_fullsnapshot() const {
      return UntaggedLibrary::InFullSnapshotBit::decode(untag()->flags_);
    }
    void set_is_in_fullsnapshot(bool value) const {
      set_flags(
          UntaggedLibrary::InFullSnapshotBit::update(value, untag()->flags_));
    }
  
    bool is_nnbd() const {
      return UntaggedLibrary::NnbdBit::decode(untag()->flags_);
    }
    void set_is_nnbd(bool value) const {
      set_flags(UntaggedLibrary::NnbdBit::update(value, untag()->flags_));
    }
  
    NNBDMode nnbd_mode() const {
      return is_nnbd() ? NNBDMode::kOptedInLib : NNBDMode::kLegacyLib;
    }
  
    NNBDCompiledMode nnbd_compiled_mode() const {
      return static_cast<NNBDCompiledMode>(
          UntaggedLibrary::NnbdCompiledModeBits::decode(untag()->flags_));
    }
    void set_nnbd_compiled_mode(NNBDCompiledMode value) const {
      set_flags(UntaggedLibrary::NnbdCompiledModeBits::update(
          static_cast<uint8_t>(value), untag()->flags_));
    }
  
    StringPtr PrivateName(const String& name) const;
  
    intptr_t index() const { return untag()->index_; }
    void set_index(intptr_t value) const {
      ASSERT((value == -1) ||
             ((value >= 0) && (value < std::numeric_limits<classid_t>::max())));
      StoreNonPointer(&untag()->index_, value);
    }
  
    void Register(Thread* thread) const;
    static void RegisterLibraries(Thread* thread,
                                  const GrowableObjectArray& libs);
  
    bool IsDebuggable() const {
      return UntaggedLibrary::DebuggableBit::decode(untag()->flags_);
    }
    void set_debuggable(bool value) const {
      set_flags(UntaggedLibrary::DebuggableBit::update(value, untag()->flags_));
    }
  
    bool is_dart_scheme() const {
      return UntaggedLibrary::DartSchemeBit::decode(untag()->flags_);
    }
    void set_is_dart_scheme(bool value) const {
      set_flags(UntaggedLibrary::DartSchemeBit::update(value, untag()->flags_));
    }
  
    // Includes 'dart:async', 'dart:typed_data', etc.
    bool IsAnyCoreLibrary() const;
  
    inline intptr_t UrlHash() const;
  
    ExternalTypedDataPtr kernel_data() const { return untag()->kernel_data(); }
    void set_kernel_data(const ExternalTypedData& data) const;
  
    intptr_t kernel_offset() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return 0;
  #else
      return untag()->kernel_offset_;
  #endif
    }
  
    void set_kernel_offset(intptr_t value) const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
  #else
      ASSERT(value >= 0);
      StoreNonPointer(&untag()->kernel_offset_, value);
  #endif
    }
  
    static LibraryPtr LookupLibrary(Thread* thread, const String& url);
    static LibraryPtr GetLibrary(intptr_t index);
  
    static void InitCoreLibrary(IsolateGroup* isolate_group);
    static void InitNativeWrappersLibrary(IsolateGroup* isolate_group,
                                          bool is_kernel_file);
  
    static LibraryPtr AsyncLibrary();
    static LibraryPtr ConvertLibrary();
    static LibraryPtr CoreLibrary();
    static LibraryPtr CollectionLibrary();
    static LibraryPtr DeveloperLibrary();
    static LibraryPtr FfiLibrary();
    static LibraryPtr InternalLibrary();
    static LibraryPtr IsolateLibrary();
    static LibraryPtr MathLibrary();
  #if !defined(DART_PRECOMPILED_RUNTIME)
    static LibraryPtr MirrorsLibrary();
  #endif
    static LibraryPtr NativeWrappersLibrary();
    static LibraryPtr TypedDataLibrary();
    static LibraryPtr VMServiceLibrary();
  
    // Eagerly compile all classes and functions in the library.
    static ErrorPtr CompileAll(bool ignore_error = false);
  #if !defined(DART_PRECOMPILED_RUNTIME)
    // Finalize all classes in all libraries.
    static ErrorPtr FinalizeAllClasses();
  #endif
  
  #if defined(DEBUG) && !defined(DART_PRECOMPILED_RUNTIME)
    // Checks function fingerprints. Prints mismatches and aborts if
    // mismatch found.
    static void CheckFunctionFingerprints();
  #endif  // defined(DEBUG) && !defined(DART_PRECOMPILED_RUNTIME).
  
    static bool IsPrivate(const String& name);
  
    // Construct the full name of a corelib member.
    static const String& PrivateCoreLibName(const String& member);
  
    // Returns true if [name] matches full name of corelib [member].
    static bool IsPrivateCoreLibName(const String& name, const String& member);
  
    // Lookup class in the core lib which also contains various VM
    // helper methods and classes. Allow look up of private classes.
    static ClassPtr LookupCoreClass(const String& class_name);
  
    // Return Function::null() if function does not exist in libs.
    static FunctionPtr GetFunction(const GrowableArray<Library*>& libs,
                                   const char* class_name,
                                   const char* function_name);
  
    // Character used to indicate a private identifier.
    static const char kPrivateIdentifierStart = '_';
  
    // Character used to separate private identifiers from
    // the library-specific key.
    static const char kPrivateKeySeparator = '@';
  
    void CheckReload(const Library& replacement,
                     ProgramReloadContext* context) const;
  
    // Returns a closure of top level function 'name' in the exported namespace
    // of this library. If a top level function 'name' does not exist we look
    // for a top level getter 'name' that returns a closure.
    ObjectPtr GetFunctionClosure(const String& name) const;
  
    // Ensures that all top-level functions and variables (fields) are loaded.
    void EnsureTopLevelClassIsFinalized() const;
  
   private:
    static const int kInitialImportsCapacity = 4;
    static const int kImportsCapacityIncrement = 8;
  
    static LibraryPtr New();
  
    // These methods are only used by the Precompiler to obfuscate
    // the name and url.
    void set_name(const String& name) const;
    void set_url(const String& url) const;
  
    void set_num_imports(intptr_t value) const;
    void set_flags(uint8_t flags) const;
    bool HasExports() const;
    ArrayPtr loaded_scripts() const { return untag()->loaded_scripts(); }
    ArrayPtr metadata() const {
      DEBUG_ASSERT(
          IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
      return untag()->metadata();
    }
    void set_metadata(const Array& value) const;
    ArrayPtr dictionary() const { return untag()->dictionary(); }
    void InitClassDictionary() const;
  
    ArrayPtr resolved_names() const { return untag()->resolved_names(); }
    bool LookupResolvedNamesCache(const String& name, Object* obj) const;
    void AddToResolvedNamesCache(const String& name, const Object& obj) const;
    void InitResolvedNamesCache() const;
    void ClearResolvedNamesCache() const;
    void InvalidateResolvedName(const String& name) const;
    void InvalidateResolvedNamesCache() const;
  
    ArrayPtr exported_names() const { return untag()->exported_names(); }
    bool LookupExportedNamesCache(const String& name, Object* obj) const;
    void AddToExportedNamesCache(const String& name, const Object& obj) const;
    void InitExportedNamesCache() const;
    void ClearExportedNamesCache() const;
    static void InvalidateExportedNamesCaches();
  
    void InitImportList() const;
    void RehashDictionary(const Array& old_dict, intptr_t new_dict_size) const;
    static LibraryPtr NewLibraryHelper(const String& url, bool import_core_lib);
    ObjectPtr LookupEntry(const String& name, intptr_t* index) const;
    ObjectPtr LookupLocalObjectAllowPrivate(const String& name) const;
    ObjectPtr LookupLocalObject(const String& name) const;
  
    void AllocatePrivateKey() const;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Library, Object);
  
    friend class Bootstrap;
    friend class Class;
    friend class Debugger;
    friend class DictionaryIterator;
    friend class Isolate;
    friend class LibraryDeserializationCluster;
    friend class Namespace;
    friend class Object;
    friend class Precompiler;
  };
  
  // A Namespace contains the names in a library dictionary, filtered by
  // the show/hide combinators.
  class Namespace : public Object {
   public:
    LibraryPtr target() const { return untag()->target(); }
    ArrayPtr show_names() const { return untag()->show_names(); }
    ArrayPtr hide_names() const { return untag()->hide_names(); }
    LibraryPtr owner() const { return untag()->owner(); }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedNamespace));
    }
  
    bool HidesName(const String& name) const;
    ObjectPtr Lookup(const String& name,
                     ZoneGrowableArray<intptr_t>* trail = nullptr) const;
  
    static NamespacePtr New(const Library& library,
                            const Array& show_names,
                            const Array& hide_names,
                            const Library& owner);
  
   private:
    static NamespacePtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Namespace, Object);
    friend class Class;
    friend class Precompiler;
  };
  
  class KernelProgramInfo : public Object {
   public:
    static KernelProgramInfoPtr New(const TypedData& string_offsets,
                                    const ExternalTypedData& string_data,
                                    const TypedData& canonical_names,
                                    const ExternalTypedData& metadata_payload,
                                    const ExternalTypedData& metadata_mappings,
                                    const ExternalTypedData& constants_table,
                                    const Array& scripts,
                                    const Array& libraries_cache,
                                    const Array& classes_cache,
                                    const Object& retained_kernel_blob,
                                    const uint32_t binary_version);
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedKernelProgramInfo));
    }
  
    TypedDataPtr string_offsets() const { return untag()->string_offsets(); }
  
    ExternalTypedDataPtr string_data() const { return untag()->string_data(); }
  
    TypedDataPtr canonical_names() const { return untag()->canonical_names(); }
  
    ExternalTypedDataPtr metadata_payloads() const {
      return untag()->metadata_payloads();
    }
  
    ExternalTypedDataPtr metadata_mappings() const {
      return untag()->metadata_mappings();
    }
  
    ExternalTypedDataPtr constants_table() const {
      return untag()->constants_table();
    }
  
    void set_constants_table(const ExternalTypedData& value) const;
  
    ArrayPtr scripts() const { return untag()->scripts(); }
    void set_scripts(const Array& scripts) const;
  
    ArrayPtr constants() const { return untag()->constants(); }
    void set_constants(const Array& constants) const;
  
    uint32_t kernel_binary_version() const {
      return untag()->kernel_binary_version_;
    }
    void set_kernel_binary_version(uint32_t version) const;
  
    // If we load a kernel blob with evaluated constants, then we delay setting
    // the native names of [Function] objects until we've read the constant table
    // (since native names are encoded as constants).
    //
    // This array will hold the functions which might need their native name set.
    GrowableObjectArrayPtr potential_natives() const {
      return untag()->potential_natives();
    }
    void set_potential_natives(const GrowableObjectArray& candidates) const;
  
    GrowableObjectArrayPtr potential_pragma_functions() const {
      return untag()->potential_pragma_functions();
    }
    void set_potential_pragma_functions(
        const GrowableObjectArray& candidates) const;
  
    ScriptPtr ScriptAt(intptr_t index) const;
  
    ArrayPtr libraries_cache() const { return untag()->libraries_cache(); }
    void set_libraries_cache(const Array& cache) const;
    LibraryPtr LookupLibrary(Thread* thread, const Smi& name_index) const;
    LibraryPtr InsertLibrary(Thread* thread,
                             const Smi& name_index,
                             const Library& lib) const;
  
    ArrayPtr classes_cache() const { return untag()->classes_cache(); }
    void set_classes_cache(const Array& cache) const;
    ClassPtr LookupClass(Thread* thread, const Smi& name_index) const;
    ClassPtr InsertClass(Thread* thread,
                         const Smi& name_index,
                         const Class& klass) const;
  
   private:
    static KernelProgramInfoPtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(KernelProgramInfo, Object);
    friend class Class;
  };
  
  // ObjectPool contains constants, immediates and addresses referenced by
  // generated code and deoptimization infos. Each entry has an type associated
  // with it which is stored in-inline after all the entries.
  class ObjectPool : public Object {
   public:
    using EntryType = compiler::ObjectPoolBuilderEntry::EntryType;
    using Patchability = compiler::ObjectPoolBuilderEntry::Patchability;
    using TypeBits = compiler::ObjectPoolBuilderEntry::TypeBits;
    using PatchableBit = compiler::ObjectPoolBuilderEntry::PatchableBit;
  
    struct Entry {
      Entry() : raw_value_(), type_() {}
      explicit Entry(const Object* obj)
          : obj_(obj), type_(EntryType::kTaggedObject) {}
      Entry(uword value, EntryType info) : raw_value_(value), type_(info) {}
      union {
        const Object* obj_;
        uword raw_value_;
      };
      EntryType type_;
    };
  
    intptr_t Length() const { return untag()->length_; }
    void SetLength(intptr_t value) const {
      StoreNonPointer(&untag()->length_, value);
    }
  
    static intptr_t length_offset() {
      return OFFSET_OF(UntaggedObjectPool, length_);
    }
    static intptr_t data_offset() {
      return OFFSET_OF_RETURNED_VALUE(UntaggedObjectPool, data);
    }
    static intptr_t element_offset(intptr_t index) {
      return OFFSET_OF_RETURNED_VALUE(UntaggedObjectPool, data) +
             sizeof(UntaggedObjectPool::Entry) * index;
    }
  
    struct ArrayTraits {
      static intptr_t elements_start_offset() {
        return ObjectPool::data_offset();
      }
  
      static constexpr intptr_t kElementSize = sizeof(UntaggedObjectPool::Entry);
    };
  
    EntryType TypeAt(intptr_t index) const {
      ASSERT((index >= 0) && (index <= Length()));
      return TypeBits::decode(untag()->entry_bits()[index]);
    }
  
    Patchability PatchableAt(intptr_t index) const {
      ASSERT((index >= 0) && (index <= Length()));
      return PatchableBit::decode(untag()->entry_bits()[index]);
    }
  
    static uint8_t EncodeBits(EntryType type, Patchability patchable) {
      return PatchableBit::encode(patchable) | TypeBits::encode(type);
    }
  
    void SetTypeAt(intptr_t index, EntryType type, Patchability patchable) const {
      ASSERT(index >= 0 && index <= Length());
      const uint8_t bits = EncodeBits(type, patchable);
      StoreNonPointer(&untag()->entry_bits()[index], bits);
    }
  
    template <std::memory_order order = std::memory_order_relaxed>
    ObjectPtr ObjectAt(intptr_t index) const {
      ASSERT(TypeAt(index) == EntryType::kTaggedObject);
      return LoadPointer<ObjectPtr, order>(&(EntryAddr(index)->raw_obj_));
    }
  
    template <std::memory_order order = std::memory_order_relaxed>
    void SetObjectAt(intptr_t index, const Object& obj) const {
      ASSERT((TypeAt(index) == EntryType::kTaggedObject) ||
             (TypeAt(index) == EntryType::kImmediate && obj.IsSmi()));
      StorePointer<ObjectPtr, order>(&EntryAddr(index)->raw_obj_, obj.ptr());
    }
  
    uword RawValueAt(intptr_t index) const {
      ASSERT(TypeAt(index) != EntryType::kTaggedObject);
      return EntryAddr(index)->raw_value_;
    }
    void SetRawValueAt(intptr_t index, uword raw_value) const {
      ASSERT(TypeAt(index) != EntryType::kTaggedObject);
      StoreNonPointer(&EntryAddr(index)->raw_value_, raw_value);
    }
  
    static intptr_t InstanceSize() {
      ASSERT(sizeof(UntaggedObjectPool) ==
             OFFSET_OF_RETURNED_VALUE(UntaggedObjectPool, data));
      return 0;
    }
  
    static const intptr_t kBytesPerElement =
        sizeof(UntaggedObjectPool::Entry) + sizeof(uint8_t);
    static const intptr_t kMaxElements = kSmiMax / kBytesPerElement;
  
    static intptr_t InstanceSize(intptr_t len) {
      // Ensure that variable length data is not adding to the object length.
      ASSERT(sizeof(UntaggedObjectPool) ==
             (sizeof(UntaggedObject) + (1 * kWordSize)));
      ASSERT(0 <= len && len <= kMaxElements);
      return RoundedAllocationSize(sizeof(UntaggedObjectPool) +
                                   (len * kBytesPerElement));
    }
  
    static ObjectPoolPtr NewFromBuilder(
        const compiler::ObjectPoolBuilder& builder);
    static ObjectPoolPtr New(intptr_t len);
  
    void CopyInto(compiler::ObjectPoolBuilder* builder) const;
  
    // Returns the pool index from the offset relative to a tagged ObjectPoolPtr,
    // adjusting for the tag-bit.
    static intptr_t IndexFromOffset(intptr_t offset) {
      ASSERT(
          Utils::IsAligned(offset + kHeapObjectTag, compiler::target::kWordSize));
  #if defined(DART_PRECOMPILER)
      return (offset + kHeapObjectTag -
              compiler::target::ObjectPool::element_offset(0)) /
             compiler::target::kWordSize;
  #else
      return (offset + kHeapObjectTag - element_offset(0)) / kWordSize;
  #endif
    }
  
    static intptr_t OffsetFromIndex(intptr_t index) {
      return element_offset(index) - kHeapObjectTag;
    }
  
    void DebugPrint() const;
  
   private:
    UntaggedObjectPool::Entry const* EntryAddr(intptr_t index) const {
      ASSERT((index >= 0) && (index < Length()));
      return &untag()->data()[index];
    }
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(ObjectPool, Object);
    friend class Class;
    friend class Object;
    friend class UntaggedObjectPool;
  };
  
  class Instructions : public Object {
   public:
    enum {
      kSizePos = 0,
      kSizeSize = 31,
      kFlagsPos = kSizePos + kSizeSize,
      kFlagsSize = 1,  // Currently, only flag is single entry flag.
    };
  
    class SizeBits : public BitField<uint32_t, uint32_t, kSizePos, kSizeSize> {};
    class FlagsBits : public BitField<uint32_t, bool, kFlagsPos, kFlagsSize> {};
  
    // Excludes HeaderSize().
    intptr_t Size() const { return SizeBits::decode(untag()->size_and_flags_); }
    static intptr_t Size(const InstructionsPtr instr) {
      return SizeBits::decode(instr->untag()->size_and_flags_);
    }
  
    bool HasMonomorphicEntry() const {
      return FlagsBits::decode(untag()->size_and_flags_);
    }
    static bool HasMonomorphicEntry(const InstructionsPtr instr) {
      return FlagsBits::decode(instr->untag()->size_and_flags_);
    }
  
    uword PayloadStart() const { return PayloadStart(ptr()); }
    uword MonomorphicEntryPoint() const { return MonomorphicEntryPoint(ptr()); }
    uword EntryPoint() const { return EntryPoint(ptr()); }
    static uword PayloadStart(const InstructionsPtr instr) {
      return reinterpret_cast<uword>(instr->untag()) + HeaderSize();
    }
  
  // Note: We keep the checked entrypoint offsets even (emitting NOPs if
  // necessary) to allow them to be seen as Smis by the GC.
  #if defined(TARGET_ARCH_IA32)
    static const intptr_t kMonomorphicEntryOffsetJIT = 6;
    static const intptr_t kPolymorphicEntryOffsetJIT = 34;
    static const intptr_t kMonomorphicEntryOffsetAOT = 0;
    static const intptr_t kPolymorphicEntryOffsetAOT = 0;
  #elif defined(TARGET_ARCH_X64)
    static const intptr_t kMonomorphicEntryOffsetJIT = 8;
    static const intptr_t kPolymorphicEntryOffsetJIT = 40;
    static const intptr_t kMonomorphicEntryOffsetAOT = 8;
    static const intptr_t kPolymorphicEntryOffsetAOT = 22;
  #elif defined(TARGET_ARCH_ARM)
    static const intptr_t kMonomorphicEntryOffsetJIT = 0;
    static const intptr_t kPolymorphicEntryOffsetJIT = 40;
    static const intptr_t kMonomorphicEntryOffsetAOT = 0;
    static const intptr_t kPolymorphicEntryOffsetAOT = 12;
  #elif defined(TARGET_ARCH_ARM64)
    static const intptr_t kMonomorphicEntryOffsetJIT = 8;
    static const intptr_t kPolymorphicEntryOffsetJIT = 48;
    static const intptr_t kMonomorphicEntryOffsetAOT = 8;
    static const intptr_t kPolymorphicEntryOffsetAOT = 20;
  #else
  #error Missing entry offsets for current architecture
  #endif
  
    static uword MonomorphicEntryPoint(const InstructionsPtr instr) {
      uword entry = PayloadStart(instr);
      if (HasMonomorphicEntry(instr)) {
        entry += !FLAG_precompiled_mode ? kMonomorphicEntryOffsetJIT
                                        : kMonomorphicEntryOffsetAOT;
      }
      return entry;
    }
  
    static uword EntryPoint(const InstructionsPtr instr) {
      uword entry = PayloadStart(instr);
      if (HasMonomorphicEntry(instr)) {
        entry += !FLAG_precompiled_mode ? kPolymorphicEntryOffsetJIT
                                        : kPolymorphicEntryOffsetAOT;
      }
      return entry;
    }
  
    static const intptr_t kMaxElements =
        (kMaxInt32 - (sizeof(UntaggedInstructions) + sizeof(UntaggedObject) +
                      (2 * kMaxObjectAlignment)));
  
    // Currently, we align bare instruction payloads on 4 byte boundaries.
    //
    // If we later decide to align on larger boundaries to put entries at the
    // start of cache lines, make sure to account for entry points that are
    // _not_ at the start of the payload.
    static const intptr_t kBarePayloadAlignment = 4;
  
    // When instructions reside in the heap we align the payloads on word
    // boundaries.
    static const intptr_t kNonBarePayloadAlignment = kWordSize;
  
    // In the precompiled runtime when running in bare instructions mode,
    // Instructions objects don't exist, just their bare payloads, so we
    // mark them as unreachable in that case.
  
    static intptr_t HeaderSize() {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
  #endif
      return Utils::RoundUp(sizeof(UntaggedInstructions),
                            kNonBarePayloadAlignment);
    }
  
    static intptr_t InstanceSize() {
      ASSERT_EQUAL(sizeof(UntaggedInstructions),
                   OFFSET_OF_RETURNED_VALUE(UntaggedInstructions, data));
      return 0;
    }
  
    static intptr_t InstanceSize(intptr_t size) {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
  #endif
      return RoundedAllocationSize(HeaderSize() + size);
    }
  
    static InstructionsPtr FromPayloadStart(uword payload_start) {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
  #endif
      return static_cast<InstructionsPtr>(payload_start - HeaderSize() +
                                          kHeapObjectTag);
    }
  
    bool Equals(const Instructions& other) const {
      return Equals(ptr(), other.ptr());
    }
  
    static bool Equals(InstructionsPtr a, InstructionsPtr b) {
      // This method should only be called on non-null Instructions objects.
      ASSERT_EQUAL(a->GetClassId(), kInstructionsCid);
      ASSERT_EQUAL(b->GetClassId(), kInstructionsCid);
      // Don't include the object header tags wholesale in the comparison,
      // because the GC tags may differ in JIT mode. In fact, we can skip checking
      // the object header entirely, as we're guaranteed that the cids match,
      // because there are no subclasses for the Instructions class, and the sizes
      // should match if the content size encoded in size_and_flags_ matches.
      if (a->untag()->size_and_flags_ != b->untag()->size_and_flags_) {
        return false;
      }
      NoSafepointScope no_safepoint;
      return memcmp(a->untag()->data(), b->untag()->data(), Size(a)) == 0;
    }
  
    uint32_t Hash() const {
      return HashBytes(reinterpret_cast<const uint8_t*>(PayloadStart()), Size());
    }
  
    CodeStatistics* stats() const;
    void set_stats(CodeStatistics* stats) const;
  
   private:
    friend struct RelocatorTestHelper;
  
    void SetSize(intptr_t value) const {
      ASSERT(value >= 0);
      StoreNonPointer(&untag()->size_and_flags_,
                      SizeBits::update(value, untag()->size_and_flags_));
    }
  
    void SetHasMonomorphicEntry(bool value) const {
      StoreNonPointer(&untag()->size_and_flags_,
                      FlagsBits::update(value, untag()->size_and_flags_));
    }
  
    // New is a private method as RawInstruction and RawCode objects should
    // only be created using the Code::FinalizeCode method. This method creates
    // the RawInstruction and RawCode objects, sets up the pointer offsets
    // and links the two in a GC safe manner.
    static InstructionsPtr New(intptr_t size, bool has_monomorphic_entry);
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Instructions, Object);
    friend class Class;
    friend class Code;
    friend class AssemblyImageWriter;
    friend class BlobImageWriter;
    friend class ImageWriter;
  };
  
  // An InstructionsSection contains extra information about serialized AOT
  // snapshots.
  //
  // To avoid changing the embedder to return more information about an AOT
  // snapshot and possibly disturbing existing clients of that interface, we
  // serialize a single InstructionsSection object at the start of any text
  // segments. In bare instructions mode, it also has the benefit of providing
  // memory accounting for the instructions payloads and avoiding special casing
  // Images with bare instructions payloads in the GC. Otherwise, it is empty
  // and the Instructions objects come after it in the Image.
  class InstructionsSection : public Object {
   public:
    // Excludes HeaderSize().
    static intptr_t Size(const InstructionsSectionPtr instr) {
      return instr->untag()->payload_length_;
    }
    static intptr_t InstanceSize() {
      ASSERT(sizeof(UntaggedInstructionsSection) ==
             OFFSET_OF_RETURNED_VALUE(UntaggedInstructionsSection, data));
      return 0;
    }
  
    static intptr_t InstanceSize(intptr_t size) {
      return Utils::RoundUp(HeaderSize() + size, kObjectAlignment);
    }
  
    static intptr_t HeaderSize() {
      return Utils::RoundUp(sizeof(UntaggedInstructionsSection),
                            Instructions::kBarePayloadAlignment);
    }
  
    // There are no public instance methods for the InstructionsSection class, as
    // all access to the contents is handled by methods on the Image class.
  
   private:
    // Note there are no New() methods for InstructionsSection. Instead, the
    // serializer writes the UntaggedInstructionsSection object manually at the
    // start of instructions Images in precompiled snapshots.
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(InstructionsSection, Object);
    friend class Class;
  };
  
  // Table which maps ranges of machine code to [Code] or
  // [CompressedStackMaps] objects.
  // Used in AOT in bare instructions mode.
  class InstructionsTable : public Object {
   public:
    static const intptr_t kBytesPerElement = sizeof(uint32_t);
    static const intptr_t kMaxElements = kIntptrMax / kBytesPerElement;
  
    static const uint32_t kHasMonomorphicEntrypointFlag = 0x1;
    static const uint32_t kPayloadAlignment = Instructions::kBarePayloadAlignment;
    static const uint32_t kPayloadMask = ~(kPayloadAlignment - 1);
    COMPILE_ASSERT((kPayloadMask & kHasMonomorphicEntrypointFlag) == 0);
  
    struct ArrayTraits {
      static intptr_t elements_start_offset() {
        return sizeof(UntaggedInstructionsTable);
      }
      static constexpr intptr_t kElementSize = kBytesPerElement;
    };
  
    static intptr_t InstanceSize() {
      ASSERT_EQUAL(sizeof(UntaggedInstructionsTable),
                   OFFSET_OF_RETURNED_VALUE(UntaggedInstructionsTable, data));
      return 0;
    }
    static intptr_t InstanceSize(intptr_t len) {
      ASSERT(0 <= len && len <= kMaxElements);
      return RoundedAllocationSize(sizeof(UntaggedInstructionsTable) +
                                   len * kBytesPerElement);
    }
  
    static InstructionsTablePtr New(intptr_t length,
                                    uword start_pc,
                                    uword end_pc);
  
    void SetEntryAt(intptr_t index,
                    uword payload_start,
                    bool has_monomorphic_entrypoint,
                    ObjectPtr descriptor) const;
  
    bool ContainsPc(uword pc) const { return ContainsPc(ptr(), pc); }
    static bool ContainsPc(InstructionsTablePtr table, uword pc);
  
    // Looks for the entry in the [table] by the given [pc].
    // Returns index of an entry which contains [pc], or -1 if not found.
    static intptr_t FindEntry(InstructionsTablePtr table, uword pc);
  
    intptr_t length() const { return InstructionsTable::length(this->ptr()); }
    static intptr_t length(InstructionsTablePtr table) {
      return table->untag()->length_;
    }
  
    // Returns descriptor object for the entry with given index.
    ObjectPtr DescriptorAt(intptr_t index) const {
      return InstructionsTable::DescriptorAt(this->ptr(), index);
    }
    static ObjectPtr DescriptorAt(InstructionsTablePtr table, intptr_t index);
  
    // Returns start address of the instructions entry with given index.
    uword PayloadStartAt(intptr_t index) const {
      return InstructionsTable::PayloadStartAt(this->ptr(), index);
    }
    static uword PayloadStartAt(InstructionsTablePtr table, intptr_t index);
  
    // Returns entry point of the instructions with given index.
    uword EntryPointAt(intptr_t index) const;
  
   private:
    uword start_pc() const { return InstructionsTable::start_pc(this->ptr()); }
    static uword start_pc(InstructionsTablePtr table) {
      return table->untag()->start_pc_;
    }
  
    uword end_pc() const { return InstructionsTable::end_pc(this->ptr()); }
    static uword end_pc(InstructionsTablePtr table) {
      return table->untag()->end_pc_;
    }
  
    ArrayPtr descriptors() const { return untag()->descriptors_; }
  
    static uint32_t DataAt(InstructionsTablePtr table, intptr_t index) {
      ASSERT((0 <= index) && (index < InstructionsTable::length(table)));
      return table->untag()->data()[index];
    }
    uint32_t PcOffsetAt(intptr_t index) const {
      return InstructionsTable::PcOffsetAt(this->ptr(), index);
    }
    static uint32_t PcOffsetAt(InstructionsTablePtr table, intptr_t index) {
      return DataAt(table, index) & kPayloadMask;
    }
    bool HasMonomorphicEntryPointAt(intptr_t index) const {
      return (DataAt(this->ptr(), index) & kHasMonomorphicEntrypointFlag) != 0;
    }
  
    void set_length(intptr_t value) const;
    void set_start_pc(uword value) const;
    void set_end_pc(uword value) const;
    void set_descriptors(const Array& value) const;
  
    uint32_t ConvertPcToOffset(uword pc) const {
      return InstructionsTable::ConvertPcToOffset(this->ptr(), pc);
    }
    static uint32_t ConvertPcToOffset(InstructionsTablePtr table, uword pc);
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(InstructionsTable, Object);
    friend class Class;
  };
  
  class LocalVarDescriptors : public Object {
   public:
    intptr_t Length() const;
  
    StringPtr GetName(intptr_t var_index) const;
  
    void SetVar(intptr_t var_index,
                const String& name,
                UntaggedLocalVarDescriptors::VarInfo* info) const;
  
    void GetInfo(intptr_t var_index,
                 UntaggedLocalVarDescriptors::VarInfo* info) const;
  
    static const intptr_t kBytesPerElement =
        sizeof(UntaggedLocalVarDescriptors::VarInfo);
    static const intptr_t kMaxElements = UntaggedLocalVarDescriptors::kMaxIndex;
  
    static intptr_t InstanceSize() {
      ASSERT(sizeof(UntaggedLocalVarDescriptors) ==
             OFFSET_OF_RETURNED_VALUE(UntaggedLocalVarDescriptors, names));
      return 0;
    }
    static intptr_t InstanceSize(intptr_t len) {
      ASSERT(0 <= len && len <= kMaxElements);
      return RoundedAllocationSize(
          sizeof(UntaggedLocalVarDescriptors) +
          (len * kWordSize)  // RawStrings for names.
          + (len * sizeof(UntaggedLocalVarDescriptors::VarInfo)));
    }
  
    static LocalVarDescriptorsPtr New(intptr_t num_variables);
  
    static const char* KindToCString(
        UntaggedLocalVarDescriptors::VarInfoKind kind);
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(LocalVarDescriptors, Object);
    friend class Class;
    friend class Object;
  };
  
  class PcDescriptors : public Object {
   public:
    static const intptr_t kBytesPerElement = 1;
    static const intptr_t kMaxElements = kMaxInt32 / kBytesPerElement;
  
    static intptr_t HeaderSize() { return sizeof(UntaggedPcDescriptors); }
    static intptr_t UnroundedSize(PcDescriptorsPtr desc) {
      return UnroundedSize(desc->untag()->length_);
    }
    static intptr_t UnroundedSize(intptr_t len) { return HeaderSize() + len; }
    static intptr_t InstanceSize() {
      ASSERT_EQUAL(sizeof(UntaggedPcDescriptors),
                   OFFSET_OF_RETURNED_VALUE(UntaggedPcDescriptors, data));
      return 0;
    }
    static intptr_t InstanceSize(intptr_t len) {
      ASSERT(0 <= len && len <= kMaxElements);
      return RoundedAllocationSize(UnroundedSize(len));
    }
  
    static PcDescriptorsPtr New(const void* delta_encoded_data, intptr_t size);
  
    // Verify (assert) assumptions about pc descriptors in debug mode.
    void Verify(const Function& function) const;
  
    static void PrintHeaderString();
  
    void PrintToJSONObject(JSONObject* jsobj, bool ref) const;
  
    // We would have a VisitPointers function here to traverse the
    // pc descriptors table to visit objects if any in the table.
    // Note: never return a reference to a UntaggedPcDescriptors::PcDescriptorRec
    // as the object can move.
    class Iterator : ValueObject {
     public:
      Iterator(const PcDescriptors& descriptors, intptr_t kind_mask)
          : descriptors_(descriptors),
            kind_mask_(kind_mask),
            byte_index_(0),
            cur_pc_offset_(0),
            cur_kind_(0),
            cur_deopt_id_(0),
            cur_token_pos_(0),
            cur_try_index_(0),
            cur_yield_index_(UntaggedPcDescriptors::kInvalidYieldIndex) {}
  
      bool MoveNext() {
        NoSafepointScope scope;
        ReadStream stream(descriptors_.untag()->data(), descriptors_.Length(),
                          byte_index_);
        // Moves to record that matches kind_mask_.
        while (byte_index_ < descriptors_.Length()) {
          const int32_t kind_and_metadata = stream.ReadSLEB128<int32_t>();
          cur_kind_ = UntaggedPcDescriptors::KindAndMetadata::DecodeKind(
              kind_and_metadata);
          cur_try_index_ = UntaggedPcDescriptors::KindAndMetadata::DecodeTryIndex(
              kind_and_metadata);
          cur_yield_index_ =
              UntaggedPcDescriptors::KindAndMetadata::DecodeYieldIndex(
                  kind_and_metadata);
  
          cur_pc_offset_ += stream.ReadSLEB128();
  
          if (!FLAG_precompiled_mode) {
            cur_deopt_id_ += stream.ReadSLEB128();
            cur_token_pos_ = Utils::AddWithWrapAround(
                cur_token_pos_, stream.ReadSLEB128<int32_t>());
          }
          byte_index_ = stream.Position();
  
          if ((cur_kind_ & kind_mask_) != 0) {
            return true;  // Current is valid.
          }
        }
        return false;
      }
  
      uword PcOffset() const { return cur_pc_offset_; }
      intptr_t DeoptId() const { return cur_deopt_id_; }
      TokenPosition TokenPos() const {
        return TokenPosition::Deserialize(cur_token_pos_);
      }
      intptr_t TryIndex() const { return cur_try_index_; }
      intptr_t YieldIndex() const { return cur_yield_index_; }
      UntaggedPcDescriptors::Kind Kind() const {
        return static_cast<UntaggedPcDescriptors::Kind>(cur_kind_);
      }
  
     private:
      friend class PcDescriptors;
  
      // For nested iterations, starting at element after.
      explicit Iterator(const Iterator& iter)
          : ValueObject(),
            descriptors_(iter.descriptors_),
            kind_mask_(iter.kind_mask_),
            byte_index_(iter.byte_index_),
            cur_pc_offset_(iter.cur_pc_offset_),
            cur_kind_(iter.cur_kind_),
            cur_deopt_id_(iter.cur_deopt_id_),
            cur_token_pos_(iter.cur_token_pos_),
            cur_try_index_(iter.cur_try_index_),
            cur_yield_index_(iter.cur_yield_index_) {}
  
      const PcDescriptors& descriptors_;
      const intptr_t kind_mask_;
      intptr_t byte_index_;
  
      intptr_t cur_pc_offset_;
      intptr_t cur_kind_;
      intptr_t cur_deopt_id_;
      int32_t cur_token_pos_;
      intptr_t cur_try_index_;
      intptr_t cur_yield_index_;
    };
  
    intptr_t Length() const;
    bool Equals(const PcDescriptors& other) const {
      if (Length() != other.Length()) {
        return false;
      }
      NoSafepointScope no_safepoint;
      return memcmp(untag(), other.untag(), InstanceSize(Length())) == 0;
    }
  
   private:
    static const char* KindAsStr(UntaggedPcDescriptors::Kind kind);
  
    static PcDescriptorsPtr New(intptr_t length);
  
    void SetLength(intptr_t value) const;
    void CopyData(const void* bytes, intptr_t size);
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(PcDescriptors, Object);
    friend class Class;
    friend class Object;
  };
  
  class CodeSourceMap : public Object {
   public:
    static const intptr_t kBytesPerElement = 1;
    static const intptr_t kMaxElements = kMaxInt32 / kBytesPerElement;
  
    static intptr_t HeaderSize() { return sizeof(UntaggedCodeSourceMap); }
    static intptr_t UnroundedSize(CodeSourceMapPtr map) {
      return UnroundedSize(map->untag()->length_);
    }
    static intptr_t UnroundedSize(intptr_t len) { return HeaderSize() + len; }
    static intptr_t InstanceSize() {
      ASSERT_EQUAL(sizeof(UntaggedCodeSourceMap),
                   OFFSET_OF_RETURNED_VALUE(UntaggedCodeSourceMap, data));
      return 0;
    }
    static intptr_t InstanceSize(intptr_t len) {
      ASSERT(0 <= len && len <= kMaxElements);
      return RoundedAllocationSize(UnroundedSize(len));
    }
  
    static CodeSourceMapPtr New(intptr_t length);
  
    intptr_t Length() const { return untag()->length_; }
    uint8_t* Data() const { return UnsafeMutableNonPointer(&untag()->data()[0]); }
  
    bool Equals(const CodeSourceMap& other) const {
      if (Length() != other.Length()) {
        return false;
      }
      NoSafepointScope no_safepoint;
      return memcmp(untag(), other.untag(), InstanceSize(Length())) == 0;
    }
  
    void PrintToJSONObject(JSONObject* jsobj, bool ref) const;
  
   private:
    void SetLength(intptr_t value) const;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(CodeSourceMap, Object);
    friend class Class;
    friend class Object;
  };
  
  class CompressedStackMaps : public Object {
   public:
    uintptr_t payload_size() const { return PayloadSizeOf(ptr()); }
    static uintptr_t PayloadSizeOf(const CompressedStackMapsPtr raw) {
      return UntaggedCompressedStackMaps::SizeField::decode(
          raw->untag()->flags_and_size_);
    }
  
    // Methods to allow use with PointerKeyValueTrait to create sets of CSMs.
    bool Equals(const CompressedStackMaps& other) const {
      // All of the table flags and payload size must match.
      if (untag()->flags_and_size_ != other.untag()->flags_and_size_) {
        return false;
      }
      NoSafepointScope no_safepoint;
      return memcmp(untag(), other.untag(), InstanceSize(payload_size())) == 0;
    }
    uword Hash() const;
  
    static intptr_t HeaderSize() { return sizeof(UntaggedCompressedStackMaps); }
    static intptr_t UnroundedSize(CompressedStackMapsPtr maps) {
      return UnroundedSize(CompressedStackMaps::PayloadSizeOf(maps));
    }
    static intptr_t UnroundedSize(intptr_t length) {
      return HeaderSize() + length;
    }
    static intptr_t InstanceSize() {
      ASSERT_EQUAL(sizeof(UntaggedCompressedStackMaps),
                   OFFSET_OF_RETURNED_VALUE(UntaggedCompressedStackMaps, data));
      return 0;
    }
    static intptr_t InstanceSize(intptr_t length) {
      return RoundedAllocationSize(UnroundedSize(length));
    }
  
    bool UsesGlobalTable() const { return UsesGlobalTable(ptr()); }
    static bool UsesGlobalTable(const CompressedStackMapsPtr raw) {
      return UntaggedCompressedStackMaps::UsesTableBit::decode(
          raw->untag()->flags_and_size_);
    }
  
    bool IsGlobalTable() const { return IsGlobalTable(ptr()); }
    static bool IsGlobalTable(const CompressedStackMapsPtr raw) {
      return UntaggedCompressedStackMaps::GlobalTableBit::decode(
          raw->untag()->flags_and_size_);
    }
  
    static CompressedStackMapsPtr NewInlined(const void* payload, intptr_t size) {
      return New(payload, size, /*is_global_table=*/false,
                 /*uses_global_table=*/false);
    }
    static CompressedStackMapsPtr NewUsingTable(const void* payload,
                                                intptr_t size) {
      return New(payload, size, /*is_global_table=*/false,
                 /*uses_global_table=*/true);
    }
  
    static CompressedStackMapsPtr NewGlobalTable(const void* payload,
                                                 intptr_t size) {
      return New(payload, size, /*is_global_table=*/true,
                 /*uses_global_table=*/false);
    }
  
    class Iterator {
     public:
      Iterator(const CompressedStackMaps& maps,
               const CompressedStackMaps& global_table);
      Iterator(Thread* thread, const CompressedStackMaps& maps);
  
      explicit Iterator(const CompressedStackMaps::Iterator& it);
  
      // Loads the next entry from [maps_], if any. If [maps_] is the null value,
      // this always returns false.
      bool MoveNext();
  
      // Finds the entry with the given PC offset starting at the current position
      // of the iterator. If [maps_] is the null value, this always returns false.
      bool Find(uint32_t pc_offset) {
        // We should never have an entry with a PC offset of 0 inside an
        // non-empty CSM, so fail.
        if (pc_offset == 0) return false;
        do {
          if (current_pc_offset_ >= pc_offset) break;
        } while (MoveNext());
        return current_pc_offset_ == pc_offset;
      }
  
      // Methods for accessing parts of an entry should not be called until
      // a successful MoveNext() or Find() call has been made.
  
      // Returns the PC offset of the loaded entry.
      uint32_t pc_offset() const {
        ASSERT(HasLoadedEntry());
        return current_pc_offset_;
      }
  
      // Returns the bit length of the loaded entry.
      intptr_t Length() const;
      // Returns the number of spill slot bits of the loaded entry.
      intptr_t SpillSlotBitCount() const;
      // Returns whether the stack entry represented by the offset contains
      // a tagged objecet.
      bool IsObject(intptr_t bit_offset) const;
  
      void WriteToBuffer(BaseTextBuffer* buffer, const char* separator) const;
  
     private:
      bool HasLoadedEntry() const { return next_offset_ > 0; }
  
      // Caches the corresponding values from the global table in the mutable
      // fields. We lazily load these as some clients only need the PC offset.
      void LazyLoadGlobalTableEntry() const;
  
      void EnsureFullyLoadedEntry() const {
        ASSERT(HasLoadedEntry());
        if (current_spill_slot_bit_count_ < 0) {
          LazyLoadGlobalTableEntry();
          ASSERT(current_spill_slot_bit_count_ >= 0);
        }
      }
  
      const CompressedStackMaps& maps_;
      const CompressedStackMaps& bits_container_;
  
      uintptr_t next_offset_ = 0;
      uint32_t current_pc_offset_ = 0;
      // Only used when looking up non-PC information in the global table.
      uintptr_t current_global_table_offset_ = 0;
      // Marked as mutable as these fields may be updated with lazily loaded
      // values from the global table when their associated accessor is called,
      // but those values will never change for a given entry once loaded..
      mutable intptr_t current_spill_slot_bit_count_ = -1;
      mutable intptr_t current_non_spill_slot_bit_count_ = -1;
      mutable intptr_t current_bits_offset_ = -1;
  
      friend class StackMapEntry;
    };
  
   private:
    static CompressedStackMapsPtr New(const void* payload,
                                      intptr_t size,
                                      bool is_global_table,
                                      bool uses_global_table);
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(CompressedStackMaps, Object);
    friend class Class;
  };
  
  class ExceptionHandlers : public Object {
   public:
    static const intptr_t kInvalidPcOffset = 0;
  
    intptr_t num_entries() const;
  
    void GetHandlerInfo(intptr_t try_index, ExceptionHandlerInfo* info) const;
  
    uword HandlerPCOffset(intptr_t try_index) const;
    intptr_t OuterTryIndex(intptr_t try_index) const;
    bool NeedsStackTrace(intptr_t try_index) const;
    bool IsGenerated(intptr_t try_index) const;
  
    void 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;
  
    ArrayPtr GetHandledTypes(intptr_t try_index) const;
    void SetHandledTypes(intptr_t try_index, const Array& handled_types) const;
    bool HasCatchAll(intptr_t try_index) const;
  
    struct ArrayTraits {
      static intptr_t elements_start_offset() {
        return sizeof(UntaggedExceptionHandlers);
      }
      static constexpr intptr_t kElementSize = sizeof(ExceptionHandlerInfo);
    };
  
    static intptr_t InstanceSize() {
      ASSERT(sizeof(UntaggedExceptionHandlers) ==
             OFFSET_OF_RETURNED_VALUE(UntaggedExceptionHandlers, data));
      return 0;
    }
    static intptr_t InstanceSize(intptr_t len) {
      return RoundedAllocationSize(sizeof(UntaggedExceptionHandlers) +
                                   (len * sizeof(ExceptionHandlerInfo)));
    }
  
    static ExceptionHandlersPtr New(intptr_t num_handlers);
    static ExceptionHandlersPtr New(const Array& handled_types_data);
  
    // We would have a VisitPointers function here to traverse the
    // exception handler table to visit objects if any in the table.
  
   private:
    // Pick somewhat arbitrary maximum number of exception handlers
    // for a function. This value is used to catch potentially
    // malicious code.
    static const intptr_t kMaxHandlers = 1024 * 1024;
  
    void set_handled_types_data(const Array& value) const;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(ExceptionHandlers, Object);
    friend class Class;
    friend class Object;
  };
  
  // A WeakSerializationReference (WSR) denotes a type of weak reference to a
  // target object. In particular, objects that can only be reached from roots via
  // WSR edges during serialization of AOT snapshots should not be serialized, but
  // instead references to these objects should be replaced with a reference to
  // the provided replacement object.
  //
  // Of course, the target object may still be serialized if there are paths to
  // the object from the roots that do not go through one of these objects. In
  // this case, references through WSRs are serialized as direct references to
  // the target.
  //
  // Unfortunately a WSR is not a proxy for the original object, so WSRs may
  // only currently be used with ObjectPtr fields. To ease this situation for
  // fields that are normally a non-ObjectPtr type outside of the precompiler,
  // use the following macros, which avoid the need to adjust other code to
  // handle the WSR case:
  //
  // * WSR_*POINTER_FIELD() in raw_object.h (i.e., just append WSR_ to the
  //   original field declaration).
  // * PRECOMPILER_WSR_FIELD_DECLARATION() in object.h
  // * PRECOMPILER_WSR_FIELD_DEFINITION() in object.cc
  class WeakSerializationReference : public Object {
   public:
    ObjectPtr target() const { return TargetOf(ptr()); }
    static ObjectPtr TargetOf(const WeakSerializationReferencePtr obj) {
      return obj->untag()->target();
    }
  
    static ObjectPtr Unwrap(ObjectPtr obj) {
  #if defined(DART_PRECOMPILER)
      if (obj->IsHeapObject() && obj->IsWeakSerializationReference()) {
        return TargetOf(static_cast<WeakSerializationReferencePtr>(obj));
      }
  #endif
      return obj;
    }
    static ObjectPtr Unwrap(const Object& obj) { return Unwrap(obj.ptr()); }
    static ObjectPtr UnwrapIfTarget(ObjectPtr obj) { return Unwrap(obj); }
    static ObjectPtr UnwrapIfTarget(const Object& obj) { return Unwrap(obj); }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedWeakSerializationReference));
    }
  
    // Returns an ObjectPtr as the target may not need wrapping (e.g., it
    // is guaranteed to be serialized).
    static ObjectPtr New(const Object& target, const Object& replacement);
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(WeakSerializationReference, Object);
  
    ObjectPtr replacement() const { return untag()->replacement(); }
  
    friend class Class;
  };
  
  class Code : public Object {
   public:
    // When dual mapping, this returns the executable view.
    InstructionsPtr active_instructions() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
      return NULL;
  #else
      return untag()->active_instructions();
  #endif
    }
  
    // When dual mapping, these return the executable view.
    InstructionsPtr instructions() const { return untag()->instructions(); }
    static InstructionsPtr InstructionsOf(const CodePtr code) {
      return code->untag()->instructions();
    }
  
    static intptr_t saved_instructions_offset() {
      return OFFSET_OF(UntaggedCode, instructions_);
    }
  
    using EntryKind = CodeEntryKind;
  
    static const char* EntryKindToCString(EntryKind kind);
    static bool ParseEntryKind(const char* str, EntryKind* out);
  
    static intptr_t entry_point_offset(EntryKind kind = EntryKind::kNormal) {
      switch (kind) {
        case EntryKind::kNormal:
          return OFFSET_OF(UntaggedCode, entry_point_);
        case EntryKind::kUnchecked:
          return OFFSET_OF(UntaggedCode, unchecked_entry_point_);
        case EntryKind::kMonomorphic:
          return OFFSET_OF(UntaggedCode, monomorphic_entry_point_);
        case EntryKind::kMonomorphicUnchecked:
          return OFFSET_OF(UntaggedCode, monomorphic_unchecked_entry_point_);
        default:
          UNREACHABLE();
      }
    }
  
    ObjectPoolPtr object_pool() const { return untag()->object_pool(); }
    static intptr_t object_pool_offset() {
      return OFFSET_OF(UntaggedCode, object_pool_);
    }
  
    intptr_t pointer_offsets_length() const {
      return PtrOffBits::decode(untag()->state_bits_);
    }
  
    bool is_optimized() const {
      return OptimizedBit::decode(untag()->state_bits_);
    }
    void set_is_optimized(bool value) const;
    static bool IsOptimized(CodePtr code) {
      return Code::OptimizedBit::decode(code->untag()->state_bits_);
    }
  
    bool is_force_optimized() const {
      return ForceOptimizedBit::decode(untag()->state_bits_);
    }
    void set_is_force_optimized(bool value) const;
  
    bool is_alive() const { return AliveBit::decode(untag()->state_bits_); }
    void set_is_alive(bool value) const;
  
    bool is_discarded() const { return IsDiscarded(ptr()); }
    static bool IsDiscarded(const CodePtr code) {
      return DiscardedBit::decode(code->untag()->state_bits_);
    }
    void set_is_discarded(bool value) const;
  
    bool HasMonomorphicEntry() const { return HasMonomorphicEntry(ptr()); }
    static bool HasMonomorphicEntry(const CodePtr code) {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return code->untag()->entry_point_ !=
             code->untag()->monomorphic_entry_point_;
  #else
      return Instructions::HasMonomorphicEntry(InstructionsOf(code));
  #endif
    }
  
    // Returns the payload start of [instructions()].
    uword PayloadStart() const { return PayloadStartOf(ptr()); }
    static uword PayloadStartOf(const CodePtr code) {
  #if defined(DART_PRECOMPILED_RUNTIME)
      if (IsUnknownDartCode(code)) return 0;
      const uword entry_offset = HasMonomorphicEntry(code)
                                     ? Instructions::kPolymorphicEntryOffsetAOT
                                     : 0;
      return EntryPointOf(code) - entry_offset;
  #else
      return Instructions::PayloadStart(InstructionsOf(code));
  #endif
    }
  
    // Returns the entry point of [instructions()].
    uword EntryPoint() const { return EntryPointOf(ptr()); }
    static uword EntryPointOf(const CodePtr code) {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return code->untag()->entry_point_;
  #else
      return Instructions::EntryPoint(InstructionsOf(code));
  #endif
    }
  
    // Returns the unchecked entry point of [instructions()].
    uword UncheckedEntryPoint() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return untag()->unchecked_entry_point_;
  #else
      return EntryPoint() + untag()->unchecked_offset_;
  #endif
    }
    // Returns the monomorphic entry point of [instructions()].
    uword MonomorphicEntryPoint() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return untag()->monomorphic_entry_point_;
  #else
      return Instructions::MonomorphicEntryPoint(instructions());
  #endif
    }
    // Returns the unchecked monomorphic entry point of [instructions()].
    uword MonomorphicUncheckedEntryPoint() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      return untag()->monomorphic_unchecked_entry_point_;
  #else
      return MonomorphicEntryPoint() + untag()->unchecked_offset_;
  #endif
    }
  
    // Returns the size of [instructions()].
    uword Size() const { return PayloadSizeOf(ptr()); }
    static uword PayloadSizeOf(const CodePtr code) {
  #if defined(DART_PRECOMPILED_RUNTIME)
      if (IsUnknownDartCode(code)) return kUwordMax;
      return code->untag()->instructions_length_;
  #else
      return Instructions::Size(InstructionsOf(code));
  #endif
    }
  
    ObjectPoolPtr GetObjectPool() const;
    // Returns whether the given PC address is in [instructions()].
    bool ContainsInstructionAt(uword addr) const {
      return ContainsInstructionAt(ptr(), addr);
    }
  
    // Returns whether the given PC address is in [InstructionsOf(code)].
    static bool ContainsInstructionAt(const CodePtr code, uword pc) {
      return UntaggedCode::ContainsPC(code, pc);
    }
  
    // Returns true if there is a debugger breakpoint set in this code object.
    bool HasBreakpoint() const;
  
    PcDescriptorsPtr pc_descriptors() const { return untag()->pc_descriptors(); }
    void set_pc_descriptors(const PcDescriptors& descriptors) const {
      ASSERT(descriptors.IsOld());
      untag()->set_pc_descriptors(descriptors.ptr());
    }
  
    CodeSourceMapPtr code_source_map() const {
      return untag()->code_source_map();
    }
  
    void set_code_source_map(const CodeSourceMap& code_source_map) const {
      ASSERT(code_source_map.IsOld());
      untag()->set_code_source_map(code_source_map.ptr());
    }
  
    // Array of DeoptInfo objects.
    ArrayPtr deopt_info_array() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
      return NULL;
  #else
      return untag()->deopt_info_array();
  #endif
    }
    void set_deopt_info_array(const Array& array) const;
  
  #if !defined(DART_PRECOMPILED_RUNTIME)
    intptr_t num_variables() const;
    void set_num_variables(intptr_t num_variables) const;
  #endif
  
  #if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
    TypedDataPtr catch_entry_moves_maps() const;
    void set_catch_entry_moves_maps(const TypedData& maps) const;
  #endif
  
    CompressedStackMapsPtr compressed_stackmaps() const {
      return untag()->compressed_stackmaps();
    }
    void set_compressed_stackmaps(const CompressedStackMaps& maps) const;
  
    enum CallKind {
      kPcRelativeCall = 1,
      kPcRelativeTTSCall = 2,
      kPcRelativeTailCall = 3,
      kCallViaCode = 4,
    };
  
    enum CallEntryPoint {
      kDefaultEntry,
      kUncheckedEntry,
    };
  
    enum SCallTableEntry {
      kSCallTableKindAndOffset = 0,
      kSCallTableCodeOrTypeTarget = 1,
      kSCallTableFunctionTarget = 2,
      kSCallTableEntryLength = 3,
    };
  
    enum class PoolAttachment {
      kAttachPool,
      kNotAttachPool,
    };
  
    class KindField : public BitField<intptr_t, CallKind, 0, 3> {};
    class EntryPointField
        : public BitField<intptr_t, CallEntryPoint, KindField::kNextBit, 1> {};
    class OffsetField
        : public BitField<intptr_t, intptr_t, EntryPointField::kNextBit, 26> {};
  
    void set_static_calls_target_table(const Array& value) const;
    ArrayPtr static_calls_target_table() const {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
      return NULL;
  #else
      return untag()->static_calls_target_table();
  #endif
    }
  
    TypedDataPtr GetDeoptInfoAtPc(uword pc,
                                  ICData::DeoptReasonId* deopt_reason,
                                  uint32_t* deopt_flags) const;
  
    // Returns null if there is no static call at 'pc'.
    FunctionPtr GetStaticCallTargetFunctionAt(uword pc) const;
    // Aborts if there is no static call at 'pc'.
    void SetStaticCallTargetCodeAt(uword pc, const Code& code) const;
    void SetStubCallTargetCodeAt(uword pc, const Code& code) const;
  
    void Disassemble(DisassemblyFormatter* formatter = NULL) const;
  
  #if defined(INCLUDE_IL_PRINTER)
    class Comments : public ZoneAllocated, public CodeComments {
     public:
      static Comments& New(intptr_t count);
  
      intptr_t Length() const override;
  
      void SetPCOffsetAt(intptr_t idx, intptr_t pc_offset);
      void SetCommentAt(intptr_t idx, const String& comment);
  
      intptr_t PCOffsetAt(intptr_t idx) const override;
      const char* CommentAt(intptr_t idx) const override;
  
     private:
      explicit Comments(const Array& comments);
  
      // Layout of entries describing comments.
      enum {kPCOffsetEntry = 0,  // PC offset to a comment as a Smi.
            kCommentEntry,       // Comment text as a String.
            kNumberOfEntries};
  
      const Array& comments_;
      String& string_;
  
      friend class Code;
  
      DISALLOW_COPY_AND_ASSIGN(Comments);
    };
  
    const CodeComments& comments() const;
    void set_comments(const CodeComments& comments) const;
  #endif  // defined(INCLUDE_IL_PRINTER)
  
    ObjectPtr return_address_metadata() const {
  #if defined(PRODUCT)
      UNREACHABLE();
      return NULL;
  #else
      return untag()->return_address_metadata();
  #endif
    }
    // Sets |return_address_metadata|.
    void SetPrologueOffset(intptr_t offset) const;
    // Returns -1 if no prologue offset is available.
    intptr_t GetPrologueOffset() const;
  
    ArrayPtr inlined_id_to_function() const;
    void set_inlined_id_to_function(const Array& value) const;
  
    // Provides the call stack at the given pc offset, with the top-of-stack in
    // the last element and the root function (this) as the first element, along
    // with the corresponding source positions. Note the token position for each
    // function except the top-of-stack is the position of the call to the next
    // function. The stack will be empty if we lack the metadata to produce it,
    // which happens for stub code.
    // The pc offset is interpreted as an instruction address (as needed by the
    // disassembler or the top frame of a profiler sample).
    void GetInlinedFunctionsAtInstruction(
        intptr_t pc_offset,
        GrowableArray<const Function*>* functions,
        GrowableArray<TokenPosition>* token_positions) const;
    // Same as above, except the pc is interpreted as a return address (as needed
    // for a stack trace or the bottom frames of a profiler sample).
    void GetInlinedFunctionsAtReturnAddress(
        intptr_t pc_offset,
        GrowableArray<const Function*>* functions,
        GrowableArray<TokenPosition>* token_positions) const {
      GetInlinedFunctionsAtInstruction(pc_offset - 1, functions, token_positions);
    }
  
    NOT_IN_PRODUCT(void PrintJSONInlineIntervals(JSONObject* object) const);
    void DumpInlineIntervals() const;
    void DumpSourcePositions(bool relative_addresses = false) const;
  
    LocalVarDescriptorsPtr var_descriptors() const {
  #if defined(PRODUCT)
      UNREACHABLE();
      return NULL;
  #else
      return untag()->var_descriptors();
  #endif
    }
    void set_var_descriptors(const LocalVarDescriptors& value) const {
  #if defined(PRODUCT)
      UNREACHABLE();
  #else
      ASSERT(value.IsOld());
      untag()->set_var_descriptors(value.ptr());
  #endif
    }
  
    // Will compute local var descriptors if necessary.
    LocalVarDescriptorsPtr GetLocalVarDescriptors() const;
  
    ExceptionHandlersPtr exception_handlers() const {
      return untag()->exception_handlers();
    }
    void set_exception_handlers(const ExceptionHandlers& handlers) const {
      ASSERT(handlers.IsOld());
      untag()->set_exception_handlers(handlers.ptr());
    }
  
    // WARNING: function() returns the owner which is not guaranteed to be
    // a Function. It is up to the caller to guarantee it isn't a stub, class,
    // or something else.
    // TODO(turnidge): Consider dropping this function and making
    // everybody use owner().  Currently this function is misused - even
    // while generating the snapshot.
    FunctionPtr function() const {
      ASSERT(IsFunctionCode());
      return Function::RawCast(owner());
    }
  
    ObjectPtr owner() const {
      return WeakSerializationReference::Unwrap(untag()->owner());
    }
    void set_owner(const Object& owner) const;
  
    classid_t OwnerClassId() const { return OwnerClassIdOf(ptr()); }
    static classid_t OwnerClassIdOf(CodePtr raw) {
      ObjectPtr owner = WeakSerializationReference::Unwrap(raw->untag()->owner());
      if (!owner->IsHeapObject()) {
        return RawSmiValue(static_cast<SmiPtr>(owner));
      }
      return owner->GetClassId();
    }
  
    static intptr_t owner_offset() { return OFFSET_OF(UntaggedCode, owner_); }
  
    // We would have a VisitPointers function here to traverse all the
    // embedded objects in the instructions using pointer_offsets.
  
    static const intptr_t kBytesPerElement =
        sizeof(reinterpret_cast<UntaggedCode*>(0)->data()[0]);
    static const intptr_t kMaxElements = kSmiMax / kBytesPerElement;
  
    struct ArrayTraits {
      static intptr_t elements_start_offset() { return sizeof(UntaggedCode); }
      static constexpr intptr_t kElementSize = kBytesPerElement;
    };
  
    static intptr_t InstanceSize() {
      ASSERT(sizeof(UntaggedCode) ==
             OFFSET_OF_RETURNED_VALUE(UntaggedCode, data));
      return 0;
    }
    static intptr_t InstanceSize(intptr_t len) {
      ASSERT(0 <= len && len <= kMaxElements);
      return RoundedAllocationSize(sizeof(UntaggedCode) +
                                   (len * kBytesPerElement));
    }
  #if !defined(DART_PRECOMPILED_RUNTIME)
    // Finalizes the generated code, by generating various kinds of metadata (e.g.
    // stack maps, pc descriptors, ...) and attach them to a newly generated
    // [Code] object.
    //
    // If Code::PoolAttachment::kAttachPool is specified for [pool_attachment]
    // then a new [ObjectPool] will be attached to the code object as well.
    // Otherwise the caller is responsible for doing this via
    // `Object::set_object_pool()`.
    static CodePtr FinalizeCode(FlowGraphCompiler* compiler,
                                compiler::Assembler* assembler,
                                PoolAttachment pool_attachment,
                                bool optimized,
                                CodeStatistics* stats);
  
    // Notifies all active [CodeObserver]s.
    static void NotifyCodeObservers(const Code& code, bool optimized);
    static void NotifyCodeObservers(const Function& function,
                                    const Code& code,
                                    bool optimized);
    static void NotifyCodeObservers(const char* name,
                                    const Code& code,
                                    bool optimized);
  
    // Calls [FinalizeCode] and also notifies [CodeObserver]s.
    static CodePtr FinalizeCodeAndNotify(const Function& function,
                                         FlowGraphCompiler* compiler,
                                         compiler::Assembler* assembler,
                                         PoolAttachment pool_attachment,
                                         bool optimized = false,
                                         CodeStatistics* stats = nullptr);
    static CodePtr FinalizeCodeAndNotify(const char* name,
                                         FlowGraphCompiler* compiler,
                                         compiler::Assembler* assembler,
                                         PoolAttachment pool_attachment,
                                         bool optimized = false,
                                         CodeStatistics* stats = nullptr);
  
  #endif
    static CodePtr LookupCode(uword pc);
    static CodePtr LookupCodeInVmIsolate(uword pc);
    static CodePtr FindCode(uword pc, int64_t timestamp);
  
    int32_t GetPointerOffsetAt(int index) const {
      NoSafepointScope no_safepoint;
      return *PointerOffsetAddrAt(index);
    }
    TokenPosition GetTokenIndexOfPC(uword pc) const;
  
    // Find pc, return 0 if not found.
    uword GetPcForDeoptId(intptr_t deopt_id,
                          UntaggedPcDescriptors::Kind kind) const;
    intptr_t GetDeoptIdForOsr(uword pc) const;
  
    const char* Name() const;
    const char* QualifiedName(const NameFormattingParams& params) const;
  
    int64_t compile_timestamp() const {
  #if defined(PRODUCT)
      return 0;
  #else
      return untag()->compile_timestamp_;
  #endif
    }
  
    bool IsStubCode() const;
    bool IsAllocationStubCode() const;
    bool IsTypeTestStubCode() const;
    bool IsFunctionCode() const;
  
    // Returns true if this Code object represents
    // Dart function code without any additional information.
    bool IsUnknownDartCode() const { return IsUnknownDartCode(ptr()); }
    static bool IsUnknownDartCode(CodePtr code);
  
    void DisableDartCode() const;
  
    void DisableStubCode() const;
  
    void Enable() const {
      if (!IsDisabled()) return;
      ResetActiveInstructions();
    }
  
    bool IsDisabled() const { return IsDisabled(ptr()); }
    static bool IsDisabled(CodePtr code) {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
      return false;
  #else
      return code->untag()->instructions() !=
             code->untag()->active_instructions();
  #endif
    }
  
    void set_object_pool(ObjectPoolPtr object_pool) const {
      untag()->set_object_pool(object_pool);
    }
  
   private:
    void set_state_bits(intptr_t bits) const;
  
    friend class UntaggedObject;  // For UntaggedObject::SizeFromClass().
    friend class UntaggedCode;
    friend struct RelocatorTestHelper;
  
    enum {
      kOptimizedBit = 0,
      kForceOptimizedBit = 1,
      kAliveBit = 2,
      kDiscardedBit = 3,
      kPtrOffBit = 4,
      kPtrOffSize = kBitsPerInt32 - kPtrOffBit,
    };
  
    class OptimizedBit : public BitField<int32_t, bool, kOptimizedBit, 1> {};
  
    // Force-optimized is true if the Code was generated for a function with
    // Function::ForceOptimize().
    class ForceOptimizedBit
        : public BitField<int32_t, bool, kForceOptimizedBit, 1> {};
  
    class AliveBit : public BitField<int32_t, bool, kAliveBit, 1> {};
  
    // Set by precompiler if this Code object doesn't contain
    // useful information besides instructions and compressed stack map.
    // Such objects are serialized in a shorter form and replaced with
    // StubCode::UnknownDartCode() during snapshot deserialization.
    class DiscardedBit : public BitField<int32_t, bool, kDiscardedBit, 1> {};
  
    class PtrOffBits
        : public BitField<int32_t, intptr_t, kPtrOffBit, kPtrOffSize> {};
  
    class SlowFindRawCodeVisitor : public FindObjectVisitor {
     public:
      explicit SlowFindRawCodeVisitor(uword pc) : pc_(pc) {}
      virtual ~SlowFindRawCodeVisitor() {}
  
      // Check if object matches find condition.
      virtual bool FindObject(ObjectPtr obj) const;
  
     private:
      const uword pc_;
  
      DISALLOW_COPY_AND_ASSIGN(SlowFindRawCodeVisitor);
    };
  
    static const intptr_t kEntrySize = sizeof(int32_t);  // NOLINT
  
    void set_compile_timestamp(int64_t timestamp) const {
  #if defined(PRODUCT)
      UNREACHABLE();
  #else
      StoreNonPointer(&untag()->compile_timestamp_, timestamp);
  #endif
    }
  
    // Initializes the cached entrypoint addresses in [code] as calculated
    // from [instructions] and [unchecked_offset].
    static void InitializeCachedEntryPointsFrom(CodePtr code,
                                                InstructionsPtr instructions,
                                                uint32_t unchecked_offset);
  
    // Sets [active_instructions_] to [instructions] and updates the cached
    // entry point addresses.
    void SetActiveInstructions(const Instructions& instructions,
                               uint32_t unchecked_offset) const;
    void SetActiveInstructionsSafe(const Instructions& instructions,
                                   uint32_t unchecked_offset) const;
  
    // Resets [active_instructions_] to its original value of [instructions_] and
    // updates the cached entry point addresses to match.
    void ResetActiveInstructions() const;
  
    void set_instructions(const Instructions& instructions) const {
      ASSERT(Thread::Current()->IsMutatorThread() || !is_alive());
      untag()->set_instructions(instructions.ptr());
    }
  #if !defined(DART_PRECOMPILED_RUNTIME)
    void set_unchecked_offset(uword offset) const {
      StoreNonPointer(&untag()->unchecked_offset_, offset);
    }
  #endif
  
    // Returns the unchecked entry point offset for [instructions_].
    uint32_t UncheckedEntryPointOffset() const {
      return UncheckedEntryPointOffsetOf(ptr());
    }
    static uint32_t UncheckedEntryPointOffsetOf(CodePtr code) {
  #if defined(DART_PRECOMPILED_RUNTIME)
      UNREACHABLE();
  #else
      return code->untag()->unchecked_offset_;
  #endif
    }
  
    void set_pointer_offsets_length(intptr_t value) {
      // The number of fixups is limited to 1-billion.
      ASSERT(Utils::IsUint(30, value));
      set_state_bits(PtrOffBits::update(value, untag()->state_bits_));
    }
    int32_t* PointerOffsetAddrAt(int index) const {
      ASSERT(index >= 0);
      ASSERT(index < pointer_offsets_length());
      // TODO(iposva): Unit test is missing for this functionality.
      return &UnsafeMutableNonPointer(untag()->data())[index];
    }
    void SetPointerOffsetAt(int index, int32_t offset_in_instructions) {
      NoSafepointScope no_safepoint;
      *PointerOffsetAddrAt(index) = offset_in_instructions;
    }
  
    intptr_t BinarySearchInSCallTable(uword pc) const;
    static CodePtr LookupCodeInIsolateGroup(IsolateGroup* isolate_group,
                                            uword pc);
  
    // New is a private method as RawInstruction and RawCode objects should
    // only be created using the Code::FinalizeCode method. This method creates
    // the RawInstruction and RawCode objects, sets up the pointer offsets
    // and links the two in a GC safe manner.
    static CodePtr New(intptr_t pointer_offsets_length);
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Code, Object);
    friend class Class;
    friend class CodeTestHelper;
    friend class StubCode;     // for set_object_pool
    friend class Precompiler;  // for set_object_pool
    friend class FunctionSerializationCluster;
    friend class CodeSerializationCluster;
    friend class CodeDeserializationCluster;
    friend class Deserializer;           // for InitializeCachedEntryPointsFrom
    friend class StubCode;               // for set_object_pool
    friend class MegamorphicCacheTable;  // for set_object_pool
    friend class CodePatcher;            // for set_instructions
    friend class ProgramVisitor;         // for set_instructions
    // So that the UntaggedFunction pointer visitor can determine whether code the
    // function points to is optimized.
    friend class UntaggedFunction;
    friend class CallSiteResetter;
    friend class CodeKeyValueTrait;  // for UncheckedEntryPointOffset
  };
  
  class Context : public Object {
   public:
    ContextPtr parent() const { return untag()->parent(); }
    void set_parent(const Context& parent) const {
      untag()->set_parent(parent.ptr());
    }
    static intptr_t parent_offset() {
      return OFFSET_OF(UntaggedContext, parent_);
    }
  
    intptr_t num_variables() const { return untag()->num_variables_; }
    static intptr_t num_variables_offset() {
      return OFFSET_OF(UntaggedContext, num_variables_);
    }
    static intptr_t NumVariables(const ContextPtr context) {
      return context->untag()->num_variables_;
    }
  
    ObjectPtr At(intptr_t context_index) const {
      return untag()->element(context_index);
    }
    inline void SetAt(intptr_t context_index, const Object& value) const;
  
    intptr_t GetLevel() const;
  
    void Dump(int indent = 0) const;
  
    static const intptr_t kBytesPerElement = kCompressedWordSize;
    static const intptr_t kMaxElements = kSmiMax / kBytesPerElement;
  
    static const intptr_t kAwaitJumpVarIndex = 0;
    static const intptr_t kAsyncFutureIndex = 1;
    static const intptr_t kControllerIndex = 1;
    // Expected context index of chained futures in recognized async functions.
    // These are used to unwind async stacks.
    static const intptr_t kIsSyncIndex = 2;
  
    struct ArrayTraits {
      static intptr_t elements_start_offset() { return sizeof(UntaggedContext); }
      static constexpr intptr_t kElementSize = kBytesPerElement;
    };
  
    static intptr_t variable_offset(intptr_t context_index) {
      return OFFSET_OF_RETURNED_VALUE(UntaggedContext, data) +
             (kBytesPerElement * context_index);
    }
  
    static bool IsValidLength(intptr_t len) {
      return 0 <= len && len <= compiler::target::Context::kMaxElements;
    }
  
    static intptr_t InstanceSize() {
      ASSERT(sizeof(UntaggedContext) ==
             OFFSET_OF_RETURNED_VALUE(UntaggedContext, data));
      return 0;
    }
  
    static intptr_t InstanceSize(intptr_t len) {
      ASSERT(IsValidLength(len));
      return RoundedAllocationSize(sizeof(UntaggedContext) +
                                   (len * kBytesPerElement));
    }
  
    static ContextPtr New(intptr_t num_variables, Heap::Space space = Heap::kNew);
  
   private:
    void set_num_variables(intptr_t num_variables) const {
      StoreNonPointer(&untag()->num_variables_, num_variables);
    }
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Context, Object);
    friend class Class;
    friend class Object;
  };
  
  // The ContextScope class makes it possible to delay the compilation of a local
  // function until it is invoked. A ContextScope instance collects the local
  // variables that are referenced by the local function to be compiled and that
  // belong to the outer scopes, that is, to the local scopes of (possibly nested)
  // functions enclosing the local function. Each captured variable is represented
  // by its token position in the source, its name, its type, its allocation index
  // in the context, and its context level. The function nesting level and loop
  // nesting level are not preserved, since they are only used until the context
  // level is assigned. In addition the ContextScope has a field 'is_implicit'
  // which is true if the ContextScope was created for an implicit closure.
  class ContextScope : public Object {
   public:
    intptr_t num_variables() const { return untag()->num_variables_; }
  
    TokenPosition TokenIndexAt(intptr_t scope_index) const;
    void SetTokenIndexAt(intptr_t scope_index, TokenPosition token_pos) const;
  
    TokenPosition DeclarationTokenIndexAt(intptr_t scope_index) const;
    void SetDeclarationTokenIndexAt(intptr_t scope_index,
                                    TokenPosition declaration_token_pos) const;
  
    StringPtr NameAt(intptr_t scope_index) const;
    void SetNameAt(intptr_t scope_index, const String& name) const;
  
    void ClearFlagsAt(intptr_t scope_index) const;
  
    bool IsFinalAt(intptr_t scope_index) const;
    void SetIsFinalAt(intptr_t scope_index, bool is_final) const;
  
    bool IsLateAt(intptr_t scope_index) const;
    void SetIsLateAt(intptr_t scope_index, bool is_late) const;
  
    intptr_t LateInitOffsetAt(intptr_t scope_index) const;
    void SetLateInitOffsetAt(intptr_t scope_index,
                             intptr_t late_init_offset) const;
  
    bool IsConstAt(intptr_t scope_index) const;
    void SetIsConstAt(intptr_t scope_index, bool is_const) const;
  
    AbstractTypePtr TypeAt(intptr_t scope_index) const;
    void SetTypeAt(intptr_t scope_index, const AbstractType& type) const;
  
    InstancePtr ConstValueAt(intptr_t scope_index) const;
    void SetConstValueAt(intptr_t scope_index, const Instance& value) const;
  
    intptr_t ContextIndexAt(intptr_t scope_index) const;
    void SetContextIndexAt(intptr_t scope_index, intptr_t context_index) const;
  
    intptr_t ContextLevelAt(intptr_t scope_index) const;
    void SetContextLevelAt(intptr_t scope_index, intptr_t context_level) const;
  
    static const intptr_t kBytesPerElement =
        sizeof(UntaggedContextScope::VariableDesc);
    static const intptr_t kMaxElements = kSmiMax / kBytesPerElement;
  
    struct ArrayTraits {
      static intptr_t elements_start_offset() {
        return sizeof(UntaggedContextScope);
      }
      static constexpr intptr_t kElementSize = kBytesPerElement;
    };
  
    static intptr_t InstanceSize() {
      ASSERT(sizeof(UntaggedContextScope) ==
             OFFSET_OF_RETURNED_VALUE(UntaggedContextScope, data));
      return 0;
    }
  
    static intptr_t InstanceSize(intptr_t len) {
      ASSERT(0 <= len && len <= kMaxElements);
      return RoundedAllocationSize(sizeof(UntaggedContextScope) +
                                   (len * kBytesPerElement));
    }
  
    static ContextScopePtr New(intptr_t num_variables, bool is_implicit);
  
   private:
    void set_num_variables(intptr_t num_variables) const {
      StoreNonPointer(&untag()->num_variables_, num_variables);
    }
  
    void set_is_implicit(bool is_implicit) const {
      StoreNonPointer(&untag()->is_implicit_, is_implicit);
    }
  
    const UntaggedContextScope::VariableDesc* VariableDescAddr(
        intptr_t index) const {
      ASSERT((index >= 0) && (index < num_variables()));
      return untag()->VariableDescAddr(index);
    }
  
    bool GetFlagAt(intptr_t scope_index, intptr_t mask) const;
    void SetFlagAt(intptr_t scope_index, intptr_t mask, bool value) const;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(ContextScope, Object);
    friend class Class;
    friend class Object;
  };
  
  // Class of special sentinel values:
  // - Object::sentinel() is a value that cannot be produced by Dart code.
  // It can be used to mark special values, for example to distinguish
  // "uninitialized" fields.
  // - Object::transition_sentinel() is a value marking that we are transitioning
  // from sentinel, e.g., computing a field value. Used to detect circular
  // initialization of static fields.
  // - Object::unknown_constant() and Object::non_constant() are optimizing
  // compiler's constant propagation constants.
  class Sentinel : public Object {
   public:
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedSentinel));
    }
  
    static SentinelPtr New();
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Sentinel, Object);
    friend class Class;
    friend class Object;
  };
  
  class MegamorphicCache : public CallSiteData {
   public:
    static const intptr_t kInitialCapacity = 16;
    static const intptr_t kSpreadFactor = 7;
    static const double kLoadFactor;
  
    enum EntryType {
      kClassIdIndex,
      kTargetFunctionIndex,
      kEntryLength,
    };
  
    ArrayPtr buckets() const;
    void set_buckets(const Array& buckets) const;
  
    intptr_t mask() const;
    void set_mask(intptr_t mask) const;
  
    intptr_t filled_entry_count() const;
    void set_filled_entry_count(intptr_t num) const;
  
    static intptr_t buckets_offset() {
      return OFFSET_OF(UntaggedMegamorphicCache, buckets_);
    }
    static intptr_t mask_offset() {
      return OFFSET_OF(UntaggedMegamorphicCache, mask_);
    }
    static intptr_t arguments_descriptor_offset() {
      return OFFSET_OF(UntaggedMegamorphicCache, args_descriptor_);
    }
  
    static MegamorphicCachePtr New(const String& target_name,
                                   const Array& arguments_descriptor);
  
    void EnsureContains(const Smi& class_id, const Object& target) const;
    ObjectPtr Lookup(const Smi& class_id) const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedMegamorphicCache));
    }
  
   private:
    friend class Class;
    friend class MegamorphicCacheTable;
    friend class ProgramVisitor;
  
    static MegamorphicCachePtr New();
  
    // The caller must hold IsolateGroup::type_feedback_mutex().
    void InsertLocked(const Smi& class_id, const Object& target) const;
    void EnsureCapacityLocked() const;
    ObjectPtr LookupLocked(const Smi& class_id) const;
  
    void InsertEntryLocked(const Smi& class_id, const Object& target) const;
  
    static inline void SetEntry(const Array& array,
                                intptr_t index,
                                const Smi& class_id,
                                const Object& target);
  
    static inline ObjectPtr GetClassId(const Array& array, intptr_t index);
    static inline ObjectPtr GetTargetFunction(const Array& array, intptr_t index);
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(MegamorphicCache, CallSiteData);
  };
  
  class SubtypeTestCache : public Object {
   public:
    enum Entries {
      kTestResult = 0,
      kInstanceCidOrSignature = 1,
      kDestinationType = 2,
      kInstanceTypeArguments = 3,
      kInstantiatorTypeArguments = 4,
      kFunctionTypeArguments = 5,
      kInstanceParentFunctionTypeArguments = 6,
      kInstanceDelayedFunctionTypeArguments = 7,
      kTestEntryLength = 8,
    };
  
    virtual intptr_t NumberOfChecks() const;
    void 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;
    void 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;
  
    // Like GetCheck(), but does not require the subtype test cache mutex and so
    // may see an outdated view of the cache.
    void 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;
  
    // Returns whether all the elements of an existing cache entry, excluding
    // the result, match the non-pointer arguments. The pointer arguments are
    // out parameters as follows:
    //
    // If [index] is not nullptr, then it is set to the matching entry's index.
    // If [result] is not nullptr, then it is set to the matching entry's result.
    bool 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;
  
    // Writes the cache entry at index [index] to the given text buffer.
    //
    // The output is comma separated on a single line if [line_prefix] is nullptr,
    // otherwise line breaks followed by [line_prefix] is used as a separator.
    void WriteEntryToBuffer(Zone* zone,
                            BaseTextBuffer* buffer,
                            intptr_t index,
                            const char* line_prefix = nullptr) const;
  
    // Like WriteEntryToBuffer(), but does not require the subtype test cache
    // mutex and so may see an outdated view of the cache.
    void WriteCurrentEntryToBuffer(Zone* zone,
                                   BaseTextBuffer* buffer,
                                   intptr_t index,
                                   const char* line_prefix = nullptr) const;
    void Reset() const;
  
    // Tests that [other] contains the same entries in the same order.
    bool Equals(const SubtypeTestCache& other) const;
  
    // Creates a separate copy of the current STC contents.
    SubtypeTestCachePtr Copy(Thread* thread) const;
  
    static SubtypeTestCachePtr New();
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedSubtypeTestCache));
    }
  
    static intptr_t cache_offset() {
      return OFFSET_OF(UntaggedSubtypeTestCache, cache_);
    }
  
    static void Init();
    static void Cleanup();
  
    ArrayPtr cache() const;
  
   private:
    void set_cache(const Array& value) const;
  
    // A VM heap allocated preinitialized empty subtype entry array.
    static ArrayPtr cached_array_;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(SubtypeTestCache, Object);
    friend class Class;
    friend class VMSerializationRoots;
    friend class VMDeserializationRoots;
  };
  
  class LoadingUnit : public Object {
   public:
    static constexpr intptr_t kIllegalId = 0;
    COMPILE_ASSERT(kIllegalId == WeakTable::kNoValue);
    static constexpr intptr_t kRootId = 1;
  
    static LoadingUnitPtr New();
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedLoadingUnit));
    }
  
    LoadingUnitPtr parent() const;
    void set_parent(const LoadingUnit& value) const;
  
    ArrayPtr base_objects() const;
    void set_base_objects(const Array& value) const;
  
    intptr_t id() const { return untag()->id_; }
    void set_id(intptr_t id) const { StoreNonPointer(&untag()->id_, id); }
  
    // True once the VM deserializes this unit's snapshot.
    bool loaded() const { return untag()->loaded_; }
    void set_loaded(bool value) const {
      StoreNonPointer(&untag()->loaded_, value);
    }
  
    // True once the VM invokes the embedder's deferred load callback until the
    // embedder calls Dart_DeferredLoadComplete[Error].
    bool load_outstanding() const { return untag()->load_outstanding_; }
    void set_load_outstanding(bool value) const {
      StoreNonPointer(&untag()->load_outstanding_, value);
    }
  
    ObjectPtr IssueLoad() const;
    ObjectPtr CompleteLoad(const String& error_message,
                           bool transient_error) const;
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(LoadingUnit, Object);
    friend class Class;
  };
  
  class Error : public Object {
   public:
    virtual const char* ToErrorCString() const;
  
   private:
    HEAP_OBJECT_IMPLEMENTATION(Error, Object);
  };
  
  class ApiError : public Error {
   public:
    StringPtr message() const { return untag()->message(); }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedApiError));
    }
  
    static ApiErrorPtr New(const String& message, Heap::Space space = Heap::kNew);
  
    virtual const char* ToErrorCString() const;
  
   private:
    void set_message(const String& message) const;
  
    static ApiErrorPtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(ApiError, Error);
    friend class Class;
  };
  
  class LanguageError : public Error {
   public:
    Report::Kind kind() const {
      return static_cast<Report::Kind>(untag()->kind_);
    }
  
    // Build, cache, and return formatted message.
    StringPtr FormatMessage() const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedLanguageError));
    }
  
    // A null script means no source and a negative token_pos means no position.
    static LanguageErrorPtr NewFormatted(const Error& prev_error,
                                         const Script& script,
                                         TokenPosition token_pos,
                                         bool report_after_token,
                                         Report::Kind kind,
                                         Heap::Space space,
                                         const char* format,
                                         ...) PRINTF_ATTRIBUTE(7, 8);
  
    static LanguageErrorPtr 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);
  
    static LanguageErrorPtr New(const String& formatted_message,
                                Report::Kind kind = Report::kError,
                                Heap::Space space = Heap::kNew);
  
    virtual const char* ToErrorCString() const;
  
    TokenPosition token_pos() const { return untag()->token_pos_; }
  
   private:
    ErrorPtr previous_error() const { return untag()->previous_error(); }
    void set_previous_error(const Error& value) const;
  
    ScriptPtr script() const { return untag()->script(); }
    void set_script(const Script& value) const;
  
    void set_token_pos(TokenPosition value) const;
  
    bool report_after_token() const { return untag()->report_after_token_; }
    void set_report_after_token(bool value);
  
    void set_kind(uint8_t value) const;
  
    StringPtr message() const { return untag()->message(); }
    void set_message(const String& value) const;
  
    StringPtr formatted_message() const { return untag()->formatted_message(); }
    void set_formatted_message(const String& value) const;
  
    static LanguageErrorPtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(LanguageError, Error);
    friend class Class;
  };
  
  class UnhandledException : public Error {
   public:
    InstancePtr exception() const { return untag()->exception(); }
    static intptr_t exception_offset() {
      return OFFSET_OF(UntaggedUnhandledException, exception_);
    }
  
    InstancePtr stacktrace() const { return untag()->stacktrace(); }
    static intptr_t stacktrace_offset() {
      return OFFSET_OF(UntaggedUnhandledException, stacktrace_);
    }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedUnhandledException));
    }
  
    static UnhandledExceptionPtr New(const Instance& exception,
                                     const Instance& stacktrace,
                                     Heap::Space space = Heap::kNew);
  
    virtual const char* ToErrorCString() const;
  
   private:
    static UnhandledExceptionPtr New(Heap::Space space = Heap::kNew);
  
    void set_exception(const Instance& exception) const;
    void set_stacktrace(const Instance& stacktrace) const;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(UnhandledException, Error);
    friend class Class;
    friend class ObjectStore;
  };
  
  class UnwindError : public Error {
   public:
    bool is_user_initiated() const { return untag()->is_user_initiated_; }
    void set_is_user_initiated(bool value) const;
  
    StringPtr message() const { return untag()->message(); }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedUnwindError));
    }
  
    static UnwindErrorPtr New(const String& message,
                              Heap::Space space = Heap::kNew);
  
    virtual const char* ToErrorCString() const;
  
   private:
    void set_message(const String& message) const;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(UnwindError, Error);
    friend class Class;
  };
  
  // Instance is the base class for all instance objects (aka the Object class
  // in Dart source code.
  class Instance : public Object {
   public:
    // Equality and identity testing.
    // 1. OperatorEquals: true iff 'this == other' is true in Dart code.
    // 2. IsIdenticalTo: true iff 'identical(this, other)' is true in Dart code.
    // 3. CanonicalizeEquals: used to canonicalize compile-time constants, e.g.,
    //    using bitwise equality of fields and list elements.
    // Subclasses where 1 and 3 coincide may also define a plain Equals, e.g.,
    // String and Integer.
    virtual bool OperatorEquals(const Instance& other) const;
    bool IsIdenticalTo(const Instance& other) const;
    virtual bool CanonicalizeEquals(const Instance& other) const;
    virtual uint32_t CanonicalizeHash() const;
  
    intptr_t SizeFromClass() const {
  #if defined(DEBUG)
      const Class& cls = Class::Handle(clazz());
      ASSERT(cls.is_finalized() || cls.is_prefinalized());
  #endif
      return (clazz()->untag()->host_instance_size_in_words_ *
              kCompressedWordSize);
    }
  
    InstancePtr Canonicalize(Thread* thread) const;
    // Caller must hold IsolateGroup::constant_canonicalization_mutex_.
    virtual InstancePtr CanonicalizeLocked(Thread* thread) const;
    virtual void CanonicalizeFieldsLocked(Thread* thread) const;
  
    InstancePtr CopyShallowToOldSpace(Thread* thread) const;
  
  #if defined(DEBUG)
    // Check if instance is canonical.
    virtual bool CheckIsCanonical(Thread* thread) const;
  #endif  // DEBUG
  
    ObjectPtr GetField(const Field& field) const;
  
    void SetField(const Field& field, const Object& value) const;
  
    AbstractTypePtr GetType(Heap::Space space) const;
  
    // Access the arguments of the [Type] of this [Instance].
    // Note: for [Type]s instead of [Instance]s with a [Type] attached, use
    // [arguments()] and [set_arguments()]
    virtual TypeArgumentsPtr GetTypeArguments() const;
    virtual void SetTypeArguments(const TypeArguments& value) const;
  
    // Check if the type of this instance is a subtype of the given other type.
    // The type argument vectors are used to instantiate the other type if needed.
    bool IsInstanceOf(const AbstractType& other,
                      const TypeArguments& other_instantiator_type_arguments,
                      const TypeArguments& other_function_type_arguments) const;
  
    // Check if this instance is assignable to the given other type.
    // The type argument vectors are used to instantiate the other type if needed.
    bool IsAssignableTo(const AbstractType& other,
                        const TypeArguments& other_instantiator_type_arguments,
                        const TypeArguments& other_function_type_arguments) const;
  
    // Return true if the null instance can be assigned to a variable of [other]
    // type. Return false if null cannot be assigned or we cannot tell (if
    // [other] is a type parameter in NNBD strong mode). Only used for checks at
    // compile time.
    static bool NullIsAssignableTo(const AbstractType& other);
  
    // Return true if the null instance can be assigned to a variable of [other]
    // type. Return false if null cannot be assigned. Used for checks at runtime,
    // when the instantiator and function type argument vectors are available.
    static bool NullIsAssignableTo(
        const AbstractType& other,
        const TypeArguments& other_instantiator_type_arguments,
        const TypeArguments& other_function_type_arguments);
  
    bool IsValidNativeIndex(int index) const {
      return ((index >= 0) && (index < clazz()->untag()->num_native_fields_));
    }
  
    intptr_t* NativeFieldsDataAddr() const;
    inline intptr_t GetNativeField(int index) const;
    inline void GetNativeFields(uint16_t num_fields,
                                intptr_t* field_values) const;
    void SetNativeFields(uint16_t num_fields, const intptr_t* field_values) const;
  
    uint16_t NumNativeFields() const {
      return clazz()->untag()->num_native_fields_;
    }
  
    void SetNativeField(int index, intptr_t value) const;
  
    // If the instance is a callable object, i.e. a closure or the instance of a
    // class implementing a 'call' method, return true and set the function
    // (if not NULL) to call.
    bool IsCallable(Function* function) const;
  
    ObjectPtr Invoke(const String& selector,
                     const Array& arguments,
                     const Array& argument_names,
                     bool respect_reflectable = true,
                     bool check_is_entrypoint = false) const;
    ObjectPtr InvokeGetter(const String& selector,
                           bool respect_reflectable = true,
                           bool check_is_entrypoint = false) const;
    ObjectPtr InvokeSetter(const String& selector,
                           const Instance& argument,
                           bool respect_reflectable = true,
                           bool check_is_entrypoint = false) const;
  
    // Evaluate the given expression as if it appeared in an instance method of
    // this instance and return the resulting value, or an error object if
    // evaluating the expression fails. The method has the formal (type)
    // parameters given in (type_)param_names, and is invoked with the (type)
    // argument values given in (type_)param_values.
    ObjectPtr EvaluateCompiledExpression(
        const Class& method_cls,
        const ExternalTypedData& kernel_buffer,
        const Array& type_definitions,
        const Array& param_values,
        const TypeArguments& type_param_values) const;
  
    // Equivalent to invoking hashCode on this instance.
    virtual ObjectPtr HashCode() const;
  
    // Equivalent to invoking identityHashCode with this instance.
    ObjectPtr IdentityHashCode() const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedInstance));
    }
  
    static InstancePtr New(const Class& cls, Heap::Space space = Heap::kNew);
  
    // Array/list element address computations.
    static intptr_t DataOffsetFor(intptr_t cid);
    static intptr_t ElementSizeFor(intptr_t cid);
  
    // Pointers may be subtyped, but their subtypes may not get extra fields.
    // The subtype runtime representation has exactly the same object layout,
    // only the class_id is different. So, it is safe to use subtype instances in
    // Pointer handles.
    virtual bool IsPointer() const;
  
    static intptr_t NextFieldOffset() { return sizeof(UntaggedInstance); }
  
    static intptr_t NativeFieldsOffset() { return sizeof(UntaggedObject); }
  
   protected:
  #ifndef PRODUCT
    virtual void PrintSharedInstanceJSON(JSONObject* jsobj, bool ref) const;
  #endif
  
   private:
    // Return true if the runtimeType of this instance is a subtype of other type.
    bool RuntimeTypeIsSubtypeOf(
        const AbstractType& other,
        const TypeArguments& other_instantiator_type_arguments,
        const TypeArguments& other_function_type_arguments) const;
  
    // Returns true if the type of this instance is a subtype of FutureOr<T>
    // specified by instantiated type 'other'.
    // Returns false if other type is not a FutureOr.
    bool RuntimeTypeIsSubtypeOfFutureOr(Zone* zone,
                                        const AbstractType& other) const;
  
    // Return true if the null instance is an instance of other type.
    static bool NullIsInstanceOf(
        const AbstractType& other,
        const TypeArguments& other_instantiator_type_arguments,
        const TypeArguments& other_function_type_arguments);
  
    CompressedObjectPtr* FieldAddrAtOffset(intptr_t offset) const {
      ASSERT(IsValidFieldOffset(offset));
      return reinterpret_cast<CompressedObjectPtr*>(raw_value() - kHeapObjectTag +
                                                    offset);
    }
    CompressedObjectPtr* FieldAddr(const Field& field) const {
      return FieldAddrAtOffset(field.HostOffset());
    }
    CompressedObjectPtr* NativeFieldsAddr() const {
      return FieldAddrAtOffset(sizeof(UntaggedObject));
    }
    void SetFieldAtOffset(intptr_t offset, const Object& value) const {
      StoreCompressedPointer(FieldAddrAtOffset(offset), value.ptr());
    }
    bool IsValidFieldOffset(intptr_t offset) const;
  
    // The following raw methods are used for morphing.
    // They are needed due to the extraction of the class in IsValidFieldOffset.
    CompressedObjectPtr* RawFieldAddrAtOffset(intptr_t offset) const {
      return reinterpret_cast<CompressedObjectPtr*>(raw_value() - kHeapObjectTag +
                                                    offset);
    }
    ObjectPtr RawGetFieldAtOffset(intptr_t offset) const {
      return RawFieldAddrAtOffset(offset)->Decompress(untag()->heap_base());
    }
    void RawSetFieldAtOffset(intptr_t offset, const Object& value) const {
      StoreCompressedPointer(RawFieldAddrAtOffset(offset), value.ptr());
    }
  
    static InstancePtr NewFromCidAndSize(SharedClassTable* shared_class_table,
                                         classid_t cid,
                                         Heap::Space heap = Heap::kNew);
  
    // TODO(iposva): Determine if this gets in the way of Smi.
    HEAP_OBJECT_IMPLEMENTATION(Instance, Object);
    friend class ByteBuffer;
    friend class Class;
    friend class Closure;
    friend class Pointer;
    friend class DeferredObject;
    friend class RegExp;
    friend class StubCode;
    friend class TypedDataView;
    friend class InstanceSerializationCluster;
    friend class InstanceDeserializationCluster;
    friend class ClassDeserializationCluster;  // vtable
    friend class InstanceMorpher;
    friend class Obfuscator;  // RawGetFieldAtOffset, RawSetFieldAtOffset
  };
  
  class LibraryPrefix : public Instance {
   public:
    StringPtr name() const { return untag()->name(); }
    virtual StringPtr DictionaryName() const { return name(); }
  
    ArrayPtr imports() const { return untag()->imports(); }
    intptr_t num_imports() const { return untag()->num_imports_; }
    LibraryPtr importer() const { return untag()->importer(); }
  
    LibraryPtr GetLibrary(int index) const;
    void AddImport(const Namespace& import) const;
  
    bool is_deferred_load() const { return untag()->is_deferred_load_; }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedLibraryPrefix));
    }
  
    static LibraryPrefixPtr New(const String& name,
                                const Namespace& import,
                                bool deferred_load,
                                const Library& importer);
  
   private:
    static const int kInitialSize = 2;
    static const int kIncrementSize = 2;
  
    void set_name(const String& value) const;
    void set_imports(const Array& value) const;
    void set_num_imports(intptr_t value) const;
    void set_importer(const Library& value) const;
  
    static LibraryPrefixPtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(LibraryPrefix, Instance);
    friend class Class;
  };
  
  // TypeParameters represents a list of formal type parameters with their bounds
  // and their default values as calculated by CFE.
  class TypeParameters : public Object {
   public:
    intptr_t Length() const;
  
    static intptr_t names_offset() {
      return OFFSET_OF(UntaggedTypeParameters, names_);
    }
    StringPtr NameAt(intptr_t index) const;
    void SetNameAt(intptr_t index, const String& value) const;
  
    static intptr_t flags_offset() {
      return OFFSET_OF(UntaggedTypeParameters, flags_);
    }
  
    static intptr_t bounds_offset() {
      return OFFSET_OF(UntaggedTypeParameters, bounds_);
    }
    AbstractTypePtr BoundAt(intptr_t index) const;
    void SetBoundAt(intptr_t index, const AbstractType& value) const;
    bool AllDynamicBounds() const;
  
    static intptr_t defaults_offset() {
      return OFFSET_OF(UntaggedTypeParameters, defaults_);
    }
    AbstractTypePtr DefaultAt(intptr_t index) const;
    void SetDefaultAt(intptr_t index, const AbstractType& value) const;
    bool AllDynamicDefaults() const;
  
    // The isGenericCovariantImpl bits are packed into SMIs in the flags array,
    // but omitted if they're 0.
    bool IsGenericCovariantImplAt(intptr_t index) const;
    void SetIsGenericCovariantImplAt(intptr_t index, bool value) const;
  
    // The number of flags per Smi should be a power of 2 in order to simplify the
    // generated code accessing the flags array.
  #if !defined(DART_COMPRESSED_POINTERS)
    static const intptr_t kFlagsPerSmiShift = kBitsPerWordLog2 - 1;
  #else
    static const intptr_t kFlagsPerSmiShift = kBitsPerWordLog2 - 2;
  #endif
    static const intptr_t kFlagsPerSmi = 1LL << kFlagsPerSmiShift;
    COMPILE_ASSERT(kFlagsPerSmi < kSmiBits);
    static const intptr_t kFlagsPerSmiMask = kFlagsPerSmi - 1;
  
    void Print(Thread* thread,
               Zone* zone,
               bool are_class_type_parameters,
               intptr_t base,
               NameVisibility name_visibility,
               BaseTextBuffer* printer) const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedTypeParameters));
    }
  
    static TypeParametersPtr New(Heap::Space space = Heap::kOld);
    static TypeParametersPtr New(intptr_t count, Heap::Space space = Heap::kOld);
  
   private:
    ArrayPtr names() const { return untag()->names(); }
    void set_names(const Array& value) const;
    ArrayPtr flags() const { return untag()->flags(); }
    void set_flags(const Array& value) const;
    TypeArgumentsPtr bounds() const { return untag()->bounds(); }
    void set_bounds(const TypeArguments& value) const;
    TypeArgumentsPtr defaults() const { return untag()->defaults(); }
    void set_defaults(const TypeArguments& value) const;
  
    // Allocate and initialize the flags array to zero.
    void AllocateFlags(Heap::Space space) const;
    // Reset the flags array to null if all flags are zero.
    void OptimizeFlags() const;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(TypeParameters, Object);
    friend class Class;
    friend class ClassFinalizer;
    friend class Function;
    friend class FunctionType;
    friend class Object;
    friend class Precompiler;
  };
  
  // A TypeArguments is an array of AbstractType.
  class TypeArguments : public Instance {
   public:
    // Hash value for a type argument vector consisting solely of dynamic types.
    static const intptr_t kAllDynamicHash = 1;
  
    // Returns whether this TypeArguments vector can be used in a context that
    // expects a vector of length [count]. Always true for the null vector.
    bool HasCount(intptr_t count) const;
    static intptr_t length_offset() {
      return OFFSET_OF(UntaggedTypeArguments, length_);
    }
    intptr_t Length() const;
    AbstractTypePtr TypeAt(intptr_t index) const;
    AbstractTypePtr TypeAtNullSafe(intptr_t index) const;
    static intptr_t types_offset() {
      return OFFSET_OF_RETURNED_VALUE(UntaggedTypeArguments, types);
    }
    static intptr_t type_at_offset(intptr_t index) {
      return types_offset() + index * kCompressedWordSize;
    }
    void SetTypeAt(intptr_t index, const AbstractType& value) const;
  
    struct ArrayTraits {
      static intptr_t elements_start_offset() {
        return TypeArguments::types_offset();
      }
  
      static constexpr intptr_t kElementSize = kCompressedWordSize;
    };
  
    // The nullability of a type argument vector represents the nullability of its
    // type elements (up to a maximum number of them, i.e. kNullabilityMaxTypes).
    // It is used at runtime in some cases (predetermined by the compiler) to
    // decide whether the instantiator type arguments (ITA) can be shared instead
    // of performing a more costly instantiation of the uninstantiated type
    // arguments (UTA).
    // The vector nullability is stored as a bit vector (in a Smi field), using
    // 2 bits per type:
    //  - the high bit is set if the type is nullable or legacy.
    //  - the low bit is set if the type is nullable.
    // The nullability is 0 if the vector is longer than kNullabilityMaxTypes.
    // The condition evaluated at runtime to decide whether UTA can share ITA is
    //   (UTA.nullability & ITA.nullability) == UTA.nullability
    // Note that this allows for ITA to be longer than UTA (the bit vector must be
    // stored in the same order as the corresponding type vector, i.e. with the
    // least significant 2 bits representing the nullability of the first type).
    static const intptr_t kNullabilityBitsPerType = 2;
    static const intptr_t kNullabilityMaxTypes =
        kSmiBits / kNullabilityBitsPerType;
    static const intptr_t kNonNullableBits = 0;
    static const intptr_t kNullableBits = 3;
    static const intptr_t kLegacyBits = 2;
    intptr_t nullability() const;
    static intptr_t nullability_offset() {
      return OFFSET_OF(UntaggedTypeArguments, nullability_);
    }
  
    // The name of this type argument vector, e.g. "<T, dynamic, List<T>, Smi>".
    StringPtr Name() const;
  
    // The name of this type argument vector, e.g. "<T, dynamic, List<T>, int>".
    // Names of internal classes are mapped to their public interfaces.
    StringPtr UserVisibleName() const;
  
    // Print the internal or public name of a subvector of this type argument
    // vector, e.g. "<T, dynamic, List<T>, int>".
    void PrintSubvectorName(intptr_t from_index,
                            intptr_t len,
                            NameVisibility name_visibility,
                            BaseTextBuffer* printer) const;
    void PrintTo(BaseTextBuffer* printer) const;
  
    // Check if the subvector of length 'len' starting at 'from_index' of this
    // type argument vector consists solely of DynamicType.
    bool IsRaw(intptr_t from_index, intptr_t len) const {
      return IsDynamicTypes(false, from_index, len);
    }
  
    // Check if this type argument vector would consist solely of DynamicType if
    // it was instantiated from both a raw (null) instantiator type arguments and
    // a raw (null) function type arguments, i.e. consider each class type
    // parameter and function type parameters as it would be first instantiated
    // from a vector of dynamic types.
    // Consider only a prefix of length 'len'.
    bool IsRawWhenInstantiatedFromRaw(intptr_t len) const {
      return IsDynamicTypes(true, 0, len);
    }
  
    // Return true if this vector contains a TypeRef.
    bool IsRecursive(TrailPtr trail = nullptr) const;
  
    // Return true if this vector contains a non-nullable type.
    bool RequireConstCanonicalTypeErasure(Zone* zone,
                                          intptr_t from_index,
                                          intptr_t len,
                                          TrailPtr trail = nullptr) const;
  
    TypeArgumentsPtr Prepend(Zone* zone,
                             const TypeArguments& other,
                             intptr_t other_length,
                             intptr_t total_length) const;
  
    // Concatenate [this] and [other] vectors of type parameters.
    TypeArgumentsPtr ConcatenateTypeParameters(Zone* zone,
                                               const TypeArguments& other) const;
  
    // Check if the vectors are equal (they may be null).
    bool Equals(const TypeArguments& other) const {
      return IsSubvectorEquivalent(other, 0, IsNull() ? 0 : Length(),
                                   TypeEquality::kCanonical);
    }
  
    bool IsEquivalent(const TypeArguments& other,
                      TypeEquality kind,
                      TrailPtr trail = nullptr) const {
      // Make a null vector a vector of dynamic as long as the other vector.
      return IsSubvectorEquivalent(other, 0, IsNull() ? other.Length() : Length(),
                                   kind, trail);
    }
    bool IsSubvectorEquivalent(const TypeArguments& other,
                               intptr_t from_index,
                               intptr_t len,
                               TypeEquality kind,
                               TrailPtr trail = nullptr) const;
  
    // Check if the vector is instantiated (it must not be null).
    bool IsInstantiated(Genericity genericity = kAny,
                        intptr_t num_free_fun_type_params = kAllFree,
                        TrailPtr trail = nullptr) const {
      return IsSubvectorInstantiated(0, Length(), genericity,
                                     num_free_fun_type_params, trail);
    }
    bool IsSubvectorInstantiated(intptr_t from_index,
                                 intptr_t len,
                                 Genericity genericity = kAny,
                                 intptr_t num_free_fun_type_params = kAllFree,
                                 TrailPtr trail = nullptr) const;
    bool IsUninstantiatedIdentity() const;
  
    // Determine whether this uninstantiated type argument vector can share its
    // instantiator (resp. function) type argument vector instead of being
    // instantiated at runtime.
    // If null is passed in for 'with_runtime_check', the answer is unconditional
    // (i.e. the answer will be false even if a runtime check may allow sharing),
    // otherwise, in case the function returns true, 'with_runtime_check'
    // indicates if a check is still required at runtime before allowing sharing.
    bool CanShareInstantiatorTypeArguments(
        const Class& instantiator_class,
        bool* with_runtime_check = nullptr) const;
    bool CanShareFunctionTypeArguments(const Function& function,
                                       bool* with_runtime_check = nullptr) const;
  
    // Return true if all types of this vector are finalized.
    bool IsFinalized() const;
  
    // Caller must hold IsolateGroup::constant_canonicalization_mutex_.
    virtual InstancePtr CanonicalizeLocked(Thread* thread) const {
      return Canonicalize(thread, nullptr);
    }
  
    // Canonicalize only if instantiated, otherwise returns 'this'.
    TypeArgumentsPtr Canonicalize(Thread* thread, TrailPtr trail = nullptr) const;
  
    // Add the class name and URI of each type argument of this vector to the uris
    // list and mark ambiguous triplets to be printed.
    void EnumerateURIs(URIs* uris) const;
  
    // Return 'this' if this type argument vector is instantiated, i.e. if it does
    // not refer to type parameters. Otherwise, return a new type argument vector
    // where each reference to a type parameter is replaced with the corresponding
    // type from the various type argument vectors (class instantiator, function,
    // or parent functions via the current context).
    TypeArgumentsPtr InstantiateFrom(
        const TypeArguments& instantiator_type_arguments,
        const TypeArguments& function_type_arguments,
        intptr_t num_free_fun_type_params,
        Heap::Space space,
        TrailPtr trail = nullptr) const;
  
    // Runtime instantiation with canonicalization. Not to be used during type
    // finalization at compile time.
    TypeArgumentsPtr InstantiateAndCanonicalizeFrom(
        const TypeArguments& instantiator_type_arguments,
        const TypeArguments& function_type_arguments) const;
  
    // Each cached instantiation consists of a 3-tuple in the instantiations_
    // array stored in each canonical uninstantiated type argument vector.
    enum Instantiation {
      kInstantiatorTypeArgsIndex = 0,
      kFunctionTypeArgsIndex,
      kInstantiatedTypeArgsIndex,
      kSizeInWords,
    };
  
    // The array is terminated by the value kNoInstantiator occuring in place of
    // the instantiator type args of the 4-tuple that would otherwise follow.
    // Therefore, kNoInstantiator must be distinct from any type arguments vector,
    // even a null one. Since arrays are initialized with 0, the instantiations_
    // array is properly terminated upon initialization.
    static const intptr_t kNoInstantiator = 0;
  
    // Return true if this type argument vector has cached instantiations.
    bool HasInstantiations() const;
  
    // Return the number of cached instantiations for this type argument vector.
    intptr_t NumInstantiations() const;
  
    static intptr_t instantiations_offset() {
      return OFFSET_OF(UntaggedTypeArguments, instantiations_);
    }
  
    static const intptr_t kBytesPerElement = kCompressedWordSize;
    static const intptr_t kMaxElements = kSmiMax / kBytesPerElement;
  
    static intptr_t InstanceSize() {
      ASSERT(sizeof(UntaggedTypeArguments) ==
             OFFSET_OF_RETURNED_VALUE(UntaggedTypeArguments, types));
      return 0;
    }
  
    static intptr_t InstanceSize(intptr_t len) {
      // Ensure that the types() is not adding to the object size, which includes
      // 4 fields: instantiations_, length_, hash_, and nullability_.
      ASSERT(sizeof(UntaggedTypeArguments) ==
             (sizeof(UntaggedObject) + (kNumFields * kCompressedWordSize)));
      ASSERT(0 <= len && len <= kMaxElements);
      return RoundedAllocationSize(sizeof(UntaggedTypeArguments) +
                                   (len * kBytesPerElement));
    }
  
    virtual uint32_t CanonicalizeHash() const {
      // Hash() is not stable until finalization is done.
      return 0;
    }
    uword Hash() const;
    uword HashForRange(intptr_t from_index, intptr_t len) const;
  
    static TypeArgumentsPtr New(intptr_t len, Heap::Space space = Heap::kOld);
  
   private:
    intptr_t ComputeNullability() const;
    void set_nullability(intptr_t value) const;
  
    uword ComputeHash() const;
    void SetHash(intptr_t value) const;
  
    // Check if the subvector of length 'len' starting at 'from_index' of this
    // type argument vector consists solely of DynamicType.
    // If raw_instantiated is true, consider each class type parameter to be first
    // instantiated from a vector of dynamic types.
    bool IsDynamicTypes(bool raw_instantiated,
                        intptr_t from_index,
                        intptr_t len) const;
  
    ArrayPtr instantiations() const;
    void set_instantiations(const Array& value) const;
    void SetLength(intptr_t value) const;
    // Number of fields in the raw object is 4:
    // instantiations_, length_, hash_ and nullability_.
    static const int kNumFields = 4;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(TypeArguments, Instance);
    friend class AbstractType;
    friend class Class;
    friend class ClearTypeHashVisitor;
    friend class Object;
  };
  
  // AbstractType is an abstract superclass.
  // Subclasses of AbstractType are Type and TypeParameter.
  class AbstractType : public Instance {
   public:
    virtual bool IsFinalized() const;
    virtual void SetIsFinalized() const;
    virtual bool IsBeingFinalized() const;
    virtual void SetIsBeingFinalized() const;
  
    virtual Nullability nullability() const;
    // Returns true if type has '?' nullability suffix, or it is a
    // built-in type which is always nullable (Null, dynamic or void).
    bool IsNullable() const { return nullability() == Nullability::kNullable; }
    // Returns true if type does not have any nullability suffix.
    // This function also returns true for type parameters without
    // nullability suffix ("T") which can be instantiated with
    // nullable or legacy types.
    bool IsNonNullable() const {
      return nullability() == Nullability::kNonNullable;
    }
    // Returns true if type has '*' nullability suffix, i.e.
    // it is from a legacy (opted-out) library.
    bool IsLegacy() const { return nullability() == Nullability::kLegacy; }
    // Returns true if it is guaranteed that null cannot be
    // assigned to this type.
    bool IsStrictlyNonNullable() const;
  
    virtual AbstractTypePtr SetInstantiatedNullability(
        const TypeParameter& type_param,
        Heap::Space space) const;
    virtual AbstractTypePtr NormalizeFutureOrType(Heap::Space space) const;
  
    virtual bool HasTypeClass() const { return type_class_id() != kIllegalCid; }
    virtual classid_t type_class_id() const;
    virtual ClassPtr type_class() const;
    virtual TypeArgumentsPtr arguments() const;
    virtual void set_arguments(const TypeArguments& value) const;
    virtual bool IsInstantiated(Genericity genericity = kAny,
                                intptr_t num_free_fun_type_params = kAllFree,
                                TrailPtr trail = nullptr) const;
    virtual bool CanonicalizeEquals(const Instance& other) const {
      return Equals(other);
    }
    virtual uint32_t CanonicalizeHash() const { return Hash(); }
    virtual bool Equals(const Instance& other) const {
      return IsEquivalent(other, TypeEquality::kCanonical);
    }
    virtual bool IsEquivalent(const Instance& other,
                              TypeEquality kind,
                              TrailPtr trail = nullptr) const;
    virtual bool IsRecursive(TrailPtr trail = nullptr) const;
    virtual bool RequireConstCanonicalTypeErasure(Zone* zone,
                                                  TrailPtr trail = nullptr) const;
  
    // Instantiate this type using the given type argument vectors.
    //
    // Note that some type parameters appearing in this type may not require
    // instantiation. Consider a class C<T> declaring a non-generic method
    // foo(bar<B>(T t, B b)). Although foo is not a generic method, it takes a
    // generic function bar<B> as argument and its function type refers to class
    // type parameter T and function type parameter B. When instantiating the
    // function type of foo for a particular value of T, function type parameter B
    // must remain uninstantiated, because only T is a free variable in this type.
    //
    // Return a new type, or return 'this' if it is already instantiated.
    virtual AbstractTypePtr InstantiateFrom(
        const TypeArguments& instantiator_type_arguments,
        const TypeArguments& function_type_arguments,
        intptr_t num_free_fun_type_params,
        Heap::Space space,
        TrailPtr trail = nullptr) const;
  
    // Caller must hold IsolateGroup::constant_canonicalization_mutex_.
    virtual InstancePtr CanonicalizeLocked(Thread* thread) const {
      return Canonicalize(thread, nullptr);
    }
  
    // Return the canonical version of this type.
    virtual AbstractTypePtr Canonicalize(Thread* thread, TrailPtr trail) const;
  
  #if defined(DEBUG)
    // Check if abstract type is canonical.
    virtual bool CheckIsCanonical(Thread* thread) const {
      UNREACHABLE();
      return false;
    }
  #endif  // DEBUG
  
    // Return the object associated with the receiver in the trail or
    // AbstractType::null() if the receiver is not contained in the trail.
    AbstractTypePtr OnlyBuddyInTrail(TrailPtr trail) const;
  
    // If the trail is null, allocate a trail, add the pair <receiver, buddy> to
    // the trail. The receiver may only be added once with its only buddy.
    void AddOnlyBuddyToTrail(TrailPtr* trail, const AbstractType& buddy) const;
  
    // Return true if the receiver is contained in the trail.
    // Otherwise, if the trail is null, allocate a trail, then add the receiver to
    // the trail and return false.
    bool TestAndAddToTrail(TrailPtr* trail) const;
  
    // Return true if the pair <receiver, buddy> is contained in the trail.
    // Otherwise, if the trail is null, allocate a trail, add the pair <receiver,
    // buddy> to the trail and return false.
    // The receiver may be added several times, each time with a different buddy.
    bool TestAndAddBuddyToTrail(TrailPtr* trail, const AbstractType& buddy) const;
  
    // Add the pair <name, uri> to the list, if not already present.
    static void AddURI(URIs* uris, const String& name, const String& uri);
  
    // Return a formatted string of the uris.
    static StringPtr PrintURIs(URIs* uris);
  
    // Returns a C-String (possibly "") representing the nullability of this type.
    // Legacy and undetermined suffixes are only displayed with kInternalName.
    virtual const char* NullabilitySuffix(NameVisibility name_visibility) const;
  
    // The name of this type, including the names of its type arguments, if any.
    virtual StringPtr Name() const;
  
    // The name of this type, including the names of its type arguments, if any.
    // Names of internal classes are mapped to their public interfaces.
    virtual StringPtr UserVisibleName() const;
  
    // The name of this type, including the names of its type arguments, if any.
    // Privacy suffixes are dropped.
    virtual StringPtr ScrubbedName() const;
  
    // Return the internal or public name of this type, including the names of its
    // type arguments, if any.
    void PrintName(NameVisibility visibility, BaseTextBuffer* printer) const;
  
    // Add the class name and URI of each occuring type to the uris
    // list and mark ambiguous triplets to be printed.
    virtual void EnumerateURIs(URIs* uris) const;
  
    virtual uword Hash() const;
  
    // The name of this type's class, i.e. without the type argument names of this
    // type.
    StringPtr ClassName() const;
  
    // Check if this type is a still uninitialized TypeRef.
    bool IsNullTypeRef() const;
  
    // Check if this type represents the 'dynamic' type.
    bool IsDynamicType() const { return type_class_id() == kDynamicCid; }
  
    // Check if this type represents the 'void' type.
    bool IsVoidType() const { return type_class_id() == kVoidCid; }
  
    // Check if this type represents the 'Null' type.
    bool IsNullType() const;
  
    // Check if this type represents the 'Never' type.
    bool IsNeverType() const;
  
    // Check if this type represents the 'Sentinel' type.
    bool IsSentinelType() const;
  
    // Check if this type represents the 'Object' type.
    bool IsObjectType() const { return type_class_id() == kInstanceCid; }
  
    // Check if this type represents a top type for subtyping,
    // assignability and 'as' type tests.
    //
    // Returns true if
    //  - any type is a subtype of this type;
    //  - any value can be assigned to a variable of this type;
    //  - 'as' type test always succeeds for this type.
    bool IsTopTypeForSubtyping() const;
  
    // Check if this type represents a top type for 'is' type tests.
    // Returns true if 'is' type test always returns true for this type.
    bool IsTopTypeForInstanceOf() const;
  
    // Check if this type represents the 'bool' type.
    bool IsBoolType() const { return type_class_id() == kBoolCid; }
  
    // Check if this type represents the 'int' type.
    bool IsIntType() const;
  
    // Check if this type represents the '_IntegerImplementation' type.
    bool IsIntegerImplementationType() const;
  
    // Check if this type represents the 'double' type.
    bool IsDoubleType() const;
  
    // Check if this type represents the 'Float32x4' type.
    bool IsFloat32x4Type() const;
  
    // Check if this type represents the 'Float64x2' type.
    bool IsFloat64x2Type() const;
  
    // Check if this type represents the 'Int32x4' type.
    bool IsInt32x4Type() const;
  
    // Check if this type represents the 'num' type.
    bool IsNumberType() const { return type_class_id() == kNumberCid; }
  
    // Check if this type represents the '_Smi' type.
    bool IsSmiType() const { return type_class_id() == kSmiCid; }
  
    // Check if this type represents the '_Mint' type.
    bool IsMintType() const { return type_class_id() == kMintCid; }
  
    // Check if this type represents the 'String' type.
    bool IsStringType() const;
  
    // Check if this type represents the Dart 'Function' type.
    bool IsDartFunctionType() const;
  
    // Check if this type represents the Dart '_Closure' type.
    bool IsDartClosureType() const;
  
    // Check if this type represents the 'Pointer' type from "dart:ffi".
    bool IsFfiPointerType() const;
  
    // Check if this type represents the 'FutureOr' type.
    bool IsFutureOrType() const { return type_class_id() == kFutureOrCid; }
  
    // Returns the type argument of this (possibly nested) 'FutureOr' type.
    // Returns unmodified type if this type is not a 'FutureOr' type.
    AbstractTypePtr UnwrapFutureOr() const;
  
    // Returns true if catching this type will catch all exceptions.
    // Exception objects are guaranteed to be non-nullable, so
    // non-nullable Object is also a catch-all type.
    bool IsCatchAllType() const { return IsDynamicType() || IsObjectType(); }
  
    // Check the subtype relationship.
    bool IsSubtypeOf(const AbstractType& other,
                     Heap::Space space,
                     TrailPtr trail = nullptr) const;
  
    // Returns true iff subtype is a subtype of supertype, false otherwise or if
    // an error occurred.
    static bool InstantiateAndTestSubtype(
        AbstractType* subtype,
        AbstractType* supertype,
        const TypeArguments& instantiator_type_args,
        const TypeArguments& function_type_args);
  
    static intptr_t type_test_stub_entry_point_offset() {
      return OFFSET_OF(UntaggedAbstractType, type_test_stub_entry_point_);
    }
  
    uword type_test_stub_entry_point() const {
      return untag()->type_test_stub_entry_point_;
    }
    CodePtr type_test_stub() const { return untag()->type_test_stub(); }
  
    // Sets the TTS to [stub].
    //
    // The update will ensure both fields (code as well as the cached entrypoint)
    // are updated together.
    //
    // Can be used concurrently by multiple threads - the updates will be applied
    // in undetermined order - but always consistently.
    void SetTypeTestingStub(const Code& stub) const;
  
    // Sets the TTS to the [stub].
    //
    // The caller has to ensure no other thread can concurrently try to update the
    // TTS. This should mainly be used when initializing newly allocated Type
    // objects.
    void InitializeTypeTestingStubNonAtomic(const Code& stub) const;
  
    void UpdateTypeTestingStubEntryPoint() const {
      StoreNonPointer(&untag()->type_test_stub_entry_point_,
                      Code::EntryPointOf(untag()->type_test_stub()));
    }
  
    // No instances of type AbstractType are allocated, but InstanceSize() and
    // NextFieldOffset() are required to register class _AbstractType.
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedAbstractType));
    }
  
    static intptr_t NextFieldOffset() { return -kWordSize; }
  
   private:
    // Returns true if this type is a subtype of FutureOr<T> specified by 'other'.
    // Returns false if other type is not a FutureOr.
    bool IsSubtypeOfFutureOr(Zone* zone,
                             const AbstractType& other,
                             Heap::Space space,
                             TrailPtr trail = nullptr) const;
  
   protected:
    HEAP_OBJECT_IMPLEMENTATION(AbstractType, Instance);
    friend class Class;
    friend class Function;
    friend class TypeArguments;
  };
  
  // A Type consists of a class, possibly parameterized with type
  // arguments. Example: C<T1, T2>.
  class Type : public AbstractType {
   public:
    static intptr_t type_class_id_offset() {
      return OFFSET_OF(UntaggedType, type_class_id_);
    }
    static intptr_t arguments_offset() {
      return OFFSET_OF(UntaggedType, arguments_);
    }
    static intptr_t type_state_offset() {
      return OFFSET_OF(UntaggedType, type_state_);
    }
    static intptr_t hash_offset() { return OFFSET_OF(UntaggedType, hash_); }
    static intptr_t nullability_offset() {
      return OFFSET_OF(UntaggedType, nullability_);
    }
    virtual bool IsFinalized() const {
      return (untag()->type_state_ == UntaggedType::kFinalizedInstantiated) ||
             (untag()->type_state_ == UntaggedType::kFinalizedUninstantiated);
    }
    virtual void SetIsFinalized() const;
    virtual bool IsBeingFinalized() const {
      return untag()->type_state_ == UntaggedType::kBeingFinalized;
    }
    virtual void SetIsBeingFinalized() const;
    virtual bool HasTypeClass() const {
      ASSERT(type_class_id() != kIllegalCid);
      return true;
    }
    virtual Nullability nullability() const {
      return static_cast<Nullability>(untag()->nullability_);
    }
    TypePtr ToNullability(Nullability value, Heap::Space space) const;
    virtual classid_t type_class_id() const;
    virtual ClassPtr type_class() const;
    void set_type_class(const Class& value) const;
    virtual TypeArgumentsPtr arguments() const { return untag()->arguments(); }
    virtual void set_arguments(const TypeArguments& value) const;
    virtual bool IsInstantiated(Genericity genericity = kAny,
                                intptr_t num_free_fun_type_params = kAllFree,
                                TrailPtr trail = nullptr) const;
    virtual bool IsEquivalent(const Instance& other,
                              TypeEquality kind,
                              TrailPtr trail = nullptr) const;
    virtual bool IsRecursive(TrailPtr trail = nullptr) const;
    virtual bool RequireConstCanonicalTypeErasure(Zone* zone,
                                                  TrailPtr trail = nullptr) const;
  
    // Return true if this type can be used as the declaration type of cls after
    // canonicalization (passed-in cls must match type_class()).
    bool IsDeclarationTypeOf(const Class& cls) const;
  
    virtual AbstractTypePtr InstantiateFrom(
        const TypeArguments& instantiator_type_arguments,
        const TypeArguments& function_type_arguments,
        intptr_t num_free_fun_type_params,
        Heap::Space space,
        TrailPtr trail = nullptr) const;
    virtual AbstractTypePtr Canonicalize(Thread* thread, TrailPtr trail) const;
  #if defined(DEBUG)
    // Check if type is canonical.
    virtual bool CheckIsCanonical(Thread* thread) const;
  #endif  // DEBUG
    virtual void EnumerateURIs(URIs* uris) const;
  
    virtual uword Hash() const;
    uword ComputeHash() const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedType));
    }
  
    // The type of the literal 'null'.
    static TypePtr NullType();
  
    // The 'dynamic' type.
    static TypePtr DynamicType();
  
    // The 'void' type.
    static TypePtr VoidType();
  
    // The 'Never' type.
    static TypePtr NeverType();
  
    // The 'Object' type.
    static TypePtr ObjectType();
  
    // The 'bool' type.
    static TypePtr BoolType();
  
    // The 'int' type.
    static TypePtr IntType();
  
    // The 'int?' type.
    static TypePtr NullableIntType();
  
    // The 'Smi' type.
    static TypePtr SmiType();
  
    // The 'Mint' type.
    static TypePtr MintType();
  
    // The 'double' type.
    static TypePtr Double();
  
    // The 'double?' type.
    static TypePtr NullableDouble();
  
    // The 'Float32x4' type.
    static TypePtr Float32x4();
  
    // The 'Float64x2' type.
    static TypePtr Float64x2();
  
    // The 'Int32x4' type.
    static TypePtr Int32x4();
  
    // The 'num' type.
    static TypePtr Number();
  
    // The 'String' type.
    static TypePtr StringType();
  
    // The 'Array' type.
    static TypePtr ArrayType();
  
    // The 'Function' type.
    static TypePtr DartFunctionType();
  
    // The 'Type' type.
    static TypePtr DartTypeType();
  
    // The finalized type of the given non-parameterized class.
    static TypePtr NewNonParameterizedType(const Class& type_class);
  
    static TypePtr New(const Class& clazz,
                       const TypeArguments& arguments,
                       Nullability nullability = Nullability::kLegacy,
                       Heap::Space space = Heap::kOld);
  
   private:
    void SetHash(intptr_t value) const;
  
    // Takes an intptr_t since the cids of some classes are larger than will fit
    // in ClassIdTagType. This allows us to guard against that case, instead of
    // silently truncating the cid.
    void set_type_class_id(intptr_t id) const;
    void set_type_state(uint8_t state) const;
    void set_nullability(Nullability value) const {
      ASSERT(!IsCanonical());
      StoreNonPointer(&untag()->nullability_, static_cast<uint8_t>(value));
    }
  
    static TypePtr New(Heap::Space space = Heap::kOld);
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Type, AbstractType);
    friend class Class;
    friend class TypeArguments;
    friend class ClearTypeHashVisitor;
  };
  
  // A FunctionType represents the type of a function. It describes most of the
  // signature of a function, excluding the names of type parameters and names
  // of parameters, but includes the names of optional named parameters.
  class FunctionType : public AbstractType {
   public:
    // Reexported so they can be used by the flow graph builders.
    using PackedNumParentTypeArguments =
        UntaggedFunctionType::PackedNumParentTypeArguments;
    using PackedNumTypeParameters = UntaggedFunctionType::PackedNumTypeParameters;
    using PackedHasNamedOptionalParameters =
        UntaggedFunctionType::PackedHasNamedOptionalParameters;
    using PackedNumImplicitParameters =
        UntaggedFunctionType::PackedNumImplicitParameters;
    using PackedNumFixedParameters =
        UntaggedFunctionType::PackedNumFixedParameters;
    using PackedNumOptionalParameters =
        UntaggedFunctionType::PackedNumOptionalParameters;
  
    static intptr_t type_state_offset() {
      return OFFSET_OF(UntaggedFunctionType, type_state_);
    }
    static intptr_t hash_offset() {
      return OFFSET_OF(UntaggedFunctionType, hash_);
    }
    static intptr_t nullability_offset() {
      return OFFSET_OF(UntaggedFunctionType, nullability_);
    }
    virtual bool IsFinalized() const {
      return (untag()->type_state_ == UntaggedType::kFinalizedInstantiated) ||
             (untag()->type_state_ == UntaggedType::kFinalizedUninstantiated);
    }
    virtual void SetIsFinalized() const;
    virtual bool IsBeingFinalized() const {
      return untag()->type_state_ == UntaggedType::kBeingFinalized;
    }
    virtual void SetIsBeingFinalized() const;
    virtual bool HasTypeClass() const { return false; }
    virtual Nullability nullability() const {
      return static_cast<Nullability>(untag()->nullability_);
    }
    FunctionTypePtr ToNullability(Nullability value, Heap::Space space) const;
    virtual classid_t type_class_id() const { return kIllegalCid; }
    virtual bool IsInstantiated(Genericity genericity = kAny,
                                intptr_t num_free_fun_type_params = kAllFree,
                                TrailPtr trail = nullptr) const;
    virtual bool IsEquivalent(const Instance& other,
                              TypeEquality kind,
                              TrailPtr trail = nullptr) const;
    virtual bool IsRecursive(TrailPtr trail = nullptr) const;
    virtual bool RequireConstCanonicalTypeErasure(Zone* zone,
                                                  TrailPtr trail = nullptr) const;
  
    virtual AbstractTypePtr InstantiateFrom(
        const TypeArguments& instantiator_type_arguments,
        const TypeArguments& function_type_arguments,
        intptr_t num_free_fun_type_params,
        Heap::Space space,
        TrailPtr trail = nullptr) const;
    virtual AbstractTypePtr Canonicalize(Thread* thread, TrailPtr trail) const;
  #if defined(DEBUG)
    // Check if type is canonical.
    virtual bool CheckIsCanonical(Thread* thread) const;
  #endif  // DEBUG
    virtual void EnumerateURIs(URIs* uris) const;
  
    virtual uword Hash() const;
    uword ComputeHash() const;
  
    bool IsSubtypeOf(const FunctionType& other, Heap::Space space) const;
  
    static intptr_t NumParentTypeArgumentsOf(FunctionTypePtr ptr) {
      return ptr->untag()
          ->packed_type_parameter_counts_.Read<PackedNumParentTypeArguments>();
    }
    // Return the number of type arguments in the enclosing signature.
    intptr_t NumParentTypeArguments() const {
      return NumParentTypeArgumentsOf(ptr());
    }
    void SetNumParentTypeArguments(intptr_t value) const;
    static intptr_t NumTypeParametersOf(FunctionTypePtr ptr) {
      return ptr->untag()
          ->packed_type_parameter_counts_.Read<PackedNumTypeParameters>();
    }
    intptr_t NumTypeParameters() const { return NumTypeParametersOf(ptr()); }
  
    static intptr_t NumTypeArgumentsOf(FunctionTypePtr ptr) {
      return NumTypeParametersOf(ptr) + NumParentTypeArgumentsOf(ptr);
    }
    intptr_t NumTypeArguments() const { return NumTypeArgumentsOf(ptr()); }
  
    intptr_t num_implicit_parameters() const {
      return untag()
          ->packed_parameter_counts_.Read<PackedNumImplicitParameters>();
    }
    void set_num_implicit_parameters(intptr_t value) const;
  
    static intptr_t NumFixedParametersOf(FunctionTypePtr ptr) {
      return ptr->untag()
          ->packed_parameter_counts_.Read<PackedNumFixedParameters>();
    }
    intptr_t num_fixed_parameters() const { return NumFixedParametersOf(ptr()); }
    void set_num_fixed_parameters(intptr_t value) const;
  
    static bool HasOptionalParameters(FunctionTypePtr ptr) {
      return ptr->untag()
                 ->packed_parameter_counts_.Read<PackedNumOptionalParameters>() >
             0;
    }
    bool HasOptionalParameters() const { return HasOptionalParameters(ptr()); }
  
    static bool HasOptionalNamedParameters(FunctionTypePtr ptr) {
      return ptr->untag()
          ->packed_parameter_counts_.Read<PackedHasNamedOptionalParameters>();
    }
    bool HasOptionalNamedParameters() const {
      return HasOptionalNamedParameters(ptr());
    }
    bool HasRequiredNamedParameters() const;
  
    static bool HasOptionalPositionalParameters(FunctionTypePtr ptr) {
      return !HasOptionalNamedParameters(ptr) && HasOptionalParameters(ptr);
    }
    bool HasOptionalPositionalParameters() const {
      return HasOptionalPositionalParameters(ptr());
    }
  
    static intptr_t NumOptionalParametersOf(FunctionTypePtr ptr) {
      return ptr->untag()
          ->packed_parameter_counts_.Read<PackedNumOptionalParameters>();
    }
    intptr_t NumOptionalParameters() const {
      return NumOptionalParametersOf(ptr());
    }
    void SetNumOptionalParameters(intptr_t num_optional_parameters,
                                  bool are_optional_positional) const;
  
    static intptr_t NumOptionalPositionalParametersOf(FunctionTypePtr ptr) {
      return HasOptionalNamedParameters(ptr) ? 0 : NumOptionalParametersOf(ptr);
    }
    intptr_t NumOptionalPositionalParameters() const {
      return NumOptionalPositionalParametersOf(ptr());
    }
  
    static intptr_t NumOptionalNamedParametersOf(FunctionTypePtr ptr) {
      return HasOptionalNamedParameters(ptr) ? NumOptionalParametersOf(ptr) : 0;
    }
    intptr_t NumOptionalNamedParameters() const {
      return NumOptionalNamedParametersOf(ptr());
    }
  
    static intptr_t NumParametersOf(FunctionTypePtr ptr) {
      return NumFixedParametersOf(ptr) + NumOptionalParametersOf(ptr);
    }
    intptr_t NumParameters() const { return NumParametersOf(ptr()); }
  
    uint32_t packed_parameter_counts() const {
      return untag()->packed_parameter_counts_;
    }
    void set_packed_parameter_counts(uint32_t packed_parameter_counts) const;
    static intptr_t packed_parameter_counts_offset() {
      return OFFSET_OF(UntaggedFunctionType, packed_parameter_counts_);
    }
    uint16_t packed_type_parameter_counts() const {
      return untag()->packed_type_parameter_counts_;
    }
    void set_packed_type_parameter_counts(uint16_t packed_parameter_counts) const;
    static intptr_t packed_type_parameter_counts_offset() {
      return OFFSET_OF(UntaggedFunctionType, packed_type_parameter_counts_);
    }
  
    // Return the type parameter declared at index.
    TypeParameterPtr TypeParameterAt(
        intptr_t index,
        Nullability nullability = Nullability::kNonNullable) const;
  
    AbstractTypePtr result_type() const { return untag()->result_type(); }
    void set_result_type(const AbstractType& value) const;
  
    // The parameters, starting with NumImplicitParameters() parameters which are
    // only visible to the VM, but not to Dart users.
    // Note that type checks exclude implicit parameters.
    AbstractTypePtr ParameterTypeAt(intptr_t index) const;
    void SetParameterTypeAt(intptr_t index, const AbstractType& value) const;
    ArrayPtr parameter_types() const { return untag()->parameter_types(); }
    void set_parameter_types(const Array& value) const;
    static intptr_t parameter_types_offset() {
      return OFFSET_OF(UntaggedFunctionType, parameter_types_);
    }
    // Parameter names are only stored for named parameters. If there are no named
    // parameters, named_parameter_names() is null.
    // If there are parameter flags (eg required) they're stored at the end of
    // this array, so the size of this array isn't necessarily
    // NumOptionalNamedParameters(), but the first NumOptionalNamedParameters()
    // elements are the names.
    ArrayPtr named_parameter_names() const {
      return untag()->named_parameter_names();
    }
    void set_named_parameter_names(const Array& value) const;
    static intptr_t named_parameter_names_offset() {
      return OFFSET_OF(UntaggedFunctionType, named_parameter_names_);
    }
    // The index for these operations is the absolute index of the parameter, not
    // the index relative to the start of the named parameters (if any).
    StringPtr ParameterNameAt(intptr_t index) const;
    // Only valid for absolute indexes of named parameters.
    void SetParameterNameAt(intptr_t index, const String& value) const;
  
    // The required flags are stored at the end of the parameter_names. The flags
    // are packed into SMIs, but omitted if they're 0.
    bool IsRequiredAt(intptr_t index) const;
    void SetIsRequiredAt(intptr_t index) const;
  
    // Sets up the signature's parameter name array, including appropriate space
    // for any possible parameter flags. This may be an overestimate if some
    // parameters don't have flags, and so FinalizeNameArray() should
    // be called after all parameter flags have been appropriately set.
    //
    // Assumes that the number of fixed and optional parameters for the signature
    // has already been set. Uses same default space as FunctionType::New.
    void CreateNameArrayIncludingFlags(Heap::Space space = Heap::kOld) const;
  
    // Truncate the parameter names array to remove any unused flag slots. Make
    // sure to only do this after calling SetIsRequiredAt as necessary.
    void FinalizeNameArray() const;
  
    // Returns the length of the parameter names array that is required to store
    // all the names plus all their flags. This may be an overestimate if some
    // parameters don't have flags.
    static intptr_t NameArrayLengthIncludingFlags(intptr_t num_parameters);
  
    // The formal type parameters, their bounds, and defaults, are specified as an
    // object of type TypeParameters.
    TypeParametersPtr type_parameters() const {
      return untag()->type_parameters();
    }
    void SetTypeParameters(const TypeParameters& value) const;
    static intptr_t type_parameters_offset() {
      return OFFSET_OF(UntaggedFunctionType, type_parameters_);
    }
  
    // Returns true if this function type has the same number of type parameters
    // with equal bounds as the other function type. Type parameter names and
    // parameter names (unless optional named) are ignored.
    bool HasSameTypeParametersAndBounds(const FunctionType& other,
                                        TypeEquality kind,
                                        TrailPtr trail = nullptr) const;
  
    // Return true if this function type declares type parameters.
    static bool IsGeneric(FunctionTypePtr ptr) {
      return ptr->untag()->type_parameters() != TypeParameters::null();
    }
    bool IsGeneric() const { return IsGeneric(ptr()); }
  
    // Return true if any enclosing signature of this signature is generic.
    bool HasGenericParent() const { return NumParentTypeArguments() > 0; }
  
    // Returns true if the type of the formal parameter at the given position in
    // this function type is contravariant with the type of the other formal
    // parameter at the given position in the other function type.
    bool IsContravariantParameter(intptr_t parameter_position,
                                  const FunctionType& other,
                                  intptr_t other_parameter_position,
                                  Heap::Space space) const;
  
    // Returns the index in the parameter names array of the corresponding flag
    // for the given parametere index. Also returns (via flag_mask) the
    // corresponding mask within the flag.
    intptr_t GetRequiredFlagIndex(intptr_t index, intptr_t* flag_mask) const;
  
    void Print(NameVisibility name_visibility, BaseTextBuffer* printer) const;
    void PrintParameters(Thread* thread,
                         Zone* zone,
                         NameVisibility name_visibility,
                         BaseTextBuffer* printer) const;
  
    StringPtr ToUserVisibleString() const;
    const char* ToUserVisibleCString() const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedFunctionType));
    }
  
    static FunctionTypePtr New(intptr_t num_parent_type_arguments = 0,
                               Nullability nullability = Nullability::kLegacy,
                               Heap::Space space = Heap::kOld);
  
   private:
    void SetHash(intptr_t value) const;
  
    void set_type_state(uint8_t state) const;
    void set_nullability(Nullability value) const {
      ASSERT(!IsCanonical());
      StoreNonPointer(&untag()->nullability_, static_cast<uint8_t>(value));
    }
  
    static FunctionTypePtr New(Heap::Space space);
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(FunctionType, AbstractType);
    friend class Class;
    friend class ClearTypeHashVisitor;
    friend class Function;
  };
  
  // A TypeRef is used to break cycles in the representation of recursive types.
  // Its only field is the recursive AbstractType it refers to, which can
  // temporarily be null during finalization.
  // Note that the cycle always involves type arguments.
  class TypeRef : public AbstractType {
   public:
    static intptr_t type_offset() { return OFFSET_OF(UntaggedTypeRef, type_); }
  
    virtual bool IsFinalized() const {
      const AbstractType& ref_type = AbstractType::Handle(type());
      return !ref_type.IsNull() && ref_type.IsFinalized();
    }
    virtual bool IsBeingFinalized() const {
      const AbstractType& ref_type = AbstractType::Handle(type());
      return ref_type.IsNull() || ref_type.IsBeingFinalized();
    }
    virtual Nullability nullability() const {
      const AbstractType& ref_type = AbstractType::Handle(type());
      ASSERT(!ref_type.IsNull());
      return ref_type.nullability();
    }
    virtual bool HasTypeClass() const {
      return (type() != AbstractType::null()) &&
             AbstractType::Handle(type()).HasTypeClass();
    }
    AbstractTypePtr type() const { return untag()->type(); }
    void set_type(const AbstractType& value) const;
    virtual classid_t type_class_id() const {
      return AbstractType::Handle(type()).type_class_id();
    }
    virtual ClassPtr type_class() const {
      return AbstractType::Handle(type()).type_class();
    }
    virtual TypeArgumentsPtr arguments() const {
      return AbstractType::Handle(type()).arguments();
    }
    virtual bool IsInstantiated(Genericity genericity = kAny,
                                intptr_t num_free_fun_type_params = kAllFree,
                                TrailPtr trail = nullptr) const;
    virtual bool IsEquivalent(const Instance& other,
                              TypeEquality kind,
                              TrailPtr trail = nullptr) const;
    virtual bool IsRecursive(TrailPtr trail = nullptr) const { return true; }
    virtual bool RequireConstCanonicalTypeErasure(Zone* zone,
                                                  TrailPtr trail = nullptr) const;
    virtual AbstractTypePtr InstantiateFrom(
        const TypeArguments& instantiator_type_arguments,
        const TypeArguments& function_type_arguments,
        intptr_t num_free_fun_type_params,
        Heap::Space space,
        TrailPtr trail = nullptr) const;
    virtual AbstractTypePtr Canonicalize(Thread* thread, TrailPtr trail) const;
  #if defined(DEBUG)
    // Check if typeref is canonical.
    virtual bool CheckIsCanonical(Thread* thread) const;
  #endif  // DEBUG
    virtual void EnumerateURIs(URIs* uris) const;
  
    virtual uword Hash() const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedTypeRef));
    }
  
    static TypeRefPtr New(const AbstractType& type);
  
   private:
    static TypeRefPtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(TypeRef, AbstractType);
    friend class Class;
  };
  
  // A TypeParameter represents a type parameter of a parameterized class.
  // It specifies its index (and its name for debugging purposes), as well as its
  // upper bound.
  // For example, the type parameter 'V' is specified as index 1 in the context of
  // the class HashMap<K, V>. At compile time, the TypeParameter is not
  // instantiated yet, i.e. it is only a place holder.
  // Upon finalization, the TypeParameter index is changed to reflect its position
  // as type argument (rather than type parameter) of the parameterized class.
  // If the type parameter is declared without an extends clause, its bound is set
  // to the ObjectType.
  class TypeParameter : public AbstractType {
   public:
    virtual bool IsFinalized() const {
      return UntaggedTypeParameter::FinalizedBit::decode(untag()->flags_);
    }
    virtual void SetIsFinalized() const;
    virtual bool IsBeingFinalized() const {
      return UntaggedTypeParameter::BeingFinalizedBit::decode(untag()->flags_);
    }
    virtual void SetIsBeingFinalized() const;
    static intptr_t flags_offset() {
      return OFFSET_OF(UntaggedTypeParameter, flags_);
    }
    static intptr_t nullability_offset() {
      return OFFSET_OF(UntaggedTypeParameter, nullability_);
    }
    virtual Nullability nullability() const {
      return static_cast<Nullability>(untag()->nullability_);
    }
    TypeParameterPtr ToNullability(Nullability value, Heap::Space space) const;
    virtual bool HasTypeClass() const { return false; }
    virtual classid_t type_class_id() const { return kIllegalCid; }
    classid_t parameterized_class_id() const;
    void set_parameterized_class_id(classid_t value) const;
    ClassPtr parameterized_class() const;
    bool IsClassTypeParameter() const {
      return parameterized_class_id() != kFunctionCid;
    }
    bool IsFunctionTypeParameter() const {
      return parameterized_class_id() == kFunctionCid;
    }
  
    static intptr_t parameterized_class_id_offset() {
      return OFFSET_OF(UntaggedTypeParameter, parameterized_class_id_);
    }
  
    intptr_t base() const { return untag()->base_; }
    void set_base(intptr_t value) const;
    intptr_t index() const { return untag()->index_; }
    void set_index(intptr_t value) const;
    static intptr_t index_offset() {
      return OFFSET_OF(UntaggedTypeParameter, index_);
    }
  
    AbstractTypePtr bound() const { return untag()->bound(); }
    void set_bound(const AbstractType& value) const;
    static intptr_t bound_offset() {
      return OFFSET_OF(UntaggedTypeParameter, bound_);
    }
  
    virtual bool IsInstantiated(Genericity genericity = kAny,
                                intptr_t num_free_fun_type_params = kAllFree,
                                TrailPtr trail = nullptr) const;
    virtual bool IsEquivalent(const Instance& other,
                              TypeEquality kind,
                              TrailPtr trail = nullptr) const;
    virtual bool IsRecursive(TrailPtr trail = nullptr) const;
    virtual bool RequireConstCanonicalTypeErasure(
        Zone* zone,
        TrailPtr trail = nullptr) const {
      return IsNonNullable();
    }
    virtual AbstractTypePtr InstantiateFrom(
        const TypeArguments& instantiator_type_arguments,
        const TypeArguments& function_type_arguments,
        intptr_t num_free_fun_type_params,
        Heap::Space space,
        TrailPtr trail = nullptr) const;
    virtual AbstractTypePtr Canonicalize(Thread* thread, TrailPtr trail) const;
  #if defined(DEBUG)
    // Check if type parameter is canonical.
    virtual bool CheckIsCanonical(Thread* thread) const;
  #endif  // DEBUG
    virtual void EnumerateURIs(URIs* uris) const { return; }
  
    virtual uword Hash() const;
  
    // Returns type corresponding to [this] type parameter from the
    // given [instantiator_type_arguments] and [function_type_arguments].
    // Unlike InstantiateFrom, nullability of type parameter is not applied to
    // the result.
    AbstractTypePtr GetFromTypeArguments(
        const TypeArguments& instantiator_type_arguments,
        const TypeArguments& function_type_arguments) const;
  
    // Return a constructed name for this nameless type parameter.
    const char* CanonicalNameCString() const {
      return CanonicalNameCString(IsClassTypeParameter(), base(), index());
    }
  
    static const char* CanonicalNameCString(bool is_class_type_parameter,
                                            intptr_t base,
                                            intptr_t index);
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedTypeParameter));
    }
  
    // 'parameterized_class' is null for a function type parameter.
    static TypeParameterPtr New(const Class& parameterized_class,
                                intptr_t base,
                                intptr_t index,
                                const AbstractType& bound,
                                Nullability nullability);
  
   private:
    uword ComputeHash() const;
    void SetHash(intptr_t value) const;
  
    void set_parameterized_class(const Class& value) const;
    void set_name(const String& value) const;
    void set_flags(uint8_t flags) const;
    void set_nullability(Nullability value) const;
  
    static TypeParameterPtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(TypeParameter, AbstractType);
    friend class Class;
    friend class ClearTypeHashVisitor;
  };
  
  class Number : public Instance {
   public:
    // TODO(iposva): Add more useful Number methods.
    StringPtr ToString(Heap::Space space) const;
  
    // Numbers are canonicalized differently from other instances/strings.
    // Caller must hold IsolateGroup::constant_canonicalization_mutex_.
    virtual InstancePtr CanonicalizeLocked(Thread* thread) const;
  
  #if defined(DEBUG)
    // Check if number is canonical.
    virtual bool CheckIsCanonical(Thread* thread) const;
  #endif  // DEBUG
  
   private:
    OBJECT_IMPLEMENTATION(Number, Instance);
  
    friend class Class;
  };
  
  class Integer : public Number {
   public:
    static IntegerPtr New(const String& str, Heap::Space space = Heap::kNew);
  
    // Creates a new Integer by given uint64_t value.
    // Silently casts value to int64_t with wrap-around if it is greater
    // than kMaxInt64.
    static IntegerPtr NewFromUint64(uint64_t value,
                                    Heap::Space space = Heap::kNew);
  
    // Returns a canonical Integer object allocated in the old gen space.
    // Returns null if integer is out of range.
    static IntegerPtr NewCanonical(const String& str);
    static IntegerPtr NewCanonical(int64_t value);
  
    static IntegerPtr New(int64_t value, Heap::Space space = Heap::kNew);
  
    // Returns true iff the given uint64_t value is representable as Dart integer.
    static bool IsValueInRange(uint64_t value);
  
    virtual bool OperatorEquals(const Instance& other) const {
      return Equals(other);
    }
    virtual bool CanonicalizeEquals(const Instance& other) const {
      return Equals(other);
    }
    virtual uint32_t CanonicalizeHash() const { return AsTruncatedUint32Value(); }
    virtual bool Equals(const Instance& other) const;
  
    virtual ObjectPtr HashCode() const { return ptr(); }
  
    virtual bool IsZero() const;
    virtual bool IsNegative() const;
  
    virtual double AsDoubleValue() const;
    virtual int64_t AsInt64Value() const;
    virtual int64_t AsTruncatedInt64Value() const { return AsInt64Value(); }
    virtual uint32_t AsTruncatedUint32Value() const;
  
    virtual bool FitsIntoSmi() const;
  
    // Returns 0, -1 or 1.
    virtual int CompareWith(const Integer& other) const;
  
    // Converts integer to hex string.
    const char* ToHexCString(Zone* zone) const;
  
    // Return the most compact presentation of an integer.
    IntegerPtr AsValidInteger() const;
  
    // Returns null to indicate that a bigint operation is required.
    IntegerPtr ArithmeticOp(Token::Kind operation,
                            const Integer& other,
                            Heap::Space space = Heap::kNew) const;
    IntegerPtr BitOp(Token::Kind operation,
                     const Integer& other,
                     Heap::Space space = Heap::kNew) const;
    IntegerPtr ShiftOp(Token::Kind operation,
                       const Integer& other,
                       Heap::Space space = Heap::kNew) const;
  
    static int64_t GetInt64Value(const IntegerPtr obj) {
      if (obj->IsSmi()) {
        return RawSmiValue(static_cast<const SmiPtr>(obj));
      } else {
        ASSERT(obj->IsMint());
        return static_cast<const MintPtr>(obj)->untag()->value_;
      }
    }
  
   private:
    OBJECT_IMPLEMENTATION(Integer, Number);
    friend class Class;
  };
  
  class Smi : public Integer {
   public:
    static const intptr_t kBits = kSmiBits;
    static const intptr_t kMaxValue = kSmiMax;
    static const intptr_t kMinValue = kSmiMin;
  
    intptr_t Value() const { return RawSmiValue(ptr()); }
  
    virtual bool Equals(const Instance& other) const;
    virtual bool IsZero() const { return Value() == 0; }
    virtual bool IsNegative() const { return Value() < 0; }
  
    virtual double AsDoubleValue() const;
    virtual int64_t AsInt64Value() const;
    virtual uint32_t AsTruncatedUint32Value() const;
  
    virtual bool FitsIntoSmi() const { return true; }
  
    virtual int CompareWith(const Integer& other) const;
  
    static intptr_t InstanceSize() { return 0; }
  
    static SmiPtr New(intptr_t value) {
      SmiPtr raw_smi = static_cast<SmiPtr>(
          (static_cast<uintptr_t>(value) << kSmiTagShift) | kSmiTag);
      ASSERT(RawSmiValue(raw_smi) == value);
      return raw_smi;
    }
  
    static SmiPtr FromAlignedAddress(uword address) {
      ASSERT((address & kSmiTagMask) == kSmiTag);
      return static_cast<SmiPtr>(address);
    }
  
    static ClassPtr Class();
  
    static intptr_t Value(const SmiPtr raw_smi) { return RawSmiValue(raw_smi); }
  #if defined(DART_COMPRESSED_POINTERS)
    static intptr_t Value(const CompressedSmiPtr raw_smi) {
      return Smi::Value(static_cast<SmiPtr>(raw_smi.DecompressSmi()));
    }
  #endif
  
    static intptr_t RawValue(intptr_t value) {
      return static_cast<intptr_t>(New(value));
    }
  
    static bool IsValid(int64_t value) { return compiler::target::IsSmi(value); }
  
    void operator=(SmiPtr value) {
      ptr_ = value;
      CHECK_HANDLE();
    }
    void operator^=(ObjectPtr value) {
      ptr_ = value;
      CHECK_HANDLE();
    }
  
   private:
    static intptr_t NextFieldOffset() {
      // Indicates this class cannot be extended by dart code.
      return -kWordSize;
    }
  
    Smi() : Integer() {}
    BASE_OBJECT_IMPLEMENTATION(Smi, Integer);
    OBJECT_SERVICE_SUPPORT(Smi);
    friend class Api;  // For ValueFromRaw
    friend class Class;
    friend class Object;
    friend class ReusableSmiHandleScope;
    friend class Thread;
  };
  
  class SmiTraits : AllStatic {
   public:
    static const char* Name() { return "SmiTraits"; }
    static bool ReportStats() { return false; }
  
    static bool IsMatch(const Object& a, const Object& b) {
      return Smi::Cast(a).Value() == Smi::Cast(b).Value();
    }
  
    static uword Hash(const Object& obj) { return Smi::Cast(obj).Value(); }
  };
  
  class Mint : public Integer {
   public:
    static const intptr_t kBits = 63;  // 64-th bit is sign.
    static const int64_t kMaxValue =
        static_cast<int64_t>(DART_2PART_UINT64_C(0x7FFFFFFF, FFFFFFFF));
    static const int64_t kMinValue =
        static_cast<int64_t>(DART_2PART_UINT64_C(0x80000000, 00000000));
  
    int64_t value() const { return untag()->value_; }
    static intptr_t value_offset() { return OFFSET_OF(UntaggedMint, value_); }
  
    virtual bool IsZero() const { return value() == 0; }
    virtual bool IsNegative() const { return value() < 0; }
  
    virtual bool Equals(const Instance& other) const;
  
    virtual double AsDoubleValue() const;
    virtual int64_t AsInt64Value() const;
    virtual uint32_t AsTruncatedUint32Value() const;
  
    virtual bool FitsIntoSmi() const;
  
    virtual int CompareWith(const Integer& other) const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedMint));
    }
  
   protected:
    // Only Integer::NewXXX is allowed to call Mint::NewXXX directly.
    friend class Integer;
    friend class MintMessageDeserializationCluster;
  
    static MintPtr New(int64_t value, Heap::Space space = Heap::kNew);
  
    static MintPtr NewCanonical(int64_t value);
    static MintPtr NewCanonicalLocked(Thread* thread, int64_t value);
  
   private:
    void set_value(int64_t value) const;
  
    MINT_OBJECT_IMPLEMENTATION(Mint, Integer, Integer);
    friend class Class;
    friend class Number;
  };
  
  // Class Double represents class Double in corelib_impl, which implements
  // abstract class double in corelib.
  class Double : public Number {
   public:
    double value() const { return untag()->value_; }
  
    bool BitwiseEqualsToDouble(double value) const;
    virtual bool OperatorEquals(const Instance& other) const;
    virtual bool CanonicalizeEquals(const Instance& other) const;
    virtual uint32_t CanonicalizeHash() const;
  
    static DoublePtr New(double d, Heap::Space space = Heap::kNew);
  
    static DoublePtr New(const String& str, Heap::Space space = Heap::kNew);
  
    // Returns a canonical double object allocated in the old gen space.
    static DoublePtr NewCanonical(double d);
    static DoublePtr NewCanonicalLocked(Thread* thread, double d);
  
    // Returns a canonical double object (allocated in the old gen space) or
    // Double::null() if str points to a string that does not convert to a
    // double value.
    static DoublePtr NewCanonical(const String& str);
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedDouble));
    }
  
    static intptr_t value_offset() { return OFFSET_OF(UntaggedDouble, value_); }
  
   private:
    void set_value(double value) const;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Double, Number);
    friend class Class;
    friend class Number;
  };
  
  // TODO(http://dartbug.com/46716): Recognize Symbol in the VM.
  class Symbol : public AllStatic {
   public:
    static bool IsSymbolCid(Thread* thread, classid_t class_id);
  
    static uint32_t CanonicalizeHash(Thread* thread, const Instance& instance);
  };
  
  // String may not be '\0' terminated.
  class String : public Instance {
   public:
    static const intptr_t kOneByteChar = 1;
    static const intptr_t kTwoByteChar = 2;
  
  // All strings share the same maximum element count to keep things
  // simple.  We choose a value that will prevent integer overflow for
  // 2 byte strings, since it is the worst case.
  #if defined(HASH_IN_OBJECT_HEADER)
    static const intptr_t kSizeofRawString = sizeof(UntaggedInstance) + kWordSize;
  #else
    static const intptr_t kSizeofRawString =
        sizeof(UntaggedInstance) + 2 * kWordSize;
  #endif
    static const intptr_t kMaxElements = kSmiMax / kTwoByteChar;
  
    static intptr_t HeaderSize() { return String::kSizeofRawString; }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedString));
    }
  
    class CodePointIterator : public ValueObject {
     public:
      explicit CodePointIterator(const String& str)
          : str_(str), ch_(0), index_(-1), end_(str.Length()) {
        ASSERT(!str_.IsNull());
      }
  
      CodePointIterator(const String& str, intptr_t start, intptr_t length)
          : str_(str), ch_(0), index_(start - 1), end_(start + length) {
        ASSERT(start >= 0);
        ASSERT(end_ <= str.Length());
      }
  
      int32_t Current() const {
        ASSERT(index_ >= 0);
        ASSERT(index_ < end_);
        return ch_;
      }
  
      bool Next();
  
     private:
      const String& str_;
      int32_t ch_;
      intptr_t index_;
      intptr_t end_;
      DISALLOW_IMPLICIT_CONSTRUCTORS(CodePointIterator);
    };
  
    intptr_t Length() const { return LengthOf(ptr()); }
    static intptr_t LengthOf(StringPtr obj) {
      return Smi::Value(obj->untag()->length());
    }
    static intptr_t length_offset() { return OFFSET_OF(UntaggedString, length_); }
  
    uword Hash() const {
      uword result = GetCachedHash(ptr());
      if (result != 0) {
        return result;
      }
      result = String::Hash(*this, 0, this->Length());
      uword set_hash = SetCachedHashIfNotSet(ptr(), result);
      ASSERT(set_hash == result);
      return result;
    }
  
    static uword Hash(StringPtr raw);
  
    bool HasHash() const {
      ASSERT(Smi::New(0) == nullptr);
      return GetCachedHash(ptr()) != 0;
    }
  
    bool IsRecursive() const { return false; }  // Required by HashSet templates.
  
    static intptr_t hash_offset() {
  #if defined(HASH_IN_OBJECT_HEADER)
      COMPILE_ASSERT(UntaggedObject::kHashTagPos % kBitsPerByte == 0);
      return OFFSET_OF(UntaggedObject, tags_) +
             UntaggedObject::kHashTagPos / kBitsPerByte;
  #else
      return OFFSET_OF(UntaggedString, hash_);
  #endif
    }
    static uword Hash(const String& str, intptr_t begin_index, intptr_t len);
    static uword Hash(const char* characters, intptr_t len);
    static uword Hash(const uint16_t* characters, intptr_t len);
    static uword Hash(const int32_t* characters, intptr_t len);
    static uword HashRawSymbol(const StringPtr symbol) {
      ASSERT(symbol->untag()->IsCanonical());
      const uword result = GetCachedHash(symbol);
      ASSERT(result != 0);
      return result;
    }
  
    // Returns the hash of str1 + str2.
    static uword HashConcat(const String& str1, const String& str2);
  
    virtual ObjectPtr HashCode() const { return Integer::New(Hash()); }
  
    uint16_t CharAt(intptr_t index) const { return CharAt(ptr(), index); }
    static uint16_t CharAt(StringPtr str, intptr_t index);
  
    intptr_t CharSize() const;
  
    inline bool Equals(const String& str) const;
  
    bool Equals(const String& str,
                intptr_t begin_index,  // begin index on 'str'.
                intptr_t len) const;   // len on 'str'.
  
    // Compares to a '\0' terminated array of UTF-8 encoded characters.
    bool Equals(const char* cstr) const;
  
    // Compares to an array of Latin-1 encoded characters.
    bool EqualsLatin1(const uint8_t* characters, intptr_t len) const {
      return Equals(characters, len);
    }
  
    // Compares to an array of UTF-16 encoded characters.
    bool Equals(const uint16_t* characters, intptr_t len) const;
  
    // Compares to an array of UTF-32 encoded characters.
    bool Equals(const int32_t* characters, intptr_t len) const;
  
    // True iff this string equals str1 + str2.
    bool EqualsConcat(const String& str1, const String& str2) const;
  
    virtual bool OperatorEquals(const Instance& other) const {
      return Equals(other);
    }
    virtual bool CanonicalizeEquals(const Instance& other) const {
      return Equals(other);
    }
    virtual uint32_t CanonicalizeHash() const { return Hash(); }
    virtual bool Equals(const Instance& other) const;
  
    intptr_t CompareTo(const String& other) const;
  
    bool StartsWith(const String& other) const {
      NoSafepointScope no_safepoint;
      return StartsWith(ptr(), other.ptr());
    }
    static bool StartsWith(StringPtr str, StringPtr prefix);
    bool EndsWith(const String& other) const;
  
    // Strings are canonicalized using the symbol table.
    // Caller must hold IsolateGroup::constant_canonicalization_mutex_.
    virtual InstancePtr CanonicalizeLocked(Thread* thread) const;
  
  #if defined(DEBUG)
    // Check if string is canonical.
    virtual bool CheckIsCanonical(Thread* thread) const;
  #endif  // DEBUG
  
    bool IsSymbol() const { return ptr()->untag()->IsCanonical(); }
  
    bool IsOneByteString() const {
      return ptr()->GetClassId() == kOneByteStringCid;
    }
  
    bool IsTwoByteString() const {
      return ptr()->GetClassId() == kTwoByteStringCid;
    }
  
    bool IsExternalOneByteString() const {
      return ptr()->GetClassId() == kExternalOneByteStringCid;
    }
  
    bool IsExternalTwoByteString() const {
      return ptr()->GetClassId() == kExternalTwoByteStringCid;
    }
  
    bool IsExternal() const {
      return IsExternalStringClassId(ptr()->GetClassId());
    }
  
    void* GetPeer() const;
  
    char* ToMallocCString() const;
    void ToUTF8(uint8_t* utf8_array, intptr_t array_len) const;
    static const char* ToCString(Thread* thread, StringPtr ptr);
  
    // Creates a new String object from a C string that is assumed to contain
    // UTF-8 encoded characters and '\0' is considered a termination character.
    // TODO(7123) - Rename this to FromCString(....).
    static StringPtr New(const char* cstr, Heap::Space space = Heap::kNew);
  
    // Creates a new String object from an array of UTF-8 encoded characters.
    static StringPtr FromUTF8(const uint8_t* utf8_array,
                              intptr_t array_len,
                              Heap::Space space = Heap::kNew);
  
    // Creates a new String object from an array of Latin-1 encoded characters.
    static StringPtr FromLatin1(const uint8_t* latin1_array,
                                intptr_t array_len,
                                Heap::Space space = Heap::kNew);
  
    // Creates a new String object from an array of UTF-16 encoded characters.
    static StringPtr FromUTF16(const uint16_t* utf16_array,
                               intptr_t array_len,
                               Heap::Space space = Heap::kNew);
  
    // Creates a new String object from an array of UTF-32 encoded characters.
    static StringPtr FromUTF32(const int32_t* utf32_array,
                               intptr_t array_len,
                               Heap::Space space = Heap::kNew);
  
    // Create a new String object from another Dart String instance.
    static StringPtr New(const String& str, Heap::Space space = Heap::kNew);
  
    // Creates a new External String object using the specified array of
    // UTF-8 encoded characters as the external reference.
    static StringPtr NewExternal(const uint8_t* utf8_array,
                                 intptr_t array_len,
                                 void* peer,
                                 intptr_t external_allocation_size,
                                 Dart_HandleFinalizer callback,
                                 Heap::Space = Heap::kNew);
  
    // Creates a new External String object using the specified array of
    // UTF-16 encoded characters as the external reference.
    static StringPtr NewExternal(const uint16_t* utf16_array,
                                 intptr_t array_len,
                                 void* peer,
                                 intptr_t external_allocation_size,
                                 Dart_HandleFinalizer callback,
                                 Heap::Space = Heap::kNew);
  
    static void Copy(const String& dst,
                     intptr_t dst_offset,
                     const uint8_t* characters,
                     intptr_t len);
    static void Copy(const String& dst,
                     intptr_t dst_offset,
                     const uint16_t* characters,
                     intptr_t len);
    static void Copy(const String& dst,
                     intptr_t dst_offset,
                     const String& src,
                     intptr_t src_offset,
                     intptr_t len);
  
    static StringPtr EscapeSpecialCharacters(const String& str);
    // Encodes 'str' for use in an Internationalized Resource Identifier (IRI),
    // a generalization of URI (percent-encoding). See RFC 3987.
    static const char* EncodeIRI(const String& str);
    // Returns null if 'str' is not a valid encoding.
    static StringPtr DecodeIRI(const String& str);
    static StringPtr Concat(const String& str1,
                            const String& str2,
                            Heap::Space space = Heap::kNew);
    static StringPtr ConcatAll(const Array& strings,
                               Heap::Space space = Heap::kNew);
    // Concat all strings in 'strings' from 'start' to 'end' (excluding).
    static StringPtr ConcatAllRange(const Array& strings,
                                    intptr_t start,
                                    intptr_t end,
                                    Heap::Space space = Heap::kNew);
  
    static StringPtr SubString(const String& str,
                               intptr_t begin_index,
                               Heap::Space space = Heap::kNew);
    static StringPtr SubString(const String& str,
                               intptr_t begin_index,
                               intptr_t length,
                               Heap::Space space = Heap::kNew) {
      return SubString(Thread::Current(), str, begin_index, length, space);
    }
    static StringPtr SubString(Thread* thread,
                               const String& str,
                               intptr_t begin_index,
                               intptr_t length,
                               Heap::Space space = Heap::kNew);
  
    static StringPtr Transform(int32_t (*mapping)(int32_t ch),
                               const String& str,
                               Heap::Space space = Heap::kNew);
  
    static StringPtr ToUpperCase(const String& str,
                                 Heap::Space space = Heap::kNew);
    static StringPtr ToLowerCase(const String& str,
                                 Heap::Space space = Heap::kNew);
  
    static StringPtr RemovePrivateKey(const String& name);
  
    static const char* ScrubName(const String& name, bool is_extension = false);
    static StringPtr ScrubNameRetainPrivate(const String& name,
                                            bool is_extension = false);
  
    static bool EqualsIgnoringPrivateKey(const String& str1, const String& str2);
  
    static StringPtr NewFormatted(const char* format, ...) PRINTF_ATTRIBUTE(1, 2);
    static StringPtr NewFormatted(Heap::Space space, const char* format, ...)
        PRINTF_ATTRIBUTE(2, 3);
    static StringPtr NewFormattedV(const char* format,
                                   va_list args,
                                   Heap::Space space = Heap::kNew);
  
    static bool ParseDouble(const String& str,
                            intptr_t start,
                            intptr_t end,
                            double* result);
  
  #if !defined(HASH_IN_OBJECT_HEADER)
    static uint32_t GetCachedHash(const StringPtr obj) {
      return Smi::Value(obj->untag()->hash_);
    }
  
    static uint32_t SetCachedHashIfNotSet(StringPtr obj, uint32_t hash) {
      ASSERT(Smi::Value(obj->untag()->hash_) == 0 ||
             Smi::Value(obj->untag()->hash_) == static_cast<intptr_t>(hash));
      return SetCachedHash(obj, hash);
    }
    static uint32_t SetCachedHash(StringPtr obj, uint32_t hash) {
      obj->untag()->hash_ = Smi::New(hash);
      return hash;
    }
  #else
    static uint32_t SetCachedHash(StringPtr obj, uint32_t hash) {
      return Object::SetCachedHashIfNotSet(obj, hash);
    }
  #endif
  
   protected:
    // These two operate on an array of Latin-1 encoded characters.
    // They are protected to avoid mistaking Latin-1 for UTF-8, but used
    // by friendly templated code (e.g., Symbols).
    bool Equals(const uint8_t* characters, intptr_t len) const;
    static uword Hash(const uint8_t* characters, intptr_t len);
  
    void SetLength(intptr_t value) const {
      // This is only safe because we create a new Smi, which does not cause
      // heap allocation.
      untag()->set_length(Smi::New(value));
    }
  
    void SetHash(intptr_t value) const {
      const intptr_t hash_set = SetCachedHashIfNotSet(ptr(), value);
      ASSERT(hash_set == value);
    }
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(String, Instance);
  
    friend class Class;
    friend class Symbols;
    friend class StringSlice;  // SetHash
    template <typename CharType>
    friend class CharArray;     // SetHash
    friend class ConcatString;  // SetHash
    friend class OneByteString;
    friend class TwoByteString;
    friend class ExternalOneByteString;
    friend class ExternalTwoByteString;
    friend class UntaggedOneByteString;
    friend class RODataSerializationCluster;  // SetHash
    friend class Pass2Visitor;                // Stack "handle"
  };
  
  // Synchronize with implementation in compiler (intrinsifier).
  class StringHasher : ValueObject {
   public:
    StringHasher() : hash_(0) {}
    void Add(uint16_t code_unit) { hash_ = CombineHashes(hash_, code_unit); }
    void Add(const uint8_t* code_units, intptr_t len) {
      while (len > 0) {
        Add(*code_units);
        code_units++;
        len--;
      }
    }
    void Add(const uint16_t* code_units, intptr_t len) {
      while (len > 0) {
        Add(LoadUnaligned(code_units));
        code_units++;
        len--;
      }
    }
    void Add(const String& str, intptr_t begin_index, intptr_t len);
    intptr_t Finalize() { return FinalizeHash(hash_, String::kHashBits); }
  
   private:
    uint32_t hash_;
  };
  
  class OneByteString : public AllStatic {
   public:
    ALLSTATIC_CONTAINS_COMPRESSED_IMPLEMENTATION(OneByteString, String);
  
    static uint16_t CharAt(const String& str, intptr_t index) {
      ASSERT(str.IsOneByteString());
      NoSafepointScope no_safepoint;
      return OneByteString::CharAt(static_cast<OneByteStringPtr>(str.ptr()),
                                   index);
    }
  
    static uint16_t CharAt(OneByteStringPtr str, intptr_t index) {
      ASSERT(index >= 0 && index < String::LengthOf(str));
      return str->untag()->data()[index];
    }
  
    static void SetCharAt(const String& str, intptr_t index, uint8_t code_unit) {
      NoSafepointScope no_safepoint;
      *CharAddr(str, index) = code_unit;
    }
    static OneByteStringPtr EscapeSpecialCharacters(const String& str);
    // We use the same maximum elements for all strings.
    static const intptr_t kBytesPerElement = 1;
    static const intptr_t kMaxElements = String::kMaxElements;
  
    struct ArrayTraits {
      static intptr_t elements_start_offset() {
        return sizeof(UntaggedOneByteString);
      }
      static constexpr intptr_t kElementSize = kBytesPerElement;
    };
  
    static intptr_t data_offset() {
      return OFFSET_OF_RETURNED_VALUE(UntaggedOneByteString, data);
    }
  
    static intptr_t UnroundedSize(OneByteStringPtr str) {
      return UnroundedSize(Smi::Value(str->untag()->length()));
    }
    static intptr_t UnroundedSize(intptr_t len) {
      return sizeof(UntaggedOneByteString) + (len * kBytesPerElement);
    }
    static intptr_t InstanceSize() {
      ASSERT(sizeof(UntaggedOneByteString) ==
             OFFSET_OF_RETURNED_VALUE(UntaggedOneByteString, data));
      return 0;
    }
    static intptr_t InstanceSize(intptr_t len) {
      ASSERT(sizeof(UntaggedOneByteString) == String::kSizeofRawString);
      ASSERT(0 <= len && len <= kMaxElements);
      return String::RoundedAllocationSize(UnroundedSize(len));
    }
  
    static OneByteStringPtr New(intptr_t len, Heap::Space space);
    static OneByteStringPtr New(const char* c_string,
                                Heap::Space space = Heap::kNew) {
      return New(reinterpret_cast<const uint8_t*>(c_string), strlen(c_string),
                 space);
    }
    static OneByteStringPtr New(const uint8_t* characters,
                                intptr_t len,
                                Heap::Space space);
    static OneByteStringPtr New(const uint16_t* characters,
                                intptr_t len,
                                Heap::Space space);
    static OneByteStringPtr New(const int32_t* characters,
                                intptr_t len,
                                Heap::Space space);
    static OneByteStringPtr New(const String& str, Heap::Space space);
    // 'other' must be OneByteString.
    static OneByteStringPtr New(const String& other_one_byte_string,
                                intptr_t other_start_index,
                                intptr_t other_len,
                                Heap::Space space);
  
    static OneByteStringPtr New(const TypedDataBase& other_typed_data,
                                intptr_t other_start_index,
                                intptr_t other_len,
                                Heap::Space space = Heap::kNew);
  
    static OneByteStringPtr Concat(const String& str1,
                                   const String& str2,
                                   Heap::Space space);
    static OneByteStringPtr ConcatAll(const Array& strings,
                                      intptr_t start,
                                      intptr_t end,
                                      intptr_t len,
                                      Heap::Space space);
  
    static OneByteStringPtr Transform(int32_t (*mapping)(int32_t ch),
                                      const String& str,
                                      Heap::Space space);
  
    // High performance version of substring for one-byte strings.
    // "str" must be OneByteString.
    static OneByteStringPtr SubStringUnchecked(const String& str,
                                               intptr_t begin_index,
                                               intptr_t length,
                                               Heap::Space space);
  
    static const ClassId kClassId = kOneByteStringCid;
  
    static OneByteStringPtr null() {
      return static_cast<OneByteStringPtr>(Object::null());
    }
  
   private:
    static OneByteStringPtr raw(const String& str) {
      return static_cast<OneByteStringPtr>(str.ptr());
    }
  
    static const UntaggedOneByteString* untag(const String& str) {
      return reinterpret_cast<const UntaggedOneByteString*>(str.untag());
    }
  
    static uint8_t* CharAddr(const String& str, intptr_t index) {
      ASSERT((index >= 0) && (index < str.Length()));
      ASSERT(str.IsOneByteString());
      return &str.UnsafeMutableNonPointer(untag(str)->data())[index];
    }
  
    static uint8_t* DataStart(const String& str) {
      ASSERT(str.IsOneByteString());
      return &str.UnsafeMutableNonPointer(untag(str)->data())[0];
    }
  
    friend class Class;
    friend class ExternalOneByteString;
    friend class ImageWriter;
    friend class String;
    friend class StringHasher;
    friend class Symbols;
    friend class Utf8;
    friend class OneByteStringMessageSerializationCluster;
  };
  
  class TwoByteString : public AllStatic {
   public:
    ALLSTATIC_CONTAINS_COMPRESSED_IMPLEMENTATION(TwoByteString, String);
  
    static uint16_t CharAt(const String& str, intptr_t index) {
      ASSERT(str.IsTwoByteString());
      NoSafepointScope no_safepoint;
      return TwoByteString::CharAt(static_cast<TwoByteStringPtr>(str.ptr()),
                                   index);
    }
  
    static uint16_t CharAt(TwoByteStringPtr str, intptr_t index) {
      ASSERT(index >= 0 && index < String::LengthOf(str));
      return str->untag()->data()[index];
    }
  
    static void SetCharAt(const String& str, intptr_t index, uint16_t ch) {
      NoSafepointScope no_safepoint;
      *CharAddr(str, index) = ch;
    }
  
    static TwoByteStringPtr EscapeSpecialCharacters(const String& str);
  
    // We use the same maximum elements for all strings.
    static const intptr_t kBytesPerElement = 2;
    static const intptr_t kMaxElements = String::kMaxElements;
  
    struct ArrayTraits {
      static intptr_t elements_start_offset() {
        return sizeof(UntaggedTwoByteString);
      }
      static constexpr intptr_t kElementSize = kBytesPerElement;
    };
  
    static intptr_t data_offset() {
      return OFFSET_OF_RETURNED_VALUE(UntaggedTwoByteString, data);
    }
    static intptr_t UnroundedSize(TwoByteStringPtr str) {
      return UnroundedSize(Smi::Value(str->untag()->length()));
    }
    static intptr_t UnroundedSize(intptr_t len) {
      return sizeof(UntaggedTwoByteString) + (len * kBytesPerElement);
    }
    static intptr_t InstanceSize() {
      ASSERT(sizeof(UntaggedTwoByteString) ==
             OFFSET_OF_RETURNED_VALUE(UntaggedTwoByteString, data));
      return 0;
    }
    static intptr_t InstanceSize(intptr_t len) {
      ASSERT(sizeof(UntaggedTwoByteString) == String::kSizeofRawString);
      ASSERT(0 <= len && len <= kMaxElements);
      return String::RoundedAllocationSize(UnroundedSize(len));
    }
  
    static TwoByteStringPtr New(intptr_t len, Heap::Space space);
    static TwoByteStringPtr New(const uint16_t* characters,
                                intptr_t len,
                                Heap::Space space);
    static TwoByteStringPtr New(intptr_t utf16_len,
                                const int32_t* characters,
                                intptr_t len,
                                Heap::Space space);
    static TwoByteStringPtr New(const String& str, Heap::Space space);
  
    static TwoByteStringPtr New(const TypedDataBase& other_typed_data,
                                intptr_t other_start_index,
                                intptr_t other_len,
                                Heap::Space space = Heap::kNew);
  
    static TwoByteStringPtr Concat(const String& str1,
                                   const String& str2,
                                   Heap::Space space);
    static TwoByteStringPtr ConcatAll(const Array& strings,
                                      intptr_t start,
                                      intptr_t end,
                                      intptr_t len,
                                      Heap::Space space);
  
    static TwoByteStringPtr Transform(int32_t (*mapping)(int32_t ch),
                                      const String& str,
                                      Heap::Space space);
  
    static TwoByteStringPtr null() {
      return static_cast<TwoByteStringPtr>(Object::null());
    }
  
    static const ClassId kClassId = kTwoByteStringCid;
  
   private:
    static TwoByteStringPtr raw(const String& str) {
      return static_cast<TwoByteStringPtr>(str.ptr());
    }
  
    static const UntaggedTwoByteString* untag(const String& str) {
      return reinterpret_cast<const UntaggedTwoByteString*>(str.untag());
    }
  
    static uint16_t* CharAddr(const String& str, intptr_t index) {
      ASSERT((index >= 0) && (index < str.Length()));
      ASSERT(str.IsTwoByteString());
      return &str.UnsafeMutableNonPointer(untag(str)->data())[index];
    }
  
    // Use this instead of CharAddr(0).  It will not assert that the index is <
    // length.
    static uint16_t* DataStart(const String& str) {
      ASSERT(str.IsTwoByteString());
      return &str.UnsafeMutableNonPointer(untag(str)->data())[0];
    }
  
    friend class Class;
    friend class ImageWriter;
    friend class String;
    friend class StringHasher;
    friend class Symbols;
    friend class TwoByteStringMessageSerializationCluster;
  };
  
  class ExternalOneByteString : public AllStatic {
   public:
    ALLSTATIC_CONTAINS_COMPRESSED_IMPLEMENTATION(ExternalOneByteString, String);
  
    static uint16_t CharAt(const String& str, intptr_t index) {
      ASSERT(str.IsExternalOneByteString());
      NoSafepointScope no_safepoint;
      return ExternalOneByteString::CharAt(
          static_cast<ExternalOneByteStringPtr>(str.ptr()), index);
    }
  
    static uint16_t CharAt(ExternalOneByteStringPtr str, intptr_t index) {
      ASSERT(index >= 0 && index < String::LengthOf(str));
      return str->untag()->external_data_[index];
    }
  
    static void* GetPeer(const String& str) { return untag(str)->peer_; }
  
    static intptr_t external_data_offset() {
      return OFFSET_OF(UntaggedExternalOneByteString, external_data_);
    }
  
    // We use the same maximum elements for all strings.
    static const intptr_t kBytesPerElement = 1;
    static const intptr_t kMaxElements = String::kMaxElements;
  
    static intptr_t InstanceSize() {
      return String::RoundedAllocationSize(sizeof(UntaggedExternalOneByteString));
    }
  
    static ExternalOneByteStringPtr New(const uint8_t* characters,
                                        intptr_t len,
                                        void* peer,
                                        intptr_t external_allocation_size,
                                        Dart_HandleFinalizer callback,
                                        Heap::Space space);
  
    static ExternalOneByteStringPtr null() {
      return static_cast<ExternalOneByteStringPtr>(Object::null());
    }
  
    static OneByteStringPtr EscapeSpecialCharacters(const String& str);
    static OneByteStringPtr EncodeIRI(const String& str);
    static OneByteStringPtr DecodeIRI(const String& str);
  
    static const ClassId kClassId = kExternalOneByteStringCid;
  
   private:
    static ExternalOneByteStringPtr raw(const String& str) {
      return static_cast<ExternalOneByteStringPtr>(str.ptr());
    }
  
    static const UntaggedExternalOneByteString* untag(const String& str) {
      return reinterpret_cast<const UntaggedExternalOneByteString*>(str.untag());
    }
  
    static const uint8_t* CharAddr(const String& str, intptr_t index) {
      ASSERT((index >= 0) && (index < str.Length()));
      ASSERT(str.IsExternalOneByteString());
      return &(untag(str)->external_data_[index]);
    }
  
    static const uint8_t* DataStart(const String& str) {
      ASSERT(str.IsExternalOneByteString());
      return untag(str)->external_data_;
    }
  
    static void SetExternalData(const String& str,
                                const uint8_t* data,
                                void* peer) {
      ASSERT(str.IsExternalOneByteString());
      ASSERT(!IsolateGroup::Current()->heap()->Contains(
          reinterpret_cast<uword>(data)));
      str.StoreNonPointer(&untag(str)->external_data_, data);
      str.StoreNonPointer(&untag(str)->peer_, peer);
    }
  
    static void Finalize(void* isolate_callback_data,
                         Dart_WeakPersistentHandle handle,
                         void* peer);
  
    static intptr_t NextFieldOffset() {
      // Indicates this class cannot be extended by dart code.
      return -kWordSize;
    }
  
    friend class Class;
    friend class String;
    friend class StringHasher;
    friend class Symbols;
    friend class Utf8;
  };
  
  class ExternalTwoByteString : public AllStatic {
   public:
    ALLSTATIC_CONTAINS_COMPRESSED_IMPLEMENTATION(ExternalTwoByteString, String);
  
    static uint16_t CharAt(const String& str, intptr_t index) {
      ASSERT(str.IsExternalTwoByteString());
      NoSafepointScope no_safepoint;
      return ExternalTwoByteString::CharAt(
          static_cast<ExternalTwoByteStringPtr>(str.ptr()), index);
    }
  
    static uint16_t CharAt(ExternalTwoByteStringPtr str, intptr_t index) {
      ASSERT(index >= 0 && index < String::LengthOf(str));
      return str->untag()->external_data_[index];
    }
  
    static void* GetPeer(const String& str) { return untag(str)->peer_; }
  
    static intptr_t external_data_offset() {
      return OFFSET_OF(UntaggedExternalTwoByteString, external_data_);
    }
  
    // We use the same maximum elements for all strings.
    static const intptr_t kBytesPerElement = 2;
    static const intptr_t kMaxElements = String::kMaxElements;
  
    static intptr_t InstanceSize() {
      return String::RoundedAllocationSize(sizeof(UntaggedExternalTwoByteString));
    }
  
    static ExternalTwoByteStringPtr New(const uint16_t* characters,
                                        intptr_t len,
                                        void* peer,
                                        intptr_t external_allocation_size,
                                        Dart_HandleFinalizer callback,
                                        Heap::Space space = Heap::kNew);
  
    static ExternalTwoByteStringPtr null() {
      return static_cast<ExternalTwoByteStringPtr>(Object::null());
    }
  
    static const ClassId kClassId = kExternalTwoByteStringCid;
  
   private:
    static ExternalTwoByteStringPtr raw(const String& str) {
      return static_cast<ExternalTwoByteStringPtr>(str.ptr());
    }
  
    static const UntaggedExternalTwoByteString* untag(const String& str) {
      return reinterpret_cast<const UntaggedExternalTwoByteString*>(str.untag());
    }
  
    static const uint16_t* CharAddr(const String& str, intptr_t index) {
      ASSERT((index >= 0) && (index < str.Length()));
      ASSERT(str.IsExternalTwoByteString());
      return &(untag(str)->external_data_[index]);
    }
  
    static const uint16_t* DataStart(const String& str) {
      ASSERT(str.IsExternalTwoByteString());
      return untag(str)->external_data_;
    }
  
    static void SetExternalData(const String& str,
                                const uint16_t* data,
                                void* peer) {
      ASSERT(str.IsExternalTwoByteString());
      ASSERT(!IsolateGroup::Current()->heap()->Contains(
          reinterpret_cast<uword>(data)));
      str.StoreNonPointer(&untag(str)->external_data_, data);
      str.StoreNonPointer(&untag(str)->peer_, peer);
    }
  
    static void Finalize(void* isolate_callback_data,
                         Dart_WeakPersistentHandle handle,
                         void* peer);
  
    static intptr_t NextFieldOffset() {
      // Indicates this class cannot be extended by dart code.
      return -kWordSize;
    }
  
    friend class Class;
    friend class String;
    friend class StringHasher;
    friend class Symbols;
  };
  
  // Class Bool implements Dart core class bool.
  class Bool : public Instance {
   public:
    bool value() const { return untag()->value_; }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedBool));
    }
  
    static const Bool& True() { return Object::bool_true(); }
  
    static const Bool& False() { return Object::bool_false(); }
  
    static const Bool& Get(bool value) {
      return value ? Bool::True() : Bool::False();
    }
  
    virtual uint32_t CanonicalizeHash() const {
      return ptr() == True().ptr() ? 1231 : 1237;
    }
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Bool, Instance);
    friend class Class;
    friend class Object;  // To initialize the true and false values.
  };
  
  class Array : public Instance {
   public:
    // Returns `true` if we use card marking for arrays of length [array_length].
    static bool UseCardMarkingForAllocation(const intptr_t array_length) {
      return Array::InstanceSize(array_length) > Heap::kNewAllocatableSize;
    }
  
    intptr_t Length() const { return LengthOf(ptr()); }
    static intptr_t LengthOf(const ArrayPtr array) {
      return Smi::Value(array->untag()->length());
    }
  
    static intptr_t length_offset() { return OFFSET_OF(UntaggedArray, length_); }
    static intptr_t data_offset() {
      return OFFSET_OF_RETURNED_VALUE(UntaggedArray, data);
    }
    static intptr_t element_offset(intptr_t index) {
      return OFFSET_OF_RETURNED_VALUE(UntaggedArray, data) +
             kBytesPerElement * index;
    }
    static intptr_t index_at_offset(intptr_t offset_in_bytes) {
      intptr_t index = (offset_in_bytes - data_offset()) / kBytesPerElement;
      ASSERT(index >= 0);
      return index;
    }
  
    struct ArrayTraits {
      static intptr_t elements_start_offset() { return Array::data_offset(); }
  
      static constexpr intptr_t kElementSize = kCompressedWordSize;
    };
  
    static bool Equals(ArrayPtr a, ArrayPtr b) {
      if (a == b) return true;
      if (a->IsRawNull() || b->IsRawNull()) return false;
      if (a->untag()->length() != b->untag()->length()) return false;
      if (a->untag()->type_arguments() != b->untag()->type_arguments()) {
        return false;
      }
      const intptr_t length = LengthOf(a);
      return memcmp(a->untag()->data(), b->untag()->data(),
                    kBytesPerElement * length) == 0;
    }
    bool Equals(const Array& other) const {
      NoSafepointScope scope;
      return Equals(ptr(), other.ptr());
    }
  
    static CompressedObjectPtr* DataOf(ArrayPtr array) {
      return array->untag()->data();
    }
  
    template <std::memory_order order = std::memory_order_relaxed>
    ObjectPtr At(intptr_t index) const {
      return untag()->element<order>(index);
    }
    template <std::memory_order order = std::memory_order_relaxed>
    void SetAt(intptr_t index, const Object& value) const {
      // TODO(iposva): Add storing NoSafepointScope.
      untag()->set_element<order>(index, value.ptr());
    }
  
    // Access to the array with acquire release semantics.
    ObjectPtr AtAcquire(intptr_t index) const {
      return untag()->element<std::memory_order_acquire>(index);
    }
    void SetAtRelease(intptr_t index, const Object& value) const {
      untag()->set_element<std::memory_order_release>(index, value.ptr());
    }
  
    bool IsImmutable() const { return ptr()->GetClassId() == kImmutableArrayCid; }
  
    // Position of element type in type arguments.
    static const intptr_t kElementTypeTypeArgPos = 0;
  
    virtual TypeArgumentsPtr GetTypeArguments() const {
      return untag()->type_arguments();
    }
    virtual void SetTypeArguments(const TypeArguments& value) const {
      // An Array is raw or takes one type argument. However, its type argument
      // vector may be longer than 1 due to a type optimization reusing the type
      // argument vector of the instantiator.
      ASSERT(value.IsNull() ||
             ((value.Length() >= 1) &&
              value.IsInstantiated() /*&& value.IsCanonical()*/));
      // TODO(asiva): Values read from a message snapshot are not properly marked
      // as canonical. See for example tests/isolate/mandel_isolate_test.dart.
      StoreArrayPointer(&untag()->type_arguments_, value.ptr());
    }
  
    virtual bool CanonicalizeEquals(const Instance& other) const;
    virtual uint32_t CanonicalizeHash() const;
  
    static const intptr_t kBytesPerElement = ArrayTraits::kElementSize;
    static const intptr_t kMaxElements = kSmiMax / kBytesPerElement;
    static const intptr_t kMaxNewSpaceElements =
        (Heap::kNewAllocatableSize - sizeof(UntaggedArray)) / kBytesPerElement;
  
    static intptr_t type_arguments_offset() {
      return OFFSET_OF(UntaggedArray, type_arguments_);
    }
  
    static bool IsValidLength(intptr_t len) {
      return 0 <= len && len <= kMaxElements;
    }
  
    static intptr_t InstanceSize() {
      ASSERT(sizeof(UntaggedArray) ==
             OFFSET_OF_RETURNED_VALUE(UntaggedArray, data));
      return 0;
    }
  
    static intptr_t InstanceSize(intptr_t len) {
      // Ensure that variable length data is not adding to the object length.
      ASSERT(sizeof(UntaggedArray) ==
             (sizeof(UntaggedInstance) + (2 * kBytesPerElement)));
      ASSERT(IsValidLength(len));
      return RoundedAllocationSize(sizeof(UntaggedArray) +
                                   (len * kBytesPerElement));
    }
  
    virtual void CanonicalizeFieldsLocked(Thread* thread) const;
  
    // Make the array immutable to Dart code by switching the class pointer
    // to ImmutableArray.
    void MakeImmutable() const;
  
    static ArrayPtr New(intptr_t len, Heap::Space space = Heap::kNew);
    static ArrayPtr New(intptr_t len,
                        const AbstractType& element_type,
                        Heap::Space space = Heap::kNew);
  
    // Creates and returns a new array with 'new_length'. Copies all elements from
    // 'source' to the new array. 'new_length' must be greater than or equal to
    // 'source.Length()'. 'source' can be null.
    static ArrayPtr Grow(const Array& source,
                         intptr_t new_length,
                         Heap::Space space = Heap::kNew);
  
    // Truncates the array to a given length. 'new_length' must be less than
    // or equal to 'source.Length()'. The remaining unused part of the array is
    // marked as an Array object or a regular Object so that it can be traversed
    // during garbage collection.
    void Truncate(intptr_t new_length) const;
  
    // Return an Array object that contains all the elements currently present
    // in the specified Growable Object Array. This is done by first truncating
    // the Growable Object Array's backing array to the currently used size and
    // returning the truncated backing array.
    // The backing array of the original Growable Object Array is
    // set to an empty array.
    // If the unique parameter is false, the function is allowed to return
    // a shared Array instance.
    static ArrayPtr MakeFixedLength(const GrowableObjectArray& growable_array,
                                    bool unique = false);
  
    ArrayPtr Slice(intptr_t start, intptr_t count, bool with_type_argument) const;
    ArrayPtr Copy() const {
      return Slice(0, Length(), /*with_type_argument=*/true);
    }
  
   protected:
    static ArrayPtr New(intptr_t class_id,
                        intptr_t len,
                        Heap::Space space = Heap::kNew);
  
   private:
    CompressedObjectPtr const* ObjectAddr(intptr_t index) const {
      // TODO(iposva): Determine if we should throw an exception here.
      ASSERT((index >= 0) && (index < Length()));
      return &untag()->data()[index];
    }
  
    void SetLength(intptr_t value) const { untag()->set_length(Smi::New(value)); }
    void SetLengthRelease(intptr_t value) const {
      untag()->set_length<std::memory_order_release>(Smi::New(value));
    }
  
    template <typename type,
              std::memory_order order = std::memory_order_relaxed,
              typename value_type>
    void StoreArrayPointer(type const* addr, value_type value) const {
      ptr()->untag()->StoreArrayPointer<type, order, value_type>(addr, value);
    }
  
    // Store a range of pointers [from, from + count) into [to, to + count).
    // TODO(koda): Use this to fix Object::Clone's broken store buffer logic.
    void StoreArrayPointers(CompressedObjectPtr const* to,
                            CompressedObjectPtr const* from,
                            intptr_t count) {
      ASSERT(Contains(reinterpret_cast<uword>(to)));
      if (ptr()->IsNewObject()) {
        memmove(const_cast<CompressedObjectPtr*>(to), from,
                count * kBytesPerElement);
      } else {
        const uword heap_base = ptr()->heap_base();
        for (intptr_t i = 0; i < count; ++i) {
          StoreArrayPointer(&to[i], from[i].Decompress(heap_base));
        }
      }
    }
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Array, Instance);
    friend class Class;
    friend class ImmutableArray;
    friend class Object;
    friend class String;
    friend class MessageDeserializer;
  };
  
  class ImmutableArray : public AllStatic {
   public:
    static constexpr bool ContainsCompressedPointers() {
      return Array::ContainsCompressedPointers();
    }
  
    static ImmutableArrayPtr New(intptr_t len, Heap::Space space = Heap::kNew);
  
    static const ClassId kClassId = kImmutableArrayCid;
  
    static intptr_t InstanceSize() { return Array::InstanceSize(); }
  
    static intptr_t InstanceSize(intptr_t len) {
      return Array::InstanceSize(len);
    }
  
   private:
    static intptr_t NextFieldOffset() {
      // Indicates this class cannot be extended by dart code.
      return -kWordSize;
    }
  
    static ImmutableArrayPtr raw(const Array& array) {
      return static_cast<ImmutableArrayPtr>(array.ptr());
    }
  
    friend class Class;
  };
  
  class GrowableObjectArray : public Instance {
   public:
    intptr_t Capacity() const {
      NoSafepointScope no_safepoint;
      ASSERT(!IsNull());
      return Smi::Value(DataArray()->length());
    }
    intptr_t Length() const {
      ASSERT(!IsNull());
      return Smi::Value(untag()->length());
    }
    void SetLength(intptr_t value) const {
      // This is only safe because we create a new Smi, which does not cause
      // heap allocation.
      untag()->set_length(Smi::New(value));
    }
  
    ArrayPtr data() const { return untag()->data(); }
    void SetData(const Array& value) const { untag()->set_data(value.ptr()); }
  
    ObjectPtr At(intptr_t index) const {
      NoSafepointScope no_safepoint;
      ASSERT(!IsNull());
      ASSERT(index < Length());
      return data()->untag()->element(index);
    }
    void SetAt(intptr_t index, const Object& value) const {
      ASSERT(!IsNull());
      ASSERT(index < Length());
  
      // TODO(iposva): Add storing NoSafepointScope.
      data()->untag()->set_element(index, value.ptr());
    }
  
    void Add(const Object& value, Heap::Space space = Heap::kNew) const;
  
    void Grow(intptr_t new_capacity, Heap::Space space = Heap::kNew) const;
    ObjectPtr RemoveLast() const;
  
    virtual TypeArgumentsPtr GetTypeArguments() const {
      return untag()->type_arguments();
    }
    virtual void SetTypeArguments(const TypeArguments& value) const {
      // A GrowableObjectArray is raw or takes one type argument. However, its
      // type argument vector may be longer than 1 due to a type optimization
      // reusing the type argument vector of the instantiator.
      ASSERT(value.IsNull() || ((value.Length() >= 1) && value.IsInstantiated() &&
                                value.IsCanonical()));
  
      untag()->set_type_arguments(value.ptr());
    }
  
    // We don't expect a growable object array to be canonicalized.
    virtual bool CanonicalizeEquals(const Instance& other) const {
      UNREACHABLE();
      return false;
    }
  
    // We don't expect a growable object array to be canonicalized.
    virtual InstancePtr CanonicalizeLocked(Thread* thread) const {
      UNREACHABLE();
      return Instance::null();
    }
  
    static intptr_t type_arguments_offset() {
      return OFFSET_OF(UntaggedGrowableObjectArray, type_arguments_);
    }
  
    static intptr_t length_offset() {
      return OFFSET_OF(UntaggedGrowableObjectArray, length_);
    }
    static intptr_t data_offset() {
      return OFFSET_OF(UntaggedGrowableObjectArray, data_);
    }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedGrowableObjectArray));
    }
  
    static GrowableObjectArrayPtr New(Heap::Space space = Heap::kNew) {
      return New(kDefaultInitialCapacity, space);
    }
    static GrowableObjectArrayPtr New(intptr_t capacity,
                                      Heap::Space space = Heap::kNew);
    static GrowableObjectArrayPtr New(const Array& array,
                                      Heap::Space space = Heap::kNew);
  
    static SmiPtr NoSafepointLength(const GrowableObjectArrayPtr array) {
      return array->untag()->length();
    }
  
    static ArrayPtr NoSafepointData(const GrowableObjectArrayPtr array) {
      return array->untag()->data();
    }
  
   private:
    UntaggedArray* DataArray() const { return data()->untag(); }
  
    static const int kDefaultInitialCapacity = 0;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(GrowableObjectArray, Instance);
    friend class Array;
    friend class Class;
  };
  
  class Float32x4 : public Instance {
   public:
    static Float32x4Ptr New(float value0,
                            float value1,
                            float value2,
                            float value3,
                            Heap::Space space = Heap::kNew);
    static Float32x4Ptr New(simd128_value_t value,
                            Heap::Space space = Heap::kNew);
  
    float x() const;
    float y() const;
    float z() const;
    float w() const;
  
    void set_x(float x) const;
    void set_y(float y) const;
    void set_z(float z) const;
    void set_w(float w) const;
  
    simd128_value_t value() const;
    void set_value(simd128_value_t value) const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedFloat32x4));
    }
  
    static intptr_t value_offset() {
      return OFFSET_OF(UntaggedFloat32x4, value_);
    }
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Float32x4, Instance);
    friend class Class;
  };
  
  class Int32x4 : public Instance {
   public:
    static Int32x4Ptr New(int32_t value0,
                          int32_t value1,
                          int32_t value2,
                          int32_t value3,
                          Heap::Space space = Heap::kNew);
    static Int32x4Ptr New(simd128_value_t value, Heap::Space space = Heap::kNew);
  
    int32_t x() const;
    int32_t y() const;
    int32_t z() const;
    int32_t w() const;
  
    void set_x(int32_t x) const;
    void set_y(int32_t y) const;
    void set_z(int32_t z) const;
    void set_w(int32_t w) const;
  
    simd128_value_t value() const;
    void set_value(simd128_value_t value) const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedInt32x4));
    }
  
    static intptr_t value_offset() { return OFFSET_OF(UntaggedInt32x4, value_); }
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Int32x4, Instance);
    friend class Class;
  };
  
  class Float64x2 : public Instance {
   public:
    static Float64x2Ptr New(double value0,
                            double value1,
                            Heap::Space space = Heap::kNew);
    static Float64x2Ptr New(simd128_value_t value,
                            Heap::Space space = Heap::kNew);
  
    double x() const;
    double y() const;
  
    void set_x(double x) const;
    void set_y(double y) const;
  
    simd128_value_t value() const;
    void set_value(simd128_value_t value) const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedFloat64x2));
    }
  
    static intptr_t value_offset() {
      return OFFSET_OF(UntaggedFloat64x2, value_);
    }
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Float64x2, Instance);
    friend class Class;
  };
  
  class PointerBase : public Instance {
   public:
    static intptr_t data_field_offset() {
      return OFFSET_OF(UntaggedPointerBase, data_);
    }
  };
  
  class TypedDataBase : public PointerBase {
   public:
    static intptr_t length_offset() {
      return OFFSET_OF(UntaggedTypedDataBase, length_);
    }
  
    SmiPtr length() const { return untag()->length(); }
  
    intptr_t Length() const {
      ASSERT(!IsNull());
      return Smi::Value(untag()->length());
    }
  
    intptr_t LengthInBytes() const {
      return ElementSizeInBytes(ptr()->GetClassId()) * Length();
    }
  
    TypedDataElementType ElementType() const {
      return ElementType(ptr()->GetClassId());
    }
  
    intptr_t ElementSizeInBytes() const {
      return element_size(ElementType(ptr()->GetClassId()));
    }
  
    static intptr_t ElementSizeInBytes(classid_t cid) {
      return element_size(ElementType(cid));
    }
  
    static TypedDataElementType ElementType(classid_t cid) {
      if (cid == kByteDataViewCid) {
        return kUint8ArrayElement;
      } else if (IsTypedDataClassId(cid)) {
        const intptr_t index =
            (cid - kTypedDataInt8ArrayCid - kTypedDataCidRemainderInternal) / 3;
        return static_cast<TypedDataElementType>(index);
      } else if (IsTypedDataViewClassId(cid)) {
        const intptr_t index =
            (cid - kTypedDataInt8ArrayCid - kTypedDataCidRemainderView) / 3;
        return static_cast<TypedDataElementType>(index);
      } else {
        ASSERT(IsExternalTypedDataClassId(cid));
        const intptr_t index =
            (cid - kTypedDataInt8ArrayCid - kTypedDataCidRemainderExternal) / 3;
        return static_cast<TypedDataElementType>(index);
      }
    }
  
    void* DataAddr(intptr_t byte_offset) const {
      ASSERT((byte_offset == 0) ||
             ((byte_offset > 0) && (byte_offset < LengthInBytes())));
      return reinterpret_cast<void*>(Validate(untag()->data_) + byte_offset);
    }
  
  #define TYPED_GETTER_SETTER(name, type)                                        \
    type Get##name(intptr_t byte_offset) const {                                 \
      NoSafepointScope no_safepoint;                                             \
      return LoadUnaligned(reinterpret_cast<type*>(DataAddr(byte_offset)));      \
    }                                                                            \
    void Set##name(intptr_t byte_offset, type value) const {                     \
      NoSafepointScope no_safepoint;                                             \
      StoreUnaligned(reinterpret_cast<type*>(DataAddr(byte_offset)), value);     \
    }
  
    TYPED_GETTER_SETTER(Int8, int8_t)
    TYPED_GETTER_SETTER(Uint8, uint8_t)
    TYPED_GETTER_SETTER(Int16, int16_t)
    TYPED_GETTER_SETTER(Uint16, uint16_t)
    TYPED_GETTER_SETTER(Int32, int32_t)
    TYPED_GETTER_SETTER(Uint32, uint32_t)
    TYPED_GETTER_SETTER(Int64, int64_t)
    TYPED_GETTER_SETTER(Uint64, uint64_t)
    TYPED_GETTER_SETTER(Float32, float)
    TYPED_GETTER_SETTER(Float64, double)
    TYPED_GETTER_SETTER(Float32x4, simd128_value_t)
    TYPED_GETTER_SETTER(Int32x4, simd128_value_t)
    TYPED_GETTER_SETTER(Float64x2, simd128_value_t)
  
  #undef TYPED_GETTER_SETTER
  
   protected:
    void SetLength(intptr_t value) const {
      ASSERT(value <= Smi::kMaxValue);
      untag()->set_length(Smi::New(value));
    }
  
    virtual uint8_t* Validate(uint8_t* data) const {
      return UnsafeMutableNonPointer(data);
    }
  
   private:
    friend class Class;
  
    static intptr_t element_size(intptr_t index) {
      ASSERT(0 <= index && index < kNumElementSizes);
      intptr_t size = element_size_table[index];
      ASSERT(size != 0);
      return size;
    }
    static const intptr_t kNumElementSizes =
        (kTypedDataFloat64x2ArrayCid - kTypedDataInt8ArrayCid) / 3 + 1;
    static const intptr_t element_size_table[kNumElementSizes];
  
    HEAP_OBJECT_IMPLEMENTATION(TypedDataBase, PointerBase);
  };
  
  class TypedData : public TypedDataBase {
   public:
    virtual bool CanonicalizeEquals(const Instance& other) const;
    virtual uint32_t CanonicalizeHash() const;
  
  #define TYPED_GETTER_SETTER(name, type)                                        \
    type Get##name(intptr_t byte_offset) const {                                 \
      ASSERT((byte_offset >= 0) &&                                               \
             (byte_offset + static_cast<intptr_t>(sizeof(type)) - 1) <           \
                 LengthInBytes());                                               \
      return LoadUnaligned(ReadOnlyDataAddr<type>(byte_offset));                 \
    }                                                                            \
    void Set##name(intptr_t byte_offset, type value) const {                     \
      NoSafepointScope no_safepoint;                                             \
      StoreUnaligned(reinterpret_cast<type*>(DataAddr(byte_offset)), value);     \
    }
  
    TYPED_GETTER_SETTER(Int8, int8_t)
    TYPED_GETTER_SETTER(Uint8, uint8_t)
    TYPED_GETTER_SETTER(Int16, int16_t)
    TYPED_GETTER_SETTER(Uint16, uint16_t)
    TYPED_GETTER_SETTER(Int32, int32_t)
    TYPED_GETTER_SETTER(Uint32, uint32_t)
    TYPED_GETTER_SETTER(Int64, int64_t)
    TYPED_GETTER_SETTER(Uint64, uint64_t)
    TYPED_GETTER_SETTER(Float32, float)
    TYPED_GETTER_SETTER(Float64, double)
    TYPED_GETTER_SETTER(Float32x4, simd128_value_t)
    TYPED_GETTER_SETTER(Int32x4, simd128_value_t)
    TYPED_GETTER_SETTER(Float64x2, simd128_value_t)
  
  #undef TYPED_GETTER_SETTER
  
    static intptr_t data_offset() { return UntaggedTypedData::payload_offset(); }
  
    static intptr_t InstanceSize() {
      ASSERT(sizeof(UntaggedTypedData) ==
             OFFSET_OF_RETURNED_VALUE(UntaggedTypedData, internal_data));
      return 0;
    }
  
    static intptr_t InstanceSize(intptr_t lengthInBytes) {
      ASSERT(0 <= lengthInBytes && lengthInBytes <= kSmiMax);
      return RoundedAllocationSize(sizeof(UntaggedTypedData) + lengthInBytes);
    }
  
    static intptr_t MaxElements(intptr_t class_id) {
      ASSERT(IsTypedDataClassId(class_id));
      return (kSmiMax / ElementSizeInBytes(class_id));
    }
  
    static intptr_t MaxNewSpaceElements(intptr_t class_id) {
      ASSERT(IsTypedDataClassId(class_id));
      return (Heap::kNewAllocatableSize - sizeof(UntaggedTypedData)) /
             ElementSizeInBytes(class_id);
    }
  
    static TypedDataPtr New(intptr_t class_id,
                            intptr_t len,
                            Heap::Space space = Heap::kNew);
  
    static void Copy(const TypedDataBase& dst,
                     intptr_t dst_offset_in_bytes,
                     const TypedDataBase& src,
                     intptr_t src_offset_in_bytes,
                     intptr_t length_in_bytes) {
      ASSERT(Utils::RangeCheck(src_offset_in_bytes, length_in_bytes,
                               src.LengthInBytes()));
      ASSERT(Utils::RangeCheck(dst_offset_in_bytes, length_in_bytes,
                               dst.LengthInBytes()));
      {
        NoSafepointScope no_safepoint;
        if (length_in_bytes > 0) {
          memmove(dst.DataAddr(dst_offset_in_bytes),
                  src.DataAddr(src_offset_in_bytes), length_in_bytes);
        }
      }
    }
  
    static void ClampedCopy(const TypedDataBase& dst,
                            intptr_t dst_offset_in_bytes,
                            const TypedDataBase& src,
                            intptr_t src_offset_in_bytes,
                            intptr_t length_in_bytes) {
      ASSERT(Utils::RangeCheck(src_offset_in_bytes, length_in_bytes,
                               src.LengthInBytes()));
      ASSERT(Utils::RangeCheck(dst_offset_in_bytes, length_in_bytes,
                               dst.LengthInBytes()));
      {
        NoSafepointScope no_safepoint;
        if (length_in_bytes > 0) {
          uint8_t* dst_data =
              reinterpret_cast<uint8_t*>(dst.DataAddr(dst_offset_in_bytes));
          int8_t* src_data =
              reinterpret_cast<int8_t*>(src.DataAddr(src_offset_in_bytes));
          for (intptr_t ix = 0; ix < length_in_bytes; ix++) {
            int8_t v = *src_data;
            if (v < 0) v = 0;
            *dst_data = v;
            src_data++;
            dst_data++;
          }
        }
      }
    }
  
    static bool IsTypedData(const Instance& obj) {
      ASSERT(!obj.IsNull());
      intptr_t cid = obj.ptr()->GetClassId();
      return IsTypedDataClassId(cid);
    }
  
   protected:
    void RecomputeDataField() { ptr()->untag()->RecomputeDataField(); }
  
   private:
    // Provides const access to non-pointer, non-aligned data within the object.
    // Such access does not need a write barrier, but it is *not* GC-safe, since
    // the object might move.
    //
    // Therefore this method is private and the call-sites in this class need to
    // ensure the returned pointer does not escape.
    template <typename FieldType>
    const FieldType* ReadOnlyDataAddr(intptr_t byte_offset) const {
      return reinterpret_cast<const FieldType*>((untag()->data()) + byte_offset);
    }
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(TypedData, TypedDataBase);
    friend class Class;
    friend class ExternalTypedData;
    friend class TypedDataView;
  };
  
  class ExternalTypedData : public TypedDataBase {
   public:
    // Alignment of data when serializing ExternalTypedData in a clustered
    // snapshot. Should be independent of word size.
    static const int kDataSerializationAlignment = 8;
  
    FinalizablePersistentHandle* AddFinalizer(void* peer,
                                              Dart_HandleFinalizer callback,
                                              intptr_t external_size) const;
  
    static intptr_t data_offset() {
      return OFFSET_OF(UntaggedExternalTypedData, data_);
    }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedExternalTypedData));
    }
  
    static intptr_t MaxElements(intptr_t class_id) {
      ASSERT(IsExternalTypedDataClassId(class_id));
      return (kSmiMax / ElementSizeInBytes(class_id));
    }
  
    static ExternalTypedDataPtr New(
        intptr_t class_id,
        uint8_t* data,
        intptr_t len,
        Heap::Space space = Heap::kNew,
        bool perform_eager_msan_initialization_check = true);
  
    static ExternalTypedDataPtr NewFinalizeWithFree(uint8_t* data, intptr_t len);
  
    static bool IsExternalTypedData(const Instance& obj) {
      ASSERT(!obj.IsNull());
      intptr_t cid = obj.ptr()->GetClassId();
      return IsExternalTypedDataClassId(cid);
    }
  
   protected:
    virtual uint8_t* Validate(uint8_t* data) const { return data; }
  
    void SetLength(intptr_t value) const {
      ASSERT(value <= Smi::kMaxValue);
      untag()->set_length(Smi::New(value));
    }
  
    void SetData(uint8_t* data) const {
      ASSERT(!IsolateGroup::Current()->heap()->Contains(
          reinterpret_cast<uword>(data)));
      StoreNonPointer(&untag()->data_, data);
    }
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(ExternalTypedData, TypedDataBase);
    friend class Class;
  };
  
  class TypedDataView : public TypedDataBase {
   public:
    static TypedDataViewPtr New(intptr_t class_id,
                                Heap::Space space = Heap::kNew);
    static TypedDataViewPtr New(intptr_t class_id,
                                const TypedDataBase& typed_data,
                                intptr_t offset_in_bytes,
                                intptr_t length,
                                Heap::Space space = Heap::kNew);
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedTypedDataView));
    }
  
    static InstancePtr Data(const TypedDataView& view) {
      return view.typed_data();
    }
  
    static SmiPtr OffsetInBytes(const TypedDataView& view) {
      return view.offset_in_bytes();
    }
  
    static bool IsExternalTypedDataView(const TypedDataView& view_obj) {
      const auto& data = Instance::Handle(Data(view_obj));
      intptr_t cid = data.ptr()->GetClassId();
      ASSERT(IsTypedDataClassId(cid) || IsExternalTypedDataClassId(cid));
      return IsExternalTypedDataClassId(cid);
    }
  
    static intptr_t data_offset() {
      return OFFSET_OF(UntaggedTypedDataView, typed_data_);
    }
  
    static intptr_t offset_in_bytes_offset() {
      return OFFSET_OF(UntaggedTypedDataView, offset_in_bytes_);
    }
  
    InstancePtr typed_data() const { return untag()->typed_data(); }
  
    void InitializeWith(const TypedDataBase& typed_data,
                        intptr_t offset_in_bytes,
                        intptr_t length) {
      const classid_t cid = typed_data.GetClassId();
      ASSERT(IsTypedDataClassId(cid) || IsExternalTypedDataClassId(cid));
      untag()->set_typed_data(typed_data.ptr());
      untag()->set_length(Smi::New(length));
      untag()->set_offset_in_bytes(Smi::New(offset_in_bytes));
  
      // Update the inner pointer.
      RecomputeDataField();
    }
  
    SmiPtr offset_in_bytes() const { return untag()->offset_in_bytes(); }
  
   protected:
    virtual uint8_t* Validate(uint8_t* data) const { return data; }
  
   private:
    void RecomputeDataField() { ptr()->untag()->RecomputeDataField(); }
  
    void Clear() {
      untag()->set_length(Smi::New(0));
      untag()->set_offset_in_bytes(Smi::New(0));
      StoreNonPointer(&untag()->data_, nullptr);
      untag()->set_typed_data(TypedDataBase::RawCast(Object::null()));
    }
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(TypedDataView, TypedDataBase);
    friend class Class;
    friend class Object;
    friend class TypedDataViewDeserializationCluster;
  };
  
  class ByteBuffer : public AllStatic {
   public:
    static constexpr bool ContainsCompressedPointers() {
      return Instance::ContainsCompressedPointers();
    }
  
    static InstancePtr Data(const Instance& view_obj) {
      ASSERT(!view_obj.IsNull());
      return reinterpret_cast<CompressedInstancePtr*>(
                 reinterpret_cast<uword>(view_obj.untag()) + data_offset())
          ->Decompress(view_obj.untag()->heap_base());
    }
  
    static intptr_t NumberOfFields() { return kNumFields; }
  
    static intptr_t data_offset() {
      return sizeof(UntaggedObject) + (kCompressedWordSize * kDataIndex);
    }
  
   private:
    enum {
      kDataIndex = 0,
      kNumFields = 1,
    };
  };
  
  class Pointer : public Instance {
   public:
    static PointerPtr New(const AbstractType& type_arg,
                          uword native_address,
                          Heap::Space space = Heap::kNew);
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedPointer));
    }
  
    static bool IsPointer(const Instance& obj);
  
    size_t NativeAddress() const {
      return reinterpret_cast<size_t>(untag()->data_);
    }
  
    void SetNativeAddress(size_t address) const {
      uint8_t* value = reinterpret_cast<uint8_t*>(address);
      StoreNonPointer(&untag()->data_, value);
    }
  
    static intptr_t type_arguments_offset() {
      return OFFSET_OF(UntaggedPointer, type_arguments_);
    }
  
    static const intptr_t kNativeTypeArgPos = 0;
  
    // Fetches the NativeType type argument.
    AbstractTypePtr type_argument() const {
      TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments());
      return type_args.TypeAtNullSafe(Pointer::kNativeTypeArgPos);
    }
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Pointer, Instance);
  
    friend class Class;
  };
  
  class DynamicLibrary : public Instance {
   public:
    static DynamicLibraryPtr New(void* handle, Heap::Space space = Heap::kNew);
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedDynamicLibrary));
    }
  
    static bool IsDynamicLibrary(const Instance& obj) {
      ASSERT(!obj.IsNull());
      intptr_t cid = obj.ptr()->GetClassId();
      return IsFfiDynamicLibraryClassId(cid);
    }
  
    void* GetHandle() const {
      ASSERT(!IsNull());
      return untag()->handle_;
    }
  
    void SetHandle(void* value) const {
      StoreNonPointer(&untag()->handle_, value);
    }
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(DynamicLibrary, Instance);
  
    friend class Class;
  };
  
  class LinkedHashBase : public Instance {
   public:
    // Keep consistent with _indexSizeToHashMask in compact_hash.dart.
    static intptr_t IndexSizeToHashMask(intptr_t index_size) {
      ASSERT(index_size >= kInitialIndexSize);
      intptr_t index_bits = Utils::BitLength(index_size) - 2;
  #if defined(HAS_SMI_63_BITS)
      return (1 << (32 - index_bits)) - 1;
  #else
      return (1 << (Object::kHashBits - index_bits)) - 1;
  #endif
    }
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedLinkedHashBase));
    }
  
    static intptr_t type_arguments_offset() {
      return OFFSET_OF(UntaggedLinkedHashBase, type_arguments_);
    }
  
    static intptr_t index_offset() {
      return OFFSET_OF(UntaggedLinkedHashBase, index_);
    }
  
    static intptr_t data_offset() {
      return OFFSET_OF(UntaggedLinkedHashBase, data_);
    }
  
    static intptr_t hash_mask_offset() {
      return OFFSET_OF(UntaggedLinkedHashBase, hash_mask_);
    }
  
    static intptr_t used_data_offset() {
      return OFFSET_OF(UntaggedLinkedHashBase, used_data_);
    }
  
    static intptr_t deleted_keys_offset() {
      return OFFSET_OF(UntaggedLinkedHashBase, deleted_keys_);
    }
  
    static const LinkedHashBase& Cast(const Object& obj) {
      ASSERT(obj.IsLinkedHashMap() || obj.IsLinkedHashSet());
      return static_cast<const LinkedHashBase&>(obj);
    }
  
    bool IsImmutable() const {
      return GetClassId() == kImmutableLinkedHashMapCid ||
             GetClassId() == kImmutableLinkedHashSetCid;
    }
  
    virtual TypeArgumentsPtr GetTypeArguments() const {
      return untag()->type_arguments();
    }
    virtual void SetTypeArguments(const TypeArguments& value) const {
      const intptr_t num_type_args = IsLinkedHashMap() ? 2 : 1;
      ASSERT(value.IsNull() ||
             ((value.Length() >= num_type_args) &&
              value.IsInstantiated() /*&& value.IsCanonical()*/));
      // TODO(asiva): Values read from a message snapshot are not properly marked
      // as canonical. See for example tests/isolate/message3_test.dart.
      untag()->set_type_arguments(value.ptr());
    }
  
    TypedDataPtr index() const { return untag()->index(); }
    void set_index(const TypedData& value) const {
      ASSERT(!value.IsNull());
      untag()->set_index(value.ptr());
    }
  
    ArrayPtr data() const { return untag()->data(); }
    void set_data(const Array& value) const { untag()->set_data(value.ptr()); }
  
    SmiPtr hash_mask() const { return untag()->hash_mask(); }
    void set_hash_mask(intptr_t value) const {
      untag()->set_hash_mask(Smi::New(value));
    }
  
    SmiPtr used_data() const { return untag()->used_data(); }
    void set_used_data(intptr_t value) const {
      untag()->set_used_data(Smi::New(value));
    }
  
    SmiPtr deleted_keys() const { return untag()->deleted_keys(); }
    void set_deleted_keys(intptr_t value) const {
      untag()->set_deleted_keys(Smi::New(value));
    }
  
    intptr_t Length() const {
      // The map or set may be uninitialized.
      if (untag()->used_data() == Object::null()) return 0;
      if (untag()->deleted_keys() == Object::null()) return 0;
  
      intptr_t used = Smi::Value(untag()->used_data());
      if (IsLinkedHashMap()) {
        used >>= 1;
      }
      const intptr_t deleted = Smi::Value(untag()->deleted_keys());
      return used - deleted;
    }
  
    // We do not compute the indices in the VM, but we do precompute the hash
    // mask to avoid a load acquire barrier on reading the combination of index
    // and hash mask.
    void ComputeAndSetHashMask() const;
  
    virtual bool CanonicalizeEquals(const Instance& other) const;
    virtual uint32_t CanonicalizeHash() const;
    virtual void CanonicalizeFieldsLocked(Thread* thread) const;
  
   protected:
    // Keep this in sync with Dart implementation (lib/compact_hash.dart).
    static const intptr_t kInitialIndexBits = 2;
    static const intptr_t kInitialIndexSize = 1 << (kInitialIndexBits + 1);
  
   private:
    LinkedHashBasePtr ptr() const { return static_cast<LinkedHashBasePtr>(ptr_); }
    UntaggedLinkedHashBase* untag() const {
      ASSERT(ptr() != null());
      return const_cast<UntaggedLinkedHashBase*>(ptr()->untag());
    }
  
    friend class Class;
    friend class ImmutableLinkedHashBase;
    friend class LinkedHashBaseDeserializationCluster;
  };
  
  class ImmutableLinkedHashBase : public AllStatic {
   public:
    static constexpr bool ContainsCompressedPointers() {
      return LinkedHashBase::ContainsCompressedPointers();
    }
  
    static intptr_t data_offset() { return LinkedHashBase::data_offset(); }
  };
  
  // Corresponds to
  // - "new Map()",
  // - non-const map literals, and
  // - the default constructor of LinkedHashMap in dart:collection.
  class LinkedHashMap : public LinkedHashBase {
   public:
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedLinkedHashMap));
    }
  
    // Allocates a map with some default capacity, just like "new Map()".
    static LinkedHashMapPtr NewDefault(intptr_t class_id = kLinkedHashMapCid,
                                       Heap::Space space = Heap::kNew);
    static LinkedHashMapPtr 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 = Heap::kNew);
  
    // This iterator differs somewhat from its Dart counterpart (_CompactIterator
    // in runtime/lib/compact_hash.dart):
    //  - There are no checks for concurrent modifications.
    //  - Accessing a key or value before the first call to MoveNext and after
    //    MoveNext returns false will result in crashes.
    class Iterator : ValueObject {
     public:
      explicit Iterator(const LinkedHashMap& map)
          : data_(Array::Handle(map.data())),
            scratch_(Object::Handle()),
            offset_(-2),
            length_(Smi::Value(map.used_data())) {}
  
      bool MoveNext() {
        while (true) {
          offset_ += 2;
          if (offset_ >= length_) {
            return false;
          }
          scratch_ = data_.At(offset_);
          if (scratch_.ptr() != data_.ptr()) {
            // Slot is not deleted (self-reference indicates deletion).
            return true;
          }
        }
      }
  
      ObjectPtr CurrentKey() const { return data_.At(offset_); }
  
      ObjectPtr CurrentValue() const { return data_.At(offset_ + 1); }
  
     private:
      const Array& data_;
      Object& scratch_;
      intptr_t offset_;
      const intptr_t length_;
    };
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(LinkedHashMap, LinkedHashBase);
  
    // Allocate a map, but leave all fields set to null.
    // Used during deserialization (since map might contain itself as key/value).
    static LinkedHashMapPtr NewUninitialized(intptr_t class_id,
                                             Heap::Space space = Heap::kNew);
  
    friend class Class;
    friend class ImmutableLinkedHashMap;
    friend class LinkedHashMapDeserializationCluster;
  };
  
  class ImmutableLinkedHashMap : public AllStatic {
   public:
    static constexpr bool ContainsCompressedPointers() {
      return LinkedHashMap::ContainsCompressedPointers();
    }
  
    static ImmutableLinkedHashMapPtr NewDefault(Heap::Space space = Heap::kNew);
  
    static ImmutableLinkedHashMapPtr NewUninitialized(
        Heap::Space space = Heap::kNew);
  
    static const ClassId kClassId = kImmutableLinkedHashMapCid;
  
    static intptr_t InstanceSize() { return LinkedHashMap::InstanceSize(); }
  
   private:
    static intptr_t NextFieldOffset() {
      // Indicates this class cannot be extended by dart code.
      return -kWordSize;
    }
  
    static ImmutableLinkedHashMapPtr raw(const LinkedHashMap& map) {
      return static_cast<ImmutableLinkedHashMapPtr>(map.ptr());
    }
  
    friend class Class;
  };
  
  class LinkedHashSet : public LinkedHashBase {
   public:
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedLinkedHashSet));
    }
  
    // Allocates a set with some default capacity, just like "new Set()".
    static LinkedHashSetPtr NewDefault(intptr_t class_id = kLinkedHashSetCid,
                                       Heap::Space space = Heap::kNew);
    static LinkedHashSetPtr 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 = Heap::kNew);
  
    // This iterator differs somewhat from its Dart counterpart (_CompactIterator
    // in runtime/lib/compact_hash.dart):
    //  - There are no checks for concurrent modifications.
    //  - Accessing a key or value before the first call to MoveNext and after
    //    MoveNext returns false will result in crashes.
    class Iterator : ValueObject {
     public:
      explicit Iterator(const LinkedHashSet& set)
          : data_(Array::Handle(set.data())),
            scratch_(Object::Handle()),
            offset_(-1),
            length_(Smi::Value(set.used_data())) {}
  
      bool MoveNext() {
        while (true) {
          offset_++;
          if (offset_ >= length_) {
            return false;
          }
          scratch_ = data_.At(offset_);
          if (scratch_.ptr() != data_.ptr()) {
            // Slot is not deleted (self-reference indicates deletion).
            return true;
          }
        }
      }
  
      ObjectPtr CurrentKey() const { return data_.At(offset_); }
  
     private:
      const Array& data_;
      Object& scratch_;
      intptr_t offset_;
      const intptr_t length_;
    };
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(LinkedHashSet, LinkedHashBase);
  
    // Allocate a set, but leave all fields set to null.
    // Used during deserialization (since set might contain itself as key/value).
    static LinkedHashSetPtr NewUninitialized(intptr_t class_id,
                                             Heap::Space space = Heap::kNew);
  
    friend class Class;
    friend class ImmutableLinkedHashSet;
    friend class LinkedHashSetDeserializationCluster;
  };
  
  class ImmutableLinkedHashSet : public AllStatic {
   public:
    static constexpr bool ContainsCompressedPointers() {
      return LinkedHashSet::ContainsCompressedPointers();
    }
  
    static ImmutableLinkedHashSetPtr NewDefault(Heap::Space space = Heap::kNew);
  
    static ImmutableLinkedHashSetPtr NewUninitialized(
        Heap::Space space = Heap::kNew);
  
    static const ClassId kClassId = kImmutableLinkedHashSetCid;
  
    static intptr_t InstanceSize() { return LinkedHashSet::InstanceSize(); }
  
   private:
    static intptr_t NextFieldOffset() {
      // Indicates this class cannot be extended by dart code.
      return -kWordSize;
    }
  
    static ImmutableLinkedHashSetPtr raw(const LinkedHashSet& map) {
      return static_cast<ImmutableLinkedHashSetPtr>(map.ptr());
    }
  
    friend class Class;
  };
  
  class Closure : public Instance {
   public:
  #if defined(DART_PRECOMPILED_RUNTIME)
    uword entry_point() const { return untag()->entry_point_; }
    void set_entry_point(uword entry_point) const {
      StoreNonPointer(&untag()->entry_point_, entry_point);
    }
    static intptr_t entry_point_offset() {
      return OFFSET_OF(UntaggedClosure, entry_point_);
    }
  #endif
  
    TypeArgumentsPtr instantiator_type_arguments() const {
      return untag()->instantiator_type_arguments();
    }
    void set_instantiator_type_arguments(const TypeArguments& args) const {
      untag()->set_instantiator_type_arguments(args.ptr());
    }
    static intptr_t instantiator_type_arguments_offset() {
      return OFFSET_OF(UntaggedClosure, instantiator_type_arguments_);
    }
  
    TypeArgumentsPtr function_type_arguments() const {
      return untag()->function_type_arguments();
    }
    void set_function_type_arguments(const TypeArguments& args) const {
      untag()->set_function_type_arguments(args.ptr());
    }
    static intptr_t function_type_arguments_offset() {
      return OFFSET_OF(UntaggedClosure, function_type_arguments_);
    }
  
    TypeArgumentsPtr delayed_type_arguments() const {
      return untag()->delayed_type_arguments();
    }
    void set_delayed_type_arguments(const TypeArguments& args) const {
      untag()->set_delayed_type_arguments(args.ptr());
    }
    static intptr_t delayed_type_arguments_offset() {
      return OFFSET_OF(UntaggedClosure, delayed_type_arguments_);
    }
  
    FunctionPtr function() const { return untag()->function(); }
    static intptr_t function_offset() {
      return OFFSET_OF(UntaggedClosure, function_);
    }
    static FunctionPtr FunctionOf(ClosurePtr closure) {
      return closure.untag()->function();
    }
  
    ContextPtr context() const { return untag()->context(); }
    static intptr_t context_offset() {
      return OFFSET_OF(UntaggedClosure, context_);
    }
    static ContextPtr ContextOf(ClosurePtr closure) {
      return closure.untag()->context();
    }
  
    // Returns whether the closure is generic, that is, it has a generic closure
    // function and no delayed type arguments.
    bool IsGeneric() const {
      return delayed_type_arguments() == Object::empty_type_arguments().ptr();
    }
  
    SmiPtr hash() const { return untag()->hash(); }
    static intptr_t hash_offset() { return OFFSET_OF(UntaggedClosure, hash_); }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedClosure));
    }
  
    virtual void CanonicalizeFieldsLocked(Thread* thread) const;
    virtual bool CanonicalizeEquals(const Instance& other) const;
    virtual uint32_t CanonicalizeHash() const {
      return Function::Handle(function()).Hash();
    }
    uword ComputeHash() const;
  
    static ClosurePtr New(const TypeArguments& instantiator_type_arguments,
                          const TypeArguments& function_type_arguments,
                          const Function& function,
                          const Context& context,
                          Heap::Space space = Heap::kNew);
  
    static ClosurePtr New(const TypeArguments& instantiator_type_arguments,
                          const TypeArguments& function_type_arguments,
                          const TypeArguments& delayed_type_arguments,
                          const Function& function,
                          const Context& context,
                          Heap::Space space = Heap::kNew);
  
    FunctionTypePtr GetInstantiatedSignature(Zone* zone) const;
  
   private:
    static ClosurePtr New();
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Closure, Instance);
    friend class Class;
  };
  
  class Capability : public Instance {
   public:
    uint64_t Id() const { return untag()->id_; }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedCapability));
    }
    static CapabilityPtr New(uint64_t id, Heap::Space space = Heap::kNew);
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(Capability, Instance);
    friend class Class;
  };
  
  class ReceivePort : public Instance {
   public:
    SendPortPtr send_port() const { return untag()->send_port(); }
    Dart_Port Id() const { return send_port()->untag()->id_; }
  
    InstancePtr handler() const { return untag()->handler(); }
    void set_handler(const Instance& value) const;
  
  #if !defined(PRODUCT)
    StackTracePtr allocation_location() const {
      return untag()->allocation_location();
    }
  
    StringPtr debug_name() const { return untag()->debug_name(); }
  #endif
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedReceivePort));
    }
    static ReceivePortPtr New(Dart_Port id,
                              const String& debug_name,
                              bool is_control_port,
                              Heap::Space space = Heap::kNew);
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(ReceivePort, Instance);
    friend class Class;
  };
  
  class SendPort : public Instance {
   public:
    Dart_Port Id() const { return untag()->id_; }
  
    Dart_Port origin_id() const { return untag()->origin_id_; }
    void set_origin_id(Dart_Port id) const {
      ASSERT(origin_id() == 0);
      StoreNonPointer(&(untag()->origin_id_), id);
    }
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedSendPort));
    }
    static SendPortPtr New(Dart_Port id, Heap::Space space = Heap::kNew);
    static SendPortPtr New(Dart_Port id,
                           Dart_Port origin_id,
                           Heap::Space space = Heap::kNew);
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(SendPort, Instance);
    friend class Class;
  };
  
  // This is allocated when new instance of TransferableTypedData is created in
  // [TransferableTypedData::New].
  class TransferableTypedDataPeer {
   public:
    // [data] backing store should be malloc'ed, not new'ed.
    TransferableTypedDataPeer(uint8_t* data, intptr_t length)
        : data_(data), length_(length), handle_(nullptr) {}
  
    ~TransferableTypedDataPeer() { free(data_); }
  
    uint8_t* data() const { return data_; }
    intptr_t length() const { return length_; }
    FinalizablePersistentHandle* handle() const { return handle_; }
    void set_handle(FinalizablePersistentHandle* handle) { handle_ = handle; }
  
    void ClearData() {
      data_ = nullptr;
      length_ = 0;
      handle_ = nullptr;
    }
  
   private:
    uint8_t* data_;
    intptr_t length_;
    FinalizablePersistentHandle* handle_;
  
    DISALLOW_COPY_AND_ASSIGN(TransferableTypedDataPeer);
  };
  
  class TransferableTypedData : public Instance {
   public:
    static TransferableTypedDataPtr New(uint8_t* data, intptr_t len);
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedTransferableTypedData));
    }
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(TransferableTypedData, Instance);
    friend class Class;
  };
  
  class DebuggerStackTrace;
  
  // Internal stacktrace object used in exceptions for printing stack traces.
  class StackTrace : public Instance {
   public:
    static const int kPreallocatedStackdepth = 90;
  
    intptr_t Length() const;
  
    StackTracePtr async_link() const { return untag()->async_link(); }
    void set_async_link(const StackTrace& async_link) const;
    void set_expand_inlined(bool value) const;
  
    ArrayPtr code_array() const { return untag()->code_array(); }
    ObjectPtr CodeAtFrame(intptr_t frame_index) const;
    void SetCodeAtFrame(intptr_t frame_index, const Object& code) const;
  
    TypedDataPtr pc_offset_array() const { return untag()->pc_offset_array(); }
    uword PcOffsetAtFrame(intptr_t frame_index) const;
    void SetPcOffsetAtFrame(intptr_t frame_index, uword pc_offset) const;
  
    bool skip_sync_start_in_parent_stack() const;
    void set_skip_sync_start_in_parent_stack(bool value) const;
  
    // The number of frames that should be cut off the top of an async stack trace
    // if it's appended to a synchronous stack trace along a sync-async call.
    //
    // Without cropping, the border would look like:
    //
    // <async function>
    // ---------------------------
    // <asynchronous gap marker>
    // <async function>
    //
    // Since it's not actually an async call, we crop off the last two
    // frames when concatenating the sync and async stacktraces.
    static constexpr intptr_t kSyncAsyncCroppedFrames = 2;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedStackTrace));
    }
    static StackTracePtr New(const Array& code_array,
                             const TypedData& pc_offset_array,
                             Heap::Space space = Heap::kNew);
  
    static StackTracePtr New(const Array& code_array,
                             const TypedData& pc_offset_array,
                             const StackTrace& async_link,
                             bool skip_sync_start_in_parent_stack,
                             Heap::Space space = Heap::kNew);
  
   private:
    void set_code_array(const Array& code_array) const;
    void set_pc_offset_array(const TypedData& pc_offset_array) const;
    bool expand_inlined() const;
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(StackTrace, Instance);
    friend class Class;
    friend class DebuggerStackTrace;
  };
  
  class RegExpFlags {
   public:
    // Flags are passed to a regex object as follows:
    // 'i': ignore case, 'g': do global matches, 'm': pattern is multi line,
    // 'u': pattern is full Unicode, not just BMP, 's': '.' in pattern matches
    // all characters including line terminators.
    enum Flags {
      kNone = 0,
      kGlobal = 1,
      kIgnoreCase = 2,
      kMultiLine = 4,
      kUnicode = 8,
      kDotAll = 16,
    };
  
    static const int kDefaultFlags = 0;
  
    RegExpFlags() : value_(kDefaultFlags) {}
    explicit RegExpFlags(int value) : value_(value) {}
  
    inline bool IsGlobal() const { return (value_ & kGlobal) != 0; }
    inline bool IgnoreCase() const { return (value_ & kIgnoreCase) != 0; }
    inline bool IsMultiLine() const { return (value_ & kMultiLine) != 0; }
    inline bool IsUnicode() const { return (value_ & kUnicode) != 0; }
    inline bool IsDotAll() const { return (value_ & kDotAll) != 0; }
  
    inline bool NeedsUnicodeCaseEquivalents() {
      // Both unicode and ignore_case flags are set. We need to use ICU to find
      // the closure over case equivalents.
      return IsUnicode() && IgnoreCase();
    }
  
    void SetGlobal() { value_ |= kGlobal; }
    void SetIgnoreCase() { value_ |= kIgnoreCase; }
    void SetMultiLine() { value_ |= kMultiLine; }
    void SetUnicode() { value_ |= kUnicode; }
    void SetDotAll() { value_ |= kDotAll; }
  
    const char* ToCString() const;
  
    int value() const { return value_; }
  
    bool operator==(const RegExpFlags& other) { return value_ == other.value_; }
    bool operator!=(const RegExpFlags& other) { return value_ != other.value_; }
  
   private:
    int value_;
  };
  
  // Internal JavaScript regular expression object.
  class RegExp : public Instance {
   public:
    // Meaning of RegExType:
    // kUninitialized: the type of th regexp has not been initialized yet.
    // kSimple: A simple pattern to match against, using string indexOf operation.
    // kComplex: A complex pattern to match.
    enum RegExType {
      kUninitialized = 0,
      kSimple = 1,
      kComplex = 2,
    };
  
    enum {
      kTypePos = 0,
      kTypeSize = 2,
      kFlagsPos = 2,
      kFlagsSize = 5,
    };
  
    class TypeBits : public BitField<int8_t, RegExType, kTypePos, kTypeSize> {};
    class FlagsBits : public BitField<int8_t, intptr_t, kFlagsPos, kFlagsSize> {};
  
    bool is_initialized() const { return (type() != kUninitialized); }
    bool is_simple() const { return (type() == kSimple); }
    bool is_complex() const { return (type() == kComplex); }
  
    intptr_t num_registers(bool is_one_byte) const {
      return is_one_byte ? untag()->num_one_byte_registers_
                         : untag()->num_two_byte_registers_;
    }
  
    StringPtr pattern() const { return untag()->pattern(); }
    intptr_t num_bracket_expressions() const {
      return untag()->num_bracket_expressions_;
    }
    ArrayPtr capture_name_map() const { return untag()->capture_name_map(); }
  
    TypedDataPtr bytecode(bool is_one_byte, bool sticky) const {
      if (sticky) {
        return TypedData::RawCast(is_one_byte ? untag()->one_byte_sticky()
                                              : untag()->two_byte_sticky());
      } else {
        return TypedData::RawCast(is_one_byte ? untag()->one_byte()
                                              : untag()->two_byte());
      }
    }
  
    static intptr_t function_offset(intptr_t cid, bool sticky) {
      if (sticky) {
        switch (cid) {
          case kOneByteStringCid:
            return OFFSET_OF(UntaggedRegExp, one_byte_sticky_);
          case kTwoByteStringCid:
            return OFFSET_OF(UntaggedRegExp, two_byte_sticky_);
          case kExternalOneByteStringCid:
            return OFFSET_OF(UntaggedRegExp, external_one_byte_sticky_);
          case kExternalTwoByteStringCid:
            return OFFSET_OF(UntaggedRegExp, external_two_byte_sticky_);
        }
      } else {
        switch (cid) {
          case kOneByteStringCid:
            return OFFSET_OF(UntaggedRegExp, one_byte_);
          case kTwoByteStringCid:
            return OFFSET_OF(UntaggedRegExp, two_byte_);
          case kExternalOneByteStringCid:
            return OFFSET_OF(UntaggedRegExp, external_one_byte_);
          case kExternalTwoByteStringCid:
            return OFFSET_OF(UntaggedRegExp, external_two_byte_);
        }
      }
  
      UNREACHABLE();
      return -1;
    }
  
    FunctionPtr function(intptr_t cid, bool sticky) const {
      if (sticky) {
        switch (cid) {
          case kOneByteStringCid:
            return static_cast<FunctionPtr>(untag()->one_byte_sticky());
          case kTwoByteStringCid:
            return static_cast<FunctionPtr>(untag()->two_byte_sticky());
          case kExternalOneByteStringCid:
            return static_cast<FunctionPtr>(untag()->external_one_byte_sticky());
          case kExternalTwoByteStringCid:
            return static_cast<FunctionPtr>(untag()->external_two_byte_sticky());
        }
      } else {
        switch (cid) {
          case kOneByteStringCid:
            return static_cast<FunctionPtr>(untag()->one_byte());
          case kTwoByteStringCid:
            return static_cast<FunctionPtr>(untag()->two_byte());
          case kExternalOneByteStringCid:
            return static_cast<FunctionPtr>(untag()->external_one_byte());
          case kExternalTwoByteStringCid:
            return static_cast<FunctionPtr>(untag()->external_two_byte());
        }
      }
  
      UNREACHABLE();
      return Function::null();
    }
  
    void set_pattern(const String& pattern) const;
    void set_function(intptr_t cid, bool sticky, const Function& value) const;
    void set_bytecode(bool is_one_byte,
                      bool sticky,
                      const TypedData& bytecode) const;
  
    void set_num_bracket_expressions(SmiPtr value) const;
    void set_num_bracket_expressions(const Smi& value) const;
    void set_num_bracket_expressions(intptr_t value) const;
    void set_capture_name_map(const Array& array) const;
    void set_is_global() const {
      RegExpFlags f = flags();
      f.SetGlobal();
      set_flags(f);
    }
    void set_is_ignore_case() const {
      RegExpFlags f = flags();
      f.SetIgnoreCase();
      set_flags(f);
    }
    void set_is_multi_line() const {
      RegExpFlags f = flags();
      f.SetMultiLine();
      set_flags(f);
    }
    void set_is_unicode() const {
      RegExpFlags f = flags();
      f.SetUnicode();
      set_flags(f);
    }
    void set_is_dot_all() const {
      RegExpFlags f = flags();
      f.SetDotAll();
      set_flags(f);
    }
    void set_is_simple() const { set_type(kSimple); }
    void set_is_complex() const { set_type(kComplex); }
    void set_num_registers(bool is_one_byte, intptr_t value) const {
      if (is_one_byte) {
        StoreNonPointer(&untag()->num_one_byte_registers_, value);
      } else {
        StoreNonPointer(&untag()->num_two_byte_registers_, value);
      }
    }
  
    RegExpFlags flags() const {
      return RegExpFlags(FlagsBits::decode(untag()->type_flags_));
    }
    void set_flags(RegExpFlags flags) const {
      StoreNonPointer(&untag()->type_flags_,
                      FlagsBits::update(flags.value(), untag()->type_flags_));
    }
    const char* Flags() const;
  
    virtual bool CanonicalizeEquals(const Instance& other) const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedRegExp));
    }
  
    static RegExpPtr New(Zone* zone, Heap::Space space = Heap::kNew);
  
   private:
    void set_type(RegExType type) const {
      StoreNonPointer(&untag()->type_flags_,
                      TypeBits::update(type, untag()->type_flags_));
    }
  
    RegExType type() const { return TypeBits::decode(untag()->type_flags_); }
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(RegExp, Instance);
    friend class Class;
  };
  
  class WeakProperty : public Instance {
   public:
    ObjectPtr key() const { return untag()->key(); }
    void set_key(const Object& key) const { untag()->set_key(key.ptr()); }
    static intptr_t key_offset() { return OFFSET_OF(UntaggedWeakProperty, key_); }
  
    ObjectPtr value() const { return untag()->value(); }
    void set_value(const Object& value) const { untag()->set_value(value.ptr()); }
    static intptr_t value_offset() {
      return OFFSET_OF(UntaggedWeakProperty, value_);
    }
  
    static WeakPropertyPtr New(Heap::Space space = Heap::kNew);
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedWeakProperty));
    }
  
    static void Clear(WeakPropertyPtr raw_weak) {
      ASSERT(raw_weak->untag()->next_ ==
             CompressedWeakPropertyPtr(WeakProperty::null()));
      // This action is performed by the GC. No barrier.
      raw_weak->untag()->key_ = Object::null();
      raw_weak->untag()->value_ = Object::null();
    }
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(WeakProperty, Instance);
    friend class Class;
  };
  
  class MirrorReference : public Instance {
   public:
    ObjectPtr referent() const { return untag()->referent(); }
  
    void set_referent(const Object& referent) const {
      untag()->set_referent(referent.ptr());
    }
  
    AbstractTypePtr GetAbstractTypeReferent() const;
  
    ClassPtr GetClassReferent() const;
  
    FieldPtr GetFieldReferent() const;
  
    FunctionPtr GetFunctionReferent() const;
  
    FunctionTypePtr GetFunctionTypeReferent() const;
  
    LibraryPtr GetLibraryReferent() const;
  
    TypeParameterPtr GetTypeParameterReferent() const;
  
    static MirrorReferencePtr New(const Object& referent,
                                  Heap::Space space = Heap::kNew);
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedMirrorReference));
    }
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(MirrorReference, Instance);
    friend class Class;
  };
  
  class UserTag : public Instance {
   public:
    uword tag() const { return untag()->tag(); }
    void set_tag(uword t) const {
      ASSERT(t >= UserTags::kUserTagIdOffset);
      ASSERT(t < UserTags::kUserTagIdOffset + UserTags::kMaxUserTags);
      StoreNonPointer(&untag()->tag_, t);
    }
    static intptr_t tag_offset() { return OFFSET_OF(UntaggedUserTag, tag_); }
  
    StringPtr label() const { return untag()->label(); }
  
    UserTagPtr MakeActive() const;
  
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedUserTag));
    }
  
    static UserTagPtr New(const String& label, Heap::Space space = Heap::kOld);
    static UserTagPtr DefaultTag();
  
    static bool TagTableIsFull(Thread* thread);
    static UserTagPtr FindTagById(uword tag_id);
  
   private:
    static UserTagPtr FindTagInIsolate(Thread* thread, const String& label);
    static void AddTagToIsolate(Thread* thread, const UserTag& tag);
  
    void set_label(const String& tag_label) const {
      untag()->set_label(tag_label.ptr());
    }
  
    FINAL_HEAP_OBJECT_IMPLEMENTATION(UserTag, Instance);
    friend class Class;
  };
  
  // Represents abstract FutureOr class in dart:async.
  class FutureOr : public Instance {
   public:
    static intptr_t InstanceSize() {
      return RoundedAllocationSize(sizeof(UntaggedFutureOr));
    }
  
    virtual TypeArgumentsPtr GetTypeArguments() const {
      return untag()->type_arguments();
    }
    static intptr_t type_arguments_offset() {
      return OFFSET_OF(UntaggedFutureOr, type_arguments_);
    }
  
   private:
    FINAL_HEAP_OBJECT_IMPLEMENTATION(FutureOr, Instance);
  
    friend class Class;
  };
  
  // Breaking cycles and loops.
  ClassPtr Object::clazz() const {
    uword raw_value = static_cast<uword>(ptr_);
    if ((raw_value & kSmiTagMask) == kSmiTag) {
      return Smi::Class();
    }
    ASSERT(!IsolateGroup::Current()->compaction_in_progress());
    return IsolateGroup::Current()->class_table()->At(ptr()->GetClassId());
  }
  
  DART_FORCE_INLINE
  void Object::SetPtr(ObjectPtr value, intptr_t default_cid) {
    ptr_ = value;
    intptr_t cid = value->GetClassIdMayBeSmi();
    // Free-list elements cannot be wrapped in a handle.
    ASSERT(cid != kFreeListElement);
    ASSERT(cid != kForwardingCorpse);
    if (cid == kNullCid) {
      cid = default_cid;
    } else if (cid >= kNumPredefinedCids) {
      cid = kInstanceCid;
    }
    set_vtable(builtin_vtables_[cid]);
  #if defined(DEBUG)
    if (FLAG_verify_handles && ptr_->IsHeapObject() && (ptr_ != Object::null())) {
      Heap* isolate_heap = IsolateGroup::Current()->heap();
      // TODO(rmacnak): Remove after rewriting StackFrame::VisitObjectPointers
      // to not use handles.
      if (!isolate_heap->new_space()->scavenging()) {
        Heap* vm_isolate_heap = Dart::vm_isolate_group()->heap();
        uword addr = UntaggedObject::ToAddr(ptr_);
        if (!isolate_heap->Contains(addr) && !vm_isolate_heap->Contains(addr)) {
          ASSERT(FLAG_write_protect_code);
          addr = UntaggedObject::ToAddr(OldPage::ToWritable(ptr_));
          ASSERT(isolate_heap->Contains(addr) || vm_isolate_heap->Contains(addr));
        }
      }
    }
  #endif
  }
  
  intptr_t Field::HostOffset() const {
    ASSERT(is_instance());  // Valid only for dart instance fields.
    return (Smi::Value(untag()->host_offset_or_field_id()) * kCompressedWordSize);
  }
  
  intptr_t Field::TargetOffset() const {
    ASSERT(is_instance());  // Valid only for dart instance fields.
  #if !defined(DART_PRECOMPILED_RUNTIME)
    return (untag()->target_offset_ * compiler::target::kCompressedWordSize);
  #else
    return HostOffset();
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
  }
  
  inline intptr_t Field::TargetOffsetOf(const FieldPtr field) {
  #if !defined(DART_PRECOMPILED_RUNTIME)
    return field->untag()->target_offset_;
  #else
    return Smi::Value(field->untag()->host_offset_or_field_id());
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
  }
  
  void Field::SetOffset(intptr_t host_offset_in_bytes,
                        intptr_t target_offset_in_bytes) const {
    ASSERT(is_instance());  // Valid only for dart instance fields.
    ASSERT(kCompressedWordSize != 0);
    untag()->set_host_offset_or_field_id(
        Smi::New(host_offset_in_bytes / kCompressedWordSize));
  #if !defined(DART_PRECOMPILED_RUNTIME)
    ASSERT(compiler::target::kCompressedWordSize != 0);
    StoreNonPointer(
        &untag()->target_offset_,
        target_offset_in_bytes / compiler::target::kCompressedWordSize);
  #else
    ASSERT(host_offset_in_bytes == target_offset_in_bytes);
  #endif  //  !defined(DART_PRECOMPILED_RUNTIME)
  }
  
  ObjectPtr Field::StaticValue() const {
    ASSERT(is_static());  // Valid only for static dart fields.
    return Isolate::Current()->field_table()->At(field_id());
  }
  
  inline intptr_t Field::field_id() const {
    return Smi::Value(untag()->host_offset_or_field_id());
  }
  
  void Field::set_field_id(intptr_t field_id) const {
    DEBUG_ASSERT(
        IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
    set_field_id_unsafe(field_id);
  }
  
  void Field::set_field_id_unsafe(intptr_t field_id) const {
    ASSERT(is_static());
    untag()->set_host_offset_or_field_id(Smi::New(field_id));
  }
  
  void Context::SetAt(intptr_t index, const Object& value) const {
    untag()->set_element(index, value.ptr());
  }
  
  intptr_t Instance::GetNativeField(int index) const {
    ASSERT(IsValidNativeIndex(index));
    NoSafepointScope no_safepoint;
    TypedDataPtr native_fields = static_cast<TypedDataPtr>(
        NativeFieldsAddr()->Decompress(untag()->heap_base()));
    if (native_fields == TypedData::null()) {
      return 0;
    }
    return reinterpret_cast<intptr_t*>(native_fields->untag()->data())[index];
  }
  
  void Instance::GetNativeFields(uint16_t num_fields,
                                 intptr_t* field_values) const {
    NoSafepointScope no_safepoint;
    ASSERT(num_fields == NumNativeFields());
    ASSERT(field_values != NULL);
    TypedDataPtr native_fields = static_cast<TypedDataPtr>(
        NativeFieldsAddr()->Decompress(untag()->heap_base()));
    if (native_fields == TypedData::null()) {
      for (intptr_t i = 0; i < num_fields; i++) {
        field_values[i] = 0;
      }
    }
    intptr_t* fields =
        reinterpret_cast<intptr_t*>(native_fields->untag()->data());
    for (intptr_t i = 0; i < num_fields; i++) {
      field_values[i] = fields[i];
    }
  }
  
  bool String::Equals(const String& str) const {
    if (ptr() == str.ptr()) {
      return true;  // Both handles point to the same raw instance.
    }
    if (str.IsNull()) {
      return false;
    }
    if (IsCanonical() && str.IsCanonical()) {
      return false;  // Two symbols that aren't identical aren't equal.
    }
    if (HasHash() && str.HasHash() && (Hash() != str.Hash())) {
      return false;  // Both sides have hash codes and they do not match.
    }
    return Equals(str, 0, str.Length());
  }
  
  intptr_t Library::UrlHash() const {
    intptr_t result = String::GetCachedHash(url());
    ASSERT(result != 0);
    return result;
  }
  
  void MegamorphicCache::SetEntry(const Array& array,
                                  intptr_t index,
                                  const Smi& class_id,
                                  const Object& target) {
    ASSERT(target.IsNull() || target.IsFunction() || target.IsSmi());
    array.SetAt((index * kEntryLength) + kClassIdIndex, class_id);
    array.SetAt((index * kEntryLength) + kTargetFunctionIndex, target);
  }
  
  ObjectPtr MegamorphicCache::GetClassId(const Array& array, intptr_t index) {
    return array.At((index * kEntryLength) + kClassIdIndex);
  }
  
  ObjectPtr MegamorphicCache::GetTargetFunction(const Array& array,
                                                intptr_t index) {
    return array.At((index * kEntryLength) + kTargetFunctionIndex);
  }
  
  inline uword Type::Hash() const {
    ASSERT(IsFinalized());
    intptr_t result = Smi::Value(untag()->hash());
    if (result != 0) {
      return result;
    }
    return ComputeHash();
  }
  
  inline void Type::SetHash(intptr_t value) const {
    // This is only safe because we create a new Smi, which does not cause
    // heap allocation.
    untag()->set_hash(Smi::New(value));
  }
  
  inline uword FunctionType::Hash() const {
    ASSERT(IsFinalized());
    intptr_t result = Smi::Value(untag()->hash());
    if (result != 0) {
      return result;
    }
    return ComputeHash();
  }
  
  inline void FunctionType::SetHash(intptr_t value) const {
    // This is only safe because we create a new Smi, which does not cause
    // heap allocation.
    untag()->set_hash(Smi::New(value));
  }
  
  inline uword TypeParameter::Hash() const {
    ASSERT(IsFinalized() || IsBeingFinalized());  // Bound may not be finalized.
    intptr_t result = Smi::Value(untag()->hash());
    if (result != 0) {
      return result;
    }
    return ComputeHash();
  }
  
  inline void TypeParameter::SetHash(intptr_t value) const {
    // This is only safe because we create a new Smi, which does not cause
    // heap allocation.
    untag()->set_hash(Smi::New(value));
  }
  
  inline uword TypeArguments::Hash() const {
    if (IsNull()) return kAllDynamicHash;
    intptr_t result = Smi::Value(untag()->hash());
    if (result != 0) {
      return result;
    }
    return ComputeHash();
  }
  
  inline void TypeArguments::SetHash(intptr_t value) const {
    // This is only safe because we create a new Smi, which does not cause
    // heap allocation.
    untag()->set_hash(Smi::New(value));
  }
  
  inline uint16_t String::CharAt(StringPtr str, intptr_t index) {
    switch (str->GetClassId()) {
      case kOneByteStringCid:
        return OneByteString::CharAt(static_cast<OneByteStringPtr>(str), index);
      case kTwoByteStringCid:
        return TwoByteString::CharAt(static_cast<TwoByteStringPtr>(str), index);
      case kExternalOneByteStringCid:
        return ExternalOneByteString::CharAt(
            static_cast<ExternalOneByteStringPtr>(str), index);
      case kExternalTwoByteStringCid:
        return ExternalTwoByteString::CharAt(
            static_cast<ExternalTwoByteStringPtr>(str), index);
    }
    UNREACHABLE();
    return 0;
  }
  
  // A view on an [Array] as a list of tuples, optionally starting at an offset.
  //
  // Example: We store a list of (kind, function, code) tuples into the
  // [Code::static_calls_target_table] array of type [Array].
  //
  // This helper class can then be used via
  //
  //     using CallTableView = ArrayOfTuplesVied<
  //         Code::Kind, std::tuple<Smi, Function, Code>>;
  //
  //     auto& array = Array::Handle(code.static_calls_targets_table());
  //     CallTableView static_calls(array);
  //
  //     // Using convenient for loop.
  //     auto& function = Function::Handle();
  //     for (auto& call : static_calls) {
  //       function = call.Get<Code::kSCallTableFunctionTarget>();
  //       call.Set<Code::kSCallTableFunctionTarget>(function);
  //     }
  //
  //     // Using manual loop.
  //     auto& function = Function::Handle();
  //     for (intptr_t i = 0; i < static_calls.Length(); ++i) {
  //       auto call = static_calls[i];
  //       function = call.Get<Code::kSCallTableFunctionTarget>();
  //       call.Set<Code::kSCallTableFunctionTarget>(function);
  //     }
  //
  //
  // Template parameters:
  //
  //   * [EnumType] must be a normal enum which enumerates the entries of the
  //     tuple
  //
  //   * [kStartOffset] is the offset at which the first tuple in the array
  //     starts (can be 0).
  //
  //   * [TupleT] must be a std::tuple<...> where "..." are the heap object handle
  //     classes (e.g. 'Code', 'Smi', 'Object')
  template <typename EnumType, typename TupleT, int kStartOffset = 0>
  class ArrayOfTuplesView {
   public:
    static constexpr intptr_t EntrySize = std::tuple_size<TupleT>::value;
  
    class Iterator;
  
    class TupleView {
     public:
      TupleView(const Array& array, intptr_t index)
          : array_(array), index_(index) {}
  
      template <EnumType kElement,
                std::memory_order order = std::memory_order_relaxed>
      typename std::tuple_element<kElement, TupleT>::type::ObjectPtrType Get()
          const {
        using object_type = typename std::tuple_element<kElement, TupleT>::type;
        return object_type::RawCast(array_.At<order>(index_ + kElement));
      }
  
      template <EnumType kElement,
                std::memory_order order = std::memory_order_relaxed>
      void Set(const typename std::tuple_element<kElement, TupleT>::type& value)
          const {
        array_.SetAt<order>(index_ + kElement, value);
      }
  
      intptr_t index() const { return (index_ - kStartOffset) / EntrySize; }
  
     private:
      const Array& array_;
      intptr_t index_;
  
      friend class Iterator;
    };
  
    class Iterator {
     public:
      Iterator(const Array& array, intptr_t index) : entry_(array, index) {}
  
      bool operator==(const Iterator& other) {
        return entry_.index_ == other.entry_.index_;
      }
      bool operator!=(const Iterator& other) {
        return entry_.index_ != other.entry_.index_;
      }
  
      const TupleView& operator*() const { return entry_; }
  
      Iterator& operator++() {
        entry_.index_ += EntrySize;
        return *this;
      }
  
     private:
      TupleView entry_;
    };
  
    explicit ArrayOfTuplesView(const Array& array) : array_(array), index_(-1) {
      ASSERT(!array.IsNull());
      ASSERT(array.Length() >= kStartOffset);
      ASSERT((array.Length() - kStartOffset) % EntrySize == kStartOffset);
    }
  
    intptr_t Length() const {
      return (array_.Length() - kStartOffset) / EntrySize;
    }
  
    TupleView At(intptr_t i) const {
      return TupleView(array_, kStartOffset + i * EntrySize);
    }
  
    TupleView operator[](intptr_t i) const { return At(i); }
  
    Iterator begin() const { return Iterator(array_, kStartOffset); }
  
    Iterator end() const {
      return Iterator(array_, kStartOffset + Length() * EntrySize);
    }
  
   private:
    const Array& array_;
    intptr_t index_;
  };
  
  using InvocationDispatcherTable =
      ArrayOfTuplesView<Class::InvocationDispatcherEntry,
                        std::tuple<String, Array, Function>>;
  
  using StaticCallsTable =
      ArrayOfTuplesView<Code::SCallTableEntry, std::tuple<Smi, Object, Function>>;
  
  using StaticCallsTableEntry = StaticCallsTable::TupleView;
  
  using SubtypeTestCacheTable = ArrayOfTuplesView<SubtypeTestCache::Entries,
                                                  std::tuple<Object,
                                                             Object,
                                                             AbstractType,
                                                             TypeArguments,
                                                             TypeArguments,
                                                             TypeArguments,
                                                             TypeArguments,
                                                             TypeArguments>>;
  
  using MegamorphicCacheEntries =
      ArrayOfTuplesView<MegamorphicCache::EntryType, std::tuple<Smi, Object>>;
  
  void DumpTypeTable(Isolate* isolate);
  void DumpTypeParameterTable(Isolate* isolate);
  void DumpTypeArgumentsTable(Isolate* isolate);
  
  EntryPointPragma FindEntryPointPragma(IsolateGroup* isolate_group,
                                        const Array& metadata,
                                        Field* reusable_field_handle,
                                        Object* reusable_object_handle);
  
  DART_WARN_UNUSED_RESULT
  ErrorPtr EntryPointFieldInvocationError(const String& getter_name);
  
  DART_WARN_UNUSED_RESULT
  ErrorPtr EntryPointMemberInvocationError(const Object& member);
  
  #undef PRECOMPILER_WSR_FIELD_DECLARATION
  
  }  // namespace dart
  
  #endif  // RUNTIME_VM_OBJECT_H_
  