mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
![Ryan Macnak](/assets/img/avatar_default.png)
TEST=ci Change-Id: I3161cad413f2d7be2bd8269306a51b5ae6a7384d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/252780 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
12782 lines
468 KiB
C++
12782 lines
468 KiB
C++
// 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/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;
|
|
class NativeArguments;
|
|
|
|
#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;
|
|
|
|
extern "C" void DFLRT_ExitSafepoint(NativeArguments __unusable_);
|
|
|
|
#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; \
|
|
friend void DFLRT_ExitSafepoint(NativeArguments __unusable_);
|
|
|
|
// 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; \
|
|
friend void DFLRT_ExitSafepoint(NativeArguments __unusable_);
|
|
|
|
#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(ExceptionHandlers, empty_async_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, no_callbacks_error) \
|
|
V(UnwindError, unwind_in_progress_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;
|
|
}
|
|
|
|
// Memcpy to account for the strict aliasing rule.
|
|
// Explicit cast to silence -Wdynamic-class-memaccess.
|
|
// This is still undefined behavior because we're messing with the internal
|
|
// representation of C++ objects, but works okay in practice with
|
|
// -fno-strict-vtable-pointers.
|
|
cpp_vtable vtable() const {
|
|
cpp_vtable result;
|
|
memcpy(&result, reinterpret_cast<const void*>(this), // NOLINT
|
|
sizeof(result));
|
|
return result;
|
|
}
|
|
void set_vtable(cpp_vtable value) {
|
|
memcpy(reinterpret_cast<void*>(this), &value, // NOLINT
|
|
sizeof(cpp_vtable));
|
|
}
|
|
|
|
static ObjectPtr Allocate(intptr_t cls_id,
|
|
intptr_t size,
|
|
Heap::Space space,
|
|
bool compressed);
|
|
|
|
static constexpr 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);
|
|
}
|
|
|
|
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);
|
|
};
|
|
|
|
// Used to declare setters and getters for untagged object fields that are
|
|
// defined with the WSR_COMPRESSED_POINTER_FIELD macro.
|
|
//
|
|
// In the precompiler, the getter transparently unwraps the
|
|
// WeakSerializationReference, if present, to get the wrapped value of the
|
|
// appropriate type, since a WeakSerializationReference object should be
|
|
// transparent to the parts of the precompiler that are not the serializer.
|
|
// Meanwhile, the setter takes an Object to allow the precompiler to set the
|
|
// field to a WeakSerializationReference.
|
|
//
|
|
// Since WeakSerializationReferences are only used during precompilation,
|
|
// this macro creates the normally expected getter and setter otherwise.
|
|
#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_); }
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
// If the interface of this class has a single concrete implementation, either
|
|
// via `extends` or by `implements`, returns its CID.
|
|
// If it has no implementation, returns kIllegalCid.
|
|
// If it has more than one implementation, returns kDynamicCid.
|
|
intptr_t implementor_cid() const { return untag()->implementor_cid_; }
|
|
|
|
// Returns true if the implementor tracking state changes and so must be
|
|
// propagated to this class's superclass and interfaces.
|
|
bool NoteImplementor(const Class& implementor) const;
|
|
#endif
|
|
|
|
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;
|
|
|
|
// 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 RehashConstants(Zone* zone) const;
|
|
|
|
bool RequireCanonicalTypeErasureOfConstants(Zone* zone) const;
|
|
|
|
static intptr_t InstanceSize() {
|
|
return RoundedAllocationSize(sizeof(UntaggedClass));
|
|
}
|
|
|
|
// Returns true if any class implements this interface via `implements`.
|
|
// Returns false if all possible implementations of this interface must be
|
|
// instances of this class or its subclasses.
|
|
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_;
|
|
}
|
|
static bool ImplementsFinalizable(ClassPtr clazz) {
|
|
ASSERT(Class::Handle(clazz).is_type_finalized());
|
|
return ImplementsFinalizableBit::decode(clazz->untag()->state_bits_);
|
|
}
|
|
|
|
#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,
|
|
kImplementsFinalizableBit,
|
|
};
|
|
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> {};
|
|
class ImplementsFinalizableBit
|
|
: public BitField<uint32_t, bool, kImplementsFinalizableBit, 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;
|
|
void set_implementor_cid(intptr_t value) 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;
|
|
|
|
bool implements_finalizable() const {
|
|
ASSERT(is_type_finalized());
|
|
return ImplementsFinalizable(ptr());
|
|
}
|
|
void set_implements_finalizable(bool value) 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.
|
|
//
|
|
// ICData's backing store is an array that logically contains several valid
|
|
// entries followed by a sentinal entry.
|
|
//
|
|
// [<entry-0>, <...>, <entry-N>, <sentinel>]
|
|
//
|
|
// Each entry has the following form:
|
|
//
|
|
// [arg0?, arg1?, argN?, count, target-function/code, exactness?]
|
|
//
|
|
// The <entry-X> need to contain valid type feedback.
|
|
// The <sentinel> entry and must have kIllegalCid value for all
|
|
// members of the entry except for the last one (`exactness` if
|
|
// present, otherwise `target-function/code`) - which we use as a backref:
|
|
//
|
|
// * For empty ICData we use a cached/shared backing store. So there is no
|
|
// unique backref, we use kIllegalCid instead.
|
|
// * For non-empty ICData the backref in the backing store array will point to
|
|
// the ICData object.
|
|
//
|
|
// Updating the ICData happens under a lock to avoid phantom-reads. The backing
|
|
// is treated as an immutable Copy-on-Write data structure: Adding to the ICData
|
|
// makes a copy with length+1 which will be store-release'd so any reader can
|
|
// see it (and doesn't need to hold a lock).
|
|
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;
|
|
|
|
intptr_t NumberOfChecks() const;
|
|
|
|
// Discounts any checks with usage of zero.
|
|
// Takes O(result)) time!
|
|
intptr_t NumberOfUsedChecks() const;
|
|
|
|
bool NumberOfChecksIs(intptr_t n) const;
|
|
|
|
bool IsValidEntryIndex(intptr_t index) const {
|
|
return 0 <= index && index < NumberOfChecks();
|
|
}
|
|
|
|
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
|
|
|
|
// NOTE: Can only be called during reload.
|
|
void Clear(const CallSiteResetter& proof_of_reload) const {
|
|
TruncateTo(0, proof_of_reload);
|
|
}
|
|
|
|
// NOTE: Can only be called during reload.
|
|
void TruncateTo(intptr_t num_checks,
|
|
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?
|
|
void SetNumArgsTested(intptr_t value) const;
|
|
void SetReceiversStaticType(const AbstractType& type) const;
|
|
DEBUG_ONLY(void AssertInvariantsAreSatisfied() 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 bool IsCachedEmptyEntry(const Array& array);
|
|
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;
|
|
const char* QualifiedScrubbedNameCString() 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 EntryPointOf(ptr());
|
|
}
|
|
static uword EntryPointOf(const FunctionPtr function) {
|
|
return function->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;
|
|
|
|
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
|
|
}
|
|
|
|
#if !defined(PRODUCT) && \
|
|
(defined(DART_PRECOMPILER) || defined(DART_PRECOMPILED_RUNTIME))
|
|
int32_t line() const {
|
|
return untag()->token_pos_.Serialize();
|
|
}
|
|
|
|
void set_line(int32_t line) const {
|
|
StoreNonPointer(&untag()->token_pos_, TokenPosition::Deserialize(line));
|
|
}
|
|
#endif
|
|
|
|
// Returns the size of the source for this function.
|
|
intptr_t SourceSize() const;
|
|
|
|
uint32_t packed_fields() const {
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
UNREACHABLE();
|
|
#else
|
|
return untag()->packed_fields_;
|
|
#endif
|
|
}
|
|
void set_packed_fields(uint32_t packed_fields) const;
|
|
|
|
// 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;
|
|
|
|
// Returns true if parameters of this function are copied into the frame
|
|
// in the function prologue.
|
|
bool MakesCopyOfParameters() const {
|
|
return HasOptionalParameters() || IsSuspendableFunction();
|
|
}
|
|
|
|
#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;
|
|
|
|
// Whether this function's |recognized_kind| requires optimization.
|
|
bool RecognizedKindForceOptimize() const;
|
|
|
|
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;
|
|
}
|
|
|
|
// Returns true for functions which execution can be suspended
|
|
// using Suspend/Resume stubs. Such functions have an artificial
|
|
// :suspend_state local variable at the fixed location of the frame.
|
|
bool IsSuspendableFunction() const {
|
|
return modifier() != UntaggedFunction::kNoModifier;
|
|
}
|
|
|
|
// Returns true if this function is marked with 'async' modifier.
|
|
bool IsAsyncFunction() const {
|
|
return modifier() == UntaggedFunction::kAsync;
|
|
}
|
|
|
|
// Returns true if this function is marked with 'sync*' modifier.
|
|
bool IsSyncGenerator() const {
|
|
return modifier() == UntaggedFunction::kSyncGen;
|
|
}
|
|
|
|
// Returns true if this function is marked with 'async*' modifier.
|
|
bool IsAsyncGenerator() const {
|
|
return modifier() == UntaggedFunction::kAsyncGen;
|
|
}
|
|
|
|
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.
|
|
// 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(PolymorphicTarget, is_polymorphic_target) \
|
|
V(HasPragma, has_pragma) \
|
|
V(IsSynthetic, is_synthetic) \
|
|
V(IsExtensionMember, is_extension_member) \
|
|
V(IsRedirectingFactory, is_redirecting_factory)
|
|
// 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 {
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
UNREACHABLE();
|
|
#else
|
|
return untag()->packed_fields_.Read<UntaggedFunction::PackedOptimizable>();
|
|
#endif
|
|
}
|
|
void set_is_optimizable(bool value) const {
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
UNREACHABLE();
|
|
#else
|
|
untag()->packed_fields_.UpdateBool<UntaggedFunction::PackedOptimizable>(
|
|
value);
|
|
#endif
|
|
}
|
|
|
|
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(bool are_mutators_stopped = false) 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 StoreFieldInstr; // 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> {};
|
|
|
|
// 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(); }
|
|
static StringPtr UrlOf(LibraryPtr lib) { return lib->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_private_key(const String& key) 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;
|
|
#elif defined(TARGET_ARCH_RISCV32)
|
|
static const intptr_t kMonomorphicEntryOffsetJIT = 6;
|
|
static const intptr_t kPolymorphicEntryOffsetJIT = 42;
|
|
static const intptr_t kMonomorphicEntryOffsetAOT = 6;
|
|
static const intptr_t kPolymorphicEntryOffsetAOT = 16;
|
|
#elif defined(TARGET_ARCH_RISCV64)
|
|
static const intptr_t kMonomorphicEntryOffsetJIT = 6;
|
|
static const intptr_t kPolymorphicEntryOffsetJIT = 42;
|
|
static const intptr_t kMonomorphicEntryOffsetAOT = 6;
|
|
static const intptr_t kPolymorphicEntryOffsetAOT = 16;
|
|
#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 Hash(ptr()); }
|
|
|
|
static uint32_t Hash(const InstructionsPtr instr) {
|
|
return HashBytes(reinterpret_cast<const uint8_t*>(PayloadStart(instr)),
|
|
Size(instr));
|
|
}
|
|
|
|
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 intptr_t InstanceSize() { return sizeof(UntaggedInstructionsTable); }
|
|
|
|
static InstructionsTablePtr New(intptr_t length,
|
|
uword start_pc,
|
|
uword end_pc,
|
|
uword rodata);
|
|
|
|
void SetCodeAt(intptr_t index, CodePtr code) const;
|
|
|
|
bool ContainsPc(uword pc) const { return ContainsPc(ptr(), pc); }
|
|
static bool ContainsPc(InstructionsTablePtr table, uword pc);
|
|
|
|
static CodePtr FindCode(InstructionsTablePtr table, uword pc);
|
|
|
|
static const UntaggedCompressedStackMaps::Payload*
|
|
FindStackMap(InstructionsTablePtr table, uword pc, uword* start_pc);
|
|
|
|
static const UntaggedCompressedStackMaps::Payload* GetCanonicalStackMap(
|
|
InstructionsTablePtr table);
|
|
|
|
const UntaggedInstructionsTable::Data* rodata() const {
|
|
return ptr()->untag()->rodata_;
|
|
}
|
|
|
|
// 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 code_objects() const { return untag()->code_objects_; }
|
|
|
|
void set_length(intptr_t value) const;
|
|
void set_start_pc(uword value) const;
|
|
void set_end_pc(uword value) const;
|
|
void set_code_objects(const Array& value) const;
|
|
void set_rodata(uword rodata) const;
|
|
|
|
uint32_t ConvertPcToOffset(uword pc) const {
|
|
return InstructionsTable::ConvertPcToOffset(this->ptr(), pc);
|
|
}
|
|
static uint32_t ConvertPcToOffset(InstructionsTablePtr table, uword pc);
|
|
|
|
static intptr_t FindEntry(InstructionsTablePtr table,
|
|
uword pc,
|
|
intptr_t start_index = 0);
|
|
|
|
FINAL_HEAP_OBJECT_IMPLEMENTATION(InstructionsTable, Object);
|
|
friend class Class;
|
|
friend class Deserializer;
|
|
};
|
|
|
|
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 : public 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;
|
|
}
|
|
|
|
uint32_t Hash() const {
|
|
NoSafepointScope no_safepoint;
|
|
return HashBytes(Data(), Length());
|
|
}
|
|
|
|
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()->payload()->flags_and_size());
|
|
}
|
|
|
|
const uint8_t* data() const { return ptr()->untag()->payload()->data(); }
|
|
|
|
// 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()->payload()->flags_and_size() !=
|
|
other.untag()->payload()->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) +
|
|
sizeof(UntaggedCompressedStackMaps::Payload::FlagsAndSizeHeader);
|
|
}
|
|
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() { 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()->payload()->flags_and_size());
|
|
}
|
|
|
|
bool IsGlobalTable() const { return IsGlobalTable(ptr()); }
|
|
static bool IsGlobalTable(const CompressedStackMapsPtr raw) {
|
|
return UntaggedCompressedStackMaps::GlobalTableBit::decode(
|
|
raw->untag()->payload()->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 RawPayloadHandle {
|
|
public:
|
|
RawPayloadHandle() {}
|
|
RawPayloadHandle(const RawPayloadHandle&) = default;
|
|
RawPayloadHandle& operator=(const RawPayloadHandle&) = default;
|
|
|
|
const UntaggedCompressedStackMaps::Payload* payload() const {
|
|
return payload_;
|
|
}
|
|
bool IsNull() const { return payload_ == nullptr; }
|
|
|
|
RawPayloadHandle& operator=(
|
|
const UntaggedCompressedStackMaps::Payload* payload) {
|
|
payload_ = payload;
|
|
return *this;
|
|
}
|
|
|
|
RawPayloadHandle& operator=(const CompressedStackMaps& maps) {
|
|
ASSERT(!maps.IsNull());
|
|
payload_ = maps.untag()->payload();
|
|
return *this;
|
|
}
|
|
|
|
RawPayloadHandle& operator=(CompressedStackMapsPtr maps) {
|
|
ASSERT(maps != CompressedStackMaps::null());
|
|
payload_ = maps.untag()->payload();
|
|
return *this;
|
|
}
|
|
|
|
uintptr_t payload_size() const {
|
|
return UntaggedCompressedStackMaps::SizeField::decode(
|
|
payload()->flags_and_size());
|
|
}
|
|
const uint8_t* data() const { return payload()->data(); }
|
|
|
|
bool UsesGlobalTable() const {
|
|
return UntaggedCompressedStackMaps::UsesTableBit::decode(
|
|
payload()->flags_and_size());
|
|
}
|
|
|
|
bool IsGlobalTable() const {
|
|
return UntaggedCompressedStackMaps::GlobalTableBit::decode(
|
|
payload()->flags_and_size());
|
|
}
|
|
|
|
private:
|
|
const UntaggedCompressedStackMaps::Payload* payload_ = nullptr;
|
|
};
|
|
|
|
template <typename PayloadHandle>
|
|
class Iterator {
|
|
public:
|
|
Iterator(const PayloadHandle& maps, const PayloadHandle& global_table)
|
|
: maps_(maps),
|
|
bits_container_(maps.UsesGlobalTable() ? global_table : maps) {
|
|
ASSERT(!maps_.IsNull());
|
|
ASSERT(!bits_container_.IsNull());
|
|
ASSERT(!maps_.IsGlobalTable());
|
|
ASSERT(!maps_.UsesGlobalTable() || bits_container_.IsGlobalTable());
|
|
}
|
|
|
|
Iterator(const Iterator& it)
|
|
: maps_(it.maps_),
|
|
bits_container_(it.bits_container_),
|
|
next_offset_(it.next_offset_),
|
|
current_pc_offset_(it.current_pc_offset_),
|
|
current_global_table_offset_(it.current_global_table_offset_),
|
|
current_spill_slot_bit_count_(it.current_spill_slot_bit_count_),
|
|
current_non_spill_slot_bit_count_(it.current_spill_slot_bit_count_),
|
|
current_bits_offset_(it.current_bits_offset_) {}
|
|
|
|
// Loads the next entry from [maps_], if any. If [maps_] is the null value,
|
|
// this always returns false.
|
|
bool MoveNext() {
|
|
if (next_offset_ >= maps_.payload_size()) {
|
|
return false;
|
|
}
|
|
|
|
NoSafepointScope scope;
|
|
ReadStream stream(maps_.data(), maps_.payload_size(), next_offset_);
|
|
|
|
auto const pc_delta = stream.ReadLEB128();
|
|
ASSERT(pc_delta <= (kMaxUint32 - current_pc_offset_));
|
|
current_pc_offset_ += pc_delta;
|
|
|
|
// Table-using CSMs have a table offset after the PC offset delta, whereas
|
|
// the post-delta part of inlined entries has the same information as
|
|
// global table entries.
|
|
// See comments in UntaggedCompressedStackMaps for description of
|
|
// encoding.
|
|
if (maps_.UsesGlobalTable()) {
|
|
current_global_table_offset_ = stream.ReadLEB128();
|
|
ASSERT(current_global_table_offset_ < bits_container_.payload_size());
|
|
|
|
// Since generally we only use entries in the GC and the GC only needs
|
|
// the rest of the entry information if the PC offset matches, we lazily
|
|
// load and cache the information stored in the global object when it is
|
|
// actually requested.
|
|
current_spill_slot_bit_count_ = -1;
|
|
current_non_spill_slot_bit_count_ = -1;
|
|
current_bits_offset_ = -1;
|
|
|
|
next_offset_ = stream.Position();
|
|
} else {
|
|
current_spill_slot_bit_count_ = stream.ReadLEB128();
|
|
ASSERT(current_spill_slot_bit_count_ >= 0);
|
|
|
|
current_non_spill_slot_bit_count_ = stream.ReadLEB128();
|
|
ASSERT(current_non_spill_slot_bit_count_ >= 0);
|
|
|
|
const auto stackmap_bits =
|
|
current_spill_slot_bit_count_ + current_non_spill_slot_bit_count_;
|
|
const uintptr_t stackmap_size =
|
|
Utils::RoundUp(stackmap_bits, kBitsPerByte) >> kBitsPerByteLog2;
|
|
ASSERT(stackmap_size <= (maps_.payload_size() - stream.Position()));
|
|
|
|
current_bits_offset_ = stream.Position();
|
|
next_offset_ = current_bits_offset_ + stackmap_size;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// 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 {
|
|
EnsureFullyLoadedEntry();
|
|
return current_spill_slot_bit_count_ + current_non_spill_slot_bit_count_;
|
|
}
|
|
// Returns the number of spill slot bits of the loaded entry.
|
|
intptr_t SpillSlotBitCount() const {
|
|
EnsureFullyLoadedEntry();
|
|
return current_spill_slot_bit_count_;
|
|
}
|
|
// Returns whether the stack entry represented by the offset contains
|
|
// a tagged object.
|
|
bool IsObject(intptr_t bit_index) const {
|
|
EnsureFullyLoadedEntry();
|
|
ASSERT(bit_index >= 0 && bit_index < Length());
|
|
const intptr_t byte_index = bit_index >> kBitsPerByteLog2;
|
|
const intptr_t bit_remainder = bit_index & (kBitsPerByte - 1);
|
|
uint8_t byte_mask = 1U << bit_remainder;
|
|
const intptr_t byte_offset = current_bits_offset_ + byte_index;
|
|
NoSafepointScope scope;
|
|
return (bits_container_.data()[byte_offset] & byte_mask) != 0;
|
|
}
|
|
|
|
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 {
|
|
ASSERT(maps_.UsesGlobalTable());
|
|
ASSERT(HasLoadedEntry());
|
|
ASSERT(current_global_table_offset_ < bits_container_.payload_size());
|
|
|
|
NoSafepointScope scope;
|
|
ReadStream stream(bits_container_.data(), bits_container_.payload_size(),
|
|
current_global_table_offset_);
|
|
|
|
current_spill_slot_bit_count_ = stream.ReadLEB128();
|
|
ASSERT(current_spill_slot_bit_count_ >= 0);
|
|
|
|
current_non_spill_slot_bit_count_ = stream.ReadLEB128();
|
|
ASSERT(current_non_spill_slot_bit_count_ >= 0);
|
|
|
|
const auto stackmap_bits = Length();
|
|
const uintptr_t stackmap_size =
|
|
Utils::RoundUp(stackmap_bits, kBitsPerByte) >> kBitsPerByteLog2;
|
|
ASSERT(stackmap_size <=
|
|
(bits_container_.payload_size() - stream.Position()));
|
|
|
|
current_bits_offset_ = stream.Position();
|
|
}
|
|
|
|
void EnsureFullyLoadedEntry() const {
|
|
ASSERT(HasLoadedEntry());
|
|
if (current_spill_slot_bit_count_ < 0) {
|
|
LazyLoadGlobalTableEntry();
|
|
ASSERT(current_spill_slot_bit_count_ >= 0);
|
|
}
|
|
}
|
|
|
|
const PayloadHandle& maps_;
|
|
const PayloadHandle& 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;
|
|
};
|
|
|
|
Iterator<CompressedStackMaps> iterator(Thread* thread) const;
|
|
|
|
void WriteToBuffer(BaseTextBuffer* buffer, const char* separator) const;
|
|
|
|
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;
|
|
|
|
bool has_async_handler() const;
|
|
void set_has_async_handler(bool value) 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 instructions_offset() {
|
|
return OFFSET_OF(UntaggedCode, instructions_);
|
|
}
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
static intptr_t active_instructions_offset() {
|
|
return OFFSET_OF(UntaggedCode, active_instructions_);
|
|
}
|
|
#endif
|
|
|
|
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
|
|
}
|
|
|
|
static uword UncheckedEntryPointOf(const CodePtr code) {
|
|
return code->untag()->unchecked_entry_point_;
|
|
}
|
|
|
|
// 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*>(kOffsetOfPtr)->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(bool is_cls_parameterized) 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;
|
|
|
|
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));
|
|
}
|
|
|
|
static intptr_t LoadingUnitOf(const Function& function);
|
|
static intptr_t LoadingUnitOf(const Code& code);
|
|
|
|
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.
|
|
IntegerPtr IdentityHashCode(Thread* thread) const;
|
|
|
|
static intptr_t InstanceSize() {
|
|
return RoundedAllocationSize(sizeof(UntaggedInstance));
|
|
}
|
|
|
|
static InstancePtr New(const Class& cls, Heap::Space space = Heap::kNew);
|
|
static InstancePtr NewAlreadyFinalized(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 occurring 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 occurring 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;
|
|
|
|
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 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);
|
|
|
|
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);
|
|
|
|
// 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 : public 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;
|
|
friend class Deserializer;
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
// Matches null_patch.dart / bool_patch.dart.
|
|
static constexpr intptr_t kNullIdentityHash = 2011;
|
|
static constexpr intptr_t kTrueIdentityHash = 1231;
|
|
static constexpr intptr_t kFalseIdentityHash = 1237;
|
|
|
|
// 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() ? kTrueIdentityHash : kFalseIdentityHash;
|
|
}
|
|
|
|
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 constexpr bool UseCardMarkingForAllocation(
|
|
const intptr_t array_length) {
|
|
return Array::InstanceSize(array_length) > Heap::kNewAllocatableSize;
|
|
}
|
|
|
|
// WB invariant restoration code only applies to arrives which have at most
|
|
// this many elements. Consequently WB elimination code should not eliminate
|
|
// WB on arrays of larger lengths across instructions that can cause GC.
|
|
// Note: we also can't restore WB invariant for arrays which use card marking.
|
|
static constexpr intptr_t kMaxLengthForWriteBarrierElimination = 8;
|
|
|
|
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 {
|
|
untag()->set_element<order>(index, value.ptr());
|
|
}
|
|
template <std::memory_order order = std::memory_order_relaxed>
|
|
void SetAt(intptr_t index, const Object& value, Thread* thread) const {
|
|
untag()->set_element<order>(index, value.ptr(), thread);
|
|
}
|
|
|
|
// 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 constexpr 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 constexpr 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) {
|
|
return New(kArrayCid, len, space);
|
|
}
|
|
// The result's type arguments and elements are GC-safe but not initialized to
|
|
// null.
|
|
static ArrayPtr NewUninitialized(intptr_t len,
|
|
Heap::Space space = Heap::kNew) {
|
|
return NewUninitialized(kArrayCid, len, space);
|
|
}
|
|
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);
|
|
static ArrayPtr NewUninitialized(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);
|
|
}
|
|
|
|
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_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 payload_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 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 typed_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 : public 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 : public 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 SuspendState : public Instance {
|
|
public:
|
|
// :suspend_state local variable index
|
|
static constexpr intptr_t kSuspendStateVarIndex = 0;
|
|
|
|
static intptr_t HeaderSize() { return sizeof(UntaggedSuspendState); }
|
|
static intptr_t UnroundedSize(SuspendStatePtr ptr) {
|
|
return UnroundedSize(ptr->untag()->frame_capacity());
|
|
}
|
|
static intptr_t UnroundedSize(intptr_t frame_capacity) {
|
|
return HeaderSize() + frame_capacity;
|
|
}
|
|
static intptr_t InstanceSize() {
|
|
ASSERT_EQUAL(sizeof(UntaggedSuspendState),
|
|
OFFSET_OF_RETURNED_VALUE(UntaggedSuspendState, payload));
|
|
return 0;
|
|
}
|
|
static intptr_t InstanceSize(intptr_t frame_capacity) {
|
|
return RoundedAllocationSize(UnroundedSize(frame_capacity));
|
|
}
|
|
|
|
// Number of extra words reserved for growth of frame size
|
|
// during SuspendState allocation. Frames do not grow in AOT.
|
|
static intptr_t FrameSizeGrowthGap() {
|
|
return ONLY_IN_PRECOMPILED(0) NOT_IN_PRECOMPILED(2);
|
|
}
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
static intptr_t frame_capacity_offset() {
|
|
return OFFSET_OF(UntaggedSuspendState, frame_capacity_);
|
|
}
|
|
#endif
|
|
static intptr_t frame_size_offset() {
|
|
return OFFSET_OF(UntaggedSuspendState, frame_size_);
|
|
}
|
|
static intptr_t pc_offset() { return OFFSET_OF(UntaggedSuspendState, pc_); }
|
|
static intptr_t function_data_offset() {
|
|
return OFFSET_OF(UntaggedSuspendState, function_data_);
|
|
}
|
|
static intptr_t then_callback_offset() {
|
|
return OFFSET_OF(UntaggedSuspendState, then_callback_);
|
|
}
|
|
static intptr_t error_callback_offset() {
|
|
return OFFSET_OF(UntaggedSuspendState, error_callback_);
|
|
}
|
|
static intptr_t payload_offset() {
|
|
return UntaggedSuspendState::payload_offset();
|
|
}
|
|
|
|
static SuspendStatePtr New(intptr_t frame_size,
|
|
const Instance& function_data,
|
|
Heap::Space space = Heap::kNew);
|
|
|
|
// Makes a copy of [src] object.
|
|
// The object should be holding a suspended frame.
|
|
static SuspendStatePtr Clone(Thread* thread,
|
|
const SuspendState& src,
|
|
Heap::Space space = Heap::kNew);
|
|
|
|
uword pc() const { return untag()->pc_; }
|
|
|
|
intptr_t frame_size() const { return untag()->frame_size_; }
|
|
|
|
InstancePtr function_data() const {
|
|
return untag()->function_data();
|
|
}
|
|
|
|
ClosurePtr then_callback() const { return untag()->then_callback(); }
|
|
|
|
ClosurePtr error_callback() const {
|
|
return untag()->error_callback();
|
|
}
|
|
|
|
// Returns Code object corresponding to the suspended function.
|
|
CodePtr GetCodeObject() const;
|
|
|
|
private:
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
void set_frame_capacity(intptr_t frame_capcity) const;
|
|
#endif
|
|
void set_frame_size(intptr_t frame_size) const;
|
|
void set_pc(uword pc) const;
|
|
void set_function_data(const Instance& function_data) const;
|
|
void set_then_callback(const Closure& then_callback) const;
|
|
void set_error_callback(const Closure& error_callback) const;
|
|
|
|
uint8_t* payload() const { return untag()->payload(); }
|
|
|
|
FINAL_HEAP_OBJECT_IMPLEMENTATION(SuspendState, Instance);
|
|
friend class Class;
|
|
};
|
|
|
|
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_seen_by_gc_ ==
|
|
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 WeakReference : public Instance {
|
|
public:
|
|
ObjectPtr target() const { return untag()->target(); }
|
|
void set_target(const Object& target) const {
|
|
untag()->set_target(target.ptr());
|
|
}
|
|
static intptr_t target_offset() {
|
|
return OFFSET_OF(UntaggedWeakReference, target_);
|
|
}
|
|
|
|
static intptr_t type_arguments_offset() {
|
|
return OFFSET_OF(UntaggedWeakReference, type_arguments_);
|
|
}
|
|
|
|
static WeakReferencePtr New(Heap::Space space = Heap::kNew);
|
|
|
|
static intptr_t InstanceSize() {
|
|
return RoundedAllocationSize(sizeof(UntaggedWeakReference));
|
|
}
|
|
|
|
private:
|
|
FINAL_HEAP_OBJECT_IMPLEMENTATION(WeakReference, Instance);
|
|
friend class Class;
|
|
};
|
|
|
|
class FinalizerBase;
|
|
class FinalizerEntry : public Instance {
|
|
public:
|
|
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(UntaggedFinalizerEntry, value_);
|
|
}
|
|
|
|
ObjectPtr detach() const { return untag()->detach(); }
|
|
void set_detach(const Object& value) const {
|
|
untag()->set_detach(value.ptr());
|
|
}
|
|
static intptr_t detach_offset() {
|
|
return OFFSET_OF(UntaggedFinalizerEntry, detach_);
|
|
}
|
|
|
|
ObjectPtr token() const { return untag()->token(); }
|
|
void set_token(const Object& value) const { untag()->set_token(value.ptr()); }
|
|
static intptr_t token_offset() {
|
|
return OFFSET_OF(UntaggedFinalizerEntry, token_);
|
|
}
|
|
|
|
FinalizerBasePtr finalizer() const { return untag()->finalizer(); }
|
|
void set_finalizer(const FinalizerBase& value) const;
|
|
static intptr_t finalizer_offset() {
|
|
return OFFSET_OF(UntaggedFinalizerEntry, finalizer_);
|
|
}
|
|
|
|
FinalizerEntryPtr next() const { return untag()->next(); }
|
|
void set_next(const FinalizerEntry& value) const {
|
|
untag()->set_next(value.ptr());
|
|
}
|
|
static intptr_t next_offset() {
|
|
return OFFSET_OF(UntaggedFinalizerEntry, next_);
|
|
}
|
|
|
|
intptr_t external_size() const { return untag()->external_size(); }
|
|
void set_external_size(intptr_t value) const {
|
|
untag()->set_external_size(value);
|
|
}
|
|
static intptr_t external_size_offset() {
|
|
return OFFSET_OF(UntaggedFinalizerEntry, external_size_);
|
|
}
|
|
|
|
static intptr_t InstanceSize() {
|
|
return RoundedAllocationSize(sizeof(UntaggedFinalizerEntry));
|
|
}
|
|
|
|
// Allocates a new FinalizerEntry, initializing the external size (to 0) and
|
|
// finalizer.
|
|
//
|
|
// Should only be used for object tests.
|
|
//
|
|
// Does not initialize `value`, `token`, and `detach` to allow for flexible
|
|
// testing code setting those manually.
|
|
//
|
|
// Does _not_ add the entry to the finalizer. We could add the entry to
|
|
// finalizer.all_entries.data, but we have no way of initializing the hashset
|
|
// index.
|
|
static FinalizerEntryPtr New(const FinalizerBase& finalizer,
|
|
Heap::Space space = Heap::kNew);
|
|
|
|
private:
|
|
FINAL_HEAP_OBJECT_IMPLEMENTATION(FinalizerEntry, Instance);
|
|
friend class Class;
|
|
};
|
|
|
|
class FinalizerBase : public Instance {
|
|
public:
|
|
static intptr_t isolate_offset() {
|
|
return OFFSET_OF(UntaggedFinalizerBase, isolate_);
|
|
}
|
|
Isolate* isolate() const { return untag()->isolate_; }
|
|
void set_isolate(Isolate* value) const { untag()->isolate_ = value; }
|
|
|
|
static intptr_t detachments_offset() {
|
|
return OFFSET_OF(UntaggedFinalizerBase, detachments_);
|
|
}
|
|
|
|
LinkedHashSetPtr all_entries() const { return untag()->all_entries(); }
|
|
void set_all_entries(const LinkedHashSet& value) const {
|
|
untag()->set_all_entries(value.ptr());
|
|
}
|
|
static intptr_t all_entries_offset() {
|
|
return OFFSET_OF(UntaggedFinalizerBase, all_entries_);
|
|
}
|
|
|
|
FinalizerEntryPtr entries_collected() const {
|
|
return untag()->entries_collected();
|
|
}
|
|
void set_entries_collected(const FinalizerEntry& value) const {
|
|
untag()->set_entries_collected(value.ptr());
|
|
}
|
|
static intptr_t entries_collected_offset() {
|
|
return OFFSET_OF(UntaggedFinalizer, entries_collected_);
|
|
}
|
|
|
|
private:
|
|
HEAP_OBJECT_IMPLEMENTATION(FinalizerBase, Instance);
|
|
friend class Class;
|
|
};
|
|
|
|
class Finalizer : public FinalizerBase {
|
|
public:
|
|
static intptr_t type_arguments_offset() {
|
|
return OFFSET_OF(UntaggedFinalizer, type_arguments_);
|
|
}
|
|
|
|
ObjectPtr callback() const { return untag()->callback(); }
|
|
static intptr_t callback_offset() {
|
|
return OFFSET_OF(UntaggedFinalizer, callback_);
|
|
}
|
|
|
|
static intptr_t InstanceSize() {
|
|
return RoundedAllocationSize(sizeof(UntaggedFinalizer));
|
|
}
|
|
|
|
static FinalizerPtr New(Heap::Space space = Heap::kNew);
|
|
|
|
private:
|
|
FINAL_HEAP_OBJECT_IMPLEMENTATION(Finalizer, FinalizerBase);
|
|
friend class Class;
|
|
};
|
|
|
|
class NativeFinalizer : public FinalizerBase {
|
|
public:
|
|
typedef void (*Callback)(void*);
|
|
|
|
PointerPtr callback() const { return untag()->callback(); }
|
|
void set_callback(const Pointer& value) const {
|
|
untag()->set_callback(value.ptr());
|
|
}
|
|
static intptr_t callback_offset() {
|
|
return OFFSET_OF(UntaggedNativeFinalizer, callback_);
|
|
}
|
|
|
|
static intptr_t InstanceSize() {
|
|
return RoundedAllocationSize(sizeof(UntaggedNativeFinalizer));
|
|
}
|
|
|
|
static NativeFinalizerPtr New(Heap::Space space = Heap::kNew);
|
|
|
|
void RunCallback(const FinalizerEntry& entry,
|
|
const char* trace_context) const;
|
|
|
|
private:
|
|
FINAL_HEAP_OBJECT_IMPLEMENTATION(NativeFinalizer, FinalizerBase);
|
|
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);
|
|
}
|
|
|
|
bool streamable() const { return untag()->streamable(); }
|
|
void set_streamable(bool streamable) {
|
|
StoreNonPointer(&untag()->streamable_, streamable);
|
|
}
|
|
|
|
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);
|
|
static UserTagPtr FindTagInIsolate(Isolate* isolate,
|
|
Thread* thread,
|
|
const String& label);
|
|
|
|
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]);
|
|
}
|
|
|
|
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 = ArrayOfTuplesView<
|
|
// 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_
|