blob: 6eff1b432da9d1c6736cf55e0d5737bf1e5310ca [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/object.h"
#include <memory>
#include "include/dart_api.h"
#include "lib/stacktrace.h"
#include "platform/assert.h"
#include "platform/text_buffer.h"
#include "platform/unaligned.h"
#include "platform/unicode.h"
#include "vm/bit_vector.h"
#include "vm/bootstrap.h"
#include "vm/canonical_tables.h"
#include "vm/class_finalizer.h"
#include "vm/closure_functions_cache.h"
#include "vm/code_comments.h"
#include "vm/code_descriptors.h"
#include "vm/code_observers.h"
#include "vm/compiler/assembler/disassembler.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/compiler/runtime_api.h"
#include "vm/cpu.h"
#include "vm/dart.h"
#include "vm/dart_api_state.h"
#include "vm/dart_entry.h"
#include "vm/datastream.h"
#include "vm/debugger.h"
#include "vm/deopt_instructions.h"
#include "vm/double_conversion.h"
#include "vm/elf.h"
#include "vm/exceptions.h"
#include "vm/growable_array.h"
#include "vm/hash.h"
#include "vm/hash_table.h"
#include "vm/heap/become.h"
#include "vm/heap/heap.h"
#include "vm/heap/weak_code.h"
#include "vm/image_snapshot.h"
#include "vm/isolate_reload.h"
#include "vm/kernel.h"
#include "vm/kernel_binary.h"
#include "vm/kernel_isolate.h"
#include "vm/kernel_loader.h"
#include "vm/native_symbol.h"
#include "vm/object_graph.h"
#include "vm/object_store.h"
#include "vm/parser.h"
#include "vm/profiler.h"
#include "vm/resolver.h"
#include "vm/reusable_handles.h"
#include "vm/runtime_entry.h"
#include "vm/scopes.h"
#include "vm/stack_frame.h"
#include "vm/stub_code.h"
#include "vm/symbols.h"
#include "vm/tags.h"
#include "vm/thread_registry.h"
#include "vm/timeline.h"
#include "vm/type_testing_stubs.h"
#include "vm/zone_text_buffer.h"
#if !defined(DART_PRECOMPILED_RUNTIME)
#include "vm/compiler/aot/precompiler.h"
#include "vm/compiler/assembler/assembler.h"
#include "vm/compiler/backend/code_statistics.h"
#include "vm/compiler/compiler_state.h"
#include "vm/compiler/frontend/kernel_fingerprints.h"
#include "vm/compiler/frontend/kernel_translation_helper.h"
#include "vm/compiler/intrinsifier.h"
#endif // !defined(DART_PRECOMPILED_RUNTIME)
namespace dart {
DEFINE_FLAG(uint64_t,
huge_method_cutoff_in_code_size,
200000,
"Huge method cutoff in unoptimized code size (in bytes).");
DEFINE_FLAG(
bool,
show_internal_names,
false,
"Show names of internal classes (e.g. \"OneByteString\") in error messages "
"instead of showing the corresponding interface names (e.g. \"String\"). "
"Also show legacy nullability in type names.");
DEFINE_FLAG(bool, use_lib_cache, false, "Use library name cache");
DEFINE_FLAG(bool, use_exp_cache, false, "Use library exported name cache");
DEFINE_FLAG(bool,
remove_script_timestamps_for_test,
false,
"Remove script timestamps to allow for deterministic testing.");
DECLARE_FLAG(bool, dual_map_code);
DECLARE_FLAG(bool, intrinsify);
DECLARE_FLAG(bool, trace_deoptimization);
DECLARE_FLAG(bool, trace_deoptimization_verbose);
DECLARE_FLAG(bool, trace_reload);
DECLARE_FLAG(bool, write_protect_code);
DECLARE_FLAG(bool, precompiled_mode);
DECLARE_FLAG(int, max_polymorphic_checks);
static const char* const kGetterPrefix = "get:";
static const intptr_t kGetterPrefixLength = strlen(kGetterPrefix);
static const char* const kSetterPrefix = "set:";
static const intptr_t kSetterPrefixLength = strlen(kSetterPrefix);
static const char* const kInitPrefix = "init:";
static const intptr_t kInitPrefixLength = strlen(kInitPrefix);
// A cache of VM heap allocated preinitialized empty ic data entry arrays.
ArrayPtr ICData::cached_icdata_arrays_[kCachedICDataArrayCount];
// A VM heap allocated preinitialized empty subtype entry array.
ArrayPtr SubtypeTestCache::cached_array_;
cpp_vtable Object::builtin_vtables_[kNumPredefinedCids] = {};
// These are initialized to a value that will force a illegal memory access if
// they are being used.
#if defined(RAW_NULL)
#error RAW_NULL should not be defined.
#endif
#define RAW_NULL static_cast<uword>(kHeapObjectTag)
#define CHECK_ERROR(error) \
{ \
ErrorPtr err = (error); \
if (err != Error::null()) { \
return err; \
} \
}
#define DEFINE_SHARED_READONLY_HANDLE(Type, name) \
Type* Object::name##_ = nullptr;
SHARED_READONLY_HANDLES_LIST(DEFINE_SHARED_READONLY_HANDLE)
#undef DEFINE_SHARED_READONLY_HANDLE
ObjectPtr Object::null_ = static_cast<ObjectPtr>(RAW_NULL);
BoolPtr Object::true_ = static_cast<BoolPtr>(RAW_NULL);
BoolPtr Object::false_ = static_cast<BoolPtr>(RAW_NULL);
ClassPtr Object::class_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::dynamic_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::void_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::type_arguments_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::patch_class_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::function_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::closure_data_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::ffi_trampoline_data_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::field_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::script_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::library_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::namespace_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::kernel_program_info_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::code_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::instructions_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::instructions_section_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::object_pool_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::pc_descriptors_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::code_source_map_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::compressed_stackmaps_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::var_descriptors_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::exception_handlers_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::context_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::context_scope_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::singletargetcache_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::unlinkedcall_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::monomorphicsmiablecall_class_ =
static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::icdata_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::megamorphic_cache_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::subtypetestcache_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::loadingunit_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::api_error_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::language_error_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::unhandled_exception_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::unwind_error_class_ = static_cast<ClassPtr>(RAW_NULL);
ClassPtr Object::weak_serialization_reference_class_ =
static_cast<ClassPtr>(RAW_NULL);
const double MegamorphicCache::kLoadFactor = 0.50;
static void AppendSubString(BaseTextBuffer* buffer,
const char* name,
intptr_t start_pos,
intptr_t len) {
buffer->Printf("%.*s", static_cast<int>(len), &name[start_pos]);
}
// Remove private keys, but retain getter/setter/constructor/mixin manglings.
StringPtr String::RemovePrivateKey(const String& name) {
ASSERT(name.IsOneByteString());
GrowableArray<uint8_t> without_key(name.Length());
intptr_t i = 0;
while (i < name.Length()) {
while (i < name.Length()) {
uint8_t c = name.CharAt(i++);
if (c == '@') break;
without_key.Add(c);
}
while (i < name.Length()) {
uint8_t c = name.CharAt(i);
if ((c < '0') || (c > '9')) break;
i++;
}
}
return String::FromLatin1(without_key.data(), without_key.length());
}
// Takes a vm internal name and makes it suitable for external user.
//
// Examples:
//
// Internal getter and setter prefixes are changed:
//
// get:foo -> foo
// set:foo -> foo=
//
// Private name mangling is removed, possibly multiple times:
//
// _ReceivePortImpl@709387912 -> _ReceivePortImpl
// _ReceivePortImpl@709387912._internal@709387912 ->
// _ReceivePortImpl._internal
// _C@6328321&_E@6328321&_F@6328321 -> _C&_E&_F
//
// The trailing . on the default constructor name is dropped:
//
// List. -> List
//
// And so forth:
//
// get:foo@6328321 -> foo
// _MyClass@6328321. -> _MyClass
// _MyClass@6328321.named -> _MyClass.named
//
// For extension methods the following demangling is done
// ext|func -> ext.func (instance extension method)
// ext|get#prop -> ext.prop (instance extension getter)
// ext|set#prop -> ext.prop= (instance extension setter)
// ext|sfunc -> ext.sfunc (static extension method)
// get:ext|sprop -> ext.sprop (static extension getter)
// set:ext|sprop -> ext.sprop= (static extension setter)
//
const char* String::ScrubName(const String& name, bool is_extension) {
Thread* thread = Thread::Current();
NoSafepointScope no_safepoint(thread);
Zone* zone = thread->zone();
ZoneTextBuffer printer(zone);
#if !defined(DART_PRECOMPILED_RUNTIME)
if (name.Equals(Symbols::TopLevel())) {
// Name of invisible top-level class.
return "";
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
const char* cname = name.ToCString();
ASSERT(strlen(cname) == static_cast<size_t>(name.Length()));
const intptr_t name_len = name.Length();
// First remove all private name mangling and if 'is_extension' is true
// substitute the first '|' character with '.'.
intptr_t start_pos = 0;
intptr_t sum_segment_len = 0;
for (intptr_t i = 0; i < name_len; i++) {
if ((cname[i] == '@') && ((i + 1) < name_len) && (cname[i + 1] >= '0') &&
(cname[i + 1] <= '9')) {
// Append the current segment to the unmangled name.
const intptr_t segment_len = i - start_pos;
sum_segment_len += segment_len;
AppendSubString(&printer, cname, start_pos, segment_len);
// Advance until past the name mangling. The private keys are only
// numbers so we skip until the first non-number.
i++; // Skip the '@'.
while ((i < name.Length()) && (name.CharAt(i) >= '0') &&
(name.CharAt(i) <= '9')) {
i++;
}
start_pos = i;
i--; // Account for for-loop increment.
} else if (is_extension && cname[i] == '|') {
// Append the current segment to the unmangled name.
const intptr_t segment_len = i - start_pos;
AppendSubString(&printer, cname, start_pos, segment_len);
// Append the '.' character (replaces '|' with '.').
AppendSubString(&printer, ".", 0, 1);
start_pos = i + 1;
// Account for length of segments added so far.
sum_segment_len += (segment_len + 1);
}
}
const char* unmangled_name = NULL;
if (start_pos == 0) {
// No name unmangling needed, reuse the name that was passed in.
unmangled_name = cname;
sum_segment_len = name_len;
} else if (name.Length() != start_pos) {
// Append the last segment.
const intptr_t segment_len = name.Length() - start_pos;
sum_segment_len += segment_len;
AppendSubString(&printer, cname, start_pos, segment_len);
}
if (unmangled_name == NULL) {
// Merge unmangled_segments.
unmangled_name = printer.buffer();
}
printer.Clear();
intptr_t start = 0;
intptr_t final_len = 0;
intptr_t len = sum_segment_len;
bool is_setter = false;
if (is_extension) {
// First scan till we see the '.' character.
for (intptr_t i = 0; i < len; i++) {
if (unmangled_name[i] == '.') {
intptr_t slen = i + 1;
intptr_t plen = slen - start;
AppendSubString(&printer, unmangled_name, start, plen);
final_len = plen;
unmangled_name += slen;
len -= slen;
break;
} else if (unmangled_name[i] == ':') {
if (start != 0) {
// Reset and break.
start = 0;
is_setter = false;
break;
}
if (unmangled_name[0] == 's') {
is_setter = true;
}
start = i + 1;
}
}
}
intptr_t dot_pos = -1; // Position of '.' in the name, if any.
start = 0;
for (intptr_t i = start; i < len; i++) {
if (unmangled_name[i] == ':' ||
(is_extension && unmangled_name[i] == '#')) {
if (start != 0) {
// Reset and break.
start = 0;
dot_pos = -1;
break;
}
ASSERT(start == 0); // Only one : is possible in getters or setters.
if (unmangled_name[0] == 's') {
ASSERT(!is_setter);
is_setter = true;
}
start = i + 1;
} else if (unmangled_name[i] == '.') {
if (dot_pos != -1) {
// Reset and break.
start = 0;
dot_pos = -1;
break;
}
ASSERT(dot_pos == -1); // Only one dot is supported.
dot_pos = i;
}
}
if (!is_extension && (start == 0) && (dot_pos == -1)) {
// This unmangled_name is fine as it is.
return unmangled_name;
}
// Drop the trailing dot if needed.
intptr_t end = ((dot_pos + 1) == len) ? dot_pos : len;
intptr_t substr_len = end - start;
final_len += substr_len;
AppendSubString(&printer, unmangled_name, start, substr_len);
if (is_setter) {
const char* equals = Symbols::Equals().ToCString();
const intptr_t equals_len = strlen(equals);
AppendSubString(&printer, equals, 0, equals_len);
final_len += equals_len;
}
return printer.buffer();
}
StringPtr String::ScrubNameRetainPrivate(const String& name,
bool is_extension) {
#if !defined(DART_PRECOMPILED_RUNTIME)
intptr_t len = name.Length();
intptr_t start = 0;
intptr_t at_pos = -1; // Position of '@' in the name, if any.
bool is_setter = false;
String& result = String::Handle();
// If extension strip out the leading prefix e.g" ext|func would strip out
// 'ext|'.
if (is_extension) {
// First scan till we see the '|' character.
for (intptr_t i = 0; i < len; i++) {
if (name.CharAt(i) == '|') {
result = String::SubString(name, start, (i - start));
result = String::Concat(result, Symbols::Dot());
start = i + 1;
break;
} else if (name.CharAt(i) == ':') {
if (start != 0) {
// Reset and break.
start = 0;
is_setter = false;
break;
}
if (name.CharAt(0) == 's') {
is_setter = true;
}
start = i + 1;
}
}
}
for (intptr_t i = start; i < len; i++) {
if (name.CharAt(i) == ':' || (is_extension && name.CharAt(i) == '#')) {
// Only one : is possible in getters or setters.
ASSERT(is_extension || start == 0);
if (name.CharAt(start) == 's') {
is_setter = true;
}
start = i + 1;
} else if (name.CharAt(i) == '@') {
// Setters should have only one @ so we know where to put the =.
ASSERT(!is_setter || (at_pos == -1));
at_pos = i;
}
}
if (start == 0) {
// This unmangled_name is fine as it is.
return name.ptr();
}
if (is_extension) {
const String& fname =
String::Handle(String::SubString(name, start, (len - start)));
result = String::Concat(result, fname);
} else {
result = String::SubString(name, start, (len - start));
}
if (is_setter) {
// Setters need to end with '='.
if (at_pos == -1) {
return String::Concat(result, Symbols::Equals());
} else {
const String& pre_at =
String::Handle(String::SubString(result, 0, at_pos - 4));
const String& post_at =
String::Handle(String::SubString(name, at_pos, len - at_pos));
result = String::Concat(pre_at, Symbols::Equals());
result = String::Concat(result, post_at);
}
}
return result.ptr();
#endif // !defined(DART_PRECOMPILED_RUNTIME)
return name.ptr(); // In AOT, return argument unchanged.
}
template <typename type>
static bool IsSpecialCharacter(type value) {
return ((value == '"') || (value == '\n') || (value == '\f') ||
(value == '\b') || (value == '\t') || (value == '\v') ||
(value == '\r') || (value == '\\') || (value == '$'));
}
static inline bool IsAsciiNonprintable(int32_t c) {
return ((0 <= c) && (c < 32)) || (c == 127);
}
static int32_t EscapeOverhead(int32_t c) {
if (IsSpecialCharacter(c)) {
return 1; // 1 additional byte for the backslash.
} else if (IsAsciiNonprintable(c)) {
return 3; // 3 additional bytes to encode c as \x00.
}
return 0;
}
template <typename type>
static type SpecialCharacter(type value) {
if (value == '"') {
return '"';
} else if (value == '\n') {
return 'n';
} else if (value == '\f') {
return 'f';
} else if (value == '\b') {
return 'b';
} else if (value == '\t') {
return 't';
} else if (value == '\v') {
return 'v';
} else if (value == '\r') {
return 'r';
} else if (value == '\\') {
return '\\';
} else if (value == '$') {
return '$';
}
UNREACHABLE();
return '\0';
}
void Object::InitNullAndBool(IsolateGroup* isolate_group) {
// Should only be run by the vm isolate.
ASSERT(isolate_group == Dart::vm_isolate_group());
auto heap = isolate_group->heap();
// TODO(iposva): NoSafepointScope needs to be added here.
ASSERT(class_class() == null_);
// Allocate and initialize the null instance.
// 'null_' must be the first object allocated as it is used in allocation to
// clear the object.
{
uword address = heap->Allocate(Instance::InstanceSize(), Heap::kOld);
null_ = static_cast<InstancePtr>(address + kHeapObjectTag);
// The call below is using 'null_' to initialize itself.
InitializeObject(address, kNullCid, Instance::InstanceSize(),
/*compressed*/ false);
null_->untag()->SetCanonical();
}
// Allocate and initialize the bool instances.
// These must be allocated such that at kBoolValueBitPosition, the address
// of true is 0 and the address of false is 1, and their addresses are
// otherwise identical.
{
// Allocate a dummy bool object to give true the desired alignment.
uword address = heap->Allocate(Bool::InstanceSize(), Heap::kOld);
InitializeObject(address, kBoolCid, Bool::InstanceSize(),
/*compressed*/ false);
static_cast<BoolPtr>(address + kHeapObjectTag)->untag()->value_ = false;
}
{
// Allocate true.
uword address = heap->Allocate(Bool::InstanceSize(), Heap::kOld);
true_ = static_cast<BoolPtr>(address + kHeapObjectTag);
InitializeObject(address, kBoolCid, Bool::InstanceSize(),
/*compressed*/ false);
true_->untag()->value_ = true;
true_->untag()->SetCanonical();
}
{
// Allocate false.
uword address = heap->Allocate(Bool::InstanceSize(), Heap::kOld);
false_ = static_cast<BoolPtr>(address + kHeapObjectTag);
InitializeObject(address, kBoolCid, Bool::InstanceSize(),
/*compressed*/ false);
false_->untag()->value_ = false;
false_->untag()->SetCanonical();
}
// Check that the objects have been allocated at appropriate addresses.
ASSERT(static_cast<uword>(true_) ==
static_cast<uword>(null_) + kTrueOffsetFromNull);
ASSERT(static_cast<uword>(false_) ==
static_cast<uword>(null_) + kFalseOffsetFromNull);
ASSERT((static_cast<uword>(true_) & kBoolValueMask) == 0);
ASSERT((static_cast<uword>(false_) & kBoolValueMask) != 0);
ASSERT(static_cast<uword>(false_) ==
(static_cast<uword>(true_) | kBoolValueMask));
ASSERT((static_cast<uword>(null_) & kBoolVsNullMask) == 0);
ASSERT((static_cast<uword>(true_) & kBoolVsNullMask) != 0);
ASSERT((static_cast<uword>(false_) & kBoolVsNullMask) != 0);
}
void Object::InitVtables() {
{
Object fake_handle;
builtin_vtables_[kObjectCid] = fake_handle.vtable();
}
#define INIT_VTABLE(clazz) \
{ \
clazz fake_handle; \
builtin_vtables_[k##clazz##Cid] = fake_handle.vtable(); \
}
CLASS_LIST_NO_OBJECT_NOR_STRING_NOR_ARRAY(INIT_VTABLE)
#undef INIT_VTABLE
#define INIT_VTABLE(clazz) \
{ \
Array fake_handle; \
builtin_vtables_[k##clazz##Cid] = fake_handle.vtable(); \
}
CLASS_LIST_ARRAYS(INIT_VTABLE)
#undef INIT_VTABLE
#define INIT_VTABLE(clazz) \
{ \
String fake_handle; \
builtin_vtables_[k##clazz##Cid] = fake_handle.vtable(); \
}
CLASS_LIST_STRINGS(INIT_VTABLE)
#undef INIT_VTABLE
{
Instance fake_handle;
builtin_vtables_[kFfiNativeTypeCid] = fake_handle.vtable();
}
#define INIT_VTABLE(clazz) \
{ \
Instance fake_handle; \
builtin_vtables_[kFfi##clazz##Cid] = fake_handle.vtable(); \
}
CLASS_LIST_FFI_TYPE_MARKER(INIT_VTABLE)
#undef INIT_VTABLE
{
Instance fake_handle;
builtin_vtables_[kFfiNativeFunctionCid] = fake_handle.vtable();
}
{
Pointer fake_handle;
builtin_vtables_[kFfiPointerCid] = fake_handle.vtable();
}
{
DynamicLibrary fake_handle;
builtin_vtables_[kFfiDynamicLibraryCid] = fake_handle.vtable();
}
#define INIT_VTABLE(clazz) \
{ \
TypedData fake_internal_handle; \
builtin_vtables_[kTypedData##clazz##Cid] = fake_internal_handle.vtable(); \
TypedDataView fake_view_handle; \
builtin_vtables_[kTypedData##clazz##ViewCid] = fake_view_handle.vtable(); \
ExternalTypedData fake_external_handle; \
builtin_vtables_[kExternalTypedData##clazz##Cid] = \
fake_external_handle.vtable(); \
}
CLASS_LIST_TYPED_DATA(INIT_VTABLE)
#undef INIT_VTABLE
{
TypedDataView fake_handle;
builtin_vtables_[kByteDataViewCid] = fake_handle.vtable();
}
{
Instance fake_handle;
builtin_vtables_[kByteBufferCid] = fake_handle.vtable();
builtin_vtables_[kNullCid] = fake_handle.vtable();
builtin_vtables_[kDynamicCid] = fake_handle.vtable();
builtin_vtables_[kVoidCid] = fake_handle.vtable();
builtin_vtables_[kNeverCid] = fake_handle.vtable();
}
}
void Object::Init(IsolateGroup* isolate_group) {
// Should only be run by the vm isolate.
ASSERT(isolate_group == Dart::vm_isolate_group());
Heap* heap = isolate_group->heap();
Thread* thread = Thread::Current();
ASSERT(thread != nullptr);
// Ensure lock checks in setters are happy.
SafepointWriteRwLocker ml(thread, isolate_group->program_lock());
InitVtables();
// Allocate the read only object handles here.
#define INITIALIZE_SHARED_READONLY_HANDLE(Type, name) \
name##_ = Type::ReadOnlyHandle();
SHARED_READONLY_HANDLES_LIST(INITIALIZE_SHARED_READONLY_HANDLE)
#undef INITIALIZE_SHARED_READONLY_HANDLE
*null_object_ = Object::null();
*null_class_ = Class::null();
*null_array_ = Array::null();
*null_string_ = String::null();
*null_instance_ = Instance::null();
*null_function_ = Function::null();
*null_function_type_ = FunctionType::null();
*null_type_arguments_ = TypeArguments::null();
*empty_type_arguments_ = TypeArguments::null();
*null_abstract_type_ = AbstractType::null();
*null_compressed_stackmaps_ = CompressedStackMaps::null();
*bool_true_ = true_;
*bool_false_ = false_;
// Initialize the empty and zero array handles to null_ in order to be able to
// check if the empty and zero arrays were allocated (RAW_NULL is not
// available).
*empty_array_ = Array::null();
*zero_array_ = Array::null();
Class& cls = Class::Handle();
// Allocate and initialize the class class.
{
intptr_t size = Class::InstanceSize();
uword address = heap->Allocate(size, Heap::kOld);
class_class_ = static_cast<ClassPtr>(address + kHeapObjectTag);
InitializeObject(address, Class::kClassId, size, /*compressed*/ true);
Class fake;
// Initialization from Class::New<Class>.
// Directly set ptr_ to break a circular dependency: SetRaw will attempt
// to lookup class class in the class table where it is not registered yet.
cls.ptr_ = class_class_;
ASSERT(builtin_vtables_[kClassCid] == fake.vtable());
cls.set_instance_size(
Class::InstanceSize(),
compiler::target::RoundedAllocationSize(RTN::Class::InstanceSize()));
const intptr_t host_next_field_offset = Class::NextFieldOffset();
const intptr_t target_next_field_offset = RTN::Class::NextFieldOffset();
cls.set_next_field_offset(host_next_field_offset, target_next_field_offset);
cls.set_id(Class::kClassId);
cls.set_state_bits(0);
cls.set_is_allocate_finalized();
cls.set_is_declaration_loaded();
cls.set_is_type_finalized();
cls.set_type_arguments_field_offset_in_words(Class::kNoTypeArguments,
RTN::Class::kNoTypeArguments);
cls.set_num_type_arguments_unsafe(0);
cls.set_num_native_fields(0);
cls.InitEmptyFields();
isolate_group->class_table()->Register(cls);
}
// Allocate and initialize the null class.
cls = Class::New<Instance, RTN::Instance>(kNullCid, isolate_group);
cls.set_num_type_arguments_unsafe(0);
isolate_group->object_store()->set_null_class(cls);
// Allocate and initialize Never class.
cls = Class::New<Instance, RTN::Instance>(kNeverCid, isolate_group);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_allocate_finalized();
cls.set_is_declaration_loaded();
cls.set_is_type_finalized();
isolate_group->object_store()->set_never_class(cls);
// Allocate and initialize the free list element class.
cls = Class::New<FreeListElement::FakeInstance,
RTN::FreeListElement::FakeInstance>(kFreeListElement,
isolate_group);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_allocate_finalized();
cls.set_is_declaration_loaded();
cls.set_is_type_finalized();
// Allocate and initialize the forwarding corpse class.
cls = Class::New<ForwardingCorpse::FakeInstance,
RTN::ForwardingCorpse::FakeInstance>(kForwardingCorpse,
isolate_group);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_allocate_finalized();
cls.set_is_declaration_loaded();
cls.set_is_type_finalized();
// Allocate and initialize the sentinel values.
{
*sentinel_ ^= Object::Allocate(kNeverCid, Instance::InstanceSize(),
Heap::kOld, /*compressed*/ false);
*transition_sentinel_ ^= Object::Allocate(
kNeverCid, Instance::InstanceSize(), Heap::kOld, /*compressed*/ false);
}
// Allocate and initialize optimizing compiler constants.
{
*unknown_constant_ ^= Object::Allocate(kNeverCid, Instance::InstanceSize(),
Heap::kOld, /*compressed*/ false);
*non_constant_ ^= Object::Allocate(kNeverCid, Instance::InstanceSize(),
Heap::kOld, /*compressed*/ false);
}
// Allocate the remaining VM internal classes.
cls = Class::New<TypeArguments, RTN::TypeArguments>(isolate_group);
type_arguments_class_ = cls.ptr();
cls = Class::New<PatchClass, RTN::PatchClass>(isolate_group);
patch_class_class_ = cls.ptr();
cls = Class::New<Function, RTN::Function>(isolate_group);
function_class_ = cls.ptr();
cls = Class::New<ClosureData, RTN::ClosureData>(isolate_group);
closure_data_class_ = cls.ptr();
cls = Class::New<FfiTrampolineData, RTN::FfiTrampolineData>(isolate_group);
ffi_trampoline_data_class_ = cls.ptr();
cls = Class::New<Field, RTN::Field>(isolate_group);
field_class_ = cls.ptr();
cls = Class::New<Script, RTN::Script>(isolate_group);
script_class_ = cls.ptr();
cls = Class::New<Library, RTN::Library>(isolate_group);
library_class_ = cls.ptr();
cls = Class::New<Namespace, RTN::Namespace>(isolate_group);
namespace_class_ = cls.ptr();
cls = Class::New<KernelProgramInfo, RTN::KernelProgramInfo>(isolate_group);
kernel_program_info_class_ = cls.ptr();
cls = Class::New<Code, RTN::Code>(isolate_group);
code_class_ = cls.ptr();
cls = Class::New<Instructions, RTN::Instructions>(isolate_group);
instructions_class_ = cls.ptr();
cls =
Class::New<InstructionsSection, RTN::InstructionsSection>(isolate_group);
instructions_section_class_ = cls.ptr();
cls = Class::New<ObjectPool, RTN::ObjectPool>(isolate_group);
object_pool_class_ = cls.ptr();
cls = Class::New<PcDescriptors, RTN::PcDescriptors>(isolate_group);
pc_descriptors_class_ = cls.ptr();
cls = Class::New<CodeSourceMap, RTN::CodeSourceMap>(isolate_group);
code_source_map_class_ = cls.ptr();
cls =
Class::New<CompressedStackMaps, RTN::CompressedStackMaps>(isolate_group);
compressed_stackmaps_class_ = cls.ptr();
cls =
Class::New<LocalVarDescriptors, RTN::LocalVarDescriptors>(isolate_group);
var_descriptors_class_ = cls.ptr();
cls = Class::New<ExceptionHandlers, RTN::ExceptionHandlers>(isolate_group);
exception_handlers_class_ = cls.ptr();
cls = Class::New<Context, RTN::Context>(isolate_group);
context_class_ = cls.ptr();
cls = Class::New<ContextScope, RTN::ContextScope>(isolate_group);
context_scope_class_ = cls.ptr();
cls = Class::New<SingleTargetCache, RTN::SingleTargetCache>(isolate_group);
singletargetcache_class_ = cls.ptr();
cls = Class::New<UnlinkedCall, RTN::UnlinkedCall>(isolate_group);
unlinkedcall_class_ = cls.ptr();
cls = Class::New<MonomorphicSmiableCall, RTN::MonomorphicSmiableCall>(
isolate_group);
monomorphicsmiablecall_class_ = cls.ptr();
cls = Class::New<ICData, RTN::ICData>(isolate_group);
icdata_class_ = cls.ptr();
cls = Class::New<MegamorphicCache, RTN::MegamorphicCache>(isolate_group);
megamorphic_cache_class_ = cls.ptr();
cls = Class::New<SubtypeTestCache, RTN::SubtypeTestCache>(isolate_group);
subtypetestcache_class_ = cls.ptr();
cls = Class::New<LoadingUnit, RTN::LoadingUnit>(isolate_group);
loadingunit_class_ = cls.ptr();
cls = Class::New<ApiError, RTN::ApiError>(isolate_group);
api_error_class_ = cls.ptr();
cls = Class::New<LanguageError, RTN::LanguageError>(isolate_group);
language_error_class_ = cls.ptr();
cls = Class::New<UnhandledException, RTN::UnhandledException>(isolate_group);
unhandled_exception_class_ = cls.ptr();
cls = Class::New<UnwindError, RTN::UnwindError>(isolate_group);
unwind_error_class_ = cls.ptr();
cls = Class::New<WeakSerializationReference, RTN::WeakSerializationReference>(
isolate_group);
weak_serialization_reference_class_ = cls.ptr();
ASSERT(class_class() != null_);
// Pre-allocate classes in the vm isolate so that we can for example create a
// symbol table and populate it with some frequently used strings as symbols.
cls = Class::New<Array, RTN::Array>(isolate_group);
isolate_group->object_store()->set_array_class(cls);
cls.set_type_arguments_field_offset(Array::type_arguments_offset(),
RTN::Array::type_arguments_offset());
cls.set_num_type_arguments_unsafe(1);
cls = Class::New<Array, RTN::Array>(kImmutableArrayCid, isolate_group);
isolate_group->object_store()->set_immutable_array_class(cls);
cls.set_type_arguments_field_offset(Array::type_arguments_offset(),
RTN::Array::type_arguments_offset());
cls.set_num_type_arguments_unsafe(1);
cls =
Class::New<GrowableObjectArray, RTN::GrowableObjectArray>(isolate_group);
isolate_group->object_store()->set_growable_object_array_class(cls);
cls.set_type_arguments_field_offset(
GrowableObjectArray::type_arguments_offset(),
RTN::GrowableObjectArray::type_arguments_offset());
cls.set_num_type_arguments_unsafe(1);
cls = Class::NewStringClass(kOneByteStringCid, isolate_group);
isolate_group->object_store()->set_one_byte_string_class(cls);
cls = Class::NewStringClass(kTwoByteStringCid, isolate_group);
isolate_group->object_store()->set_two_byte_string_class(cls);
cls = Class::New<Mint, RTN::Mint>(isolate_group);
isolate_group->object_store()->set_mint_class(cls);
cls = Class::New<Double, RTN::Double>(isolate_group);
isolate_group->object_store()->set_double_class(cls);
// Ensure that class kExternalTypedDataUint8ArrayCid is registered as we
// need it when reading in the token stream of bootstrap classes in the VM
// isolate.
Class::NewExternalTypedDataClass(kExternalTypedDataUint8ArrayCid,
isolate_group);
// Needed for object pools of VM isolate stubs.
Class::NewTypedDataClass(kTypedDataInt8ArrayCid, isolate_group);
// Allocate and initialize the empty_array instance.
{
uword address = heap->Allocate(Array::InstanceSize(0), Heap::kOld);
InitializeObject(address, kImmutableArrayCid, Array::InstanceSize(0),
/*compressed*/ false);
Array::initializeHandle(empty_array_,
static_cast<ArrayPtr>(address + kHeapObjectTag));
empty_array_->untag()->set_length(Smi::New(0));
empty_array_->SetCanonical();
}
Smi& smi = Smi::Handle();
// Allocate and initialize the zero_array instance.
{
uword address = heap->Allocate(Array::InstanceSize(1), Heap::kOld);
InitializeObject(address, kImmutableArrayCid, Array::InstanceSize(1),
/*compressed*/ false);
Array::initializeHandle(zero_array_,
static_cast<ArrayPtr>(address + kHeapObjectTag));
zero_array_->untag()->set_length(Smi::New(1));
smi = Smi::New(0);
zero_array_->SetAt(0, smi);
zero_array_->SetCanonical();
}
// Allocate and initialize the canonical empty context scope object.
{
uword address = heap->Allocate(ContextScope::InstanceSize(0), Heap::kOld);
InitializeObject(address, kContextScopeCid, ContextScope::InstanceSize(0),
/*compressed*/ true);
ContextScope::initializeHandle(
empty_context_scope_,
static_cast<ContextScopePtr>(address + kHeapObjectTag));
empty_context_scope_->StoreNonPointer(
&empty_context_scope_->untag()->num_variables_, 0);
empty_context_scope_->StoreNonPointer(
&empty_context_scope_->untag()->is_implicit_, true);
empty_context_scope_->SetCanonical();
}
// Allocate and initialize the canonical empty object pool object.
{
uword address = heap->Allocate(ObjectPool::InstanceSize(0), Heap::kOld);
InitializeObject(address, kObjectPoolCid, ObjectPool::InstanceSize(0),
/*compressed*/ false);
ObjectPool::initializeHandle(
empty_object_pool_,
static_cast<ObjectPoolPtr>(address + kHeapObjectTag));
empty_object_pool_->StoreNonPointer(&empty_object_pool_->untag()->length_,
0);
empty_object_pool_->SetCanonical();
}
// Allocate and initialize the empty_compressed_stackmaps instance.
{
const intptr_t instance_size = CompressedStackMaps::InstanceSize(0);
uword address = heap->Allocate(instance_size, Heap::kOld);
InitializeObject(address, kCompressedStackMapsCid, instance_size,
/*compressed*/ true);
CompressedStackMaps::initializeHandle(
empty_compressed_stackmaps_,
static_cast<CompressedStackMapsPtr>(address + kHeapObjectTag));
empty_compressed_stackmaps_->StoreNonPointer(
&empty_compressed_stackmaps_->untag()->flags_and_size_, 0);
empty_compressed_stackmaps_->SetCanonical();
}
// Allocate and initialize the empty_descriptors instance.
{
uword address = heap->Allocate(PcDescriptors::InstanceSize(0), Heap::kOld);
InitializeObject(address, kPcDescriptorsCid, PcDescriptors::InstanceSize(0),
/*compressed*/ true);
PcDescriptors::initializeHandle(
empty_descriptors_,
static_cast<PcDescriptorsPtr>(address + kHeapObjectTag));
empty_descriptors_->StoreNonPointer(&empty_descriptors_->untag()->length_,
0);
empty_descriptors_->SetCanonical();
}
// Allocate and initialize the canonical empty variable descriptor object.
{
uword address =
heap->Allocate(LocalVarDescriptors::InstanceSize(0), Heap::kOld);
InitializeObject(address, kLocalVarDescriptorsCid,
LocalVarDescriptors::InstanceSize(0), /*compressed*/ true);
LocalVarDescriptors::initializeHandle(
empty_var_descriptors_,
static_cast<LocalVarDescriptorsPtr>(address + kHeapObjectTag));
empty_var_descriptors_->StoreNonPointer(
&empty_var_descriptors_->untag()->num_entries_, 0);
empty_var_descriptors_->SetCanonical();
}
// Allocate and initialize the canonical empty exception handler info object.
// The vast majority of all functions do not contain an exception handler
// and can share this canonical descriptor.
{
uword address =
heap->Allocate(ExceptionHandlers::InstanceSize(0), Heap::kOld);
InitializeObject(address, kExceptionHandlersCid,
ExceptionHandlers::InstanceSize(0), /*compressed*/ true);
ExceptionHandlers::initializeHandle(
empty_exception_handlers_,
static_cast<ExceptionHandlersPtr>(address + kHeapObjectTag));
empty_exception_handlers_->StoreNonPointer(
&empty_exception_handlers_->untag()->num_entries_, 0);
empty_exception_handlers_->SetCanonical();
}
// Allocate and initialize the canonical empty type arguments object.
{
uword address = heap->Allocate(TypeArguments::InstanceSize(0), Heap::kOld);
InitializeObject(address, kTypeArgumentsCid, TypeArguments::InstanceSize(0),
/*compressed*/ false);
TypeArguments::initializeHandle(
empty_type_arguments_,
static_cast<TypeArgumentsPtr>(address + kHeapObjectTag));
empty_type_arguments_->untag()->set_length(Smi::New(0));
empty_type_arguments_->untag()->set_hash(Smi::New(0));
empty_type_arguments_->ComputeHash();
empty_type_arguments_->SetCanonical();
}
// The VM isolate snapshot object table is initialized to an empty array
// as we do not have any VM isolate snapshot at this time.
*vm_isolate_snapshot_object_table_ = Object::empty_array().ptr();
cls = Class::New<Instance, RTN::Instance>(kDynamicCid, isolate_group);
cls.set_is_abstract();
cls.set_num_type_arguments_unsafe(0);
cls.set_is_allocate_finalized();
cls.set_is_declaration_loaded();
cls.set_is_type_finalized();
dynamic_class_ = cls.ptr();
cls = Class::New<Instance, RTN::Instance>(kVoidCid, isolate_group);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_allocate_finalized();
cls.set_is_declaration_loaded();
cls.set_is_type_finalized();
void_class_ = cls.ptr();
cls = Class::New<Type, RTN::Type>(isolate_group);
cls.set_is_allocate_finalized();
cls.set_is_declaration_loaded();
cls.set_is_type_finalized();
cls = Class::New<FunctionType, RTN::FunctionType>(isolate_group);
cls.set_is_allocate_finalized();
cls.set_is_declaration_loaded();
cls.set_is_type_finalized();
cls = dynamic_class_;
*dynamic_type_ =
Type::New(cls, Object::null_type_arguments(), Nullability::kNullable);
dynamic_type_->SetIsFinalized();
dynamic_type_->ComputeHash();
dynamic_type_->SetCanonical();
cls = void_class_;
*void_type_ =
Type::New(cls, Object::null_type_arguments(), Nullability::kNullable);
void_type_->SetIsFinalized();
void_type_->ComputeHash();
void_type_->SetCanonical();
// Since TypeArguments objects are passed as function arguments, make them
// behave as Dart instances, although they are just VM objects.
// Note that we cannot set the super type to ObjectType, which does not live
// in the vm isolate. See special handling in Class::SuperClass().
cls = type_arguments_class_;
cls.set_interfaces(Object::empty_array());
cls.SetFields(Object::empty_array());
cls.SetFunctions(Object::empty_array());
cls = Class::New<Bool, RTN::Bool>(isolate_group);
isolate_group->object_store()->set_bool_class(cls);
*smi_illegal_cid_ = Smi::New(kIllegalCid);
*smi_zero_ = Smi::New(0);
String& error_str = String::Handle();
error_str = String::New(
"Internal Dart data pointers have been acquired, please release them "
"using Dart_TypedDataReleaseData.",
Heap::kOld);
*typed_data_acquire_error_ = ApiError::New(error_str, Heap::kOld);
error_str = String::New("SnapshotWriter Error", Heap::kOld);
*snapshot_writer_error_ =
LanguageError::New(error_str, Report::kError, Heap::kOld);
error_str = String::New("Branch offset overflow", Heap::kOld);
*branch_offset_error_ =
LanguageError::New(error_str, Report::kBailout, Heap::kOld);
error_str = String::New("Speculative inlining failed", Heap::kOld);
*speculative_inlining_error_ =
LanguageError::New(error_str, Report::kBailout, Heap::kOld);
error_str = String::New("Background Compilation Failed", Heap::kOld);
*background_compilation_error_ =
LanguageError::New(error_str, Report::kBailout, Heap::kOld);
error_str = String::New("Out of memory", Heap::kOld);
*out_of_memory_error_ =
LanguageError::New(error_str, Report::kBailout, Heap::kOld);
// Allocate the parameter arrays for method extractor types and names.
*extractor_parameter_types_ = Array::New(1, Heap::kOld);
extractor_parameter_types_->SetAt(0, Object::dynamic_type());
*extractor_parameter_names_ = Array::New(1, Heap::kOld);
// Fill in extractor_parameter_names_ later, after symbols are initialized
// (in Object::FinalizeVMIsolate). extractor_parameter_names_ object
// needs to be created earlier as VM isolate snapshot reader references it
// before Object::FinalizeVMIsolate.
// Some thread fields need to be reinitialized as null constants have not been
// initialized until now.
thread->ClearStickyError();
thread->clear_pending_functions();
ASSERT(!null_object_->IsSmi());
ASSERT(!null_class_->IsSmi());
ASSERT(null_class_->IsClass());
ASSERT(!null_array_->IsSmi());
ASSERT(null_array_->IsArray());
ASSERT(!null_string_->IsSmi());
ASSERT(null_string_->IsString());
ASSERT(!null_instance_->IsSmi());
ASSERT(null_instance_->IsInstance());
ASSERT(!null_function_->IsSmi());
ASSERT(null_function_->IsFunction());
ASSERT(!null_function_type_->IsSmi());
ASSERT(null_function_type_->IsFunctionType());
ASSERT(!null_type_arguments_->IsSmi());
ASSERT(null_type_arguments_->IsTypeArguments());
ASSERT(!null_compressed_stackmaps_->IsSmi());
ASSERT(null_compressed_stackmaps_->IsCompressedStackMaps());
ASSERT(!empty_array_->IsSmi());
ASSERT(empty_array_->IsArray());
ASSERT(!zero_array_->IsSmi());
ASSERT(zero_array_->IsArray());
ASSERT(!empty_type_arguments_->IsSmi());
ASSERT(empty_type_arguments_->IsTypeArguments());
ASSERT(!empty_context_scope_->IsSmi());
ASSERT(empty_context_scope_->IsContextScope());
ASSERT(!empty_compressed_stackmaps_->IsSmi());
ASSERT(empty_compressed_stackmaps_->IsCompressedStackMaps());
ASSERT(!empty_descriptors_->IsSmi());
ASSERT(empty_descriptors_->IsPcDescriptors());
ASSERT(!empty_var_descriptors_->IsSmi());
ASSERT(empty_var_descriptors_->IsLocalVarDescriptors());
ASSERT(!empty_exception_handlers_->IsSmi());
ASSERT(empty_exception_handlers_->IsExceptionHandlers());
ASSERT(!sentinel_->IsSmi());
ASSERT(sentinel_->IsInstance());
ASSERT(!transition_sentinel_->IsSmi());
ASSERT(transition_sentinel_->IsInstance());
ASSERT(!unknown_constant_->IsSmi());
ASSERT(unknown_constant_->IsInstance());
ASSERT(!non_constant_->IsSmi());
ASSERT(non_constant_->IsInstance());
ASSERT(!bool_true_->IsSmi());
ASSERT(bool_true_->IsBool());
ASSERT(!bool_false_->IsSmi());
ASSERT(bool_false_->IsBool());
ASSERT(smi_illegal_cid_->IsSmi());
ASSERT(smi_zero_->IsSmi());
ASSERT(!typed_data_acquire_error_->IsSmi());
ASSERT(typed_data_acquire_error_->IsApiError());
ASSERT(!snapshot_writer_error_->IsSmi());
ASSERT(snapshot_writer_error_->IsLanguageError());
ASSERT(!branch_offset_error_->IsSmi());
ASSERT(branch_offset_error_->IsLanguageError());
ASSERT(!speculative_inlining_error_->IsSmi());
ASSERT(speculative_inlining_error_->IsLanguageError());
ASSERT(!background_compilation_error_->IsSmi());
ASSERT(background_compilation_error_->IsLanguageError());
ASSERT(!out_of_memory_error_->IsSmi());
ASSERT(out_of_memory_error_->IsLanguageError());
ASSERT(!vm_isolate_snapshot_object_table_->IsSmi());
ASSERT(vm_isolate_snapshot_object_table_->IsArray());
ASSERT(!extractor_parameter_types_->IsSmi());
ASSERT(extractor_parameter_types_->IsArray());
ASSERT(!extractor_parameter_names_->IsSmi());
ASSERT(extractor_parameter_names_->IsArray());
}
void Object::FinishInit(IsolateGroup* isolate_group) {
// The type testing stubs we initialize in AbstractType objects for the
// canonical type of kDynamicCid/kVoidCid need to be set in this
// method, which is called after StubCode::InitOnce().
Code& code = Code::Handle();
code = TypeTestingStubGenerator::DefaultCodeForType(*dynamic_type_);
dynamic_type_->SetTypeTestingStub(code);
code = TypeTestingStubGenerator::DefaultCodeForType(*void_type_);
void_type_->SetTypeTestingStub(code);
}
void Object::Cleanup() {
null_ = static_cast<ObjectPtr>(RAW_NULL);
true_ = static_cast<BoolPtr>(RAW_NULL);
false_ = static_cast<BoolPtr>(RAW_NULL);
class_class_ = static_cast<ClassPtr>(RAW_NULL);
dynamic_class_ = static_cast<ClassPtr>(RAW_NULL);
void_class_ = static_cast<ClassPtr>(RAW_NULL);
type_arguments_class_ = static_cast<ClassPtr>(RAW_NULL);
patch_class_class_ = static_cast<ClassPtr>(RAW_NULL);
function_class_ = static_cast<ClassPtr>(RAW_NULL);
closure_data_class_ = static_cast<ClassPtr>(RAW_NULL);
ffi_trampoline_data_class_ = static_cast<ClassPtr>(RAW_NULL);
field_class_ = static_cast<ClassPtr>(RAW_NULL);
script_class_ = static_cast<ClassPtr>(RAW_NULL);
library_class_ = static_cast<ClassPtr>(RAW_NULL);
namespace_class_ = static_cast<ClassPtr>(RAW_NULL);
kernel_program_info_class_ = static_cast<ClassPtr>(RAW_NULL);
code_class_ = static_cast<ClassPtr>(RAW_NULL);
instructions_class_ = static_cast<ClassPtr>(RAW_NULL);
instructions_section_class_ = static_cast<ClassPtr>(RAW_NULL);
object_pool_class_ = static_cast<ClassPtr>(RAW_NULL);
pc_descriptors_class_ = static_cast<ClassPtr>(RAW_NULL);
code_source_map_class_ = static_cast<ClassPtr>(RAW_NULL);
compressed_stackmaps_class_ = static_cast<ClassPtr>(RAW_NULL);
var_descriptors_class_ = static_cast<ClassPtr>(RAW_NULL);
exception_handlers_class_ = static_cast<ClassPtr>(RAW_NULL);
context_class_ = static_cast<ClassPtr>(RAW_NULL);
context_scope_class_ = static_cast<ClassPtr>(RAW_NULL);
singletargetcache_class_ = static_cast<ClassPtr>(RAW_NULL);
unlinkedcall_class_ = static_cast<ClassPtr>(RAW_NULL);
monomorphicsmiablecall_class_ = static_cast<ClassPtr>(RAW_NULL);
icdata_class_ = static_cast<ClassPtr>(RAW_NULL);
megamorphic_cache_class_ = static_cast<ClassPtr>(RAW_NULL);
subtypetestcache_class_ = static_cast<ClassPtr>(RAW_NULL);
loadingunit_class_ = static_cast<ClassPtr>(RAW_NULL);
api_error_class_ = static_cast<ClassPtr>(RAW_NULL);
language_error_class_ = static_cast<ClassPtr>(RAW_NULL);
unhandled_exception_class_ = static_cast<ClassPtr>(RAW_NULL);
unwind_error_class_ = static_cast<ClassPtr>(RAW_NULL);
}
// An object visitor which will mark all visited objects. This is used to
// premark all objects in the vm_isolate_ heap. Also precalculates hash
// codes so that we can get the identity hash code of objects in the read-
// only VM isolate.
class FinalizeVMIsolateVisitor : public ObjectVisitor {
public:
FinalizeVMIsolateVisitor()
#if defined(HASH_IN_OBJECT_HEADER)
: counter_(1337)
#endif
{
}
void VisitObject(ObjectPtr obj) {
// Free list elements should never be marked.
ASSERT(!obj->untag()->IsMarked());
// No forwarding corpses in the VM isolate.
ASSERT(!obj->IsForwardingCorpse());
if (!obj->IsFreeListElement()) {
obj->untag()->SetMarkBitUnsynchronized();
Object::FinalizeReadOnlyObject(obj);
#if defined(HASH_IN_OBJECT_HEADER)
// These objects end up in the read-only VM isolate which is shared
// between isolates, so we have to prepopulate them with identity hash
// codes, since we can't add hash codes later.
if (Object::GetCachedHash(obj) == 0) {
// Some classes have identity hash codes that depend on their contents,
// not per object.
ASSERT(!obj->IsStringInstance());
if (!obj->IsMint() && !obj->IsDouble() && !obj->IsRawNull() &&
!obj->IsBool()) {
counter_ += 2011; // The year Dart was announced and a prime.
counter_ &= 0x3fffffff;
if (counter_ == 0) counter_++;
Object::SetCachedHash(obj, counter_);
}
}
#endif
}
}
private:
#if defined(HASH_IN_OBJECT_HEADER)
int32_t counter_;
#endif
};
#define SET_CLASS_NAME(class_name, name) \
cls = class_name##_class(); \
cls.set_name(Symbols::name());
void Object::FinalizeVMIsolate(IsolateGroup* isolate_group) {
// Should only be run by the vm isolate.
ASSERT(isolate_group == Dart::vm_isolate_group());
// Finish initialization of extractor_parameter_names_ which was
// Started in Object::InitOnce()
extractor_parameter_names_->SetAt(0, Symbols::This());
// Set up names for all VM singleton classes.
Class& cls = Class::Handle();
SET_CLASS_NAME(class, Class);
SET_CLASS_NAME(dynamic, Dynamic);
SET_CLASS_NAME(void, Void);
SET_CLASS_NAME(type_arguments, TypeArguments);
SET_CLASS_NAME(patch_class, PatchClass);
SET_CLASS_NAME(function, Function);
SET_CLASS_NAME(closure_data, ClosureData);
SET_CLASS_NAME(ffi_trampoline_data, FfiTrampolineData);
SET_CLASS_NAME(field, Field);
SET_CLASS_NAME(script, Script);
SET_CLASS_NAME(library, LibraryClass);
SET_CLASS_NAME(namespace, Namespace);
SET_CLASS_NAME(kernel_program_info, KernelProgramInfo);
SET_CLASS_NAME(weak_serialization_reference, WeakSerializationReference);
SET_CLASS_NAME(code, Code);
SET_CLASS_NAME(instructions, Instructions);
SET_CLASS_NAME(instructions_section, InstructionsSection);
SET_CLASS_NAME(object_pool, ObjectPool);
SET_CLASS_NAME(code_source_map, CodeSourceMap);
SET_CLASS_NAME(pc_descriptors, PcDescriptors);
SET_CLASS_NAME(compressed_stackmaps, CompressedStackMaps);
SET_CLASS_NAME(var_descriptors, LocalVarDescriptors);
SET_CLASS_NAME(exception_handlers, ExceptionHandlers);
SET_CLASS_NAME(context, Context);
SET_CLASS_NAME(context_scope, ContextScope);
SET_CLASS_NAME(singletargetcache, SingleTargetCache);
SET_CLASS_NAME(unlinkedcall, UnlinkedCall);
SET_CLASS_NAME(monomorphicsmiablecall, MonomorphicSmiableCall);
SET_CLASS_NAME(icdata, ICData);
SET_CLASS_NAME(megamorphic_cache, MegamorphicCache);
SET_CLASS_NAME(subtypetestcache, SubtypeTestCache);
SET_CLASS_NAME(loadingunit, LoadingUnit);
SET_CLASS_NAME(api_error, ApiError);
SET_CLASS_NAME(language_error, LanguageError);
SET_CLASS_NAME(unhandled_exception, UnhandledException);
SET_CLASS_NAME(unwind_error, UnwindError);
// Set up names for classes which are also pre-allocated in the vm isolate.
cls = isolate_group->object_store()->array_class();
cls.set_name(Symbols::_List());
cls = isolate_group->object_store()->one_byte_string_class();
cls.set_name(Symbols::OneByteString());
cls = isolate_group->object_store()->never_class();
cls.set_name(Symbols::Never());
// Set up names for the pseudo-classes for free list elements and forwarding
// corpses. Mainly this makes VM debugging easier.
cls = isolate_group->class_table()->At(kFreeListElement);
cls.set_name(Symbols::FreeListElement());
cls = isolate_group->class_table()->At(kForwardingCorpse);
cls.set_name(Symbols::ForwardingCorpse());
#if defined(DART_PRECOMPILER)
const auto& function =
Function::Handle(StubCode::UnknownDartCode().function());
function.set_name(Symbols::OptimizedOut());
#endif // defined(DART_PRECOMPILER)
{
ASSERT(isolate_group == Dart::vm_isolate_group());
Thread* thread = Thread::Current();
WritableVMIsolateScope scope(thread);
HeapIterationScope iteration(thread);
FinalizeVMIsolateVisitor premarker;
ASSERT(isolate_group->heap()->UsedInWords(Heap::kNew) == 0);
iteration.IterateOldObjectsNoImagePages(&premarker);
// Make the VM isolate read-only again after setting all objects as marked.
// Note objects in image pages are already pre-marked.
}
}
void Object::FinalizeReadOnlyObject(ObjectPtr object) {
NoSafepointScope no_safepoint;
intptr_t cid = object->GetClassId();
if (cid == kOneByteStringCid) {
OneByteStringPtr str = static_cast<OneByteStringPtr>(object);
if (String::GetCachedHash(str) == 0) {
intptr_t hash = String::Hash(str);
String::SetCachedHash(str, hash);
}
intptr_t size = OneByteString::UnroundedSize(str);
ASSERT(size <= str->untag()->HeapSize());
memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(str) + size), 0,
str->untag()->HeapSize() - size);
} else if (cid == kTwoByteStringCid) {
TwoByteStringPtr str = static_cast<TwoByteStringPtr>(object);
if (String::GetCachedHash(str) == 0) {
intptr_t hash = String::Hash(str);
String::SetCachedHash(str, hash);
}
ASSERT(String::GetCachedHash(str) != 0);
intptr_t size = TwoByteString::UnroundedSize(str);
ASSERT(size <= str->untag()->HeapSize());
memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(str) + size), 0,
str->untag()->HeapSize() - size);
} else if (cid == kExternalOneByteStringCid) {
ExternalOneByteStringPtr str =
static_cast<ExternalOneByteStringPtr>(object);
if (String::GetCachedHash(str) == 0) {
intptr_t hash = String::Hash(str);
String::SetCachedHash(str, hash);
}
} else if (cid == kExternalTwoByteStringCid) {
ExternalTwoByteStringPtr str =
static_cast<ExternalTwoByteStringPtr>(object);
if (String::GetCachedHash(str) == 0) {
intptr_t hash = String::Hash(str);
String::SetCachedHash(str, hash);
}
} else if (cid == kCodeSourceMapCid) {
CodeSourceMapPtr map = CodeSourceMap::RawCast(object);
intptr_t size = CodeSourceMap::UnroundedSize(map);
ASSERT(size <= map->untag()->HeapSize());
memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(map) + size), 0,
map->untag()->HeapSize() - size);
} else if (cid == kCompressedStackMapsCid) {
CompressedStackMapsPtr maps = CompressedStackMaps::RawCast(object);
intptr_t size = CompressedStackMaps::UnroundedSize(maps);
ASSERT(size <= maps->untag()->HeapSize());
memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(maps) + size), 0,
maps->untag()->HeapSize() - size);
} else if (cid == kPcDescriptorsCid) {
PcDescriptorsPtr desc = PcDescriptors::RawCast(object);
intptr_t size = PcDescriptors::UnroundedSize(desc);
ASSERT(size <= desc->untag()->HeapSize());
memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(desc) + size), 0,
desc->untag()->HeapSize() - size);
}
}
void Object::set_vm_isolate_snapshot_object_table(const Array& table) {
ASSERT(Isolate::Current() == Dart::vm_isolate());
*vm_isolate_snapshot_object_table_ = table.ptr();
}
// Make unused space in an object whose type has been transformed safe
// for traversing during GC.
// The unused part of the transformed object is marked as an TypedDataInt8Array
// object.
void Object::MakeUnusedSpaceTraversable(const Object& obj,
intptr_t original_size,
intptr_t used_size) {
ASSERT(Thread::Current()->no_safepoint_scope_depth() > 0);
ASSERT(!obj.IsNull());
ASSERT(original_size >= used_size);
if (original_size > used_size) {
intptr_t leftover_size = original_size - used_size;
uword addr = UntaggedObject::ToAddr(obj.ptr()) + used_size;
if (leftover_size >= TypedData::InstanceSize(0)) {
// Update the leftover space as a TypedDataInt8Array object.
TypedDataPtr raw =
static_cast<TypedDataPtr>(UntaggedObject::FromAddr(addr));
uword new_tags =
UntaggedObject::ClassIdTag::update(kTypedDataInt8ArrayCid, 0);
new_tags = UntaggedObject::SizeTag::update(leftover_size, new_tags);
const bool is_old = obj.ptr()->IsOldObject();
new_tags = UntaggedObject::OldBit::update(is_old, new_tags);
new_tags = UntaggedObject::OldAndNotMarkedBit::update(is_old, new_tags);
new_tags =
UntaggedObject::OldAndNotRememberedBit::update(is_old, new_tags);
new_tags = UntaggedObject::NewBit::update(!is_old, new_tags);
// On architectures with a relaxed memory model, the concurrent marker may
// observe the write of the filler object's header before observing the
// new array length, and so treat it as a pointer. Ensure it is a Smi so
// the marker won't dereference it.
ASSERT((new_tags & kSmiTagMask) == kSmiTag);
raw->untag()->tags_ = new_tags;
intptr_t leftover_len = (leftover_size - TypedData::InstanceSize(0));
ASSERT(TypedData::InstanceSize(leftover_len) == leftover_size);
raw->untag()->set_length(Smi::New(leftover_len));
raw->untag()->RecomputeDataField();
} else {
// Update the leftover space as a basic object.
ASSERT(leftover_size == Object::InstanceSize());
ObjectPtr raw = static_cast<ObjectPtr>(UntaggedObject::FromAddr(addr));
uword new_tags = UntaggedObject::ClassIdTag::update(kInstanceCid, 0);
new_tags = UntaggedObject::SizeTag::update(leftover_size, new_tags);
const bool is_old = obj.ptr()->IsOldObject();
new_tags = UntaggedObject::OldBit::update(is_old, new_tags);
new_tags = UntaggedObject::OldAndNotMarkedBit::update(is_old, new_tags);
new_tags =
UntaggedObject::OldAndNotRememberedBit::update(is_old, new_tags);
new_tags = UntaggedObject::NewBit::update(!is_old, new_tags);
// On architectures with a relaxed memory model, the concurrent marker may
// observe the write of the filler object's header before observing the
// new array length, and so treat it as a pointer. Ensure it is a Smi so
// the marker won't dereference it.
ASSERT((new_tags & kSmiTagMask) == kSmiTag);
raw->untag()->tags_ = new_tags;
}
}
}
void Object::VerifyBuiltinVtables() {
#if defined(DEBUG)
ASSERT(builtin_vtables_[kIllegalCid] == 0);
ASSERT(builtin_vtables_[kFreeListElement] == 0);
ASSERT(builtin_vtables_[kForwardingCorpse] == 0);
ClassTable* table = IsolateGroup::Current()->class_table();
for (intptr_t cid = kObjectCid; cid < kNumPredefinedCids; cid++) {
if (table->HasValidClassAt(cid)) {
ASSERT(builtin_vtables_[cid] != 0);
}
}
#endif
}
void Object::RegisterClass(const Class& cls,
const String& name,
const Library& lib) {
ASSERT(name.Length() > 0);
ASSERT(name.CharAt(0) != '_');
cls.set_name(name);
lib.AddClass(cls);
}
void Object::RegisterPrivateClass(const Class& cls,
const String& public_class_name,
const Library& lib) {
ASSERT(public_class_name.Length() > 0);
ASSERT(public_class_name.CharAt(0) == '_');
String& str = String::Handle();
str = lib.PrivateName(public_class_name);
cls.set_name(str);
lib.AddClass(cls);
}
// Initialize a new isolate from source or from a snapshot.
//
// There are three possibilities:
// 1. Running a Kernel binary. This function will bootstrap from the KERNEL
// file.
// 2. There is no vm snapshot. This function will bootstrap from source.
// 3. There is a vm snapshot. The caller should initialize from the snapshot.
//
// A non-NULL kernel argument indicates (1).
// A NULL kernel indicates (2) or (3).
ErrorPtr Object::Init(IsolateGroup* isolate_group,
const uint8_t* kernel_buffer,
intptr_t kernel_buffer_size) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
ASSERT(isolate_group == thread->isolate_group());
TIMELINE_DURATION(thread, Isolate, "Object::Init");
#if defined(DART_PRECOMPILED_RUNTIME)
const bool bootstrapping = false;
#else
const bool is_kernel = (kernel_buffer != NULL);
const bool bootstrapping =
(Dart::vm_snapshot_kind() == Snapshot::kNone) || is_kernel;
#endif // defined(DART_PRECOMPILED_RUNTIME).
if (bootstrapping) {
#if !defined(DART_PRECOMPILED_RUNTIME)
// Object::Init version when we are bootstrapping from source or from a
// Kernel binary.
// This will initialize isolate group object_store, shared by all isolates
// running in the isolate group.
ObjectStore* object_store = isolate_group->object_store();
SafepointWriteRwLocker ml(thread, isolate_group->program_lock());
Class& cls = Class::Handle(zone);
Type& type = Type::Handle(zone);
Array& array = Array::Handle(zone);
Library& lib = Library::Handle(zone);
TypeArguments& type_args = TypeArguments::Handle(zone);
// All RawArray fields will be initialized to an empty array, therefore
// initialize array class first.
cls = Class::New<Array, RTN::Array>(isolate_group);
ASSERT(object_store->array_class() == Class::null());
object_store->set_array_class(cls);
// VM classes that are parameterized (Array, ImmutableArray,
// GrowableObjectArray, and LinkedHashMap) are also pre-finalized, so
// CalculateFieldOffsets() is not called, so we need to set the offset of
// their type_arguments_ field, which is explicitly declared in their
// respective Raw* classes.
cls.set_type_arguments_field_offset(Array::type_arguments_offset(),
RTN::Array::type_arguments_offset());
cls.set_num_type_arguments_unsafe(1);
// Set up the growable object array class (Has to be done after the array
// class is setup as one of its field is an array object).
cls = Class::New<GrowableObjectArray, RTN::GrowableObjectArray>(
isolate_group);
object_store->set_growable_object_array_class(cls);
cls.set_type_arguments_field_offset(
GrowableObjectArray::type_arguments_offset(),
RTN::GrowableObjectArray::type_arguments_offset());
cls.set_num_type_arguments_unsafe(1);
// Initialize hash set for canonical types.
const intptr_t kInitialCanonicalTypeSize = 16;
array = HashTables::New<CanonicalTypeSet>(kInitialCanonicalTypeSize,
Heap::kOld);
object_store->set_canonical_types(array);
// Initialize hash set for canonical function types.
const intptr_t kInitialCanonicalFunctionTypeSize = 16;
array = HashTables::New<CanonicalFunctionTypeSet>(
kInitialCanonicalFunctionTypeSize, Heap::kOld);
object_store->set_canonical_function_types(array);
// Initialize hash set for canonical type parameters.
const intptr_t kInitialCanonicalTypeParameterSize = 4;
array = HashTables::New<CanonicalTypeParameterSet>(
kInitialCanonicalTypeParameterSize, Heap::kOld);
object_store->set_canonical_type_parameters(array);
// Initialize hash set for canonical_type_arguments_.
const intptr_t kInitialCanonicalTypeArgumentsSize = 4;
array = HashTables::New<CanonicalTypeArgumentsSet>(
kInitialCanonicalTypeArgumentsSize, Heap::kOld);
object_store->set_canonical_type_arguments(array);
// Setup type class early in the process.
const Class& type_cls =
Class::Handle(zone, Class::New<Type, RTN::Type>(isolate_group));
const Class& function_type_cls = Class::Handle(
zone, Class::New<FunctionType, RTN::FunctionType>(isolate_group));
const Class& type_ref_cls =
Class::Handle(zone, Class::New<TypeRef, RTN::TypeRef>(isolate_group));
const Class& type_parameter_cls = Class::Handle(
zone, Class::New<TypeParameter, RTN::TypeParameter>(isolate_group));
const Class& library_prefix_cls = Class::Handle(
zone, Class::New<LibraryPrefix, RTN::LibraryPrefix>(isolate_group));
// Pre-allocate the OneByteString class needed by the symbol table.
cls = Class::NewStringClass(kOneByteStringCid, isolate_group);
object_store->set_one_byte_string_class(cls);
// Pre-allocate the TwoByteString class needed by the symbol table.
cls = Class::NewStringClass(kTwoByteStringCid, isolate_group);
object_store->set_two_byte_string_class(cls);
// Setup the symbol table for the symbols created in the isolate.
Symbols::SetupSymbolTable(isolate_group);
// Set up the libraries array before initializing the core library.
const GrowableObjectArray& libraries =
GrowableObjectArray::Handle(zone, GrowableObjectArray::New(Heap::kOld));
object_store->set_libraries(libraries);
// Pre-register the core library.
Library::InitCoreLibrary(isolate_group);
// Basic infrastructure has been setup, initialize the class dictionary.
const Library& core_lib = Library::Handle(zone, Library::CoreLibrary());
ASSERT(!core_lib.IsNull());
const GrowableObjectArray& pending_classes =
GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
object_store->set_pending_classes(pending_classes);
// Now that the symbol table is initialized and that the core dictionary as
// well as the core implementation dictionary have been setup, preallocate
// remaining classes and register them by name in the dictionaries.
String& name = String::Handle(zone);
cls = object_store->array_class(); // Was allocated above.
RegisterPrivateClass(cls, Symbols::_List(), core_lib);
pending_classes.Add(cls);
// We cannot use NewNonParameterizedType(), because Array is
// parameterized. Warning: class _List has not been patched yet. Its
// declared number of type parameters is still 0. It will become 1 after
// patching. The array type allocated below represents the raw type _List
// and not _List<E> as we could expect. Use with caution.
type = Type::New(Class::Handle(zone, cls.ptr()),
TypeArguments::Handle(zone), Nullability::kNonNullable);
type.SetIsFinalized();
type ^= type.Canonicalize(thread, nullptr);
object_store->set_array_type(type);
type = type.ToNullability(Nullability::kLegacy, Heap::kOld);
object_store->set_legacy_array_type(type);
type = type.ToNullability(Nullability::kNonNullable, Heap::kOld);
object_store->set_non_nullable_array_type(type);
cls = object_store->growable_object_array_class(); // Was allocated above.
RegisterPrivateClass(cls, Symbols::_GrowableList(), core_lib);
pending_classes.Add(cls);
cls = Class::New<Array, RTN::Array>(kImmutableArrayCid, isolate_group);
object_store->set_immutable_array_class(cls);
cls.set_type_arguments_field_offset(Array::type_arguments_offset(),
RTN::Array::type_arguments_offset());
cls.set_num_type_arguments_unsafe(1);
ASSERT(object_store->immutable_array_class() !=
object_store->array_class());
cls.set_is_prefinalized();
RegisterPrivateClass(cls, Symbols::_ImmutableList(), core_lib);
pending_classes.Add(cls);
cls = object_store->one_byte_string_class(); // Was allocated above.
RegisterPrivateClass(cls, Symbols::OneByteString(), core_lib);
pending_classes.Add(cls);
cls = object_store->two_byte_string_class(); // Was allocated above.
RegisterPrivateClass(cls, Symbols::TwoByteString(), core_lib);
pending_classes.Add(cls);
cls = Class::NewStringClass(kExternalOneByteStringCid, isolate_group);
object_store->set_external_one_byte_string_class(cls);
RegisterPrivateClass(cls, Symbols::ExternalOneByteString(), core_lib);
pending_classes.Add(cls);
cls = Class::NewStringClass(kExternalTwoByteStringCid, isolate_group);
object_store->set_external_two_byte_string_class(cls);
RegisterPrivateClass(cls, Symbols::ExternalTwoByteString(), core_lib);
pending_classes.Add(cls);
// Pre-register the isolate library so the native class implementations can
// be hooked up before compiling it.
Library& isolate_lib = Library::Handle(
zone, Library::LookupLibrary(thread, Symbols::DartIsolate()));
if (isolate_lib.IsNull()) {
isolate_lib = Library::NewLibraryHelper(Symbols::DartIsolate(), true);
isolate_lib.SetLoadRequested();
isolate_lib.Register(thread);
}
object_store->set_bootstrap_library(ObjectStore::kIsolate, isolate_lib);
ASSERT(!isolate_lib.IsNull());
ASSERT(isolate_lib.ptr() == Library::IsolateLibrary());
cls = Class::New<Capability, RTN::Capability>(isolate_group);
RegisterPrivateClass(cls, Symbols::_CapabilityImpl(), isolate_lib);
pending_classes.Add(cls);
cls = Class::New<ReceivePort, RTN::ReceivePort>(isolate_group);
RegisterPrivateClass(cls, Symbols::_RawReceivePortImpl(), isolate_lib);
pending_classes.Add(cls);
cls = Class::New<SendPort, RTN::SendPort>(isolate_group);
RegisterPrivateClass(cls, Symbols::_SendPortImpl(), isolate_lib);
pending_classes.Add(cls);
cls = Class::New<TransferableTypedData, RTN::TransferableTypedData>(
isolate_group);
RegisterPrivateClass(cls, Symbols::_TransferableTypedDataImpl(),
isolate_lib);
pending_classes.Add(cls);
const Class& stacktrace_cls = Class::Handle(
zone, Class::New<StackTrace, RTN::StackTrace>(isolate_group));
RegisterPrivateClass(stacktrace_cls, Symbols::_StackTrace(), core_lib);
pending_classes.Add(stacktrace_cls);
// Super type set below, after Object is allocated.
cls = Class::New<RegExp, RTN::RegExp>(isolate_group);
RegisterPrivateClass(cls, Symbols::_RegExp(), core_lib);
pending_classes.Add(cls);
// Initialize the base interfaces used by the core VM classes.
// Allocate and initialize the pre-allocated classes in the core library.
// The script and token index of these pre-allocated classes is set up when
// the corelib script is compiled.
cls = Class::New<Instance, RTN::Instance>(kInstanceCid, isolate_group);
object_store->set_object_class(cls);
cls.set_name(Symbols::Object());
cls.set_num_type_arguments_unsafe(0);
cls.set_is_prefinalized();
cls.set_is_const();
core_lib.AddClass(cls);
pending_classes.Add(cls);
type = Type::NewNonParameterizedType(cls);
ASSERT(type.IsCanonical());
object_store->set_object_type(type);
type = type.ToNullability(Nullability::kLegacy, Heap::kOld);
ASSERT(type.IsCanonical());
object_store->set_legacy_object_type(type);
type = type.ToNullability(Nullability::kNonNullable, Heap::kOld);
ASSERT(type.IsCanonical());
object_store->set_non_nullable_object_type(type);
type = type.ToNullability(Nullability::kNullable, Heap::kOld);
ASSERT(type.IsCanonical());
object_store->set_nullable_object_type(type);
cls = Class::New<Bool, RTN::Bool>(isolate_group);
object_store->set_bool_class(cls);
RegisterClass(cls, Symbols::Bool(), core_lib);
pending_classes.Add(cls);
cls = Class::New<Instance, RTN::Instance>(kNullCid, isolate_group);
object_store->set_null_class(cls);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_prefinalized();
RegisterClass(cls, Symbols::Null(), core_lib);
pending_classes.Add(cls);
cls = Class::New<Instance, RTN::Instance>(kNeverCid, isolate_group);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_allocate_finalized();
cls.set_is_declaration_loaded();
cls.set_is_type_finalized();
cls.set_name(Symbols::Never());
object_store->set_never_class(cls);
ASSERT(!library_prefix_cls.IsNull());
RegisterPrivateClass(library_prefix_cls, Symbols::_LibraryPrefix(),
core_lib);
pending_classes.Add(library_prefix_cls);
RegisterPrivateClass(type_cls, Symbols::_Type(), core_lib);
pending_classes.Add(type_cls);
RegisterPrivateClass(function_type_cls, Symbols::_FunctionType(), core_lib);
pending_classes.Add(function_type_cls);
RegisterPrivateClass(type_ref_cls, Symbols::_TypeRef(), core_lib);
pending_classes.Add(type_ref_cls);
RegisterPrivateClass(type_parameter_cls, Symbols::_TypeParameter(),
core_lib);
pending_classes.Add(type_parameter_cls);
cls = Class::New<Integer, RTN::Integer>(isolate_group);
object_store->set_integer_implementation_class(cls);
RegisterPrivateClass(cls, Symbols::_IntegerImplementation(), core_lib);
pending_classes.Add(cls);
cls = Class::New<Smi, RTN::Smi>(isolate_group);
object_store->set_smi_class(cls);
RegisterPrivateClass(cls, Symbols::_Smi(), core_lib);
pending_classes.Add(cls);
cls = Class::New<Mint, RTN::Mint>(isolate_group);
object_store->set_mint_class(cls);
RegisterPrivateClass(cls, Symbols::_Mint(), core_lib);
pending_classes.Add(cls);
cls = Class::New<Double, RTN::Double>(isolate_group);
object_store->set_double_class(cls);
RegisterPrivateClass(cls, Symbols::_Double(), core_lib);
pending_classes.Add(cls);
// Class that represents the Dart class _Closure and C++ class Closure.
cls = Class::New<Closure, RTN::Closure>(isolate_group);
object_store->set_closure_class(cls);
RegisterPrivateClass(cls, Symbols::_Closure(), core_lib);
pending_classes.Add(cls);
cls = Class::New<WeakProperty, RTN::WeakProperty>(isolate_group);
object_store->set_weak_property_class(cls);
RegisterPrivateClass(cls, Symbols::_WeakProperty(), core_lib);
// Pre-register the mirrors library so we can place the vm class
// MirrorReference there rather than the core library.
lib = Library::LookupLibrary(thread, Symbols::DartMirrors());
if (lib.IsNull()) {
lib = Library::NewLibraryHelper(Symbols::DartMirrors(), true);
lib.SetLoadRequested();
lib.Register(thread);
}
object_store->set_bootstrap_library(ObjectStore::kMirrors, lib);
ASSERT(!lib.IsNull());
ASSERT(lib.ptr() == Library::MirrorsLibrary());
cls = Class::New<MirrorReference, RTN::MirrorReference>(isolate_group);
RegisterPrivateClass(cls, Symbols::_MirrorReference(), lib);
// Pre-register the collection library so we can place the vm class
// LinkedHashMap there rather than the core library.
lib = Library::LookupLibrary(thread, Symbols::DartCollection());
if (lib.IsNull()) {
lib = Library::NewLibraryHelper(Symbols::DartCollection(), true);
lib.SetLoadRequested();
lib.Register(thread);
}
object_store->set_bootstrap_library(ObjectStore::kCollection, lib);
ASSERT(!lib.IsNull());
ASSERT(lib.ptr() == Library::CollectionLibrary());
cls = Class::New<LinkedHashMap, RTN::LinkedHashMap>(isolate_group);
object_store->set_linked_hash_map_class(cls);
cls.set_type_arguments_field_offset(
LinkedHashMap::type_arguments_offset(),
RTN::LinkedHashMap::type_arguments_offset());
cls.set_num_type_arguments_unsafe(2);
RegisterPrivateClass(cls, Symbols::_LinkedHashMap(), lib);
pending_classes.Add(cls);
// Pre-register the async library so we can place the vm class
// FutureOr there rather than the core library.
lib = Library::LookupLibrary(thread, Symbols::DartAsync());
if (lib.IsNull()) {
lib = Library::NewLibraryHelper(Symbols::DartAsync(), true);
lib.SetLoadRequested();
lib.Register(thread);
}
object_store->set_bootstrap_library(ObjectStore::kAsync, lib);
ASSERT(!lib.IsNull());
ASSERT(lib.ptr() == Library::AsyncLibrary());
cls = Class::New<FutureOr, RTN::FutureOr>(isolate_group);
cls.set_type_arguments_field_offset(FutureOr::type_arguments_offset(),
RTN::FutureOr::type_arguments_offset());
cls.set_num_type_arguments_unsafe(1);
RegisterClass(cls, Symbols::FutureOr(), lib);
pending_classes.Add(cls);
// Pre-register the developer library so we can place the vm class
// UserTag there rather than the core library.
lib = Library::LookupLibrary(thread, Symbols::DartDeveloper());
if (lib.IsNull()) {
lib = Library::NewLibraryHelper(Symbols::DartDeveloper(), true);
lib.SetLoadRequested();
lib.Register(thread);
}
object_store->set_bootstrap_library(ObjectStore::kDeveloper, lib);
ASSERT(!lib.IsNull());
ASSERT(lib.ptr() == Library::DeveloperLibrary());
cls = Class::New<UserTag, RTN::UserTag>(isolate_group);
RegisterPrivateClass(cls, Symbols::_UserTag(), lib);
pending_classes.Add(cls);
// Setup some default native field classes which can be extended for
// specifying native fields in dart classes.
Library::InitNativeWrappersLibrary(isolate_group, is_kernel);
ASSERT(object_store->native_wrappers_library() != Library::null());
// Pre-register the typed_data library so the native class implementations
// can be hooked up before compiling it.
lib = Library::LookupLibrary(thread, Symbols::DartTypedData());
if (lib.IsNull()) {
lib = Library::NewLibraryHelper(Symbols::DartTypedData(), true);
lib.SetLoadRequested();
lib.Register(thread);
}
object_store->set_bootstrap_library(ObjectStore::kTypedData, lib);
ASSERT(!lib.IsNull());
ASSERT(lib.ptr() == Library::TypedDataLibrary());
#define REGISTER_TYPED_DATA_CLASS(clazz) \
cls = Class::NewTypedDataClass(kTypedData##clazz##ArrayCid, isolate_group); \
RegisterPrivateClass(cls, Symbols::_##clazz##List(), lib);
DART_CLASS_LIST_TYPED_DATA(REGISTER_TYPED_DATA_CLASS);
#undef REGISTER_TYPED_DATA_CLASS
#define REGISTER_TYPED_DATA_VIEW_CLASS(clazz) \
cls = \
Class::NewTypedDataViewClass(kTypedData##clazz##ViewCid, isolate_group); \
RegisterPrivateClass(cls, Symbols::_##clazz##View(), lib); \
pending_classes.Add(cls);
CLASS_LIST_TYPED_DATA(REGISTER_TYPED_DATA_VIEW_CLASS);
cls = Class::NewTypedDataViewClass(kByteDataViewCid, isolate_group);
RegisterPrivateClass(cls, Symbols::_ByteDataView(), lib);
pending_classes.Add(cls);
#undef REGISTER_TYPED_DATA_VIEW_CLASS
#define REGISTER_EXT_TYPED_DATA_CLASS(clazz) \
cls = Class::NewExternalTypedDataClass(kExternalTypedData##clazz##Cid, \
isolate_group); \
RegisterPrivateClass(cls, Symbols::_External##clazz(), lib);
cls = Class::New<Instance, RTN::Instance>(kByteBufferCid, isolate_group,
/*register_class=*/false);
cls.set_instance_size(0, 0);
cls.set_next_field_offset(-kWordSize, -compiler::target::kWordSize);
isolate_group->class_table()->Register(cls);
RegisterPrivateClass(cls, Symbols::_ByteBuffer(), lib);
pending_classes.Add(cls);
CLASS_LIST_TYPED_DATA(REGISTER_EXT_TYPED_DATA_CLASS);
#undef REGISTER_EXT_TYPED_DATA_CLASS
// Register Float32x4, Int32x4, and Float64x2 in the object store.
cls = Class::New<Float32x4, RTN::Float32x4>(isolate_group);
RegisterPrivateClass(cls, Symbols::_Float32x4(), lib);
pending_classes.Add(cls);
object_store->set_float32x4_class(cls);
cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
/*register_class=*/true,
/*is_abstract=*/true);
RegisterClass(cls, Symbols::Float32x4(), lib);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_prefinalized();
type = Type::NewNonParameterizedType(cls);
object_store->set_float32x4_type(type);
cls = Class::New<Int32x4, RTN::Int32x4>(isolate_group);
RegisterPrivateClass(cls, Symbols::_Int32x4(), lib);
pending_classes.Add(cls);
object_store->set_int32x4_class(cls);
cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
/*register_class=*/true,
/*is_abstract=*/true);
RegisterClass(cls, Symbols::Int32x4(), lib);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_prefinalized();
type = Type::NewNonParameterizedType(cls);
object_store->set_int32x4_type(type);
cls = Class::New<Float64x2, RTN::Float64x2>(isolate_group);
RegisterPrivateClass(cls, Symbols::_Float64x2(), lib);
pending_classes.Add(cls);
object_store->set_float64x2_class(cls);
cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
/*register_class=*/true,
/*is_abstract=*/true);
RegisterClass(cls, Symbols::Float64x2(), lib);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_prefinalized();
type = Type::NewNonParameterizedType(cls);
object_store->set_float64x2_type(type);
// Set the super type of class StackTrace to Object type so that the
// 'toString' method is implemented.
type = object_store->object_type();
stacktrace_cls.set_super_type(type);
// Abstract class that represents the Dart class Type.
// Note that this class is implemented by Dart class _AbstractType.
cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
/*register_class=*/true,
/*is_abstract=*/true);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_prefinalized();
RegisterClass(cls, Symbols::Type(), core_lib);
pending_classes.Add(cls);
type = Type::NewNonParameterizedType(cls);
object_store->set_type_type(type);
// Abstract class that represents the Dart class Function.
cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
/*register_class=*/true,
/*is_abstract=*/true);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_prefinalized();
RegisterClass(cls, Symbols::Function(), core_lib);
pending_classes.Add(cls);
type = Type::NewNonParameterizedType(cls);
object_store->set_function_type(type);
type = type.ToNullability(Nullability::kLegacy, Heap::kOld);
object_store->set_legacy_function_type(type);
type = type.ToNullability(Nullability::kNonNullable, Heap::kOld);
object_store->set_non_nullable_function_type(type);
cls = Class::New<Number, RTN::Number>(isolate_group);
RegisterClass(cls, Symbols::Number(), core_lib);
pending_classes.Add(cls);
type = Type::NewNonParameterizedType(cls);
object_store->set_number_type(type);
type = type.ToNullability(Nullability::kLegacy, Heap::kOld);
object_store->set_legacy_number_type(type);
type = type.ToNullability(Nullability::kNonNullable, Heap::kOld);
object_store->set_non_nullable_number_type(type);
cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
/*register_class=*/true,
/*is_abstract=*/true);
RegisterClass(cls, Symbols::Int(), core_lib);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_prefinalized();
pending_classes.Add(cls);
type = Type::NewNonParameterizedType(cls);
object_store->set_int_type(type);
type = type.ToNullability(Nullability::kLegacy, Heap::kOld);
object_store->set_legacy_int_type(type);
type = type.ToNullability(Nullability::kNonNullable, Heap::kOld);
object_store->set_non_nullable_int_type(type);
type = type.ToNullability(Nullability::kNullable, Heap::kOld);
object_store->set_nullable_int_type(type);
cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
/*register_class=*/true,
/*is_abstract=*/true);
RegisterClass(cls, Symbols::Double(), core_lib);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_prefinalized();
pending_classes.Add(cls);
type = Type::NewNonParameterizedType(cls);
object_store->set_double_type(type);
type = type.ToNullability(Nullability::kLegacy, Heap::kOld);
object_store->set_legacy_double_type(type);
type = type.ToNullability(Nullability::kNonNullable, Heap::kOld);
object_store->set_non_nullable_double_type(type);
type = type.ToNullability(Nullability::kNullable, Heap::kOld);
object_store->set_nullable_double_type(type);
name = Symbols::_String().ptr();
cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group,
/*register_class=*/true,
/*is_abstract=*/true);
RegisterClass(cls, name, core_lib);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_prefinalized();
pending_classes.Add(cls);
type = Type::NewNonParameterizedType(cls);
object_store->set_string_type(type);
type = type.ToNullability(Nullability::kLegacy, Heap::kOld);
object_store->set_legacy_string_type(type);
type = type.ToNullability(Nullability::kNonNullable, Heap::kOld);
object_store->set_non_nullable_string_type(type);
cls = object_store->bool_class();
type = Type::NewNonParameterizedType(cls);
object_store->set_bool_type(type);
type = type.ToNullability(Nullability::kLegacy, Heap::kOld);
object_store->set_legacy_bool_type(type);
type = type.ToNullability(Nullability::kNonNullable, Heap::kOld);
object_store->set_non_nullable_bool_type(type);
cls = object_store->smi_class();
type = Type::NewNonParameterizedType(cls);
object_store->set_smi_type(type);
type = type.ToNullability(Nullability::kLegacy, Heap::kOld);
object_store->set_legacy_smi_type(type);
type = type.ToNullability(Nullability::kNonNullable, Heap::kOld);
object_store->set_non_nullable_smi_type(type);
cls = object_store->mint_class();
type = Type::NewNonParameterizedType(cls);
object_store->set_mint_type(type);
type = type.ToNullability(Nullability::kLegacy, Heap::kOld);
object_store->set_legacy_mint_type(type);
type = type.ToNullability(Nullability::kNonNullable, Heap::kOld);
object_store->set_non_nullable_mint_type(type);
// The classes 'void' and 'dynamic' are phony classes to make type checking
// more regular; they live in the VM isolate. The class 'void' is not
// registered in the class dictionary because its name is a reserved word.
// The class 'dynamic' is registered in the class dictionary because its
// name is a built-in identifier (this is wrong). The corresponding types
// are stored in the object store.
cls = object_store->null_class();
type =
Type::New(cls, Object::null_type_arguments(), Nullability::kNullable);
type.SetIsFinalized();
type ^= type.Canonicalize(thread, nullptr);
object_store->set_null_type(type);
ASSERT(type.IsNullable());
// Consider removing when/if Null becomes an ordinary class.
type = object_store->object_type();
cls.set_super_type(type);
cls = object_store->never_class();
type = Type::New(cls, Object::null_type_arguments(),
Nullability::kNonNullable);
type.SetIsFinalized();
type ^= type.Canonicalize(thread, nullptr);
object_store->set_never_type(type);
// Create and cache commonly used type arguments <int>, <double>,
// <String>, <String, dynamic> and <String, String>.
type_args = TypeArguments::New(1);
type = object_store->int_type();
type_args.SetTypeAt(0, type);
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_int(type_args);
type_args = TypeArguments::New(1);
type = object_store->legacy_int_type();
type_args.SetTypeAt(0, type);
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_legacy_int(type_args);
type_args = TypeArguments::New(1);
type = object_store->non_nullable_int_type();
type_args.SetTypeAt(0, type);
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_non_nullable_int(type_args);
type_args = TypeArguments::New(1);
type = object_store->double_type();
type_args.SetTypeAt(0, type);
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_double(type_args);
type_args = TypeArguments::New(1);
type = object_store->legacy_double_type();
type_args.SetTypeAt(0, type);
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_legacy_double(type_args);
type_args = TypeArguments::New(1);
type = object_store->non_nullable_double_type();
type_args.SetTypeAt(0, type);
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_non_nullable_double(type_args);
type_args = TypeArguments::New(1);
type = object_store->string_type();
type_args.SetTypeAt(0, type);
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_string(type_args);
type_args = TypeArguments::New(1);
type = object_store->legacy_string_type();
type_args.SetTypeAt(0, type);
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_legacy_string(type_args);
type_args = TypeArguments::New(1);
type = object_store->non_nullable_string_type();
type_args.SetTypeAt(0, type);
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_non_nullable_string(type_args);
type_args = TypeArguments::New(2);
type = object_store->string_type();
type_args.SetTypeAt(0, type);
type_args.SetTypeAt(1, Object::dynamic_type());
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_string_dynamic(type_args);
type_args = TypeArguments::New(2);
type = object_store->legacy_string_type();
type_args.SetTypeAt(0, type);
type_args.SetTypeAt(1, Object::dynamic_type());
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_legacy_string_dynamic(type_args);
type_args = TypeArguments::New(2);
type = object_store->non_nullable_string_type();
type_args.SetTypeAt(0, type);
type_args.SetTypeAt(1, Object::dynamic_type());
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_non_nullable_string_dynamic(type_args);
type_args = TypeArguments::New(2);
type = object_store->string_type();
type_args.SetTypeAt(0, type);
type_args.SetTypeAt(1, type);
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_string_string(type_args);
type_args = TypeArguments::New(2);
type = object_store->legacy_string_type();
type_args.SetTypeAt(0, type);
type_args.SetTypeAt(1, type);
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_legacy_string_legacy_string(type_args);
type_args = TypeArguments::New(2);
type = object_store->non_nullable_string_type();
type_args.SetTypeAt(0, type);
type_args.SetTypeAt(1, type);
type_args = type_args.Canonicalize(thread, nullptr);
object_store->set_type_argument_non_nullable_string_non_nullable_string(
type_args);
lib = Library::LookupLibrary(thread, Symbols::DartFfi());
if (lib.IsNull()) {
lib = Library::NewLibraryHelper(Symbols::DartFfi(), true);
lib.SetLoadRequested();
lib.Register(thread);
}
object_store->set_bootstrap_library(ObjectStore::kFfi, lib);
cls = Class::New<Instance, RTN::Instance>(kFfiNativeTypeCid, isolate_group);
cls.set_num_type_arguments_unsafe(0);
cls.set_is_prefinalized();
pending_classes.Add(cls);
object_store->set_ffi_native_type_class(cls);
RegisterClass(cls, Symbols::FfiNativeType(), lib);
#define REGISTER_FFI_TYPE_MARKER(clazz) \
cls = Class::New<Instance, RTN::Instance>(kFfi##clazz##Cid, isolate_group); \
cls.set_num_type_arguments_unsafe(0); \
cls.set_is_prefinalized(); \
pending_classes.Add(cls); \
RegisterClass(cls, Symbols::Ffi##clazz(), lib);
CLASS_LIST_FFI_TYPE_MARKER(REGISTER_FFI_TYPE_MARKER);
#undef REGISTER_FFI_TYPE_MARKER
cls = Class::New<Instance, RTN::Instance>(kFfiNativeFunctionCid,
isolate_group);
cls.set_type_arguments_field_offset(Pointer::type_arguments_offset(),
RTN::Pointer::type_arguments_offset());
cls.set_num_type_arguments_unsafe(1);
cls.set_is_prefinalized();
pending_classes.Add(cls);
RegisterClass(cls, Symbols::FfiNativeFunction(), lib);
cls = Class::NewPointerClass(kFfiPointerCid, isolate_group);
object_store->set_ffi_pointer_class(cls);
pending_classes.Add(cls);
RegisterClass(cls, Symbols::FfiPointer(), lib);
cls = Class::New<DynamicLibrary, RTN::DynamicLibrary>(kFfiDynamicLibraryCid,
isolate_group);
cls.set_instance_size(DynamicLibrary::InstanceSize(),
compiler::target::RoundedAllocationSize(
RTN::DynamicLibrary::InstanceSize()));
cls.set_is_prefinalized();
pending_classes.Add(cls);
RegisterClass(cls, Symbols::FfiDynamicLibrary(), lib);
// Finish the initialization by compiling the bootstrap scripts containing
// the base interfaces and the implementation of the internal classes.
const Error& error = Error::Handle(
zone, Bootstrap::DoBootstrapping(kernel_buffer, kernel_buffer_size));
if (!error.IsNull()) {
return error.ptr();
}
isolate_group->class_table()->CopySizesFromClassObjects();
ClassFinalizer::VerifyBootstrapClasses();
// Set up the intrinsic state of all functions (core, math and typed data).
compiler::Intrinsifier::InitializeState();
// Adds static const fields (class ids) to the class 'ClassID');
lib = Library::LookupLibrary(thread, Symbols::DartInternal());
ASSERT(!lib.IsNull());
cls = lib.LookupClassAllowPrivate(Symbols::ClassID());
ASSERT(!cls.IsNull());
const bool injected = cls.InjectCIDFields();
ASSERT(injected);
isolate_group->object_store()->InitKnownObjects();
// Set up recognized state of all functions (core, math and typed data).
MethodRecognizer::InitializeState();
#endif // !defined(DART_PRECOMPILED_RUNTIME)
} else {
// Object::Init version when we are running in a version of dart that has a
// full snapshot linked in and an isolate is initialized using the full
// snapshot.
ObjectStore* object_store = isolate_group->object_store();
SafepointWriteRwLocker ml(thread, isolate_group->program_lock());
Class& cls = Class::Handle(zone);
// Set up empty classes in the object store, these will get initialized
// correctly when we read from the snapshot. This is done to allow
// bootstrapping of reading classes from the snapshot. Some classes are not
// stored in the object store. Yet we still need to create their Class
// object so that they get put into the class_table (as a side effect of
// Class::New()).
cls = Class::New<Instance, RTN::Instance>(kInstanceCid, isolate_group);
object_store->set_object_class(cls);
cls = Class::New<LibraryPrefix, RTN::LibraryPrefix>(isolate_group);
cls = Class::New<Type, RTN::Type>(isolate_group);
cls = Class::New<FunctionType, RTN::FunctionType>(isolate_group);
cls = Class::New<TypeRef, RTN::TypeRef>(isolate_group);
cls = Class::New<TypeParameter, RTN::TypeParameter>(isolate_group);
cls = Class::New<Array, RTN::Array>(isolate_group);
object_store->set_array_class(cls);
cls = Class::New<Array, RTN::Array>(kImmutableArrayCid, isolate_group);
object_store->set_immutable_array_class(cls);
cls = Class::New<GrowableObjectArray, RTN::GrowableObjectArray>(
isolate_group);
object_store->set_growable_object_array_class(cls);
cls = Class::New<LinkedHashMap, RTN::LinkedHashMap>(isolate_group);
object_store->set_linked_hash_map_class(cls);
cls = Class::New<Float32x4, RTN::Float32x4>(isolate_group);
object_store->set_float32x4_class(cls);
cls = Class::New<Int32x4, RTN::Int32x4>(isolate_group);
object_store->set_int32x4_class(cls);
cls = Class::New<Float64x2, RTN::Float64x2>(isolate_group);
object_store->set_float64x2_class(cls);
#define REGISTER_TYPED_DATA_CLASS(clazz) \
cls = Class::NewTypedDataClass(kTypedData##clazz##Cid, isolate_group);
CLASS_LIST_TYPED_DATA(REGISTER_TYPED_DATA_CLASS);
#undef REGISTER_TYPED_DATA_CLASS
#define REGISTER_TYPED_DATA_VIEW_CLASS(clazz) \
cls = Class::NewTypedDataViewClass(kTypedData##clazz##ViewCid, isolate_group);
CLASS_LIST_TYPED_DATA(REGISTER_TYPED_DATA_VIEW_CLASS);
#undef REGISTER_TYPED_DATA_VIEW_CLASS
cls = Class::NewTypedDataViewClass(kByteDataViewCid, isolate_group);
#define REGISTER_EXT_TYPED_DATA_CLASS(clazz) \
cls = Class::NewExternalTypedDataClass(kExternalTypedData##clazz##Cid, \
isolate_group);
CLASS_LIST_TYPED_DATA(REGISTER_EXT_TYPED_DATA_CLASS);
#undef REGISTER_EXT_TYPED_DATA_CLASS
cls = Class::New<Instance, RTN::Instance>(kFfiNativeTypeCid, isolate_group);
object_store->set_ffi_native_type_class(cls);
#define REGISTER_FFI_CLASS(clazz) \
cls = Class::New<Instance, RTN::Instance>(kFfi##clazz##Cid, isolate_group);
CLASS_LIST_FFI_TYPE_MARKER(REGISTER_FFI_CLASS);
#undef REGISTER_FFI_CLASS
cls = Class::New<Instance, RTN::Instance>(kFfiNativeFunctionCid,
isolate_group);
cls = Class::NewPointerClass(kFfiPointerCid, isolate_group);
object_store->set_ffi_pointer_class(cls);
cls = Class::New<DynamicLibrary, RTN::DynamicLibrary>(kFfiDynamicLibraryCid,
isolate_group);
cls = Class::New<Instance, RTN::Instance>(kByteBufferCid, isolate_group,
/*register_isolate_group=*/false);
cls.set_instance_size_in_words(0, 0);
isolate_group->class_table()->Register(cls);
cls = Class::New<Integer, RTN::Integer>(isolate_group);
object_store->set_integer_implementation_class(cls);
cls = Class::New<Smi, RTN::Smi>(isolate_group);
object_store->set_smi_class(cls);
cls = Class::New<Mint, RTN::Mint>(isolate_group);
object_store->set_mint_class(cls);
cls = Class::New<Double, RTN::Double>(isolate_group);
object_store->set_double_class(cls);
cls = Class::New<Closure, RTN::Closure>(isolate_group);
object_store->set_closure_class(cls);
cls = Class::NewStringClass(kOneByteStringCid, isolate_group);
object_store->set_one_byte_string_class(cls);
cls = Class::NewStringClass(kTwoByteStringCid, isolate_group);
object_store->set_two_byte_string_class(cls);
cls = Class::NewStringClass(kExternalOneByteStringCid, isolate_group);
object_store->set_external_one_byte_string_class(cls);
cls = Class::NewStringClass(kExternalTwoByteStringCid, isolate_group);
object_store->set_external_two_byte_string_class(cls);
cls = Class::New<Bool, RTN::Bool>(isolate_group);
object_store->set_bool_class(cls);
cls = Class::New<Instance, RTN::Instance>(kNullCid, isolate_group);
object_store->set_null_class(cls);
cls = Class::New<Instance, RTN::Instance>(kNeverCid, isolate_group);
object_store->set_never_class(cls);
cls = Class::New<Capability, RTN::Capability>(isolate_group);
cls = Class::New<ReceivePort, RTN::ReceivePort>(isolate_group);
cls = Class::New<SendPort, RTN::SendPort>(isolate_group);
cls = Class::New<StackTrace, RTN::StackTrace>(isolate_group);
cls = Class::New<RegExp, RTN::RegExp>(isolate_group);
cls = Class::New<Number, RTN::Number>(isolate_group);
cls = Class::New<WeakProperty, RTN::WeakProperty>(isolate_group);
object_store->set_weak_property_class(cls);
cls = Class::New<MirrorReference, RTN::MirrorReference>(isolate_group);
cls = Class::New<UserTag, RTN::UserTag>(isolate_group);
cls = Class::New<FutureOr, RTN::FutureOr>(isolate_group);
cls = Class::New<TransferableTypedData, RTN::TransferableTypedData>(
isolate_group);
}
return Error::null();
}
#if defined(DEBUG)
bool Object::InVMIsolateHeap() const {
if (FLAG_verify_handles && ptr()->untag()->InVMIsolateHeap()) {
Heap* vm_isolate_heap = Dart::vm_isolate_group()->heap();
uword addr = UntaggedObject::ToAddr(ptr());
if (!vm_isolate_heap->Contains(addr)) {
ASSERT(FLAG_write_protect_code);
addr = UntaggedObject::ToAddr(OldPage::ToWritable(ptr()));
ASSERT(vm_isolate_heap->Contains(addr));
}
}
return ptr()->untag()->InVMIsolateHeap();
}
#endif // DEBUG
void Object::Print() const {
THR_Print("%s\n", ToCString());
}
StringPtr Object::DictionaryName() const {
return String::null();
}
void Object::InitializeObject(uword address,
intptr_t class_id,
intptr_t size,
bool compressed) {
// Note: we skip the header word here to avoid a racy read in the concurrent
// marker from observing the null object when it reads into a heap page
// allocated after marking started.
uword cur = address + sizeof(UntaggedObject);
uword end = address + size;
if (class_id == kInstructionsCid) {
compiler::target::uword initial_value = kBreakInstructionFiller;
while (cur < end) {
*reinterpret_cast<compiler::target::uword*>(cur) = initial_value;
cur += compiler::target::kWordSize;
}
} else {
uword initial_value;
bool needs_init;
if (IsTypedDataBaseClassId(class_id)) {
initial_value = 0;
// If the size is greater than both kNewAllocatableSize and
// kAllocatablePageSize, the object must have been allocated to a new
// large page, which must already have been zero initialized by the OS.
#if defined(DART_COMPRESSED_POINTERS)
needs_init = true;
#else
needs_init = Heap::IsAllocatableInNewSpace(size) ||
Heap::IsAllocatableViaFreeLists(size);
#endif
} else {
initial_value = static_cast<uword>(null_);
#if defined(DART_COMPRESSED_POINTERS)
if (compressed) {
initial_value &= 0xFFFFFFFF;
initial_value |= initial_value << 32;
}
#endif
needs_init = true;
}
if (needs_init) {
while (cur < end) {
*reinterpret_cast<uword*>(cur) = initial_value;
cur += kWordSize;
}
} else {
// Check that MemorySantizer understands this is initialized.
MSAN_CHECK_INITIALIZED(reinterpret_cast<void*>(address), size);
#if defined(DEBUG)
while (cur < end) {
ASSERT(*reinterpret_cast<uword*>(cur) == initial_value);
cur += kWordSize;
}
#endif
}
}
uword tags = 0;
ASSERT(class_id != kIllegalCid);
tags = UntaggedObject::ClassIdTag::update(class_id, tags);
tags = UntaggedObject::SizeTag::update(size, tags);
const bool is_old =
(address & kNewObjectAlignmentOffset) == kOldObjectAlignmentOffset;
tags = UntaggedObject::OldBit::update(is_old, tags);
tags = UntaggedObject::OldAndNotMarkedBit::update(is_old, tags);
tags = UntaggedObject::OldAndNotRememberedBit::update(is_old, tags);
tags = UntaggedObject::NewBit::update(!is_old, tags);
reinterpret_cast<UntaggedObject*>(address)->tags_ = tags;
}
void Object::CheckHandle() const {
#if defined(DEBUG)
if (ptr_ != Object::null()) {
intptr_t cid = ptr_->GetClassIdMayBeSmi();
if (cid >= kNumPredefinedCids) {
cid = kInstanceCid;
}
ASSERT(vtable() == builtin_vtables_[cid]);
if (FLAG_verify_handles && ptr_->IsHeapObject()) {
Heap* isolate_heap = IsolateGroup::Current()->heap();
if (!isolate_heap->new_space()->scavenging()) {
Heap* vm_isolate_heap = Dart::vm_isolate_group()->heap();
uword addr = UntaggedObject::ToAddr(ptr_);
if (!isolate_heap->Contains(addr) && !vm_isolate_heap->Contains(addr)) {
ASSERT(FLAG_write_protect_code);
addr = UntaggedObject::ToAddr(OldPage::ToWritable(ptr_));
ASSERT(isolate_heap->Contains(addr) ||
vm_isolate_heap->Contains(addr));
}
}
}
}
#endif
}
ObjectPtr Object::Allocate(intptr_t cls_id,
intptr_t size,
Heap::Space space,
bool compressed) {
ASSERT(Utils::IsAligned(size, kObjectAlignment));
Thread* thread = Thread::Current();
ASSERT(thread->execution_state() == Thread::kThreadInVM);
ASSERT(thread->no_safepoint_scope_depth() == 0);
ASSERT(thread->no_callback_scope_depth() == 0);
Heap* heap = thread->heap();
uword address = heap->Allocate(size, space);
if (UNLIKELY(address == 0)) {
// SuspendLongJumpScope during Dart entry ensures that if a longjmp base is
// available, it is the innermost error handler, so check for a longjmp base
// before checking for an exit frame.
if (thread->long_jump_base() != nullptr) {
Report::LongJump(Object::out_of_memory_error());
UNREACHABLE();
} else if (thread->top_exit_frame_info() != 0) {
// Use the preallocated out of memory exception to avoid calling
// into dart code or allocating any code.
const Instance& exception = Instance::Handle(
thread->isolate_group()->object_store()->out_of_memory());
Exceptions::Throw(thread, exception);
UNREACHABLE();
} else {
// Nowhere to propagate an exception to.
OUT_OF_MEMORY();
}
}
NoSafepointScope no_safepoint;
ObjectPtr raw_obj;
InitializeObject(address, cls_id, size, compressed);
raw_obj = static_cast<ObjectPtr>(address + kHeapObjectTag);
ASSERT(cls_id == UntaggedObject::ClassIdTag::decode(raw_obj->untag()->tags_));
if (raw_obj->IsOldObject() && UNLIKELY(thread->is_marking())) {
// Black allocation. Prevents a data race between the mutator and
// concurrent marker on ARM and ARM64 (the marker may observe a
// publishing store of this object before the stores that initialize its
// slots), and helps the collection to finish sooner.
raw_obj->untag()->SetMarkBitUnsynchronized();
// Setting the mark bit must not be ordered after a publishing store of
// this object. Adding a barrier here is cheaper than making every store
// into the heap a store-release. Compare Scavenger::ScavengePointer.
std::atomic_thread_fence(std::memory_order_release);
heap->old_space()->AllocateBlack(size);
}
#ifndef PRODUCT
auto class_table = thread->isolate_group()->shared_class_table();
if (class_table->TraceAllocationFor(cls_id)) {
uint32_t hash =
HeapSnapshotWriter::GetHeapSnapshotIdentityHash(thread, raw_obj);
Profiler::SampleAllocation(thread, cls_id, hash);
}
#endif // !PRODUCT
return raw_obj;
}
class WriteBarrierUpdateVisitor : public ObjectPointerVisitor {
public:
explicit WriteBarrierUpdateVisitor(Thread* thread, ObjectPtr obj)
: ObjectPointerVisitor(thread->isolate_group()),
thread_(thread),
old_obj_(obj) {
ASSERT(old_obj_->IsOldObject());
}
void VisitPointers(ObjectPtr* from, ObjectPtr* to) {
if (old_obj_->IsArray()) {
for (ObjectPtr* slot = from; slot <= to; ++slot) {
ObjectPtr value = *slot;
if (value->IsHeapObject()) {
old_obj_->untag()->CheckArrayPointerStore(slot, value, thread_);
}
}
} else {
for (ObjectPtr* slot = from; slot <= to; ++slot) {
ObjectPtr value = *slot;
if (value->IsHeapObject()) {
old_obj_->untag()->CheckHeapPointerStore(value, thread_);
}
}
}
}
void VisitCompressedPointers(uword heap_base,
CompressedObjectPtr* from,
CompressedObjectPtr* to) {
if (old_obj_->IsArray()) {
for (CompressedObjectPtr* slot = from; slot <= to; ++slot) {
ObjectPtr value = slot->Decompress(heap_base);
if (value->IsHeapObject()) {
old_obj_->untag()->CheckArrayPointerStore(slot, value, thread_);
}
}
} else {
for (CompressedObjectPtr* slot = from; slot <= to; ++slot) {
ObjectPtr value = slot->Decompress(heap_base);
if (value->IsHeapObject()) {
old_obj_->untag()->CheckHeapPointerStore(value, thread_);
}
}
}
}
private:
Thread* thread_;
ObjectPtr old_obj_;
DISALLOW_COPY_AND_ASSIGN(WriteBarrierUpdateVisitor);
};
bool Object::IsReadOnlyHandle() const {
return Dart::IsReadOnlyHandle(reinterpret_cast<uword>(this));
}
bool Object::IsNotTemporaryScopedHandle() const {
return (IsZoneHandle() || IsReadOnlyHandle());
}
ObjectPtr Object::Clone(const Object& orig, Heap::Space space) {
const Class& cls = Class::Handle(orig.clazz());
intptr_t size = orig.ptr()->untag()->HeapSize();
ObjectPtr raw_clone =
Object::Allocate(cls.id(), size, space, /*compressed*/ false);
NoSafepointScope no_safepoint;
// Copy the body of the original into the clone.
uword orig_addr = UntaggedObject::ToAddr(orig.ptr());
uword clone_addr = UntaggedObject::ToAddr(raw_clone);
static const intptr_t kHeaderSizeInBytes = sizeof(UntaggedObject);
memmove(reinterpret_cast<uint8_t*>(clone_addr + kHeaderSizeInBytes),
reinterpret_cast<uint8_t*>(orig_addr + kHeaderSizeInBytes),
size - kHeaderSizeInBytes);
// Add clone to store buffer, if needed.
if (!raw_clone->IsOldObject()) {
// No need to remember an object in new space.
return raw_clone;
}
WriteBarrierUpdateVisitor visitor(Thread::Current(), raw_clone);
raw_clone->untag()->VisitPointers(&visitor);
return raw_clone;
}
StringPtr Class::Name() const {
return untag()->name();
}
StringPtr Class::ScrubbedName() const {
return Symbols::New(Thread::Current(), ScrubbedNameCString());
}
const char* Class::ScrubbedNameCString() const {
return String::ScrubName(String::Handle(Name()));
}
StringPtr Class::UserVisibleName() const {
#if !defined(PRODUCT)
ASSERT(untag()->user_name() != String::null());
return untag()->user_name();
#endif // !defined(PRODUCT)
// No caching in PRODUCT, regenerate.
return Symbols::New(Thread::Current(), GenerateUserVisibleName());
}
const char* Class::UserVisibleNameCString() const {
#if !defined(PRODUCT)
ASSERT(untag()->user_name() != String::null());
return String::Handle(untag()->user_name()).ToCString();
#endif // !defined(PRODUCT)
return GenerateUserVisibleName(); // No caching in PRODUCT, regenerate.
}
const char* Class::NameCString(NameVisibility name_visibility) const {
switch (name_visibility) {
case Object::kInternalName:
return String::Handle(Name()).ToCString();
case Object::kScrubbedName:
return ScrubbedNameCString();
case Object::kUserVisibleName:
return UserVisibleNameCString();
default:
UNREACHABLE();
return nullptr;
}
}
ClassPtr Class::Mixin() const {
if (is_transformed_mixin_application()) {
const Array& interfaces = Array::Handle(this->interfaces());
const Type& mixin_type =
Type::Handle(Type::RawCast(interfaces.At(interfaces.Length() - 1)));
return mixin_type.type_class();
}
return ptr();
}
NNBDMode Class::nnbd_mode() const {
return Library::Handle(library()).nnbd_mode();
}
bool Class::IsInFullSnapshot() const {
NoSafepointScope no_safepoint;
return UntaggedLibrary::InFullSnapshotBit::decode(
untag()->library()->untag()->flags_);
}
AbstractTypePtr Class::RareType() const {
if (!IsGeneric() && !IsClosureClass()) {
return DeclarationType();
}
ASSERT(is_declaration_loaded());
const Type& type = Type::Handle(Type::New(
*this, Object::null_type_arguments(), Nullability::kNonNullable));
return ClassFinalizer::FinalizeType(type);
}
template <class FakeObject, class TargetFakeObject>
ClassPtr Class::New(IsolateGroup* isolate_group, bool register_class) {
ASSERT(Object::class_class() != Class::null());
Class& result = Class::Handle();
{
ObjectPtr raw = Object::Allocate(Class::kClassId, Class::InstanceSize(),
Heap::kOld, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
Object::VerifyBuiltinVtable<FakeObject>(FakeObject::kClassId);
result.set_token_pos(TokenPosition::kNoSource);
result.set_end_token_pos(TokenPosition::kNoSource);
result.set_instance_size(FakeObject::InstanceSize(),
compiler::target::RoundedAllocationSize(
TargetFakeObject::InstanceSize()));
result.set_type_arguments_field_offset_in_words(kNoTypeArguments,
RTN::Class::kNoTypeArguments);
const intptr_t host_next_field_offset = FakeObject::NextFieldOffset();
const intptr_t target_next_field_offset = TargetFakeObject::NextFieldOffset();
result.set_next_field_offset(host_next_field_offset,
target_next_field_offset);
COMPILE_ASSERT((FakeObject::kClassId != kInstanceCid));
result.set_id(FakeObject::kClassId);
result.set_num_type_arguments_unsafe(0);
result.set_num_native_fields(0);
result.set_state_bits(0);
if ((FakeObject::kClassId < kInstanceCid) ||
(FakeObject::kClassId == kTypeArgumentsCid)) {
// VM internal classes are done. There is no finalization needed or
// possible in this case.
result.set_is_declaration_loaded();
result.set_is_type_finalized();
result.set_is_allocate_finalized();
} else if (FakeObject::kClassId != kClosureCid) {
// VM backed classes are almost ready: run checks and resolve class
// references, but do not recompute size.
result.set_is_prefinalized();
}
NOT_IN_PRECOMPILED(result.set_kernel_offset(0));
result.InitEmptyFields();
if (register_class) {
isolate_group->class_table()->Register(result);
}
return result.ptr();
}
static void ReportTooManyTypeArguments(const Class& cls) {
Report::MessageF(Report::kError, Script::Handle(cls.script()),
cls.token_pos(), Report::AtLocation,
"too many type parameters declared in class '%s' or in its "
"super classes",
String::Handle(cls.Name()).ToCString());
UNREACHABLE();
}
void Class::set_num_type_arguments(intptr_t value) const {
if (!Utils::IsInt(16, value)) {
ReportTooManyTypeArguments(*this);
}
// We allow concurrent calculation of the number of type arguments. If two
// threads perform this operation it doesn't matter which one wins.
DEBUG_ONLY(intptr_t old_value = num_type_arguments());
DEBUG_ASSERT(old_value == kUnknownNumTypeArguments || old_value == value);
StoreNonPointer<int16_t, int16_t, std::memory_order_relaxed>(
&untag()->num_type_arguments_, value);
}
void Class::set_num_type_arguments_unsafe(intptr_t value) const {
StoreNonPointer(&untag()->num_type_arguments_, value);
}
void Class::set_has_pragma(bool value) const {
set_state_bits(HasPragmaBit::update(value, state_bits()));
}
// Initialize class fields of type Array with empty array.
void Class::InitEmptyFields() {
if (Object::empty_array().ptr() == Array::null()) {
// The empty array has not been initialized yet.
return;
}
untag()->set_interfaces(Object::empty_array().ptr());
untag()->set_constants(Object::null_array().ptr());
set_functions(Object::empty_array());
set_fields(Object::empty_array());
set_invocation_dispatcher_cache(Object::empty_array());
}
ArrayPtr Class::OffsetToFieldMap(bool original_classes) const {
if (untag()->offset_in_words_to_field() == Array::null()) {
ASSERT(is_finalized());
const intptr_t length = untag()->host_instance_size_in_words_;
const Array& array = Array::Handle(Array::New(length, Heap::kOld));
Class& cls = Class::Handle(this->ptr());
Array& fields = Array::Handle();
Field& f = Field::Handle();
while (!cls.IsNull()) {
fields = cls.fields();
for (intptr_t i = 0; i < fields.Length(); ++i) {
f ^= fields.At(i);
if (f.is_instance()) {
array.SetAt(f.HostOffset() >> kWordSizeLog2, f);
}
}
cls = cls.SuperClass(original_classes);
}
untag()->set_offset_in_words_to_field(array.ptr());
}
return untag()->offset_in_words_to_field();
}
bool Class::HasInstanceFields() const {
const Array& field_array = Array::Handle(fields());
Field& field = Field::Handle();
for (intptr_t i = 0; i < field_array.Length(); ++i) {
field ^= field_array.At(i);
if (!field.is_static()) {
return true;
}
}
return false;
}
class FunctionName {
public:
FunctionName(const String& name, String* tmp_string)
: name_(name), tmp_string_(tmp_string) {}
bool Matches(const Function& function) const {
if (name_.IsSymbol()) {
return name_.ptr() == function.name();
} else {
*tmp_string_ = function.name();
return name_.Equals(*tmp_string_);
}
}
intptr_t Hash() const { return name_.Hash(); }
private:
const String& name_;
String* tmp_string_;
};
// Traits for looking up Functions by name.
class ClassFunctionsTraits {
public:
static const char* Name() { return "ClassFunctionsTraits"; }
static bool ReportStats() { return false; }
// Called when growing the table.
static bool IsMatch(const Object& a, const Object& b) {
ASSERT(a.IsFunction() && b.IsFunction());
// Function objects are always canonical.
return a.ptr() == b.ptr();
}
static bool IsMatch(const FunctionName& name, const Object& obj) {
return name.Matches(Function::Cast(obj));
}
static uword Hash(const Object& key) {
return String::HashRawSymbol(Function::Cast(key).name());
}
static uword Hash(const FunctionName& name) { return name.Hash(); }
};
typedef UnorderedHashSet<ClassFunctionsTraits> ClassFunctionsSet;
void Class::SetFunctions(const Array& value) const {
ASSERT(!value.IsNull());
const intptr_t len = value.Length();
#if defined(DEBUG)
Thread* thread = Thread::Current();
ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
if (is_finalized()) {
Function& function = Function::Handle();
FunctionType& signature = FunctionType::Handle();
for (intptr_t i = 0; i < len; ++i) {
function ^= value.At(i);
signature = function.signature();
ASSERT(signature.IsFinalized());
}
}
#endif
set_functions(value);
if (len >= kFunctionLookupHashTreshold) {
ClassFunctionsSet set(HashTables::New<ClassFunctionsSet>(len, Heap::kOld));
Function& func = Function::Handle();
for (intptr_t i = 0; i < len; ++i) {
func ^= value.At(i);
// Verify that all the functions in the array have this class as owner.
ASSERT(func.Owner() == ptr());
set.Insert(func);
}
untag()->set_functions_hash_table(set.Release().ptr());
} else {
untag()->set_functions_hash_table(Array::null());
}
}
void Class::AddFunction(const Function& function) const {
#if defined(DEBUG)
Thread* thread = Thread::Current();
ASSERT(thread->IsMutatorThread());
ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
ASSERT(!is_finalized() ||
FunctionType::Handle(function.signature()).IsFinalized());
#endif
const Array& arr = Array::Handle(functions());
const Array& new_array =
Array::Handle(Array::Grow(arr, arr.Length() + 1, Heap::kOld));
new_array.SetAt(arr.Length(), function);
set_functions(new_array);
// Add to hash table, if any.
const intptr_t new_len = new_array.Length();
if (new_len == kFunctionLookupHashTreshold) {
// Transition to using hash table.
SetFunctions(new_array);
} else if (new_len > kFunctionLookupHashTreshold) {
ClassFunctionsSet set(untag()->functions_hash_table());
set.Insert(function);
untag()->set_functions_hash_table(set.Release().ptr());
}
}
FunctionPtr Class::FunctionFromIndex(intptr_t idx) const {
const Array& funcs = Array::Handle(current_functions());
if ((idx < 0) || (idx >= funcs.Length())) {
return Function::null();
}
Function& func = Function::Handle();
func ^= funcs.At(idx);
ASSERT(!func.IsNull());
return func.ptr();
}
FunctionPtr Class::ImplicitClosureFunctionFromIndex(intptr_t idx) const {
const Array& funcs = Array::Handle(current_functions());
if ((idx < 0) || (idx >= funcs.Length())) {
return Function::null();
}
Function& func = Function::Handle();
func ^= funcs.At(idx);
ASSERT(!func.IsNull());
if (!func.HasImplicitClosureFunction()) {
return Function::null();
}
const Function& closure_func =
Function::Handle(func.ImplicitClosureFunction());
ASSERT(!closure_func.IsNull());
return closure_func.ptr();
}
intptr_t Class::FindImplicitClosureFunctionIndex(const Function& needle) const {
Thread* thread = Thread::Current();
if (EnsureIsFinalized(thread) != Error::null()) {
return -1;
}
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_FUNCTION_HANDLESCOPE(thread);
Array& funcs = thread->ArrayHandle();
Function& function = thread->FunctionHandle();
funcs = current_functions();
ASSERT(!funcs.IsNull());
Function& implicit_closure = Function::Handle(thread->zone());
const intptr_t len = funcs.Length();
for (intptr_t i = 0; i < len; i++) {
function ^= funcs.At(i);
implicit_closure = function.implicit_closure_function();
if (implicit_closure.IsNull()) {
// Skip non-implicit closure functions.
continue;
}
if (needle.ptr() == implicit_closure.ptr()) {
return i;
}
}
// No function found.
return -1;
}
intptr_t Class::FindInvocationDispatcherFunctionIndex(
const Function& needle) const {
Thread* thread = Thread::Current();
if (EnsureIsFinalized(thread) != Error::null()) {
return -1;
}
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_OBJECT_HANDLESCOPE(thread);
Array& funcs = thread->ArrayHandle();
Object& object = thread->ObjectHandle();
funcs = invocation_dispatcher_cache();
ASSERT(!funcs.IsNull());
const intptr_t len = funcs.Length();
for (intptr_t i = 0; i < len; i++) {
object = funcs.At(i);
// The invocation_dispatcher_cache is a table with some entries that
// are functions.
if (object.IsFunction()) {
if (Function::Cast(object).ptr() == needle.ptr()) {
return i;
}
}
}
// No function found.
return -1;
}
FunctionPtr Class::InvocationDispatcherFunctionFromIndex(intptr_t idx) const {
Thread* thread = Thread::Current();
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_OBJECT_HANDLESCOPE(thread);
Array& dispatcher_cache = thread->ArrayHandle();
Object& object = thread->ObjectHandle();
dispatcher_cache = invocation_dispatcher_cache();
object = dispatcher_cache.At(idx);
if (!object.IsFunction()) {
return Function::null();
}
return Function::Cast(object).ptr();
}
void Class::set_state_bits(intptr_t bits) const {
StoreNonPointer<uint32_t, uint32_t, std::memory_order_release>(
&untag()->state_bits_, static_cast<uint32_t>(bits));
}
void Class::set_library(const Library& value) const {
untag()->set_library(value.ptr());
}
void Class::set_type_parameters(const TypeArguments& value) const {
ASSERT((num_type_arguments() == kUnknownNumTypeArguments) ||
is_prefinalized());
untag()->set_type_parameters(value.ptr());
}
void Class::set_functions(const Array& value) const {
// Ensure all writes to the [Function]s are visible by the time the array
// is visible.
untag()->set_functions<std::memory_order_release>(value.ptr());
}
void Class::set_fields(const Array& value) const {
// Ensure all writes to the [Field]s are visible by the time the array
// is visible.
untag()->set_fields<std::memory_order_release>(value.ptr());
}
void Class::set_invocation_dispatcher_cache(const Array& cache) const {
// Ensure all writes to the cache are visible by the time the array
// is visible.
untag()->set_invocation_dispatcher_cache<std::memory_order_release>(
cache.ptr());
}
intptr_t Class::NumTypeParameters(Thread* thread) const {
if (!is_declaration_loaded()) {
ASSERT(is_prefinalized());
const intptr_t cid = id();
if ((cid == kArrayCid) || (cid == kImmutableArrayCid) ||
(cid == kGrowableObjectArrayCid)) {
return 1; // List's type parameter may not have been parsed yet.
}
return 0;
}
if (type_parameters() == TypeArguments::null()) {
return 0;
}
REUSABLE_TYPE_ARGUMENTS_HANDLESCOPE(thread);
TypeArguments& type_params = thread->TypeArgumentsHandle();
type_params = type_parameters();
return type_params.Length();
}
intptr_t Class::ComputeNumTypeArguments() const {
ASSERT(is_declaration_loaded());
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
const intptr_t num_type_params = NumTypeParameters();
if ((super_type() == AbstractType::null()) ||
(super_type() == isolate_group->object_store()->object_type())) {
return num_type_params;
}
const auto& sup_type = AbstractType::Handle(zone, super_type());
ASSERT(sup_type.IsType());
const auto& sup_class = Class::Handle(zone, sup_type.type_class());
const intptr_t sup_class_num_type_args = sup_class.NumTypeArguments();
if (num_type_params == 0) {
return sup_class_num_type_args;
}
const auto& sup_type_args = TypeArguments::Handle(zone, sup_type.arguments());
if (sup_type_args.IsNull()) {
// The super type is raw or the super class is non generic.
// In either case, overlapping is not possible.
return sup_class_num_type_args + num_type_params;
}
const intptr_t sup_type_args_length = sup_type_args.Length();
// At this point, the super type may or may not be finalized. In either case,
// the result of this function must remain the same.
// The value of num_sup_type_args may increase when the super type is
// finalized, but the last [sup_type_args_length] type arguments will not be
// modified by finalization, only shifted to higher indices in the vector.
// The super type may not even be resolved yet. This is not necessary, since
// we only check for matching type parameters, which are resolved by default.
// Determine the maximum overlap of a prefix of the vector consisting of the
// type parameters of this class with a suffix of the vector consisting of the
// type arguments of the super type of this class.
// The number of own type arguments of this class is the number of its type
// parameters minus the number of type arguments in the overlap.
// Attempt to overlap the whole vector of type parameters; reduce the size
// of the vector (keeping the first type parameter) until it fits or until
// its size is zero.
auto& sup_type_arg = AbstractType::Handle(zone);
for (intptr_t num_overlapping_type_args =
(num_type_params < sup_type_args_length) ? num_type_params
: sup_type_args_length;
num_overlapping_type_args > 0; num_overlapping_type_args--) {
intptr_t i = 0;
for (; i < num_overlapping_type_args; i++) {
sup_type_arg = sup_type_args.TypeAt(sup_type_args_length -
num_overlapping_type_args + i);
ASSERT(!sup_type_arg.IsNull());
if (!sup_type_arg.IsTypeParameter()) break;
// The only type parameters appearing in the type arguments of the super
// type are those declared by this class. Their finalized indices depend
// on the number of type arguments being computed here. Therefore, they
// cannot possibly be finalized yet.
ASSERT(!TypeParameter::Cast(sup_type_arg).IsFinalized());
if (TypeParameter::Cast(sup_type_arg).index() != i ||
TypeParameter::Cast(sup_type_arg).IsNullable()) {
break;
}
}
if (i == num_overlapping_type_args) {
// Overlap found.
return sup_class_num_type_args + num_type_params -
num_overlapping_type_args;
}
}
// No overlap found.
return sup_class_num_type_args + num_type_params;
}
intptr_t Class::NumTypeArguments() const {
// Return cached value if already calculated.
intptr_t num_type_args = num_type_arguments();
if (num_type_args != kUnknownNumTypeArguments) {
return num_type_args;
}
num_type_args = ComputeNumTypeArguments();
ASSERT(num_type_args != kUnknownNumTypeArguments);
set_num_type_arguments(num_type_args);
return num_type_args;
}
static TypeArgumentsPtr InstantiateTypeArgumentsToBounds(
Thread* thread,
const TypeArguments& parameters) {
ASSERT(thread != nullptr);
if (parameters.IsNull()) {
return Object::empty_type_arguments().ptr();
}
auto const zone = thread->zone();
const auto& result = TypeArguments::Handle(
zone, TypeArguments::New(parameters.Length(), Heap::kNew));
auto& param = TypeParameter::Handle(zone);
auto& type = AbstractType::Handle(zone);
for (intptr_t i = 0, n = parameters.Length(); i < n; i++) {
param ^= parameters.TypeAt(i);
type = param.default_argument();
ASSERT(type.IsFinalized());
result.SetTypeAt(i, type);
}
return result.Canonicalize(thread);
}
TypeArgumentsPtr Class::InstantiateToBounds(Thread* thread) const {
const auto& type_params =
TypeArguments::Handle(thread->zone(), type_parameters());
return InstantiateTypeArgumentsToBounds(thread, type_params);
}
ClassPtr Class::SuperClass(bool original_classes) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
if (super_type() == AbstractType::null()) {
if (id() == kTypeArgumentsCid) {
// Pretend TypeArguments objects are Dart instances.
return isolate_group->class_table()->At(kInstanceCid);
}
return Class::null();
}
const AbstractType& sup_type = AbstractType::Handle(zone, super_type());
const intptr_t type_class_id = sup_type.type_class_id();
if (original_classes) {
return isolate_group->GetClassForHeapWalkAt(type_class_id);
} else {
return isolate_group->class_table()->At(type_class_id);
}
}
void Class::set_super_type(const AbstractType& value) const {
ASSERT(value.IsNull() || (value.IsType() && !value.IsDynamicType()));
untag()->set_super_type(value.ptr());
}
TypeParameterPtr Class::LookupTypeParameter(const String& type_name) const {
ASSERT(!type_name.IsNull());
Thread* thread = Thread::Current();
REUSABLE_TYPE_ARGUMENTS_HANDLESCOPE(thread);
REUSABLE_TYPE_PARAMETER_HANDLESCOPE(thread);
REUSABLE_STRING_HANDLESCOPE(thread);
TypeArguments& type_params = thread->TypeArgumentsHandle();
TypeParameter& type_param = thread->TypeParameterHandle();
String& type_param_name = thread->StringHandle();
type_params = type_parameters();
if (!type_params.IsNull()) {
const intptr_t num_type_params = type_params.Length();
for (intptr_t i = 0; i < num_type_params; i++) {
type_param ^= type_params.TypeAt(i);
type_param_name = type_param.name();
if (type_param_name.Equals(type_name)) {
return type_param.ptr();
}
}
}
return TypeParameter::null();
}
UnboxedFieldBitmap Class::CalculateFieldOffsets() const {
Array& flds = Array::Handle(fields());
const Class& super = Class::Handle(SuperClass());
intptr_t host_offset = 0;
UnboxedFieldBitmap host_bitmap{};
// Target offsets might differ if the word size are different
intptr_t target_offset = 0;
intptr_t host_type_args_field_offset = kNoTypeArguments;
intptr_t target_type_args_field_offset = RTN::Class::kNoTypeArguments;
if (super.IsNull()) {
host_offset = Instance::NextFieldOffset();
target_offset = RTN::Instance::NextFieldOffset();
ASSERT(host_offset > 0);
ASSERT(target_offset > 0);
} else {
ASSERT(super.is_finalized() || super.is_prefinalized());
host_type_args_field_offset = super.host_type_arguments_field_offset();
target_type_args_field_offset = super.target_type_arguments_field_offset();
host_offset = super.host_next_field_offset();
ASSERT(host_offset > 0);
target_offset = super.target_next_field_offset();
ASSERT(target_offset > 0);
// We should never call CalculateFieldOffsets for native wrapper
// classes, assert this.
ASSERT(num_native_fields() == 0);
set_num_native_fields(super.num_native_fields());
if (FLAG_precompiled_mode) {
host_bitmap =
IsolateGroup::Current()->shared_class_table()->GetUnboxedFieldsMapAt(
super.id());
}
}
// If the super class is parameterized, use the same type_arguments field,
// otherwise, if this class is the first in the super chain to be
// parameterized, introduce a new type_arguments field.
if (host_type_args_field_offset == kNoTypeArguments) {
ASSERT(target_type_args_field_offset == RTN::Class::kNoTypeArguments);
const TypeArguments& type_params = TypeArguments::Handle(type_parameters());
if (!type_params.IsNull()) {
ASSERT(type_params.Length() > 0);
// The instance needs a type_arguments field.
host_type_args_field_offset = host_offset;
target_type_args_field_offset = target_offset;
host_offset += kWordSize;
target_offset += compiler::target::kWordSize;
}
} else {
ASSERT(target_type_args_field_offset != RTN::Class::kNoTypeArguments);
}
set_type_arguments_field_offset(host_type_args_field_offset,
target_type_args_field_offset);
ASSERT(host_offset > 0);
ASSERT(target_offset > 0);
Field& field = Field::Handle();
const intptr_t len = flds.Length();
for (intptr_t i = 0; i < len; i++) {
field ^= flds.At(i);
// Offset is computed only for instance fields.
if (!field.is_static()) {
ASSERT(field.HostOffset() == 0);
ASSERT(field.TargetOffset() == 0);
field.SetOffset(host_offset, target_offset);
if (FLAG_precompiled_mode && field.is_unboxing_candidate()) {
intptr_t field_size;
switch (field.guarded_cid()) {
case kDoubleCid:
field_size = sizeof(UntaggedDouble::value_);
break;
case kFloat32x4Cid:
field_size = sizeof(UntaggedFloat32x4::value_);
break;
case kFloat64x2Cid:
field_size = sizeof(UntaggedFloat64x2::value_);
break;
default:
if (field.is_non_nullable_integer()) {
field_size = sizeof(UntaggedMint::value_);
} else {
UNREACHABLE();
field_size = 0;
}
break;
}
const intptr_t host_num_words = field_size / kWordSize;
const intptr_t host_next_offset = host_offset + field_size;
const intptr_t host_next_position = host_next_offset / kWordSize;
const intptr_t target_next_offset = target_offset + field_size;
const intptr_t target_next_position =
target_next_offset / compiler::target::kWordSize;
// The bitmap has fixed length. Checks if the offset position is smaller
// than its length. If it is not, than the field should be boxed
if (host_next_position <= UnboxedFieldBitmap::Length() &&
target_next_position <= UnboxedFieldBitmap::Length()) {
for (intptr_t j = 0; j < host_num_words; j++) {
// Activate the respective bit in the bitmap, indicating that the
// content is not a pointer
host_bitmap.Set(host_offset / kWordSize);
host_offset += kWordSize;
}
ASSERT(host_offset == host_next_offset);
target_offset = target_next_offset;
} else {
// Make the field boxed
field.set_is_unboxing_candidate(false);
host_offset += kWordSize;
target_offset += compiler::target::kWordSize;
}
} else {
host_offset += kWordSize;
target_offset += compiler::target::kWordSize;
}
}
}
set_instance_size(RoundedAllocationSize(host_offset),
compiler::target::RoundedAllocationSize(target_offset));
set_next_field_offset(host_offset, target_offset);
return host_bitmap;
}
void Class::AddInvocationDispatcher(const String& target_name,
const Array& args_desc,
const Function& dispatcher) const {
auto thread = Thread::Current();
ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
auto zone = thread->zone();
auto& cache = Array::Handle(zone, invocation_dispatcher_cache());
InvocationDispatcherTable dispatchers(cache);
intptr_t i = 0;
for (auto dispatcher : dispatchers) {
if (dispatcher.Get<kInvocationDispatcherName>() == String::null()) {
break;
}
i++;
}
if (i == dispatchers.Length()) {
const intptr_t new_len =
cache.Length() == 0
? static_cast<intptr_t>(Class::kInvocationDispatcherEntrySize)
: cache.Length() * 2;
cache = Array::Grow(cache, new_len);
set_invocation_dispatcher_cache(cache);
}
// Ensure all stores are visible at the point the name is visible.
auto entry = dispatchers[i];
entry.Set<Class::kInvocationDispatcherArgsDesc>(args_desc);
entry.Set<Class::kInvocationDispatcherFunction>(dispatcher);
entry.Set<Class::kInvocationDispatcherName, std::memory_order_release>(
target_name);
}
FunctionPtr Class::GetInvocationDispatcher(const String& target_name,
const Array& args_desc,
UntaggedFunction::Kind kind,
bool create_if_absent) const {
ASSERT(kind == UntaggedFunction::kNoSuchMethodDispatcher ||
kind == UntaggedFunction::kInvokeFieldDispatcher ||
kind == UntaggedFunction::kDynamicInvocationForwarder);
auto thread = Thread::Current();
auto Z = thread->zone();
auto& function = Function::Handle(Z);
auto& name = String::Handle(Z);
auto& desc = Array::Handle(Z);
auto& cache = Array::Handle(Z);
auto find_entry = [&]() {
cache = invocation_dispatcher_cache();
ASSERT(!cache.IsNull());
InvocationDispatcherTable dispatchers(cache);
for (auto dispatcher : dispatchers) {
// Ensure all loads are done after loading the name.
name = dispatcher.Get<Class::kInvocationDispatcherName,
std::memory_order_acquire>();
if (name.IsNull()) break; // Reached last entry.
if (!name.Equals(target_name)) continue;
desc = dispatcher.Get<Class::kInvocationDispatcherArgsDesc>();
if (desc.ptr() != args_desc.ptr()) continue;
function = dispatcher.Get<Class::kInvocationDispatcherFunction>();
if (function.kind() == kind) {
return function.ptr();
}
}
return Function::null();
};
// First we'll try to find it without using locks.
function = find_entry();
if (!function.IsNull() || !create_if_absent) {
return function.ptr();
}
// If we failed to find it and possibly need to create it, use a write lock.
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
// Try to find it again & return if it was added in the meantime.
function = find_entry();
if (!function.IsNull()) return function.ptr();
// Otherwise create it & add it.
function = CreateInvocationDispatcher(target_name, args_desc, kind);
AddInvocationDispatcher(target_name, args_desc, function);
return function.ptr();
}
FunctionPtr Class::CreateInvocationDispatcher(
const String& target_name,
const Array& args_desc,
UntaggedFunction::Kind kind) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
FunctionType& signature = FunctionType::Handle(zone, FunctionType::New());
Function& invocation = Function::Handle(
zone, Function::New(
signature,
String::Handle(zone, Symbols::New(thread, target_name)), kind,
false, // Not static.
false, // Not const.
false, // Not abstract.
false, // Not external.
false, // Not native.
*this, TokenPosition::kMinSource));
ArgumentsDescriptor desc(args_desc);
if (desc.TypeArgsLen() > 0) {
// Make dispatcher function generic, since type arguments are passed.
invocation.SetNumTypeParameters(desc.TypeArgsLen());
}
invocation.set_num_fixed_parameters(desc.PositionalCount());
invocation.SetNumOptionalParameters(desc.NamedCount(),
false); // Not positional.
signature.set_parameter_types(
Array::Handle(zone, Array::New(desc.Count(), Heap::kOld)));
signature.CreateNameArrayIncludingFlags(Heap::kOld);
// Receiver.
signature.SetParameterTypeAt(0, Object::dynamic_type());
signature.SetParameterNameAt(0, Symbols::This());
// Remaining positional parameters.
for (intptr_t i = 1; i < desc.PositionalCount(); i++) {
signature.SetParameterTypeAt(i, Object::dynamic_type());
char name[64];
Utils::SNPrint(name, 64, ":p%" Pd, i);
signature.SetParameterNameAt(
i, String::Handle(zone, Symbols::New(thread, name)));
}
// Named parameters.
for (intptr_t i = 0; i < desc.NamedCount(); i++) {
const intptr_t param_index = desc.PositionAt(i);
const auto& param_name = String::Handle(zone, desc.NameAt(i));
signature.SetParameterTypeAt(param_index, Object::dynamic_type());
signature.SetParameterNameAt(param_index, param_name);
}
signature.FinalizeNameArrays(invocation);
signature.set_result_type(Object::dynamic_type());
invocation.set_is_debuggable(false);
invocation.set_is_visible(false);
invocation.set_is_reflectable(false);
invocation.set_saved_args_desc(args_desc);
signature ^= ClassFinalizer::FinalizeType(signature);
invocation.set_signature(signature);
return invocation.ptr();
}
// Method extractors are used to create implicit closures from methods.
// When an expression obj.M is evaluated for the first time and receiver obj
// does not have a getter called M but has a method called M then an extractor
// is created and injected as a getter (under the name get:M) into the class
// owning method M.
FunctionPtr Function::CreateMethodExtractor(const String& getter_name) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
ASSERT(Field::IsGetterName(getter_name));
const Function& closure_function =
Function::Handle(zone, ImplicitClosureFunction());
const Class& owner = Class::Handle(zone, closure_function.Owner());
FunctionType& signature = FunctionType::Handle(zone, FunctionType::New());
const Function& extractor = Function::Handle(
zone,
Function::New(signature,
String::Handle(zone, Symbols::New(thread, getter_name)),
UntaggedFunction::kMethodExtractor,
false, // Not static.
false, // Not const.
is_abstract(),
false, // Not external.
false, // Not native.
owner, TokenPosition::kMethodExtractor));
// Initialize signature: receiver is a single fixed parameter.
const intptr_t kNumParameters = 1;
extractor.set_num_fixed_parameters(kNumParameters);
extractor.SetNumOptionalParameters(0, false);
signature.set_parameter_types(Object::extractor_parameter_types());
signature.set_parameter_names(Object::extractor_parameter_names());
extractor.SetParameterNamesFrom(signature);
signature.set_result_type(Object::dynamic_type());
extractor.InheritKernelOffsetFrom(*this);
extractor.set_extracted_method_closure(closure_function);
extractor.set_is_debuggable(false);
extractor.set_is_visible(false);
signature ^= ClassFinalizer::FinalizeType(signature);
extractor.set_signature(signature);
owner.AddFunction(extractor);
return extractor.ptr();
}
FunctionPtr Function::GetMethodExtractor(const String& getter_name) const {
ASSERT(Field::IsGetterName(getter_name));
const Function& closure_function =
Function::Handle(ImplicitClosureFunction());
const Class& owner = Class::Handle(closure_function.Owner());
Thread* thread = Thread::Current();
if (owner.EnsureIsFinalized(thread) != Error::null()) {
return Function::null();
}
IsolateGroup* group = thread->isolate_group();
Function& result = Function::Handle(
Resolver::ResolveDynamicFunction(thread->zone(), owner, getter_name));
if (result.IsNull()) {
SafepointWriteRwLocker ml(thread, group->program_lock());
result = owner.LookupDynamicFunctionUnsafe(getter_name);
if (result.IsNull()) {
result = CreateMethodExtractor(getter_name);
}
}
ASSERT(result.kind() == UntaggedFunction::kMethodExtractor);
return result.ptr();
}
bool Library::FindPragma(Thread* T,
bool only_core,
const Object& obj,
const String& pragma_name,
bool multiple,
Object* options) {
auto IG = T->isolate_group();
auto Z = T->zone();
auto& lib = Library::Handle(Z);
if (obj.IsClass()) {
auto& klass = Class::Cast(obj);
if (!klass.has_pragma()) return false;
lib = klass.library();
} else if (obj.IsFunction()) {
auto& function = Function::Cast(obj);
if (!function.has_pragma()) return false;
lib = Class::Handle(Z, function.Owner()).library();
} else if (obj.IsField()) {
auto& field = Field::Cast(obj);
if (!field.has_pragma()) return false;
lib = Class::Handle(Z, field.Owner()).library();
} else {
UNREACHABLE();
}
if (only_core && !lib.IsAnyCoreLibrary()) {
return false;
}
Object& metadata_obj = Object::Handle(Z, lib.GetMetadata(obj));
if (metadata_obj.IsUnwindError()) {
Report::LongJump(UnwindError::Cast(metadata_obj));
}
// If there is a compile-time error while evaluating the metadata, we will
// simply claim there was no @pragma annotation.
if (metadata_obj.IsNull() || metadata_obj.IsLanguageError()) {
return false;
}
ASSERT(metadata_obj.IsArray());
auto& metadata = Array::Cast(metadata_obj);
auto& pragma_class = Class::Handle(Z, IG->object_store()->pragma_class());
auto& pragma_name_field =
Field::Handle(Z, pragma_class.LookupField(Symbols::name()));
auto& pragma_options_field =
Field::Handle(Z, pragma_class.LookupField(Symbols::options()));
auto& pragma = Object::Handle(Z);
bool found = false;
auto& options_value = Object::Handle(Z);
auto& results = GrowableObjectArray::Handle(Z);
if (multiple) {
ASSERT(options != nullptr);
results ^= GrowableObjectArray::New(1);
}
for (intptr_t i = 0; i < metadata.Length(); ++i) {
pragma = metadata.At(i);
if (pragma.clazz() != pragma_class.ptr() ||
Instance::Cast(pragma).GetField(pragma_name_field) !=
pragma_name.ptr()) {
continue;
}
options_value = Instance::Cast(pragma).GetField(pragma_options_field);
found = true;
if (multiple) {
results.Add(options_value);
continue;
}
if (options != nullptr) {
*options = options_value.ptr();
}
return true;
}
if (found && options != nullptr) {
*options = results.ptr();
}
return found;
}
bool Function::IsDynamicInvocationForwarderName(const String& name) {
return IsDynamicInvocationForwarderName(name.ptr());
}
bool Function::IsDynamicInvocationForwarderName(StringPtr name) {
return String::StartsWith(name, Symbols::DynamicPrefix().ptr());
}
StringPtr Function::DemangleDynamicInvocationForwarderName(const String& name) {
const intptr_t kDynamicPrefixLength = 4; // "dyn:"
ASSERT(Symbols::DynamicPrefix().Length() == kDynamicPrefixLength);
return Symbols::New(Thread::Current(), name, kDynamicPrefixLength,
name.Length() - kDynamicPrefixLength);
}
StringPtr Function::CreateDynamicInvocationForwarderName(const String& name) {
return Symbols::FromConcat(Thread::Current(), Symbols::DynamicPrefix(), name);
}
#if !defined(DART_PRECOMPILED_RUNTIME)
FunctionPtr Function::CreateDynamicInvocationForwarder(
const String& mangled_name) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Function& forwarder = Function::Handle(zone);
forwarder ^= Object::Clone(*this, Heap::kOld);
forwarder.reset_unboxed_parameters_and_return();
forwarder.set_name(mangled_name);
forwarder.set_is_native(false);
// TODO(dartbug.com/37737): Currently, we intentionally keep the recognized
// kind when creating the dynamic invocation forwarder.
forwarder.set_kind(UntaggedFunction::kDynamicInvocationForwarder);
forwarder.set_is_debuggable(false);
// TODO(vegorov) for error reporting reasons it is better to make this
// function visible and instead use a TailCall to invoke the target.
// Our TailCall instruction is not ready for such usage though it
// blocks inlining and can't take Function-s only Code objects.
forwarder.set_is_visible(false);
forwarder.ClearICDataArray();
forwarder.ClearCode();
forwarder.set_usage_counter(0);
forwarder.set_deoptimization_counter(0);
forwarder.set_optimized_instruction_count(0);
forwarder.set_inlining_depth(0);
forwarder.set_optimized_call_site_count(0);
forwarder.InheritKernelOffsetFrom(*this);
const Array& checks = Array::Handle(zone, Array::New(1));
checks.SetAt(0, *this);
forwarder.SetForwardingChecks(checks);
return forwarder.ptr();
}
FunctionPtr Function::GetDynamicInvocationForwarder(
const String& mangled_name,
bool allow_add /*=true*/) const {
ASSERT(IsDynamicInvocationForwarderName(mangled_name));
auto thread = Thread::Current();
auto zone = thread->zone();
const Class& owner = Class::Handle(zone, Owner());
Function& result = Function::Handle(zone);
// First we'll try to find it without using locks.
result = owner.GetInvocationDispatcher(
mangled_name, Array::null_array(),
UntaggedFunction::kDynamicInvocationForwarder,
/*create_if_absent=*/false);
if (!result.IsNull()) return result.ptr();
const bool needs_dyn_forwarder =
kernel::NeedsDynamicInvocationForwarder(*this);
if (!allow_add) {
return needs_dyn_forwarder ? Function::null() : ptr();
}
// If we failed to find it and possibly need to create it, use a write lock.
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
// Try to find it again & return if it was added in the mean time.
result = owner.GetInvocationDispatcher(
mangled_name, Array::null_array(),
UntaggedFunction::kDynamicInvocationForwarder,
/*create_if_absent=*/false);
if (!result.IsNull()) return result.ptr();
// Otherwise create it & add it.
result = needs_dyn_forwarder ? CreateDynamicInvocationForwarder(mangled_name)
: ptr();
owner.AddInvocationDispatcher(mangled_name, Array::null_array(), result);
return result.ptr();
}
#endif
bool AbstractType::InstantiateAndTestSubtype(
AbstractType* subtype,
AbstractType* supertype,
const TypeArguments& instantiator_type_args,
const TypeArguments& function_type_args) {
if (!subtype->IsInstantiated()) {
*subtype = subtype->InstantiateFrom(
instantiator_type_args, function_type_args, kAllFree, Heap::kOld);
}
if (!supertype->IsInstantiated()) {
*supertype = supertype->InstantiateFrom(
instantiator_type_args, function_type_args, kAllFree, Heap::kOld);
}
return subtype->IsSubtypeOf(*supertype, Heap::kOld);
}
ArrayPtr Class::invocation_dispatcher_cache() const {
return untag()->invocation_dispatcher_cache<std::memory_order_acquire>();
}
void Class::Finalize() const {
auto thread = Thread::Current();
auto isolate_group = thread->isolate_group();
ASSERT(!thread->isolate_group()->all_classes_finalized());
ASSERT(!is_finalized());
// Prefinalized classes have a VM internal representation and no Dart fields.
// Their instance size is precomputed and field offsets are known.
if (!is_prefinalized()) {
// Compute offsets of instance fields, instance size and bitmap for unboxed
// fields.
const auto host_bitmap = CalculateFieldOffsets();
if (ptr() == isolate_group->class_table()->At(id())) {
// Sets the new size in the class table.
isolate_group->class_table()->SetAt(id(), ptr());
if (FLAG_precompiled_mode && !ClassTable::IsTopLevelCid(id())) {
isolate_group->shared_class_table()->SetUnboxedFieldsMapAt(id(),
host_bitmap);
}
}
}
#if defined(DEBUG)
if (is_const()) {
// Double-check that all fields are final (CFE should guarantee that if it
// marks the class as having a constant constructor).
auto Z = thread->zone();
const auto& super_class = Class::Handle(Z, SuperClass());
ASSERT(super_class.IsNull() || super_class.is_const());
const auto& fields = Array::Handle(Z, this->fields());
auto& field = Field::Handle(Z);
for (intptr_t i = 0; i < fields.Length(); ++i) {
field ^= fields.At(i);
ASSERT(field.is_static() || field.is_final());
}
}
#endif
set_is_finalized();
}
class CHACodeArray : public WeakCodeReferences {
public:
explicit CHACodeArray(const Class& cls)
: WeakCodeReferences(Array::Handle(cls.dependent_code())), cls_(cls) {}
virtual void UpdateArrayTo(const Array& value) {
// TODO(fschneider): Fails for classes in the VM isolate.
cls_.set_dependent_code(value);
}
virtual void ReportDeoptimization(const Code& code) {
if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) {
Function& function = Function::Handle(code.function());
THR_Print("Deoptimizing %s because CHA optimized (%s).\n",
function.ToFullyQualifiedCString(), cls_.ToCString());
}
}
virtual void ReportSwitchingCode(const Code& code) {
if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) {
Function& function = Function::Handle(code.function());
THR_Print(
"Switching %s to unoptimized code because CHA invalid"
" (%s)\n",
function.ToFullyQualifiedCString(), cls_.ToCString());
}
}
private:
const Class& cls_;
DISALLOW_COPY_AND_ASSIGN(CHACodeArray);
};
#if defined(DEBUG)
static bool IsMutatorOrAtSafepoint() {
Thread* thread = Thread::Current();
return thread->IsMutatorThread() || thread->IsAtSafepoint();
}
#endif
void Class::RegisterCHACode(const Code& code) {
if (FLAG_trace_cha) {
THR_Print("RegisterCHACode '%s' depends on class '%s'\n",
Function::Handle(code.function()).ToQualifiedCString(),
ToCString());
}
DEBUG_ASSERT(IsMutatorOrAtSafepoint());
ASSERT(code.is_optimized());
CHACodeArray a(*this);
a.Register(code);
}
void Class::DisableCHAOptimizedCode(const Class& subclass) {
DEBUG_ASSERT(
IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
CHACodeArray a(*this);
if (FLAG_trace_deoptimization && a.HasCodes()) {
if (subclass.IsNull()) {
THR_Print("Deopt for CHA (all)\n");
} else {
THR_Print("Deopt for CHA (new subclass %s)\n", subclass.ToCString());
}
}
a.DisableCode();
}
void Class::DisableAllCHAOptimizedCode() {
DisableCHAOptimizedCode(Class::Handle());
}
bool Class::TraceAllocation(IsolateGroup* isolate_group) const {
#ifndef PRODUCT
auto class_table = isolate_group->shared_class_table();
return class_table->TraceAllocationFor(id());
#else
return false;
#endif
}
void Class::SetTraceAllocation(bool trace_allocation) const {
#ifndef PRODUCT
auto isolate_group = IsolateGroup::Current();
const bool changed = trace_allocation != this->TraceAllocation(isolate_group);
if (changed) {
auto class_table = isolate_group->shared_class_table();
class_table->SetTraceAllocationFor(id(), trace_allocation);
DisableAllocationStub();
}
#else
UNREACHABLE();
#endif
}
ArrayPtr Class::dependent_code() const {
DEBUG_ASSERT(
IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
return untag()->dependent_code();
}
void Class::set_dependent_code(const Array& array) const {
DEBUG_ASSERT(
IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
untag()->set_dependent_code(array.ptr());
}
// Conventions:
// * For throwing a NSM in a class klass we use its runtime type as receiver,
// i.e., klass.RareType().
// * For throwing a NSM in a library, we just pass the null instance as
// receiver.
static ObjectPtr ThrowNoSuchMethod(const Instance& receiver,
const String& function_name,
const Array& arguments,
const Array& argument_names,
const InvocationMirror::Level level,
const InvocationMirror::Kind kind) {
const Smi& invocation_type =
Smi::Handle(Smi::New(InvocationMirror::EncodeType(level, kind)));
const Array& args = Array::Handle(Array::New(7));
args.SetAt(0, receiver);
args.SetAt(1, function_name);
args.SetAt(2, invocation_type);
args.SetAt(3, Object::smi_zero()); // Type arguments length.
args.SetAt(4, Object::null_type_arguments());
args.SetAt(5, arguments);
args.SetAt(6, argument_names);
const Library& libcore = Library::Handle(Library::CoreLibrary());
const Class& cls =
Class::Handle(libcore.LookupClass(Symbols::NoSuchMethodError()));
ASSERT(!cls.IsNull());
const auto& error = cls.EnsureIsFinalized(Thread::Current());
ASSERT(error == Error::null());
const Function& throwNew =
Function::Handle(cls.LookupFunctionAllowPrivate(Symbols::ThrowNew()));
return DartEntry::InvokeFunction(throwNew, args);
}
static ObjectPtr ThrowTypeError(const TokenPosition token_pos,
const Instance& src_value,
const AbstractType& dst_type,
const String& dst_name) {
const Array& args = Array::Handle(Array::New(4));
const Smi& pos = Smi::Handle(Smi::New(token_pos.Serialize()));
args.SetAt(0, pos);
args.SetAt(1, src_value);
args.SetAt(2, dst_type);
args.SetAt(3, dst_name);
const Library& libcore = Library::Handle(Library::CoreLibrary());
const Class& cls =
Class::Handle(libcore.LookupClassAllowPrivate(Symbols::TypeError()));
const auto& error = cls.EnsureIsFinalized(Thread::Current());
ASSERT(error == Error::null());
const Function& throwNew =
Function::Handle(cls.LookupFunctionAllowPrivate(Symbols::ThrowNew()));
return DartEntry::InvokeFunction(throwNew, args);
}
ObjectPtr Class::InvokeGetter(const String& getter_name,
bool throw_nsm_if_absent,
bool respect_reflectable,
bool check_is_entrypoint) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
CHECK_ERROR(EnsureIsFinalized(thread));
// Note static fields do not have implicit getters.
const Field& field = Field::Handle(zone, LookupStaticField(getter_name));
if (!field.IsNull() && check_is_entrypoint) {
CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
}
if (field.IsNull() || field.IsUninitialized()) {
const String& internal_getter_name =
String::Handle(zone, Field::GetterName(getter_name));
Function& getter =
Function::Handle(zone, LookupStaticFunction(internal_getter_name));
if (field.IsNull() && !getter.IsNull() && check_is_entrypoint) {
CHECK_ERROR(getter.VerifyCallEntryPoint());
}
if (getter.IsNull() || (respect_reflectable && !getter.is_reflectable())) {
if (getter.IsNull()) {
getter = LookupStaticFunction(getter_name);
if (!getter.IsNull()) {
if (check_is_entrypoint) {
CHECK_ERROR(getter.VerifyClosurizedEntryPoint());
}
if (getter.SafeToClosurize()) {
// Looking for a getter but found a regular method: closurize it.
const Function& closure_function =
Function::Handle(zone, getter.ImplicitClosureFunction());
return closure_function.ImplicitStaticClosure();
}
}
}
if (throw_nsm_if_absent) {
return ThrowNoSuchMethod(
AbstractType::Handle(zone, RareType()), getter_name,
Object::null_array(), Object::null_array(),
InvocationMirror::kStatic, InvocationMirror::kGetter);
}
// Fall through case: Indicate that we didn't find any function or field
// using a special null instance. This is different from a field being
// null. Callers make sure that this null does not leak into Dartland.
return Object::sentinel().ptr();
}
// Invoke the getter and return the result.
return DartEntry::InvokeFunction(getter, Object::empty_array());
}
return field.StaticValue();
}
ObjectPtr Class::InvokeSetter(const String& setter_name,
const Instance& value,
bool respect_reflectable,
bool check_is_entrypoint) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
CHECK_ERROR(EnsureIsFinalized(thread));
// Check for real fields and user-defined setters.
const Field& field = Field::Handle(zone, LookupStaticField(setter_name));
const String& internal_setter_name =
String::Handle(zone, Field::SetterName(setter_name));
if (!field.IsNull() && check_is_entrypoint) {
CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kSetterOnly));
}
AbstractType& parameter_type = AbstractType::Handle(zone);
if (field.IsNull()) {
const Function& setter =
Function::Handle(zone, LookupStaticFunction(internal_setter_name));
if (!setter.IsNull() && check_is_entrypoint) {
CHECK_ERROR(setter.VerifyCallEntryPoint());
}
const int kNumArgs = 1;
const Array& args = Array::Handle(zone, Array::New(kNumArgs));
args.SetAt(0, value);
if (setter.IsNull() || (respect_reflectable && !setter.is_reflectable())) {
return ThrowNoSuchMethod(AbstractType::Handle(zone, RareType()),
internal_setter_name, args, Object::null_array(),
InvocationMirror::kStatic,
InvocationMirror::kSetter);
}
parameter_type = setter.ParameterTypeAt(0);
if (!value.RuntimeTypeIsSubtypeOf(parameter_type,
Object::null_type_arguments(),
Object::null_type_arguments())) {
const String& argument_name =
String::Handle(zone, setter.ParameterNameAt(0));
return ThrowTypeError(setter.token_pos(), value, parameter_type,
argument_name);
}
// Invoke the setter and return the result.
return DartEntry::InvokeFunction(setter, args);
}
if (field.is_final() || (respect_reflectable && !field.is_reflectable())) {
const int kNumArgs = 1;
const Array& args = Array::Handle(zone, Array::New(kNumArgs));
args.SetAt(0, value);
return ThrowNoSuchMethod(AbstractType::Handle(zone, RareType()),
internal_setter_name, args, Object::null_array(),
InvocationMirror::kStatic,
InvocationMirror::kSetter);
}
parameter_type = field.type();
if (!value.RuntimeTypeIsSubtypeOf(parameter_type,
Object::null_type_arguments(),
Object::null_type_arguments())) {
const String& argument_name = String::Handle(zone, field.name());
return ThrowTypeError(field.token_pos(), value, parameter_type,
argument_name);
}
field.SetStaticValue(value);
return value.ptr();
}
// Creates a new array of boxed arguments suitable for invoking the callable
// from the original boxed arguments for a static call. Also sets the contents
// of the handle pointed to by [callable_args_desc_array_out] to an appropriate
// arguments descriptor array for the new arguments.
//
// Assumes [arg_names] are consistent with [static_args_descriptor].
static ArrayPtr CreateCallableArgumentsFromStatic(
Zone* zone,
const Instance& receiver,
const Array& static_args,
const Array& arg_names,
const ArgumentsDescriptor& static_args_descriptor) {
const intptr_t num_static_type_args = static_args_descriptor.TypeArgsLen();
const intptr_t num_static_args = static_args_descriptor.Count();
// Double check that the static args descriptor expects boxed arguments
// and the static args descriptor is consistent with the static arguments.
ASSERT_EQUAL(static_args_descriptor.Size(), num_static_args);
ASSERT_EQUAL(static_args.Length(),
num_static_args + (num_static_type_args > 0 ? 1 : 0));
// Add an additional slot to store the callable as the receiver.
const auto& callable_args =
Array::Handle(zone, Array::New(static_args.Length() + 1));
const intptr_t first_arg_index = static_args_descriptor.FirstArgIndex();
auto& temp = Object::Handle(zone);
// Copy the static args into the corresponding slots of the callable args.
if (num_static_type_args > 0) {
temp = static_args.At(0);
callable_args.SetAt(0, temp);
}
for (intptr_t i = first_arg_index; i < static_args.Length(); i++) {
temp = static_args.At(i);
callable_args.SetAt(i + 1, temp);
}
// Set the receiver slot in the callable args.
callable_args.SetAt(first_arg_index, receiver);
return callable_args.ptr();
}
ObjectPtr Class::Invoke(const String& function_name,
const Array& args,
const Array& arg_names,
bool respect_reflectable,
bool check_is_entrypoint) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
CHECK_ERROR(EnsureIsFinalized(thread));
// We don't pass any explicit type arguments, which will be understood as
// using dynamic for any function type arguments by lower layers.
const int kTypeArgsLen = 0;
const Array& args_descriptor_array = Array::Handle(
zone, ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(),
arg_names, Heap::kNew));
ArgumentsDescriptor args_descriptor(args_descriptor_array);
Function& function =
Function::Handle(zone, LookupStaticFunction(function_name));
if (!function.IsNull() && check_is_entrypoint) {
CHECK_ERROR(function.VerifyCallEntryPoint());
}
if (function.IsNull()) {
// Didn't find a method: try to find a getter and invoke call on its result.
const Object& getter_result = Object::Handle(
zone, InvokeGetter(function_name, false, respect_reflectable,
check_is_entrypoint));
if (getter_result.ptr() != Object::sentinel().ptr()) {
if (check_is_entrypoint) {
CHECK_ERROR(EntryPointFieldInvocationError(function_name));
}
const auto& call_args_descriptor_array = Array::Handle(
zone, ArgumentsDescriptor::NewBoxed(args_descriptor.TypeArgsLen(),
args_descriptor.Count() + 1,
arg_names, Heap::kNew));
const auto& call_args = Array::Handle(
zone,
CreateCallableArgumentsFromStatic(zone, Instance::Cast(getter_result),
args, arg_names, args_descriptor));
return DartEntry::InvokeClosure(thread, call_args,
call_args_descriptor_array);
}
}
if (function.IsNull() ||
!function.AreValidArguments(args_descriptor, nullptr) ||
(respect_reflectable && !function.is_reflectable())) {
return ThrowNoSuchMethod(
AbstractType::Handle(zone, RareType()), function_name, args, arg_names,
InvocationMirror::kStatic, InvocationMirror::kMethod);
}
// This is a static function, so we pass an empty instantiator tav.
ASSERT(function.is_static());
ObjectPtr type_error = function.DoArgumentTypesMatch(
args, args_descriptor, Object::empty_type_arguments());
if (type_error != Error::null()) {
return type_error;
}
return DartEntry::InvokeFunction(function, args, args_descriptor_array);
}
static ObjectPtr EvaluateCompiledExpressionHelper(
const ExternalTypedData& kernel_buffer,
const Array& type_definitions,
const String& library_url,
const String& klass,
const Array& arguments,
const TypeArguments& type_arguments);
ObjectPtr Class::EvaluateCompiledExpression(
const ExternalTypedData& kernel_buffer,
const Array& type_definitions,
const Array& arguments,
const TypeArguments& type_arguments) const {
ASSERT(Thread::Current()->IsMutatorThread());
if (id() < kInstanceCid || id() == kTypeArgumentsCid) {
const Instance& exception = Instance::Handle(String::New(
"Expressions can be evaluated only with regular Dart instances"));
const Instance& stacktrace = Instance::Handle();
return UnhandledException::New(exception, stacktrace);
}
return EvaluateCompiledExpressionHelper(
kernel_buffer, type_definitions,
String::Handle(Library::Handle(library()).url()),
IsTopLevel() ? String::Handle() : String::Handle(UserVisibleName()),
arguments, type_arguments);
}
void Class::EnsureDeclarationLoaded() const {
if (!is_declaration_loaded()) {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
FATAL1("Unable to use class %s which is not loaded yet.", ToCString());
#endif
}
}
// Ensure that top level parsing of the class has been done.
ErrorPtr Class::EnsureIsFinalized(Thread* thread) const {
ASSERT(!IsNull());
if (is_finalized()) {
return Error::null();
}
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
if (is_finalized()) {
return Error::null();
}
LeaveCompilerScope ncs(thread);
ASSERT(thread != NULL);
const Error& error =
Error::Handle(thread->zone(), ClassFinalizer::LoadClassMembers(*this));
if (!error.IsNull()) {
ASSERT(thread == Thread::Current());
if (thread->long_jump_base() != NULL) {
Report::LongJump(error);
UNREACHABLE();
}
}
return error.ptr();
}
// Ensure that code outdated by finalized class is cleaned up, new instance of
// this class is ready to be allocated.
ErrorPtr Class::EnsureIsAllocateFinalized(Thread* thread) const {
ASSERT(!IsNull());
if (is_allocate_finalized()) {
return Error::null();
}
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
if (is_allocate_finalized()) {
return Error::null();
}
ASSERT(thread != NULL);
Error& error = Error::Handle(thread->zone(), EnsureIsFinalized(thread));
if (!error.IsNull()) {
ASSERT(thread == Thread::Current());
if (thread->long_jump_base() != NULL) {
Report::LongJump(error);
UNREACHABLE();
}
}
// May be allocate-finalized recursively during EnsureIsFinalized.
if (is_allocate_finalized()) {
return Error::null();
}
error ^= ClassFinalizer::AllocateFinalizeClass(*this);
return error.ptr();
}
void Class::SetFields(const Array& value) const {
ASSERT(!value.IsNull());
#if defined(DEBUG)
Thread* thread = Thread::Current();
ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
// Verify that all the fields in the array have this class as owner.
Field& field = Field::Handle();
intptr_t len = value.Length();
for (intptr_t i = 0; i < len; i++) {
field ^= value.At(i);
ASSERT(field.IsOriginal());
ASSERT(field.Owner() == ptr());
}
#endif
// The value of static fields is already initialized to null.
set_fields(value);
}
void Class::AddField(const Field& field) const {
#if defined(DEBUG)
Thread* thread = Thread::Current();
ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
#endif
const Array& arr = Array::Handle(fields());
const Array& new_arr = Array::Handle(Array::Grow(arr, arr.Length() + 1));
new_arr.SetAt(arr.Length(), field);
SetFields(new_arr);
}
void Class::AddFields(const GrowableArray<const Field*>& new_fields) const {
#if defined(DEBUG)
Thread* thread = Thread::Current();
ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
#endif
const intptr_t num_new_fields = new_fields.length();
if (num_new_fields == 0) return;
const Array& arr = Array::Handle(fields());
const intptr_t num_old_fields = arr.Length();
const Array& new_arr = Array::Handle(
Array::Grow(arr, num_old_fields + num_new_fields, Heap::kOld));
for (intptr_t i = 0; i < num_new_fields; i++) {
new_arr.SetAt(i + num_old_fields, *new_fields.At(i));
}
SetFields(new_arr);
}
bool Class::InjectCIDFields() const {
if (library() != Library::InternalLibrary() ||
Name() != Symbols::ClassID().ptr()) {
return false;
}
auto thread = Thread::Current();
auto isolate_group = thread->isolate_group();
auto zone = thread->zone();
Field& field = Field::Handle(zone);
Smi& value = Smi::Handle(zone);
String& field_name = String::Handle(zone);
static const struct {
const char* const field_name;
const intptr_t cid;
} cid_fields[] = {
#define CLASS_LIST_WITH_NULL(V) \
V(Null) \
CLASS_LIST_NO_OBJECT(V)
#define ADD_SET_FIELD(clazz) {"cid" #clazz, k##clazz##Cid},
CLASS_LIST_WITH_NULL(ADD_SET_FIELD)
#undef ADD_SET_FIELD
#define ADD_SET_FIELD(clazz) {"cid" #clazz "View", kTypedData##clazz##ViewCid},
CLASS_LIST_TYPED_DATA(ADD_SET_FIELD)
#undef ADD_SET_FIELD
#define ADD_SET_FIELD(clazz) {"cid" #clazz, kTypedData##clazz##Cid},
CLASS_LIST_TYPED_DATA(ADD_SET_FIELD)
#undef ADD_SET_FIELD
#define ADD_SET_FIELD(clazz) \
{"cidExternal" #clazz, kExternalTypedData##clazz##Cid},
CLASS_LIST_TYPED_DATA(ADD_SET_FIELD)
#undef ADD_SET_FIELD
#undef CLASS_LIST_WITH_NULL
};
const AbstractType& field_type = Type::Handle(zone, Type::IntType());
for (size_t i = 0; i < ARRAY_SIZE(cid_fields); i++) {
field_name = Symbols::New(thread, cid_fields[i].field_name);
field = Field::New(field_name, /* is_static = */ true,
/* is_final = */ false,
/* is_const = */ true,
/* is_reflectable = */ false,
/* is_late = */ false, *this, field_type,
TokenPosition::kMinSource, TokenPosition::kMinSource);
value = Smi::New(cid_fields[i].cid);
isolate_group->RegisterStaticField(field, value);
AddField(field);
}
return true;
}
template <class FakeInstance, class TargetFakeInstance>
ClassPtr Class::NewCommon(intptr_t index) {
ASSERT(Object::class_class() != Class::null());
Class& result = Class::Handle();
{
ObjectPtr raw = Object::Allocate(Class::kClassId, Class::InstanceSize(),
Heap::kOld, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
// Here kIllegalCid means not-yet-assigned.
Object::VerifyBuiltinVtable<FakeInstance>(index == kIllegalCid ? kInstanceCid
: index);
result.set_token_pos(TokenPosition::kNoSource);
result.set_end_token_pos(TokenPosition::kNoSource);
const intptr_t host_instance_size = FakeInstance::InstanceSize();
const intptr_t target_instance_size = compiler::target::RoundedAllocationSize(
TargetFakeInstance::InstanceSize());
result.set_instance_size(host_instance_size, target_instance_size);
result.set_type_arguments_field_offset_in_words(kNoTypeArguments,
RTN::Class::kNoTypeArguments);
const intptr_t host_next_field_offset = FakeInstance::NextFieldOffset();
const intptr_t target_next_field_offset =
TargetFakeInstance::NextFieldOffset();
result.set_next_field_offset(host_next_field_offset,
target_next_field_offset);
result.set_id(index);
result.set_num_type_arguments_unsafe(kUnknownNumTypeArguments);
result.set_num_native_fields(0);
result.set_state_bits(0);
NOT_IN_PRECOMPILED(result.set_kernel_offset(0));
result.InitEmptyFields();
return result.ptr();
}
template <class FakeInstance, class TargetFakeInstance>
ClassPtr Class::New(intptr_t index,
IsolateGroup* isolate_group,
bool register_class,
bool is_abstract) {
Class& result =
Class::Handle(NewCommon<FakeInstance, TargetFakeInstance>(index));
if (is_abstract) {
result.set_is_abstract();
}
if (register_class) {
isolate_group->class_table()->Register(result);
}
return result.ptr();
}
ClassPtr Class::New(const Library& lib,
const String& name,
const Script& script,
TokenPosition token_pos,
bool register_class) {
Class& result =
Class::Handle(NewCommon<Instance, RTN::Instance>(kIllegalCid));
result.set_library(lib);
result.set_name(name);
result.set_script(script);
result.set_token_pos(token_pos);
// The size gets initialized to 0. Once the class gets finalized the class
// finalizer will set the correct size.
ASSERT(!result.is_finalized() && !result.is_prefinalized());
result.set_instance_size_in_words(0, 0);
if (register_class) {
IsolateGroup::Current()->RegisterClass(result);
}
return result.ptr();
}
ClassPtr Class::NewInstanceClass() {
return Class::New<Instance, RTN::Instance>(kIllegalCid,
IsolateGroup::Current());
}
ClassPtr Class::NewNativeWrapper(const Library& library,
const String& name,
int field_count) {
Class& cls = Class::Handle(library.LookupClass(name));
if (cls.IsNull()) {
cls = New(library, name, Script::Handle(), TokenPosition::kNoSource);
cls.SetFields(Object::empty_array());
cls.SetFunctions(Object::empty_array());
// Set super class to Object.
cls.set_super_type(Type::Handle(Type::ObjectType()));
// Compute instance size. First word contains a pointer to a properly
// sized typed array once the first native field has been set.
const intptr_t host_instance_size = sizeof(UntaggedInstance) + kWordSize;
#if defined(DART_PRECOMPILER)
const intptr_t target_instance_size =
compiler::target::Instance::InstanceSize() +
compiler::target::kWordSize;
#else
const intptr_t target_instance_size =
sizeof(UntaggedInstance) + compiler::target::kWordSize;
#endif
cls.set_instance_size(
RoundedAllocationSize(host_instance_size),
compiler::target::RoundedAllocationSize(target_instance_size));
cls.set_next_field_offset(host_instance_size, target_instance_size);
cls.set_num_native_fields(field_count);
cls.set_is_allocate_finalized();
// The signature of the constructor yet to be added to this class will have
// to be finalized explicitly, since the class is prematurely marked as
// 'is_allocate_finalized' and finalization of member types will not occur.
cls.set_is_declaration_loaded();
cls.set_is_type_finalized();
cls.set_is_synthesized_class();
library.AddClass(cls);
return cls.ptr();
} else {
return Class::null();
}
}
ClassPtr Class::NewStringClass(intptr_t class_id, IsolateGroup* isolate_group) {
intptr_t host_instance_size, target_instance_size;
if (class_id == kOneByteStringCid) {
host_instance_size = OneByteString::InstanceSize();
target_instance_size = compiler::target::RoundedAllocationSize(
RTN::OneByteString::InstanceSize());
} else if (class_id == kTwoByteStringCid) {
host_instance_size = TwoByteString::InstanceSize();
target_instance_size = compiler::target::RoundedAllocationSize(
RTN::TwoByteString::InstanceSize());
} else if (class_id == kExternalOneByteStringCid) {
host_instance_size = ExternalOneByteString::InstanceSize();
target_instance_size = compiler::target::RoundedAllocationSize(
RTN::ExternalOneByteString::InstanceSize());
} else {
ASSERT(class_id == kExternalTwoByteStringCid);
host_instance_size = ExternalTwoByteString::InstanceSize();
target_instance_size = compiler::target::RoundedAllocationSize(
RTN::ExternalTwoByteString::InstanceSize());
}
Class& result = Class::Handle(New<String, RTN::String>(
class_id, isolate_group, /*register_class=*/false));
result.set_instance_size(host_instance_size, target_instance_size);
const intptr_t host_next_field_offset = String::NextFieldOffset();
const intptr_t target_next_field_offset = RTN::String::NextFieldOffset();
result.set_next_field_offset(host_next_field_offset,
target_next_field_offset);
result.set_is_prefinalized();
isolate_group->class_table()->Register(result);
return result.ptr();
}
ClassPtr Class::NewTypedDataClass(intptr_t class_id,
IsolateGroup* isolate_group) {
ASSERT(IsTypedDataClassId(class_id));
const intptr_t host_instance_size = TypedData::InstanceSize();
const intptr_t target_instance_size =
compiler::target::RoundedAllocationSize(RTN::TypedData::InstanceSize());
Class& result = Class::Handle(New<TypedData, RTN::TypedData>(
class_id, isolate_group, /*register_class=*/false));
result.set_instance_size(host_instance_size, target_instance_size);
const intptr_t host_next_field_offset = TypedData::NextFieldOffset();
const intptr_t target_next_field_offset = RTN::TypedData::NextFieldOffset();
result.set_next_field_offset(host_next_field_offset,
target_next_field_offset);
result.set_is_prefinalized();
isolate_group->class_table()->Register(result);
return result.ptr();
}
ClassPtr Class::NewTypedDataViewClass(intptr_t class_id,
IsolateGroup* isolate_group) {
ASSERT(IsTypedDataViewClassId(class_id));
const intptr_t host_instance_size = TypedDataView::InstanceSize();
const intptr_t target_instance_size = compiler::target::RoundedAllocationSize(
RTN::TypedDataView::InstanceSize());
Class& result = Class::Handle(New<TypedDataView, RTN::TypedDataView>(
class_id, isolate_group, /*register_class=*/false));
result.set_instance_size(host_instance_size, target_instance_size);
const intptr_t host_next_field_offset = TypedDataView::NextFieldOffset();
const intptr_t target_next_field_offset =
RTN::TypedDataView::NextFieldOffset();
result.set_next_field_offset(host_next_field_offset,
target_next_field_offset);
result.set_is_prefinalized();
isolate_group->class_table()->Register(result);
return result.ptr();
}
ClassPtr Class::NewExternalTypedDataClass(intptr_t class_id,
IsolateGroup* isolate_group) {
ASSERT(IsExternalTypedDataClassId(class_id));
const intptr_t host_instance_size = ExternalTypedData::InstanceSize();
const intptr_t target_instance_size = compiler::target::RoundedAllocationSize(
RTN::ExternalTypedData::InstanceSize());
Class& result = Class::Handle(New<ExternalTypedData, RTN::ExternalTypedData>(
class_id, isolate_group, /*register_class=*/false));
const intptr_t host_next_field_offset = ExternalTypedData::NextFieldOffset();
const intptr_t target_next_field_offset =
RTN::ExternalTypedData::NextFieldOffset();
result.set_instance_size(host_instance_size, target_instance_size);
result.set_next_field_offset(host_next_field_offset,
target_next_field_offset);
result.set_is_prefinalized();
isolate_group->class_table()->Register(result);
return result.ptr();
}
ClassPtr Class::NewPointerClass(intptr_t class_id,
IsolateGroup* isolate_group) {
ASSERT(IsFfiPointerClassId(class_id));
intptr_t host_instance_size = Pointer::InstanceSize();
intptr_t target_instance_size =
compiler::target::RoundedAllocationSize(RTN::Pointer::InstanceSize());
Class& result = Class::Handle(New<Pointer, RTN::Pointer>(
class_id, isolate_group, /*register_class=*/false));
result.set_instance_size(host_instance_size, target_instance_size);
result.set_type_arguments_field_offset(Pointer::type_arguments_offset(),
RTN::Pointer::type_arguments_offset());
const intptr_t host_next_field_offset = Pointer::NextFieldOffset();
const intptr_t target_next_field_offset = RTN::Pointer::NextFieldOffset();
result.set_next_field_offset(host_next_field_offset,
target_next_field_offset);
result.set_is_prefinalized();
isolate_group->class_table()->Register(result);
return result.ptr();
}
void Class::set_name(const String& value) const {
ASSERT(untag()->name() == String::null());
ASSERT(value.IsSymbol());
untag()->set_name(value.ptr());
#if !defined(PRODUCT)
if (untag()->user_name() == String::null()) {
// TODO(johnmccutchan): Eagerly set user name for VM isolate classes,
// lazily set user name for the other classes.
// Generate and set user_name.
const String& user_name = String::Handle(
Symbols::New(Thread::Current(), GenerateUserVisibleName()));
set_user_name(user_name);
}
#endif // !defined(PRODUCT)
}
#if !defined(PRODUCT)
void Class::set_user_name(const String& value) const {
untag()->set_user_name(value.ptr());
}
#endif // !defined(PRODUCT)
const char* Class::GenerateUserVisibleName() const {
if (FLAG_show_internal_names) {
return String::Handle(Name()).ToCString();
}
switch (id()) {
case kFloat32x4Cid:
return Symbols::Float32x4().ToCString();
case kInt32x4Cid:
return Symbols::Int32x4().ToCString();
case kTypedDataInt8ArrayCid:
case kExternalTypedDataInt8ArrayCid:
return Symbols::Int8List().ToCString();
case kTypedDataUint8ArrayCid:
case kExternalTypedDataUint8ArrayCid:
return Symbols::Uint8List().ToCString();
case kTypedDataUint8ClampedArrayCid:
case kExternalTypedDataUint8ClampedArrayCid:
return Symbols::Uint8ClampedList().ToCString();
case kTypedDataInt16ArrayCid:
case kExternalTypedDataInt16ArrayCid:
return Symbols::Int16List().ToCString();
case kTypedDataUint16ArrayCid:
case kExternalTypedDataUint16ArrayCid:
return Symbols::Uint16List().ToCString();
case kTypedDataInt32ArrayCid:
case kExternalTypedDataInt32ArrayCid:
return Symbols::Int32List().ToCString();
case kTypedDataUint32ArrayCid:
case kExternalTypedDataUint32ArrayCid:
return Symbols::Uint32List().ToCString();
case kTypedDataInt64ArrayCid:
case kExternalTypedDataInt64ArrayCid:
return Symbols::Int64List().ToCString();
case kTypedDataUint64ArrayCid:
case kExternalTypedDataUint64ArrayCid:
return Symbols::Uint64List().ToCString();
case kTypedDataInt32x4ArrayCid:
case kExternalTypedDataInt32x4ArrayCid:
return Symbols::Int32x4List().ToCString();
case kTypedDataFloat32x4ArrayCid:
case kExternalTypedDataFloat32x4ArrayCid:
return Symbols::Float32x4List().ToCString();
case kTypedDataFloat64x2ArrayCid:
case kExternalTypedDataFloat64x2ArrayCid:
return Symbols::Float64x2List().ToCString();
case kTypedDataFloat32ArrayCid:
case kExternalTypedDataFloat32ArrayCid:
return Symbols::Float32List().ToCString();
case kTypedDataFloat64ArrayCid:
case kExternalTypedDataFloat64ArrayCid:
return Symbols::Float64List().ToCString();
case kFfiPointerCid:
return Symbols::FfiPointer().ToCString();
case kFfiDynamicLibraryCid:
return Symbols::FfiDynamicLibrary().ToCString();
#if !defined(PRODUCT)
case kNullCid:
return Symbols::Null().ToCString();
case kDynamicCid:
return Symbols::Dynamic().ToCString();
case kVoidCid:
return Symbols::Void().ToCString();
case kNeverCid:
return Symbols::Never().ToCString();
case kClassCid:
return Symbols::Class().ToCString();
case kTypeArgumentsCid:
return Symbols::TypeArguments().ToCString();
case kPatchClassCid:
return Symbols::PatchClass().ToCString();
case kFunctionCid:
return Symbols::Function().ToCString();
case kClosureDataCid:
return Symbols::ClosureData().ToCString();
case kFfiTrampolineDataCid:
return Symbols::FfiTrampolineData().ToCString();
case kFieldCid:
return Symbols::Field().ToCString();
case kScriptCid:
return Symbols::Script().ToCString();
case kLibraryCid:
return Symbols::Library().ToCString();
case kLibraryPrefixCid:
return Symbols::LibraryPrefix().ToCString();
case kNamespaceCid:
return Symbols::Namespace().ToCString();
case kKernelProgramInfoCid:
return Symbols::KernelProgramInfo().ToCString();
case kWeakSerializationReferenceCid:
return Symbols::WeakSerializationReference().ToCString();
case kCodeCid:
return Symbols::Code().ToCString();
case kInstructionsCid:
return Symbols::Instructions().ToCString();
case kInstructionsSectionCid:
return Symbols::InstructionsSection().ToCString();
case kObjectPoolCid:
return Symbols::ObjectPool().ToCString();
case kCodeSourceMapCid:
return Symbols::CodeSourceMap().ToCString();
case kPcDescriptorsCid:
return Symbols::PcDescriptors().ToCString();
case kCompressedStackMapsCid:
return Symbols::CompressedStackMaps().ToCString();
case kLocalVarDescriptorsCid:
return Symbols::LocalVarDescriptors().ToCString();
case kExceptionHandlersCid:
return Symbols::ExceptionHandlers().ToCString();
case kContextCid:
return Symbols::Context().ToCString();
case kContextScopeCid:
return Symbols::ContextScope().ToCString();
case kSingleTargetCacheCid:
return Symbols::SingleTargetCache().ToCString();
case kICDataCid:
return Symbols::ICData().ToCString();
case kMegamorphicCacheCid:
return Symbols::MegamorphicCache().ToCString();
case kSubtypeTestCacheCid:
return Symbols::SubtypeTestCache().ToCString();
case kLoadingUnitCid:
return Symbols::LoadingUnit().ToCString();
case kApiErrorCid:
return Symbols::ApiError().ToCString();
case kLanguageErrorCid:
return Symbols::LanguageError().ToCString();
case kUnhandledExceptionCid:
return Symbols::UnhandledException().ToCString();
case kUnwindErrorCid:
return Symbols::UnwindError().ToCString();
case kIntegerCid:
case kSmiCid:
case kMintCid:
return Symbols::Int().ToCString();
case kDoubleCid:
return Symbols::Double().ToCString();
case kOneByteStringCid:
case kTwoByteStringCid:
case kExternalOneByteStringCid:
case kExternalTwoByteStringCid:
return Symbols::_String().ToCString();
case kArrayCid:
case kImmutableArrayCid:
case kGrowableObjectArrayCid:
return Symbols::List().ToCString();
#endif // !defined(PRODUCT)
}
String& name = String::Handle(Name());
name = Symbols::New(Thread::Current(), String::ScrubName(name));
if (name.ptr() == Symbols::FutureImpl().ptr() &&
library() == Library::AsyncLibrary()) {
return Symbols::Future().ToCString();
}
return name.ToCString();
}
void Class::set_script(const Script& value) const {
untag()->set_script(value.ptr());
}
void Class::set_token_pos(TokenPosition token_pos) const {
ASSERT(!token_pos.IsClassifying());
StoreNonPointer(&untag()->token_pos_, token_pos);
}
void Class::set_end_token_pos(TokenPosition token_pos) const {
ASSERT(!token_pos.IsClassifying());
StoreNonPointer(&untag()->end_token_pos_, token_pos);
}
int32_t Class::SourceFingerprint() const {
#if !defined(DART_PRECOMPILED_RUNTIME)
return kernel::KernelSourceFingerprintHelper::CalculateClassFingerprint(
*this);
#else
return 0;
#endif // !defined(DART_PRECOMPILED_RUNTIME)
}
void Class::set_is_implemented() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
set_is_implemented_unsafe();
}
void Class::set_is_implemented_unsafe() const {
set_state_bits(ImplementedBit::update(true, state_bits()));
}
void Class::set_is_abstract() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
set_state_bits(AbstractBit::update(true, state_bits()));
}
void Class::set_is_declaration_loaded() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
set_is_declaration_loaded_unsafe();
}
void Class::set_is_declaration_loaded_unsafe() const {
ASSERT(!is_declaration_loaded());
set_state_bits(ClassLoadingBits::update(UntaggedClass::kDeclarationLoaded,
state_bits()));
}
void Class::set_is_type_finalized() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
ASSERT(is_declaration_loaded());
ASSERT(!is_type_finalized());
set_state_bits(
ClassLoadingBits::update(UntaggedClass::kTypeFinalized, state_bits()));
}
void Class::set_is_synthesized_class() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
set_is_synthesized_class_unsafe();
}
void Class::set_is_synthesized_class_unsafe() const {
set_state_bits(SynthesizedClassBit::update(true, state_bits()));
}
void Class::set_is_enum_class() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
set_state_bits(EnumBit::update(true, state_bits()));
}
void Class::set_is_const() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
set_state_bits(ConstBit::update(true, state_bits()));
}
void Class::set_is_transformed_mixin_application() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
set_state_bits(TransformedMixinApplicationBit::update(true, state_bits()));
}
void Class::set_is_fields_marked_nullable() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
set_state_bits(FieldsMarkedNullableBit::update(true, state_bits()));
}
void Class::set_is_allocated(bool value) const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
set_is_allocated_unsafe(value);
}
void Class::set_is_allocated_unsafe(bool value) const {
set_state_bits(IsAllocatedBit::update(value, state_bits()));
}
void Class::set_is_loaded(bool value) const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
set_state_bits(IsLoadedBit::update(value, state_bits()));
}
void Class::set_is_finalized() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
ASSERT(!is_finalized());
set_state_bits(
ClassFinalizedBits::update(UntaggedClass::kFinalized, state_bits()));
}
void Class::set_is_allocate_finalized() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
ASSERT(!is_allocate_finalized());
set_state_bits(ClassFinalizedBits::update(UntaggedClass::kAllocateFinalized,
state_bits()));
}
void Class::set_is_prefinalized() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
ASSERT(!is_finalized());
set_state_bits(
ClassFinalizedBits::update(UntaggedClass::kPreFinalized, state_bits()));
}
void Class::set_interfaces(const Array& value) const {
ASSERT(!value.IsNull());
untag()->set_interfaces(value.ptr());
}
void Class::AddDirectImplementor(const Class& implementor,
bool is_mixin) const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
ASSERT(is_implemented());
ASSERT(!implementor.IsNull());
GrowableObjectArray& direct_implementors =
GrowableObjectArray::Handle(untag()->direct_implementors());
if (direct_implementors.IsNull()) {
direct_implementors = GrowableObjectArray::New(4, Heap::kOld);
untag()->set_direct_implementors(direct_implementors.ptr());
}
#if defined(DEBUG)
// Verify that the same class is not added twice.
// The only exception is mixins: when mixin application is transformed,
// mixin is added to the end of interfaces list and may be duplicated:
// class X = A with B implements B;
// This is rare and harmless.
if (!is_mixin) {
for (intptr_t i = 0; i < direct_implementors.Length(); i++) {
ASSERT(direct_implementors.At(i) != implementor.ptr());
}
}
#endif
direct_implementors.Add(implementor, Heap::kOld);
}
void Class::ClearDirectImplementors() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
untag()->set_direct_implementors(GrowableObjectArray::null());
}
void Class::AddDirectSubclass(const Class& subclass) const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
ASSERT(!subclass.IsNull());
ASSERT(subclass.SuperClass() == ptr());
// Do not keep track of the direct subclasses of class Object.
ASSERT(!IsObjectClass());
GrowableObjectArray& direct_subclasses =
GrowableObjectArray::Handle(untag()->direct_subclasses());
if (direct_subclasses.IsNull()) {
direct_subclasses = GrowableObjectArray::New(4, Heap::kOld);
untag()->set_direct_subclasses(direct_subclasses.ptr());
}
#if defined(DEBUG)
// Verify that the same class is not added twice.
for (intptr_t i = 0; i < direct_subclasses.Length(); i++) {
ASSERT(direct_subclasses.At(i) != subclass.ptr());
}
#endif
direct_subclasses.Add(subclass, Heap::kOld);
}
void Class::ClearDirectSubclasses() const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
untag()->set_direct_subclasses(GrowableObjectArray::null());
}
ArrayPtr Class::constants() const {
return untag()->constants();
}
void Class::set_constants(const Array& value) const {
untag()->set_constants(value.ptr());
}
void Class::set_declaration_type(const Type& value) const {
ASSERT(id() != kDynamicCid && id() != kVoidCid);
ASSERT(!value.IsNull() && value.IsCanonical() && value.IsOld());
ASSERT((declaration_type() == Object::null()) ||
(declaration_type() == value.ptr())); // Set during own finalization.
// Since DeclarationType is used as the runtime type of instances of a
// non-generic class, its nullability must be kNonNullable.
// The exception is DeclarationType of Null which is kNullable.
ASSERT(value.type_class_id() != kNullCid || value.IsNullable());
ASSERT(value.type_class_id() == kNullCid || value.IsNonNullable());
untag()->set_declaration_type<std::memory_order_release>(value.ptr());
}
TypePtr Class::DeclarationType() const {
ASSERT(is_declaration_loaded());
if (IsNullClass()) {
return Type::NullType();
}
if (IsDynamicClass()) {
return Type::DynamicType();
}
if (IsVoidClass()) {
return Type::VoidType();
}
if (declaration_type() != Type::null()) {
return declaration_type();
}
// For efficiency, the runtimeType intrinsic returns the type cached by
// DeclarationType without checking its nullability. Therefore, we
// consistently cache the kNonNullable version of the type.
// The exception is type Null which is stored as kNullable.
Type& type =
Type::Handle(Type::New(*this, TypeArguments::Handle(type_parameters()),
Nullability::kNonNullable));
type ^= ClassFinalizer::FinalizeType(type);
set_declaration_type(type);
return type.ptr();
}
void Class::set_allocation_stub(const Code& value) const {
// Never clear the stub as it may still be a target, but will be GC-d if
// not referenced.
ASSERT(!value.IsNull());
ASSERT(untag()->allocation_stub() == Code::null());
untag()->set_allocation_stub(value.ptr());
}
void Class::DisableAllocationStub() const {
{
const Code& existing_stub = Code::Handle(allocation_stub());
if (existing_stub.IsNull()) {
return;
}
}
auto thread = Thread::Current();
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
const Code& existing_stub = Code::Handle(allocation_stub());
if (existing_stub.IsNull()) {
return;
}
ASSERT(!existing_stub.IsDisabled());
// Change the stub so that the next caller will regenerate the stub.
existing_stub.DisableStubCode();
// Disassociate the existing stub from class.
untag()->set_allocation_stub(Code::null());
}
bool Class::IsDartFunctionClass() const {
return ptr() == Type::Handle(Type::DartFunctionType()).type_class();
}
bool Class::IsFutureClass() const {
// Looking up future_class in the object store would not work, because
// this function is called during class finalization, before the object store
// field would be initialized by InitKnownObjects().
return (Name() == Symbols::Future().ptr()) &&
(library() == Library::AsyncLibrary());
}
// Checks if type T0 is a subtype of type T1.
// Type T0 is specified by class 'cls' parameterized with 'type_arguments' and
// by 'nullability', and type T1 is specified by 'other' and must have a type
// class.
bool Class::IsSubtypeOf(const Class& cls,
const TypeArguments& type_arguments,
Nullability nullability,
const AbstractType& other,
Heap::Space space,
TrailPtr trail) {
// This function does not support Null, Never, dynamic, or void as type T0.
classid_t this_cid = cls.id();
ASSERT(this_cid != kNullCid && this_cid != kNeverCid &&
this_cid != kDynamicCid && this_cid != kVoidCid);
// Type T1 must have a type class (e.g. not a type param or a function type).
ASSERT(other.HasTypeClass());
const classid_t other_cid = other.type_class_id();
if (other_cid == kDynamicCid || other_cid == kVoidCid) {
return true;
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
// Nullability of left and right hand sides is verified in strong mode only.
const bool verified_nullability =
!isolate_group->use_strict_null_safety_checks() ||
nullability != Nullability::kNullable || !other.IsNonNullable();
// Right Object.
if (other_cid == kObjectCid) {
return verified_nullability;
}
const Class& other_class = Class::Handle(zone, other.type_class());
const TypeArguments& other_type_arguments =
TypeArguments::Handle(zone, other.arguments());
// Use the 'this_class' object as if it was the receiver of this method, but
// instead of recursing, reset it to the super class and loop.
Class& this_class = Class::Handle(zone, cls.ptr());
while (true) {
// Apply additional subtyping rules if T0 or T1 are 'FutureOr'.
// Left FutureOr:
// if T0 is FutureOr<S0> then:
// T0 <: T1 iff Future<S0> <: T1 and S0 <: T1
if (this_cid == kFutureOrCid) {
// Check Future<S0> <: T1.
ObjectStore* object_store = IsolateGroup::Current()->object_store();
const Class& future_class =
Class::Handle(zone, object_store->future_class());
ASSERT(!future_class.IsNull() && future_class.NumTypeParameters() == 1 &&
this_class.NumTypeParameters() == 1);
ASSERT(type_arguments.IsNull() || type_arguments.Length() >= 1);
if (Class::IsSubtypeOf(future_class, type_arguments,
Nullability::kNonNullable, other, space, trail)) {
// Check S0 <: T1.
const AbstractType& type_arg =
AbstractType::Handle(zone, type_arguments.TypeAtNullSafe(0));
if (type_arg.IsSubtypeOf(other, space, trail)) {
return verified_nullability;
}
}
}
// Right FutureOr:
// if T1 is FutureOr<S1> then:
// T0 <: T1 iff any of the following hold:
// either T0 <: Future<S1>
// or T0 <: S1
// or T0 is X0 and X0 has bound S0 and S0 <: T1 (checked elsewhere)
if (other_cid == kFutureOrCid) {
const AbstractType& other_type_arg =
AbstractType::Handle(zone, other_type_arguments.TypeAtNullSafe(0));
// Check if S1 is a top type.
if (other_type_arg.IsTopTypeForSubtyping()) {
return true;
}
// Check T0 <: Future<S1> when T0 is Future<S0>.
if (this_class.IsFutureClass()) {
const AbstractType& type_arg =
AbstractType::Handle(zone, type_arguments.TypeAtNullSafe(0));
// If T0 is Future<S0>, then T0 <: Future<S1>, iff S0 <: S1.
if (type_arg.IsSubtypeOf(other_type_arg, space, trail)) {
if (verified_nullability) {
return true;
}
}
}
// Check T0 <: Future<S1> when T0 is FutureOr<S0> is already done.
// Check T0 <: S1.
if (other_type_arg.HasTypeClass() &&
Class::IsSubtypeOf(this_class, type_arguments, nullability,
other_type_arg, space, trail)) {
return true;
}
}
// Left nullable:
// if T0 is S0? then:
// T0 <: T1 iff S0 <: T1 and Null <: T1
if (!verified_nullability) {
return false;
}
// Check for reflexivity.
if (this_class.ptr() == other_class.ptr()) {
const intptr_t num_type_params = this_class.NumTypeParameters();
if (num_type_params == 0) {
return true;
}
const intptr_t num_type_args = this_class.NumTypeArguments();
const intptr_t from_index = num_type_args - num_type_params;
// Since we do not truncate the type argument vector of a subclass (see
// below), we only check a subvector of the proper length.
// Check for covariance.
if (other_type_arguments.IsNull()) {
return true;
}
AbstractType& type = AbstractType::Handle(zone);
AbstractType& other_type = AbstractType::Handle(zone);
for (intptr_t i = 0; i < num_type_params; ++i) {
type = type_arguments.TypeAtNullSafe(from_index + i);
other_type = other_type_arguments.TypeAt(from_index + i);
ASSERT(!type.IsNull() && !other_type.IsNull());
if (!type.IsSubtypeOf(other_type, space, trail)) {
return false;
}
}
return true;
}
// Check for 'direct super type' specified in the implements clause
// and check for transitivity at the same time.
Array& interfaces = Array::Handle(zone, this_class.interfaces());
AbstractType& interface = AbstractType::Handle(zone);
Class& interface_class = Class::Handle(zone);
TypeArguments& interface_args = TypeArguments::Handle(zone);
for (intptr_t i = 0; i < interfaces.Length(); i++) {
interface ^= interfaces.At(i);
ASSERT(interface.IsFinalized());
interface_class = interface.type_class();
interface_args = interface.arguments();
if (!interface_args.IsNull() && !interface_args.IsInstantiated()) {
// This type class implements an interface that is parameterized with
// generic type(s), e.g. it implements List<T>.
// The uninstantiated type T must be instantiated using the type
// parameters of this type before performing the type test.
// The type arguments of this type that are referred to by the type
// parameters of the interface are at the end of the type vector,
// after the type arguments of the super type of this type.
// The index of the type parameters is adjusted upon finalization.
interface_args = interface_args.InstantiateFrom(
type_arguments, Object::null_type_arguments(), kNoneFree, space);
}
// In Dart 2, implementing Function has no meaning.
// TODO(regis): Can we encounter and skip Object as well?
if (interface_class.IsDartFunctionClass()) {
continue;
}
// No need to pass the trail as cycles are not possible via interfaces.
if (Class::IsSubtypeOf(interface_class, interface_args,
Nullability::kNonNullable, other, space)) {
return true;
}
}
// "Recurse" up the class hierarchy until we have reached the top.
this_class = this_class.SuperClass();
if (this_class.IsNull()) {
return false;
}
this_cid = this_class.id();
}
UNREACHABLE();
return false;
}
bool Class::IsTopLevel() const {
return Name() == Symbols::TopLevel().ptr();
}
bool Class::IsPrivate() const {
return Library::IsPrivate(String::Handle(Name()));
}
FunctionPtr Class::LookupDynamicFunctionUnsafe(const String& name) const {
return LookupFunctionReadLocked(name, kInstance);
}
FunctionPtr Class::LookupDynamicFunctionAllowPrivate(const String& name) const {
return LookupFunctionAllowPrivate(name, kInstance);
}
FunctionPtr Class::LookupStaticFunction(const String& name) const {
Thread* thread = Thread::Current();
SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
return LookupFunctionReadLocked(name, kStatic);
}
FunctionPtr Class::LookupStaticFunctionAllowPrivate(const String& name) const {
return LookupFunctionAllowPrivate(name, kStatic);
}
FunctionPtr Class::LookupConstructor(const String& name) const {
Thread* thread = Thread::Current();
SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
return LookupFunctionReadLocked(name, kConstructor);
}
FunctionPtr Class::LookupConstructorAllowPrivate(const String& name) const {
return LookupFunctionAllowPrivate(name, kConstructor);
}
FunctionPtr Class::LookupFactory(const String& name) const {
Thread* thread = Thread::Current();
SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
return LookupFunctionReadLocked(name, kFactory);
}
FunctionPtr Class::LookupFactoryAllowPrivate(const String& name) const {
return LookupFunctionAllowPrivate(name, kFactory);
}
FunctionPtr Class::LookupFunctionAllowPrivate(const String& name) const {
return LookupFunctionAllowPrivate(name, kAny);
}
FunctionPtr Class::LookupFunctionReadLocked(const String& name) const {
return LookupFunctionReadLocked(name, kAny);
}
// Returns true if 'prefix' and 'accessor_name' match 'name'.
static bool MatchesAccessorName(const String& name,
const char* prefix,
intptr_t prefix_length,
const String& accessor_name) {
intptr_t name_len = name.Length();
intptr_t accessor_name_len = accessor_name.Length();
if (name_len != (accessor_name_len + prefix_length)) {
return false;
}
for (intptr_t i = 0; i < prefix_length; i++) {
if (name.CharAt(i) != prefix[i]) {
return false;
}
}
for (intptr_t i = 0, j = prefix_length; i < accessor_name_len; i++, j++) {
if (name.CharAt(j) != accessor_name.CharAt(i)) {
return false;
}
}
return true;
}
FunctionPtr Class::CheckFunctionType(const Function& func, MemberKind kind) {
if ((kind == kInstance) || (kind == kInstanceAllowAbstract)) {
if (func.IsDynamicFunction(kind == kInstanceAllowAbstract)) {
return func.ptr();
}
} else if (kind == kStatic) {
if (func.IsStaticFunction()) {
return func.ptr();
}
} else if (kind == kConstructor) {
if (func.IsGenerativeConstructor()) {
ASSERT(!func.is_static());
return func.ptr();
}
} else if (kind == kFactory) {
if (func.IsFactory()) {
ASSERT(func.is_static());
return func.ptr();
}
} else if (kind == kAny) {
return func.ptr();
}
return Function::null();
}
FunctionPtr Class::LookupFunctionReadLocked(const String& name,
MemberKind kind) const {
ASSERT(!IsNull());
Thread* thread = Thread::Current();
RELEASE_ASSERT(is_finalized());
// Caller needs to ensure they grab program_lock because this method
// can be invoked with either ReadRwLock or WriteRwLock.
#if defined(DEBUG)
ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadReader());
#endif
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_FUNCTION_HANDLESCOPE(thread);
Array& funcs = thread->ArrayHandle();
funcs = functions();
ASSERT(!funcs.IsNull());
const intptr_t len = funcs.Length();
Function& function = thread->FunctionHandle();
if (len >= kFunctionLookupHashTreshold) {
// TODO(dartbug.com/36097): We require currently a read lock in the resolver
// to avoid read-write race access to this hash table.
// If we want to increase resolver speed by avoiding the need for read lock,
// we could make change this hash table to be lock-free for the reader.
const Array& hash_table =
Array::Handle(thread->zone(), untag()->functions_hash_table());
if (!hash_table.IsNull()) {
ClassFunctionsSet set(hash_table.ptr());
REUSABLE_STRING_HANDLESCOPE(thread);
function ^= set.GetOrNull(FunctionName(name, &(thread->StringHandle())));
// No mutations.
ASSERT(set.Release().ptr() == hash_table.ptr());
return function.IsNull() ? Function::null()
: CheckFunctionType(function, kind);
}
}
if (name.IsSymbol()) {
// Quick Symbol compare.
NoSafepointScope no_safepoint;
for (intptr_t i = 0; i < len; i++) {
function ^= funcs.At(i);
if (function.name() == name.ptr()) {
return CheckFunctionType(function, kind);
}
}
} else {
REUSABLE_STRING_HANDLESCOPE(thread);
String& function_name = thread->StringHandle();
for (intptr_t i = 0; i < len; i++) {
function ^= funcs.At(i);
function_name = function.name();
if (function_name.Equals(name)) {
return CheckFunctionType(function, kind);
}
}
}
// No function found.
return Function::null();
}
FunctionPtr Class::LookupFunctionAllowPrivate(const String& name,
MemberKind kind) const {
ASSERT(!IsNull());
Thread* thread = Thread::Current();
RELEASE_ASSERT(is_finalized());
SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_FUNCTION_HANDLESCOPE(thread);
REUSABLE_STRING_HANDLESCOPE(thread);
Array& funcs = thread->ArrayHandle();
funcs = current_functions();
ASSERT(!funcs.IsNull());
const intptr_t len = funcs.Length();
Function& function = thread->FunctionHandle();
String& function_name = thread->StringHandle();
for (intptr_t i = 0; i < len; i++) {
function ^= funcs.At(i);
function_name = function.name();
if (String::EqualsIgnoringPrivateKey(function_name, name)) {
return CheckFunctionType(function, kind);
}
}
// No function found.
return Function::null();
}
FunctionPtr Class::LookupGetterFunction(const String& name) const {
return LookupAccessorFunction(kGetterPrefix, kGetterPrefixLength, name);
}
FunctionPtr Class::LookupSetterFunction(const String& name) const {
return LookupAccessorFunction(kSetterPrefix, kSetterPrefixLength, name);
}
FunctionPtr Class::LookupAccessorFunction(const char* prefix,
intptr_t prefix_length,
const String& name) const {
ASSERT(!IsNull());
Thread* thread = Thread::Current();
if (EnsureIsFinalized(thread) != Error::null()) {
return Function::null();
}
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_FUNCTION_HANDLESCOPE(thread);
REUSABLE_STRING_HANDLESCOPE(thread);
Array& funcs = thread->ArrayHandle();
funcs = current_functions();
intptr_t len = funcs.Length();
Function& function = thread->FunctionHandle();
String& function_name = thread->StringHandle();
for (intptr_t i = 0; i < len; i++) {
function ^= funcs.At(i);
function_name = function.name();
if (MatchesAccessorName(function_name, prefix, prefix_length, name)) {
return function.ptr();
}
}
// No function found.
return Function::null();
}
FieldPtr Class::LookupInstanceField(const String& name) const {
return LookupField(name, kInstance);
}
FieldPtr Class::LookupStaticField(const String& name) const {
return LookupField(name, kStatic);
}
FieldPtr Class::LookupField(const String& name) const {
return LookupField(name, kAny);
}
FieldPtr Class::LookupField(const String& name, MemberKind kind) const {
ASSERT(!IsNull());
Thread* thread = Thread::Current();
if (EnsureIsFinalized(thread) != Error::null()) {
return Field::null();
}
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_FIELD_HANDLESCOPE(thread);
REUSABLE_STRING_HANDLESCOPE(thread);
Array& flds = thread->ArrayHandle();
flds = fields();
ASSERT(!flds.IsNull());
intptr_t len = flds.Length();
Field& field = thread->FieldHandle();
if (name.IsSymbol()) {
// Use fast raw pointer string compare for symbols.
for (intptr_t i = 0; i < len; i++) {
field ^= flds.At(i);
if (name.ptr() == field.name()) {
if (kind == kInstance) {
return field.is_static() ? Field::null() : field.ptr();
} else if (kind == kStatic) {
return field.is_static() ? field.ptr() : Field::null();
}
ASSERT(kind == kAny);
return field.ptr();
}
}
} else {
String& field_name = thread->StringHandle();
for (intptr_t i = 0; i < len; i++) {
field ^= flds.At(i);
field_name = field.name();
if (name.Equals(field_name)) {
if (kind == kInstance) {
return field.is_static() ? Field::null() : field.ptr();
} else if (kind == kStatic) {
return field.is_static() ? field.ptr() : Field::null();
}
ASSERT(kind == kAny);
return field.ptr();
}
}
}
return Field::null();
}
FieldPtr Class::LookupFieldAllowPrivate(const String& name,
bool instance_only) const {
ASSERT(!IsNull());
// Use slow string compare, ignoring privacy name mangling.
Thread* thread = Thread::Current();
if (EnsureIsFinalized(thread) != Error::null()) {
return Field::null();
}
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_FIELD_HANDLESCOPE(thread);
REUSABLE_STRING_HANDLESCOPE(thread);
Array& flds = thread->ArrayHandle();
flds = fields();
ASSERT(!flds.IsNull());
intptr_t len = flds.Length();
Field& field = thread->FieldHandle();
String& field_name = thread->StringHandle();
for (intptr_t i = 0; i < len; i++) {
field ^= flds.At(i);
field_name = field.name();
if (field.is_static() && instance_only) {
// If we only care about instance fields, skip statics.
continue;
}
if (String::EqualsIgnoringPrivateKey(field_name, name)) {
return field.ptr();
}
}
return Field::null();
}
FieldPtr Class::LookupInstanceFieldAllowPrivate(const String& name) const {
Field& field = Field::Handle(LookupFieldAllowPrivate(name, true));
if (!field.IsNull() && !field.is_static()) {
return field.ptr();
}
return Field::null();
}
FieldPtr Class::LookupStaticFieldAllowPrivate(const String& name) const {
Field& field = Field::Handle(LookupFieldAllowPrivate(name));
if (!field.IsNull() && field.is_static()) {
return field.ptr();
}
return Field::null();
}
const char* Class::ToCString() const {
NoSafepointScope no_safepoint;
const Library& lib = Library::Handle(library());
const char* library_name = lib.IsNull() ? "" : lib.ToCString();
const char* class_name = String::Handle(Name()).ToCString();
return OS::SCreate(Thread::Current()->zone(), "%s Class: %s", library_name,
class_name);
}
// Thomas Wang, Integer Hash Functions.
// https://gist.github.com/badboy/6267743
// "64 bit to 32 bit Hash Functions"
static uword Hash64To32(uint64_t v) {
v = ~v + (v << 18);
v = v ^ (v >> 31);
v = v * 21;
v = v ^ (v >> 11);
v = v + (v << 6);
v = v ^ (v >> 22);
return static_cast<uint32_t>(v);
}
class CanonicalDoubleKey {
public:
explicit CanonicalDoubleKey(const Double& key)
: key_(&key), value_(key.value()) {}
explicit CanonicalDoubleKey(const double value) : key_(NULL), value_(value) {}
bool Matches(const Double& obj) const {
return obj.BitwiseEqualsToDouble(value_);
}
uword Hash() const { return Hash(value_); }
static uword Hash(double value) {
return Hash64To32(bit_cast<uint64_t>(value));
}
const Double* key_;
const double value_;
private:
DISALLOW_ALLOCATION();
};
class CanonicalMintKey {
public:
explicit CanonicalMintKey(const Mint& key)
: key_(&key), value_(key.value()) {}
explicit CanonicalMintKey(const int64_t value) : key_(NULL), value_(value) {}
bool Matches(const Mint& obj) const { return obj.value() == value_; }
uword Hash() const { return Hash(value_); }
static uword Hash(int64_t value) {
return Hash64To32(bit_cast<uint64_t>(value));
}
const Mint* key_;
const int64_t value_;
private:
DISALLOW_ALLOCATION();
};
// Traits for looking up Canonical numbers based on a hash of the value.
template <typename ObjectType, typename KeyType>
class CanonicalNumberTraits {
public:
static const char* Name() { return "CanonicalNumberTraits"; }
static bool ReportStats() { return false; }
// Called when growing the table.
static bool IsMatch(const Object& a, const Object& b) {
return a.ptr() == b.ptr();
}
static bool IsMatch(const KeyType& a, const Object& b) {
return a.Matches(ObjectType::Cast(b));
}
static uword Hash(const Object& key) {
return KeyType::Hash(ObjectType::Cast(key).value());
}
static uword Hash(const KeyType& key) { return key.Hash(); }
static ObjectPtr NewKey(const KeyType& obj) {
if (obj.key_ != NULL) {
return obj.key_->ptr();
} else {
UNIMPLEMENTED();
return NULL;
}
}
};
typedef UnorderedHashSet<CanonicalNumberTraits<Double, CanonicalDoubleKey> >
CanonicalDoubleSet;
typedef UnorderedHashSet<CanonicalNumberTraits<Mint, CanonicalMintKey> >
CanonicalMintSet;
// Returns an instance of Double or Double::null().
DoublePtr Class::LookupCanonicalDouble(Zone* zone, double value) const {
ASSERT(this->ptr() ==
IsolateGroup::Current()->object_store()->double_class());
if (this->constants() == Array::null()) return Double::null();
Double& canonical_value = Double::Handle(zone);
CanonicalDoubleSet constants(zone, this->constants());
canonical_value ^= constants.GetOrNull(CanonicalDoubleKey(value));
this->set_constants(constants.Release());
return canonical_value.ptr();
}
// Returns an instance of Mint or Mint::null().
MintPtr Class::LookupCanonicalMint(Zone* zone, int64_t value) const {
ASSERT(this->ptr() == IsolateGroup::Current()->object_store()->mint_class());
if (this->constants() == Array::null()) return Mint::null();
Mint& canonical_value = Mint::Handle(zone);
CanonicalMintSet constants(zone, this->constants());
canonical_value ^= constants.GetOrNull(CanonicalMintKey(value));
this->set_constants(constants.Release());
return canonical_value.ptr();
}
class CanonicalInstanceKey {
public:
explicit CanonicalInstanceKey(const Instance& key) : key_(key) {
ASSERT(!(key.IsString() || key.IsInteger() || key.IsAbstractType()));
}
bool Matches(const Instance& obj) const {
ASSERT(!(obj.IsString() || obj.IsInteger() || obj.IsAbstractType()));
if (key_.CanonicalizeEquals(obj)) {
ASSERT(obj.IsCanonical());
return true;
}
return false;
}
uword Hash() const { return key_.CanonicalizeHash(); }
const Instance& key_;
private:
DISALLOW_ALLOCATION();
};
// Traits for looking up Canonical Instances based on a hash of the fields.
class CanonicalInstanceTraits {
public:
static const char* Name() { return "CanonicalInstanceTraits"; }
static bool ReportStats() { return false; }
// Called when growing the table.
static bool IsMatch(const Object& a, const Object& b) {
ASSERT(!(a.IsString() || a.IsInteger() || a.IsAbstractType()));
ASSERT(!(b.IsString() || b.IsInteger() || b.IsAbstractType()));
return a.ptr() == b.ptr();
}
static bool IsMatch(const CanonicalInstanceKey& a, const Object& b) {
return a.Matches(Instance::Cast(b));
}
static uword Hash(const Object& key) {
ASSERT(!(key.IsString() || key.IsNumber() || key.IsAbstractType()));
ASSERT(key.IsInstance());
return Instance::Cast(key).CanonicalizeHash();
}
static uword Hash(const CanonicalInstanceKey& key) { return key.Hash(); }
static ObjectPtr NewKey(const CanonicalInstanceKey& obj) {
return obj.key_.ptr();
}
};
typedef UnorderedHashSet<CanonicalInstanceTraits> CanonicalInstancesSet;
InstancePtr Class::LookupCanonicalInstance(Zone* zone,
const Instance& value) const {
ASSERT(this->ptr() == value.clazz());
ASSERT(is_finalized() || is_prefinalized());
Instance& canonical_value = Instance::Handle(zone);
if (this->constants() != Array::null()) {
CanonicalInstancesSet constants(zone, this->constants());
canonical_value ^= constants.GetOrNull(CanonicalInstanceKey(value));
this->set_constants(constants.Release());
}
return canonical_value.ptr();
}
InstancePtr Class::InsertCanonicalConstant(Zone* zone,
const Instance& constant) const {
ASSERT(this->ptr() == constant.clazz());
Instance& canonical_value = Instance::Handle(zone);
if (this->constants() == Array::null()) {
CanonicalInstancesSet constants(
HashTables::New<CanonicalInstancesSet>(128, Heap::kOld));
canonical_value ^= constants.InsertNewOrGet(CanonicalInstanceKey(constant));
this->set_constants(constants.Release());
} else {
CanonicalInstancesSet constants(Thread::Current()->zone(),
this->constants());
canonical_value ^= constants.InsertNewOrGet(CanonicalInstanceKey(constant));
this->set_constants(constants.Release());
}
return canonical_value.ptr();
}
void Class::InsertCanonicalDouble(Zone* zone, const Double& constant) const {
if (this->constants() == Array::null()) {
this->set_constants(Array::Handle(
zone, HashTables::New<CanonicalDoubleSet>(128, Heap::kOld)));
}
CanonicalDoubleSet constants(zone, this->constants());
constants.InsertNewOrGet(CanonicalDoubleKey(constant));
this->set_constants(constants.Release());
}
void Class::InsertCanonicalMint(Zone* zone, const Mint& constant) const {
if (this->constants() == Array::null()) {
this->set_constants(Array::Handle(
zone, HashTables::New<CanonicalMintSet>(128, Heap::kOld)));
}
CanonicalMintSet constants(zone, this->constants());
constants.InsertNewOrGet(CanonicalMintKey(constant));
this->set_constants(constants.Release());
}
void Class::RehashConstants(Zone* zone) const {
intptr_t cid = id();
if ((cid == kMintCid) || (cid == kDoubleCid)) {
// Constants stored as a plain list or in a hashset with a stable hashcode,
// which only depends on the actual value of the constant.
return;
}
const Array& old_constants = Array::Handle(zone, constants());
if (old_constants.IsNull()) return;
set_constants(Object::null_array());
CanonicalInstancesSet set(zone, old_constants.ptr());
Instance& constant = Instance::Handle(zone);
CanonicalInstancesSet::Iterator it(&set);
while (it.MoveNext()) {
constant ^= set.GetKey(it.Current());
ASSERT(!constant.IsNull());
// Shape changes lose the canonical bit because they may result/ in merging
// constants. E.g., [x1, y1], [x1, y2] -> [x1].
DEBUG_ASSERT(constant.IsCanonical() ||
IsolateGroup::Current()->HasAttemptedReload());
InsertCanonicalConstant(zone, constant);
}
set.Release();
}
bool Class::RequireCanonicalTypeErasureOfConstants(Zone* zone) const {
const intptr_t num_type_params = NumTypeParameters();
const intptr_t num_type_args = NumTypeArguments();
const intptr_t from_index = num_type_args - num_type_params;
Instance& constant = Instance::Handle(zone);
TypeArguments& type_arguments = TypeArguments::Handle(zone);
CanonicalInstancesSet set(zone, constants());
CanonicalInstancesSet::Iterator it(&set);
bool result = false;
while (it.MoveNext()) {
constant ^= set.GetKey(it.Current());
ASSERT(!constant.IsNull());
ASSERT(!constant.IsTypeArguments());
ASSERT(!constant.IsType());
type_arguments = constant.GetTypeArguments();
if (type_arguments.RequireConstCanonicalTypeErasure(zone, from_index,
num_type_params)) {
result = true;
break;
}
}
set.Release();
return result;
}
intptr_t TypeArguments::ComputeNullability() const {
if (IsNull()) return 0;
const intptr_t num_types = Length();
intptr_t result = 0;
if (num_types <= kNullabilityMaxTypes) {
AbstractType& type = AbstractType::Handle();
for (intptr_t i = 0; i < num_types; i++) {
type = TypeAt(i);
intptr_t type_bits = 0;
if (!type.IsNull() && !type.IsNullTypeRef()) {
switch (type.nullability()) {
case Nullability::kNullable:
type_bits = kNullableBits;
break;
case Nullability::kNonNullable:
type_bits = kNonNullableBits;
break;
case Nullability::kLegacy:
type_bits = kLegacyBits;
break;
default:
UNREACHABLE();
}
}
result |= (type_bits << (i * kNullabilityBitsPerType));
}
}
set_nullability(result);
return result;
}
void TypeArguments::set_nullability(intptr_t value) const {
untag()->set_nullability(Smi::New(value));
}
intptr_t TypeArguments::HashForRange(intptr_t from_index, intptr_t len) const {
if (IsNull()) return kAllDynamicHash;
if (IsRaw(from_index, len)) return kAllDynamicHash;
uint32_t result = 0;
AbstractType& type = AbstractType::Handle();
for (intptr_t i = 0; i < len; i++) {
type = TypeAt(from_index + i);
// The hash may be calculated during type finalization (for debugging
// purposes only) while a type argument is still temporarily null.
if (type.IsNull() || type.IsNullTypeRef()) {
return 0; // Do not cache hash, since it will still change.
}
if (type.IsTypeRef()) {
// Unwrapping the TypeRef here cannot lead to infinite recursion, because
// traversal during hash computation stops at the TypeRef. Indeed,
// unwrapping the TypeRef does not always remove it completely, but may
// only rotate the cycle. The same TypeRef can be encountered when calling
// type.Hash() below after traversing the whole cycle. The class id of the
// referenced type is used and the traversal stops.
// By dereferencing the TypeRef, we maximize the information reflected by
// the hash value. Two equal vectors may have some of their type arguments
// 'oriented' differently, i.e. pointing to identical (TypeRef containing)
// cyclic type graphs, but to two different nodes in the cycle, thereby
// breaking the hash computation earlier for one vector and yielding two
// different hash values for identical type graphs.
type = TypeRef::Cast(type).type();
}
result = CombineHashes(result, type.Hash());
}
result = FinalizeHash(result, kHashBits);
return result;
}
intptr_t TypeArguments::ComputeHash() const {
if (IsNull()) return kAllDynamicHash;
const intptr_t num_types = Length();
const uint32_t result = HashForRange(0, num_types);
if (result != 0) {
SetHash(result);
}
return result;
}
TypeArgumentsPtr TypeArguments::Prepend(Zone* zone,
const TypeArguments& other,
intptr_t other_length,
intptr_t total_length) const {
if (other_length == 0) {
ASSERT(IsCanonical());
return ptr();
} else if (other_length == total_length) {
ASSERT(other.IsCanonical());
return other.ptr();
} else if (IsNull() && other.IsNull()) {
return TypeArguments::null();
}
const TypeArguments& result =
TypeArguments::Handle(zone, TypeArguments::New(total_length, Heap::kNew));
AbstractType& type = AbstractType::Handle(zone);
for (intptr_t i = 0; i < other_length; i++) {
type = other.IsNull() ? Type::DynamicType() : other.TypeAt(i);
result.SetTypeAt(i, type);
}
for (intptr_t i = other_length; i < total_length; i++) {
type = IsNull() ? Type::DynamicType() : TypeAt(i - other_length);
result.SetTypeAt(i, type);
}
return result.Canonicalize(Thread::Current(), nullptr);
}
TypeArgumentsPtr TypeArguments::ConcatenateTypeParameters(
Zone* zone,
const TypeArguments& other) const {
ASSERT(!IsNull() && !other.IsNull());
const intptr_t this_len = Length();
const intptr_t other_len = other.Length();
const auto& result = TypeArguments::Handle(
zone, TypeArguments::New(this_len + other_len, Heap::kNew));
auto& type = AbstractType::Handle(zone);
for (intptr_t i = 0; i < this_len; ++i) {
type = TypeAt(i);
result.SetTypeAt(i, type);
}
for (intptr_t i = 0; i < other_len; ++i) {
type = other.TypeAt(i);
result.SetTypeAt(this_len + i, type);
}
return result.ptr();
}
StringPtr TypeArguments::Name() const {
Thread* thread = Thread::Current();
ZoneTextBuffer printer(thread->zone());
PrintSubvectorName(0, Length(), kInternalName, &printer);
return Symbols::New(thread, printer.buffer());
}
StringPtr TypeArguments::UserVisibleName() const {
Thread* thread = Thread::Current();
ZoneTextBuffer printer(thread->zone());
PrintSubvectorName(0, Length(), kUserVisibleName, &printer);
return Symbols::New(thread, printer.buffer());
}
void TypeArguments::PrintSubvectorName(intptr_t from_index,
intptr_t len,
NameVisibility name_visibility,
BaseTextBuffer* printer) const {
printer->AddString("<");
AbstractType& type = AbstractType::Handle();
for (intptr_t i = 0; i < len; i++) {
if (from_index + i < Length()) {
type = TypeAt(from_index + i);
if (type.IsNull()) {
printer->AddString("null"); // Unfinalized vector.
} else {
type.PrintName(name_visibility, printer);
}
} else {
printer->AddString("dynamic");
}
if (i < len - 1) {
printer->AddString(", ");
}
}
printer->AddString(">");
}
void TypeArguments::PrintTo(BaseTextBuffer* buffer) const {
buffer->AddString("TypeArguments: ");
if (IsNull()) {
return buffer->AddString("null");
}
buffer->Printf("(H%" Px ")", Smi::Value(untag()->hash()));
auto& type_at = AbstractType::Handle();
for (intptr_t i = 0; i < Length(); i++) {
type_at = TypeAt(i);
buffer->Printf(" [%s]", type_at.IsNull() ? "null" : type_at.ToCString());
}
}
bool TypeArguments::IsSubvectorEquivalent(const TypeArguments& other,
intptr_t from_index,
intptr_t len,
TypeEquality kind,
TrailPtr trail) const {
if (this->ptr() == other.ptr()) {
return true;
}
if (IsNull() || other.IsNull()) {
return false;
}
const intptr_t num_types = Length();
if (num_types != other.Length()) {
return false;
}
AbstractType& type = AbstractType::Handle();
AbstractType& other_type = AbstractType::Handle();
for (intptr_t i = from_index; i < from_index + len; i++) {
type = TypeAt(i);
other_type = other.TypeAt(i);
// Still unfinalized vectors should not be considered equivalent.
if (type.IsNull() || !type.IsEquivalent(other_type, kind, trail)) {
return false;
}
}
return true;
}
bool TypeArguments::IsRecursive(TrailPtr trail) const {
if (IsNull()) return false;
const intptr_t num_types = Length();
AbstractType& type = AbstractType::Handle();
for (intptr_t i = 0; i < num_types; i++) {
type = TypeAt(i);
// If this type argument is null, the type parameterized with this type
// argument is still being finalized and is definitely recursive. The null
// type argument will be replaced by a non-null type before the type is
// marked as finalized.
if (type.IsNull() || type.IsRecursive(trail)) {
return true;
}
}
return false;
}
bool TypeArguments::RequireConstCanonicalTypeErasure(Zone* zone,
intptr_t from_index,
intptr_t len,
TrailPtr trail) const {
if (IsNull()) return false;
ASSERT(Length() >= (from_index + len));
AbstractType& type = AbstractType::Handle(zone);
for (intptr_t i = 0; i < len; i++) {
type = TypeAt(from_index + i);
if (type.IsNonNullable() ||
(type.IsNullable() &&
type.RequireConstCanonicalTypeErasure(zone, trail))) {
// It is not possible for a legacy type to have non-nullable type
// arguments or for a legacy function type to have non-nullable type in
// its signature.
return true;
}
}
return false;
}
bool TypeArguments::IsDynamicTypes(bool raw_instantiated,
intptr_t from_index,
intptr_t len) const {
ASSERT(Length() >= (from_index + len));
AbstractType& type = AbstractType::Handle();
Class& type_class = Class::Handle();
for (intptr_t i = 0; i < len; i++) {
type = TypeAt(from_index + i);
if (type.IsNull()) {
return false;
}
if (!type.HasTypeClass()) {
if (raw_instantiated && type.IsTypeParameter()) {
// An uninstantiated type parameter is equivalent to dynamic.
continue;
}
return false;
}
type_class = type.type_class();
if (!type_class.IsDynamicClass()) {
return false;
}
}
return true;
}
bool TypeArguments::HasInstantiations() const {
const Array& prior_instantiations = Array::Handle(instantiations());
ASSERT(prior_instantiations.Length() > 0); // Always at least a sentinel.
return prior_instantiations.Length() > 1;
}
intptr_t TypeArguments::NumInstantiations() const {
const Array& prior_instantiations = Array::Handle(instantiations());
ASSERT(prior_instantiations.Length() > 0); // Always at least a sentinel.
intptr_t num = 0;
intptr_t i = 0;
while (prior_instantiations.At(i) !=
Smi::New(TypeArguments::kNoInstantiator)) {
i += TypeArguments::Instantiation::kSizeInWords;
num++;
}
return num;
}
ArrayPtr TypeArguments::instantiations() const {
// We rely on the fact that any loads from the array are dependent loads and
// avoid the load-acquire barrier here.
return untag()->instantiations();
}
void TypeArguments::set_instantiations(const Array& value) const {
// We have to ensure that initializing stores to the array are available
// when releasing the pointer to the array pointer.
// => We have to use store-release here.
ASSERT(!value.IsNull());
untag()->set_instantiations<std::memory_order_release>(value.ptr());
}
bool TypeArguments::HasCount(intptr_t count) const {
if (IsNull()) {
return true;
}
return Length() == count;
}
intptr_t TypeArguments::Length() const {
if (IsNull()) {
return 0;
}
return Smi::Value(untag()->length());
}
intptr_t TypeArguments::nullability() const {
if (IsNull()) {
return 0;
}
return Smi::Value(untag()->nullability());
}
AbstractTypePtr TypeArguments::TypeAt(intptr_t index) const {
ASSERT(!IsNull());
ASSERT((index >= 0) && (index < Length()));
return untag()->element(index);
}
AbstractTypePtr TypeArguments::TypeAtNullSafe(intptr_t index) const {
if (IsNull()) {
// null vector represents infinite list of dynamics
return Type::dynamic_type().ptr();
}
ASSERT((index >= 0) && (index < Length()));
return TypeAt(index);
}
void TypeArguments::SetTypeAt(intptr_t index, const AbstractType& value) const {
ASSERT(!IsCanonical());
ASSERT((index >= 0) && (index < Length()));
return untag()->set_element(index, value.ptr());
}
bool TypeArguments::IsSubvectorInstantiated(intptr_t from_index,
intptr_t len,
Genericity genericity,
intptr_t num_free_fun_type_params,
TrailPtr trail) const {
ASSERT(!IsNull());
AbstractType& type = AbstractType::Handle();
for (intptr_t i = 0; i < len; i++) {
type = TypeAt(from_index + i);
// If this type argument T is null, the type A containing T in its flattened
// type argument vector V is recursive and is still being finalized.
// T is the type argument of a super type of A. T is being instantiated
// during finalization of V, which is also the instantiator. T depends
// solely on the type parameters of A and will be replaced by a non-null
// type before A is marked as finalized.
if (!type.IsNull() &&
!type.IsInstantiated(genericity, num_free_fun_type_params, trail)) {
return false;
}
}
return true;
}
bool TypeArguments::IsUninstantiatedIdentity() const {
AbstractType& type = AbstractType::Handle();
const intptr_t num_types = Length();
for (intptr_t i = 0; i < num_types; i++) {
type = TypeAt(i);
if (type.IsNull()) {
return false; // Still unfinalized, too early to tell.
}
if (!type.IsTypeParameter()) {
return false;
}
const TypeParameter& type_param = TypeParameter::Cast(type);
ASSERT(type_param.IsFinalized());
if ((type_param.index() != i) || type_param.IsFunctionTypeParameter()) {
return false;
}
// Instantiating nullable and legacy type parameters may change
// nullability of a type, so type arguments vector containing such type
// parameters cannot be substituted with instantiator type arguments.
if (type_param.IsNullable() || type_param.IsLegacy()) {
return false;
}
}
return true;
// Note that it is not necessary to verify at runtime that the instantiator
// type vector is long enough, since this uninstantiated vector contains as
// many different type parameters as it is long.
}
// Return true if this uninstantiated type argument vector, once instantiated
// at runtime, is a prefix of the type argument vector of its instantiator.
// A runtime check may be required, as indicated by with_runtime_check.
bool TypeArguments::CanShareInstantiatorTypeArguments(
const Class& instantiator_class,
bool* with_runtime_check) const {
ASSERT(!IsInstantiated());
if (with_runtime_check != nullptr) {
*with_runtime_check = false;
}
const intptr_t num_type_args = Length();
const intptr_t num_instantiator_type_args =
instantiator_class.NumTypeArguments();
if (num_type_args > num_instantiator_type_args) {
// This vector cannot be a prefix of a shorter vector.
return false;
}
const intptr_t num_instantiator_type_params =
instantiator_class.NumTypeParameters();
const intptr_t first_type_param_offset =
num_instantiator_type_args - num_instantiator_type_params;
// At compile time, the type argument vector of the instantiator consists of
// the type argument vector of its super type, which may refer to the type
// parameters of the instantiator class, followed by (or overlapping partially
// or fully with) the type parameters of the instantiator class in declaration
// order.
// In other words, the only variables are the type parameters of the
// instantiator class.
// This uninstantiated type argument vector is also expressed in terms of the
// type parameters of the instantiator class. Therefore, in order to be a
// prefix once instantiated at runtime, every one of its type argument must be
// equal to the type argument of the instantiator vector at the same index.
// As a first requirement, the last num_instantiator_type_params type
// arguments of this type argument vector must refer to the corresponding type
// parameters of the instantiator class.
AbstractType& type_arg = AbstractType::Handle();
for (intptr_t i = first_type_param_offset; i < num_type_args; i++) {
type_arg = TypeAt(i);
if (!type_arg.IsTypeParameter()) {
return false;
}
const TypeParameter& type_param = TypeParameter::Cast(type_arg);
ASSERT(type_param.IsFinalized());
if ((type_param.index() != i) || type_param.IsFunctionTypeParameter()) {
return false;
}
// Instantiating nullable and legacy type parameters may change nullability
// of a type, so type arguments vector containing such type parameters
// cannot be substituted with instantiator type arguments, unless we check
// at runtime the nullability of the first 1 or 2 type arguments of the
// instantiator.
// Note that the presence of non-overlapping super type arguments (i.e.
// first_type_param_offset > 0) will prevent this optimization.
if (type_param.IsNullable() || type_param.IsLegacy()) {
if (with_runtime_check == nullptr || i >= kNullabilityMaxTypes) {
return false;
}
*with_runtime_check = true;
}
}
// As a second requirement, the type arguments corresponding to the super type
// must be identical. Overlapping ones have already been checked starting at
// first_type_param_offset.
if (first_type_param_offset == 0) {
return true;
}
AbstractType& super_type =
AbstractType::Handle(instantiator_class.super_type());
const TypeArguments& super_type_args =
TypeArguments::Handle(super_type.arguments());
if (super_type_args.IsNull()) {
ASSERT(!IsUninstantiatedIdentity());
return false;
}
AbstractType& super_type_arg = AbstractType::Handle();
for (intptr_t i = 0; (i < first_type_param_offset) && (i < num_type_args);
i++) {
type_arg = TypeAt(i);
super_type_arg = super_type_args.TypeAt(i);
if (!type_arg.Equals(super_type_arg)) {
ASSERT(!IsUninstantiatedIdentity());
return false;
}
}
return true;
}
// Return true if this uninstantiated type argument vector, once instantiated
// at runtime, is a prefix of the enclosing function type arguments.
// A runtime check may be required, as indicated by with_runtime_check.
bool TypeArguments::CanShareFunctionTypeArguments(
const Function& function,
bool* with_runtime_check) const {
ASSERT(!IsInstantiated());
if (with_runtime_check != nullptr) {
*with_runtime_check = false;
}
const intptr_t num_type_args = Length();
const intptr_t num_parent_type_args = function.NumParentTypeArguments();
const intptr_t num_function_type_params = function.NumTypeParameters();
const intptr_t num_function_type_args =
num_parent_type_args + num_function_type_params;
if (num_type_args > num_function_type_args) {
// This vector cannot be a prefix of a shorter vector.
return false;
}
AbstractType& type_arg = AbstractType::Handle();
for (intptr_t i = 0; i < num_type_args; i++) {
type_arg = TypeAt(i);
if (!type_arg.IsTypeParameter()) {
return false;
}
const TypeParameter& type_param = TypeParameter::Cast(type_arg);
ASSERT(type_param.IsFinalized());
if ((type_param.index() != i) || !type_param.IsFunctionTypeParameter()) {
return false;
}
// Instantiating nullable and legacy type parameters may change nullability
// of a type, so type arguments vector containing such type parameters
// cannot be substituted with the enclosing function type arguments, unless
// we check at runtime the nullability of the first 1 or 2 type arguments of
// the enclosing function type arguments.
if (type_param.IsNullable() || type_param.IsLegacy()) {
if (with_runtime_check == nullptr || i >= kNullabilityMaxTypes) {
return false;
}
*with_runtime_check = true;
}
}
return true;
}
bool TypeArguments::IsFinalized() const {
ASSERT(!IsNull());
AbstractType& type = AbstractType::Handle();
const intptr_t num_types = Length();
for (intptr_t i = 0; i < num_types; i++) {
type = TypeAt(i);
if (!type.IsFinalized()) {
return false;
}
}
return true;
}
TypeArgumentsPtr TypeArguments::InstantiateFrom(
const TypeArguments& instantiator_type_arguments,
const TypeArguments& function_type_arguments,
intptr_t num_free_fun_type_params,
Heap::Space space,
TrailPtr trail) const {
ASSERT(!IsInstantiated(kAny, num_free_fun_type_params));
if ((instantiator_type_arguments.IsNull() ||
instantiator_type_arguments.Length() == Length()) &&
IsUninstantiatedIdentity()) {
return instantiator_type_arguments.ptr();
}
const intptr_t num_types = Length();
TypeArguments& instantiated_array =
TypeArguments::Handle(TypeArguments::New(num_types, space));
AbstractType& type = AbstractType::Handle();
for (intptr_t i = 0; i < num_types; i++) {
type = TypeAt(i);
// If this type argument T is null, the type A containing T in its flattened
// type argument vector V is recursive and is still being finalized.
// T is the type argument of a super type of A. T is being instantiated
// during finalization of V, which is also the instantiator. T depends
// solely on the type parameters of A and will be replaced by a non-null
// type before A is marked as finalized.
if (!type.IsNull() &&
!type.IsInstantiated(kAny, num_free_fun_type_params)) {
type = type.InstantiateFrom(instantiator_type_arguments,
function_type_arguments,
num_free_fun_type_params, space, trail);
// A returned null type indicates a failed instantiation in dead code that
// must be propagated up to the caller, the optimizing compiler.
if (type.IsNull()) {
return Object::empty_type_arguments().ptr();
}
}
instantiated_array.SetTypeAt(i, type);
}
return instantiated_array.ptr();
}
TypeArgumentsPtr TypeArguments::InstantiateAndCanonicalizeFrom(
const TypeArguments& instantiator_type_arguments,
const TypeArguments& function_type_arguments) const {
auto thread = Thread::Current();
auto zone = thread->zone();
SafepointMutexLocker ml(
thread->isolate_group()->type_arguments_canonicalization_mutex());
ASSERT(!IsInstantiated());
ASSERT(instantiator_type_arguments.IsNull() ||
instantiator_type_arguments.IsCanonical());
ASSERT(function_type_arguments.IsNull() ||
function_type_arguments.IsCanonical());
// Lookup instantiators and if found, return instantiated result.
Array& prior_instantiations = Array::Handle(zone, instantiations());
ASSERT(!prior_instantiations.IsNull() && prior_instantiations.IsArray());
// The instantiations cache is initialized with Object::zero_array() and is
// therefore guaranteed to contain kNoInstantiator. No length check needed.
ASSERT(prior_instantiations.Length() > 0); // Always at least a sentinel.
intptr_t index = 0;
while (true) {
if ((prior_instantiations.At(
index +
TypeArguments::Instantiation::kInstantiatorTypeArgsIndex) ==
instantiator_type_arguments.ptr()) &&
(prior_instantiations.At(
index + TypeArguments::Instantiation::kFunctionTypeArgsIndex) ==
function_type_arguments.ptr())) {
return TypeArguments::RawCast(prior_instantiations.At(
index + TypeArguments::Instantiation::kInstantiatedTypeArgsIndex));
}
if (prior_instantiations.At(index) ==
Smi::New(TypeArguments::kNoInstantiator)) {
break;
}
index += TypeArguments::Instantiation::kSizeInWords;
}
// Cache lookup failed. Instantiate the type arguments.
TypeArguments& result = TypeArguments::Handle(zone);
result = InstantiateFrom(instantiator_type_arguments, function_type_arguments,
kAllFree, Heap::kOld);
// Canonicalize type arguments.
result = result.Canonicalize(thread, nullptr);
// InstantiateAndCanonicalizeFrom is not reentrant. It cannot have been called
// indirectly, so the prior_instantiations array cannot have grown.
ASSERT(prior_instantiations.ptr() == instantiations());
// Add instantiator and function type args and result to instantiations array.
intptr_t length = prior_instantiations.Length();
if ((index + TypeArguments::Instantiation::kSizeInWords) >= length) {
// TODO(regis): Should we limit the number of cached instantiations?
// Grow the instantiations array by about 50%, but at least by 1.
// The initial array is Object::zero_array() of length 1.
intptr_t entries =
(length - 1) / TypeArguments::Instantiation::kSizeInWords;
intptr_t new_entries = entries + (entries >> 1) + 1;
length = new_entries * TypeArguments::Instantiation::kSizeInWords + 1;
prior_instantiations =
Array::Grow(prior_instantiations, length, Heap::kOld);
set_instantiations(prior_instantiations);
ASSERT((index + TypeArguments::Instantiation::kSizeInWords) < length);
}
// Set sentinel marker at next position.
prior_instantiations.SetAt(
index + TypeArguments::Instantiation::kSizeInWords +
TypeArguments::Instantiation::kInstantiatorTypeArgsIndex,
Smi::Handle(zone, Smi::New(TypeArguments::kNoInstantiator)));
prior_instantiations.SetAt(
index + TypeArguments::Instantiation::kFunctionTypeArgsIndex,
function_type_arguments);
prior_instantiations.SetAt(
index + TypeArguments::Instantiation::kInstantiatedTypeArgsIndex, result);
// We let any concurrently running mutator thread now see the new entry by
// using a store-release barrier.
ASSERT(
prior_instantiations.At(
index + TypeArguments::Instantiation::kInstantiatorTypeArgsIndex) ==
Smi::New(TypeArguments::kNoInstantiator));
prior_instantiations.SetAtRelease(
index + TypeArguments::Instantiation::kInstantiatorTypeArgsIndex,
instantiator_type_arguments);
return result.ptr();
}
TypeArgumentsPtr TypeArguments::New(intptr_t len, Heap::Space space) {
if (len < 0 || len > kMaxElements) {
// This should be caught before we reach here.
FATAL1("Fatal error in TypeArguments::New: invalid len %" Pd "\n", len);
}
TypeArguments& result = TypeArguments::Handle();
{
ObjectPtr raw = Object::Allocate(TypeArguments::kClassId,
TypeArguments::InstanceSize(len), space,
/*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
// Length must be set before we start storing into the array.
result.SetLength(len);
result.SetHash(0);
result.set_nullability(0);
}
// The zero array should have been initialized.
ASSERT(Object::zero_array().ptr() != Array::null());
COMPILE_ASSERT(TypeArguments::kNoInstantiator == 0);
result.set_instantiations(Object::zero_array());
return result.ptr();
}
void TypeArguments::SetLength(intptr_t value) const {
ASSERT(!IsCanonical());
// This is only safe because we create a new Smi, which does not cause
// heap allocation.
untag()->set_length(Smi::New(value));
}
TypeArgumentsPtr TypeArguments::Canonicalize(Thread* thread,
TrailPtr trail) const {
if (IsNull() || IsCanonical()) {
ASSERT(IsOld());
return this->ptr();
}
const intptr_t num_types = Length();
if (num_types == 0) {
return TypeArguments::empty_type_arguments().ptr();
} else if (IsRaw(0, num_types)) {
return TypeArguments::null();
}
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
ObjectStore* object_store = isolate_group->object_store();
TypeArguments& result = TypeArguments::Handle(zone);
{
SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
CanonicalTypeArgumentsSet table(zone,
object_store->canonical_type_arguments());
result ^= table.GetOrNull(CanonicalTypeArgumentsKey(*this));
object_store->set_canonical_type_arguments(table.Release());
}
if (result.IsNull()) {
// Canonicalize each type argument.
AbstractType& type_arg = AbstractType::Handle(zone);
for (intptr_t i = 0; i < num_types; i++) {
type_arg = TypeAt(i);
type_arg = type_arg.Canonicalize(thread, trail);
if (IsCanonical()) {
// Canonicalizing this type_arg canonicalized this type.
ASSERT(IsRecursive());
return this->ptr();
}
SetTypeAt(i, type_arg);
}
// Canonicalization of a type argument of a recursive type argument vector
// may change the hash of the vector, so invalidate.
if (IsRecursive()) {
SetHash(0);
}
SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
CanonicalTypeArgumentsSet table(zone,
object_store->canonical_type_arguments());
// Since we canonicalized some type arguments above we need to lookup
// in the table again to make sure we don't already have an equivalent
// canonical entry.
result ^= table.GetOrNull(CanonicalTypeArgumentsKey(*this));
if (result.IsNull()) {
// Make sure we have an old space object and add it to the table.
if (this->IsNew()) {
result ^= Object::Clone(*this, Heap::kOld);
} else {
result = this->ptr();
}
ASSERT(result.IsOld());
result.ComputeNullability();
result.SetCanonical(); // Mark object as being canonical.
// Now add this TypeArgument into the canonical list of type arguments.
bool present = table.Insert(result);
ASSERT(!present);
}
object_store->set_canonical_type_arguments(table.Release());
}
ASSERT(result.Equals(*this));
ASSERT(!result.IsNull());
ASSERT(result.IsTypeArguments());
ASSERT(result.IsCanonical());
return result.ptr();
}
void TypeArguments::EnumerateURIs(URIs* uris) const {
if (IsNull()) {
return;
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
AbstractType& type = AbstractType::Handle(zone);
const intptr_t num_types = Length();
for (intptr_t i = 0; i < num_types; i++) {
type = TypeAt(i);
type.EnumerateURIs(uris);
}
}
const char* TypeArguments::ToCString() const {
if (IsNull()) {
return "TypeArguments: null"; // Optimizing the frequent case.
}
ZoneTextBuffer buffer(Thread::Current()->zone());
PrintTo(&buffer);
return buffer.buffer();
}
const char* PatchClass::ToCString() const {
const Class& cls = Class::Handle(patched_class());
const char* cls_name = cls.ToCString();
return OS::SCreate(Thread::Current()->zone(), "PatchClass for %s", cls_name);
}
PatchClassPtr PatchClass::New(const Class& patched_class,
const Class& origin_class) {
const PatchClass& result = PatchClass::Handle(PatchClass::New());
result.set_patched_class(patched_class);
result.set_origin_class(origin_class);
result.set_script(Script::Handle(origin_class.script()));
result.set_library_kernel_offset(-1);
return result.ptr();
}
PatchClassPtr PatchClass::New(const Class& patched_class,
const Script& script) {
const PatchClass& result = PatchClass::Handle(PatchClass::New());
result.set_patched_class(patched_class);
result.set_origin_class(patched_class);
result.set_script(script);
result.set_library_kernel_offset(-1);
return result.ptr();
}
PatchClassPtr PatchClass::New() {
ASSERT(Object::patch_class_class() != Class::null());
ObjectPtr raw =
Object::Allocate(PatchClass::kClassId, PatchClass::InstanceSize(),
Heap::kOld, /*compressed*/ true);
return static_cast<PatchClassPtr>(raw);
}
void PatchClass::set_patched_class(const Class& value) const {
untag()->set_patched_class(value.ptr());
}
void PatchClass::set_origin_class(const Class& value) const {
untag()->set_origin_class(value.ptr());
}
void PatchClass::set_script(const Script& value) const {
untag()->set_script(value.ptr());
}
void PatchClass::set_library_kernel_data(const ExternalTypedData& data) const {
untag()->set_library_kernel_data(data.ptr());
}
intptr_t Function::Hash() const {
return String::HashRawSymbol(name());
}
bool Function::HasBreakpoint() const {
#if defined(PRODUCT)
return false;
#else
auto thread = Thread::Current();
return thread->isolate_group()->debugger()->HasBreakpoint(thread, *this);
#endif
}
void Function::InstallOptimizedCode(const Code& code) const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
// We may not have previous code if FLAG_precompile is set.
// Hot-reload may have already disabled the current code.
if (HasCode() && !Code::Handle(CurrentCode()).IsDisabled()) {
Code::Handle(CurrentCode()).DisableDartCode();
}
AttachCode(code);
}
void Function::SetInstructions(const Code& value) const {
// Ensure that nobody is executing this function when we install it.
if (untag()->code() != Code::null() && HasCode()) {
SafepointOperationScope safepoint(Thread::Current());
SetInstructionsSafe(value);
} else {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
SetInstructionsSafe(value);
}
}
void Function::SetInstructionsSafe(const Code& value) const {
untag()->set_code(value.ptr());
StoreNonPointer(&untag()->entry_point_, value.EntryPoint());
StoreNonPointer(&untag()->unchecked_entry_point_,
value.UncheckedEntryPoint());
}
void Function::AttachCode(const Code& value) const {
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
// Finish setting up code before activating it.
value.set_owner(*this);
SetInstructions(value);
ASSERT(Function::Handle(value.function()).IsNull() ||
(value.function() == this->ptr()));
}
bool Function::HasCode() const {
NoSafepointScope no_safepoint;
ASSERT(untag()->code() != Code::null());
return untag()->code() != StubCode::LazyCompile().ptr();
}
bool Function::HasCode(FunctionPtr function) {
NoSafepointScope no_safepoint;
ASSERT(function->untag()->code() != Code::null());
return function->untag()->code() != StubCode::LazyCompile().ptr();
}
void Function::ClearCode() const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
untag()->set_unoptimized_code(Code::null());
SetInstructions(StubCode::LazyCompile());
#endif // defined(DART_PRECOMPILED_RUNTIME)
}
void Function::ClearCodeSafe() const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
untag()->set_unoptimized_code(Code::null());
SetInstructionsSafe(StubCode::LazyCompile());
#endif // defined(DART_PRECOMPILED_RUNTIME)
}
void Function::EnsureHasCompiledUnoptimizedCode() const {
ASSERT(!ForceOptimize());
Thread* thread = Thread::Current();
ASSERT(thread->IsMutatorThread());
// TODO(35224): DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame());
Zone* zone = thread->zone();
const Error& error =
Error::Handle(zone, Compiler::EnsureUnoptimizedCode(thread, *this));
if (!error.IsNull()) {
Exceptions::PropagateError(error);
}
}
void Function::SwitchToUnoptimizedCode() const {
ASSERT(HasOptimizedCode());
Thread* thread = Thread::Current();
DEBUG_ASSERT(
thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
Zone* zone = thread->zone();
// TODO(35224): DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame());
const Code& current_code = Code::Handle(zone, CurrentCode());
if (FLAG_trace_deoptimization_verbose) {
THR_Print("Disabling optimized code: '%s' entry: %#" Px "\n",
ToFullyQualifiedCString(), current_code.EntryPoint());
}
current_code.DisableDartCode();
const Error& error =
Error::Handle(zone, Compiler::EnsureUnoptimizedCode(thread, *this));
if (!error.IsNull()) {
Exceptions::PropagateError(error);
}
const Code& unopt_code = Code::Handle(zone, unoptimized_code());
unopt_code.Enable();
AttachCode(unopt_code);
}
void Function::SwitchToLazyCompiledUnoptimizedCode() const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
if (!HasOptimizedCode()) {
return;
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
ASSERT(thread->IsMutatorThread());
const Code& current_code = Code::Handle(zone, CurrentCode());
TIR_Print("Disabling optimized code for %s\n", ToCString());
current_code.DisableDartCode();
const Code& unopt_code = Code::Handle(zone, unoptimized_code());
if (unopt_code.IsNull()) {
// Set the lazy compile stub code.
TIR_Print("Switched to lazy compile stub for %s\n", ToCString());
SetInstructions(StubCode::LazyCompile());
return;
}
TIR_Print("Switched to unoptimized code for %s\n", ToCString());
AttachCode(unopt_code);
unopt_code.Enable();
#endif
}
void Function::set_unoptimized_code(const Code& value) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
DEBUG_ASSERT(IsMutatorOrAtSafepoint());
ASSERT(value.IsNull() || !value.is_optimized());
untag()->set_unoptimized_code(value.ptr());
#endif
}
ContextScopePtr Function::context_scope() const {
if (IsClosureFunction()) {
const Object& obj = Object::Handle(untag()->data());
ASSERT(!obj.IsNull());
return ClosureData::Cast(obj).context_scope();
}
return ContextScope::null();
}
void Function::set_context_scope(const ContextScope& value) const {
if (IsClosureFunction()) {
const Object& obj = Object::Handle(untag()->data());
ASSERT(!obj.IsNull());
ClosureData::Cast(obj).set_context_scope(value);
return;
}
UNREACHABLE();
}
InstancePtr Function::implicit_static_closure() const {
if (IsImplicitStaticClosureFunction()) {
const Object& obj = Object::Handle(untag()->data());
ASSERT(!obj.IsNull());
return ClosureData::Cast(obj).implicit_static_closure();
}
return Instance::null();
}
void Function::set_implicit_static_closure(const Instance& closure) const {
if (IsImplicitStaticClosureFunction()) {
const Object& obj = Object::Handle(untag()->data());
ASSERT(!obj.IsNull());
ClosureData::Cast(obj).set_implicit_static_closure(closure);
return;
}
UNREACHABLE();
}
ScriptPtr Function::eval_script() const {
const Object& obj = Object::Handle(untag()->data());
if (obj.IsScript()) {
return Script::Cast(obj).ptr();
}
return Script::null();
}
void Function::set_eval_script(const Script& script) const {
ASSERT(token_pos() == TokenPosition::kMinSource);
ASSERT(untag()->data() == Object::null());
set_data(script);
}
FunctionPtr Function::extracted_method_closure() const {
ASSERT(kind() == UntaggedFunction::kMethodExtractor);
const Object& obj = Object::Handle(untag()->data());
ASSERT(obj.IsFunction());
return Function::Cast(obj).ptr();
}
void Function::set_extracted_method_closure(const Function& value) const {
ASSERT(kind() == UntaggedFunction::kMethodExtractor);
ASSERT(untag()->data() == Object::null());
set_data(value);
}
ArrayPtr Function::saved_args_desc() const {
ASSERT(kind() == UntaggedFunction::kNoSuchMethodDispatcher ||
kind() == UntaggedFunction::kInvokeFieldDispatcher);
const Object& obj = Object::Handle(untag()->data());
ASSERT(obj.IsArray());
return Array::Cast(obj).ptr();
}
void Function::set_saved_args_desc(const Array& value) const {
ASSERT(kind() == UntaggedFunction::kNoSuchMethodDispatcher ||
kind() == UntaggedFunction::kInvokeFieldDispatcher);
ASSERT(untag()->data() == Object::null());
set_data(value);
}
FieldPtr Function::accessor_field() const {
ASSERT(kind() == UntaggedFunction::kImplicitGetter ||
kind() == UntaggedFunction::kImplicitSetter ||
kind() == UntaggedFunction::kImplicitStaticGetter ||
kind() == UntaggedFunction::kFieldInitializer);
return Field::RawCast(untag()->data());
}
void Function::set_accessor_field(const Field& value) const {
ASSERT(kind() == UntaggedFunction::kImplicitGetter ||
kind() == UntaggedFunction::kImplicitSetter ||
kind() == UntaggedFunction::kImplicitStaticGetter ||
kind() == UntaggedFunction::kFieldInitializer);
// Top level classes may be finalized multiple times.
ASSERT(untag()->data() == Object::null() || untag()->data() == value.ptr());
set_data(value);
}
FunctionPtr Function::parent_function() const {
if (!IsClosureFunction()) return Function::null();
Object& obj = Object::Handle(untag()->data());
ASSERT(!obj.IsNull());
#if defined(DART_PRECOMPILER)
obj = ClosureData::Cast(obj).parent_function();
obj = WeakSerializationReference::Unwrap(obj);
if (!obj.IsFunction()) return Function::null();
return Function::RawCast(obj.ptr());
#else
return ClosureData::Cast(obj).parent_function();
#endif
}
void Function::set_parent_function(const Function& value) const {
ASSERT(IsClosureFunction());
const Object& obj = Object::Handle(untag()->data());
ASSERT(!obj.IsNull());
ClosureData::Cast(obj).set_parent_function(value);
}
TypeArgumentsPtr Function::InstantiateToBounds(
Thread* thread,
DefaultTypeArgumentsKind* kind_out) const {
if (CachesDefaultTypeArguments()) {
// Always use the cached version, even if the type parameters are null,
// to catch cases where the cache isn't properly initialized.
return default_type_arguments(kind_out);
}
// No cached version, so just retrieve from the type parameters and return
// a canonicalized version..
if (type_parameters() == TypeArguments::null()) {
if (kind_out != nullptr) {
*kind_out = DefaultTypeArgumentsKind::kIsInstantiated;
}
return Object::empty_type_arguments().ptr();
}
auto& result = TypeArguments::Handle(thread->zone(), type_parameters());
result = InstantiateTypeArgumentsToBounds(thread, result);
if (kind_out != nullptr) {
// We just return is/is not instantiated if the value isn't cached, as
// the other checks may be more overhead at runtime than just doing the
// instantiation.
*kind_out = result.IsNull() || result.IsInstantiated()
? DefaultTypeArgumentsKind::kIsInstantiated
: DefaultTypeArgumentsKind::kNeedsInstantiation;
}
return result.ptr();
}
void Function::UpdateCachedDefaultTypeArguments(Thread* thread) const {
auto const zone = thread->zone();
auto& closure_function = Function::Handle(zone);
if (HasImplicitClosureFunction()) {
closure_function = ImplicitClosureFunction();
}
if (CachesDefaultTypeArguments()) {
auto defaults = &Object::empty_type_arguments();
const FunctionType& sig = FunctionType::Handle(zone, signature());
if (sig.NumTypeParameters(thread) > 0) {
const auto& params = TypeArguments::Handle(zone, sig.type_parameters());
const intptr_t num_params = params.Length();
auto& new_defaults = TypeArguments::Handle(
zone, TypeArguments::New(num_params, Heap::kNew));
// Only canonicalize the result if all the default arguments have been
// canonicalized, to avoid premature canonicalization of the arguments.
bool all_canonical = true;
auto& type = AbstractType::Handle(zone);
for (intptr_t i = 0; i < num_params; i++) {
type = params.TypeAt(i);
type = TypeParameter::Cast(type).default_argument();
if (!type.IsCanonical()) {
all_canonical = false;
}
new_defaults.SetTypeAt(i, type);
}
if (all_canonical) {
new_defaults = new_defaults.Canonicalize(thread);
}
defaults = &new_defaults;
}
set_default_type_arguments(*defaults);
if (!closure_function.IsNull()) {
closure_function.set_default_type_arguments(*defaults);
}
} else if (!closure_function.IsNull()) {
closure_function.UpdateCachedDefaultTypeArguments(thread);
}
}
TypeArgumentsPtr Function::default_type_arguments(
DefaultTypeArgumentsKind* kind_out) const {
if (!CachesDefaultTypeArguments()) {
UNREACHABLE();
}
const auto& closure_data = ClosureData::Handle(ClosureData::RawCast(data()));
ASSERT(!closure_data.IsNull());
if (kind_out != nullptr) {
*kind_out = closure_data.default_type_arguments_kind();
}
return closure_data.default_type_arguments();
}
void Function::set_default_type_arguments(const TypeArguments& value) const {
if (!CachesDefaultTypeArguments()) {
UNREACHABLE();
}
const auto& closure_data = ClosureData::Handle(ClosureData::RawCast(data()));
ASSERT(!closure_data.IsNull());
auto kind = DefaultTypeArgumentsKindFor(value);
ASSERT(kind != DefaultTypeArgumentsKind::kInvalid);
closure_data.set_default_type_arguments_kind(kind);
// We could just store null for the ksharesFunction/kSharesInstantiator cases,
// assuming all clients retrieve the DefaultTypeArgumentsKind to distinguish.
closure_data.set_default_type_arguments(value);
}
Function::DefaultTypeArgumentsKind Function::DefaultTypeArgumentsKindFor(
const TypeArguments& value) const {
if (value.IsNull() || value.IsInstantiated()) {
return DefaultTypeArgumentsKind::kIsInstantiated;
}
if (value.CanShareFunctionTypeArguments(*this)) {
return DefaultTypeArgumentsKind::kSharesFunctionTypeArguments;
}
const auto& cls = Class::Handle(Owner());
if (value.CanShareInstantiatorTypeArguments(cls)) {
return DefaultTypeArgumentsKind::kSharesInstantiatorTypeArguments;
}
return DefaultTypeArgumentsKind::kNeedsInstantiation;
}
// Enclosing outermost function of this local function.
FunctionPtr Function::GetOutermostFunction() const {
FunctionPtr parent = parent_function();
if (parent == Object::null()) {
return ptr();
}
Function& function = Function::Handle();
do {
function = parent;
parent = function.parent_function();
} while (parent != Object::null());
return function.ptr();
}
FunctionPtr Function::implicit_closure_function() const {
if (IsClosureFunction() || IsFactory() || IsDispatcherOrImplicitAccessor() ||
IsFieldInitializer() || IsFfiTrampoline()) {
return Function::null();
}
const Object& obj = Object::Handle(data());
ASSERT(obj.IsNull() || obj.IsScript() || obj.IsFunction() || obj.IsArray());
if (obj.IsNull() || obj.IsScript()) {
return Function::null();
}
if (obj.IsFunction()) {
return Function::Cast(obj).ptr();
}
ASSERT(is_native());
ASSERT(obj.IsArray());
const Object& res = Object::Handle(Array::Cast(obj).At(1));
return res.IsNull() ? Function::null() : Function::Cast(res).ptr();
}
void Function::set_implicit_closure_function(const Function& value) const {
ASSERT(!IsClosureFunction());
const Object& old_data = Object::Handle(data());
if (is_native()) {
ASSERT(old_data.IsArray());
ASSERT((Array::Cast(old_data).At(1) == Object::null()) || value.IsNull());
Array::Cast(old_data).SetAt(1, value);
} else {
// Maybe this function will turn into a native later on :-/
if (old_data.IsArray()) {
ASSERT((Array::Cast(old_data).At(1) == Object::null()) || value.IsNull());
Array::Cast(old_data).SetAt(1, value);
} else {
ASSERT(old_data.IsNull() || value.IsNull());
set_data(value);
}
}
}
void Function::SetFfiCSignature(const FunctionType& sig) const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(data());
ASSERT(!obj.IsNull());
FfiTrampolineData::Cast(obj).set_c_signature(sig);
}
FunctionTypePtr Function::FfiCSignature() const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(data());
ASSERT(!obj.IsNull());
return FfiTrampolineData::Cast(obj).c_signature();
}
bool Function::FfiCSignatureContainsHandles() const {
ASSERT(IsFfiTrampoline());
const FunctionType& c_signature = FunctionType::Handle(FfiCSignature());
const intptr_t num_params = c_signature.num_fixed_parameters();
for (intptr_t i = 0; i < num_params; i++) {
const bool is_handle =
AbstractType::Handle(c_signature.ParameterTypeAt(i)).type_class_id() ==
kFfiHandleCid;
if (is_handle) {
return true;
}
}
return AbstractType::Handle(c_signature.result_type()).type_class_id() ==
kFfiHandleCid;
}
bool Function::FfiCSignatureReturnsStruct() const {
ASSERT(IsFfiTrampoline());
const FunctionType& c_signature = FunctionType::Handle(FfiCSignature());
const auto& return_type = AbstractType::Handle(c_signature.result_type());
const bool predefined = IsFfiTypeClassId(return_type.type_class_id());
return !predefined;
}
int32_t Function::FfiCallbackId() const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(data());
ASSERT(!obj.IsNull());
return FfiTrampolineData::Cast(obj).callback_id();
}
void Function::SetFfiCallbackId(int32_t value) const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(data());
ASSERT(!obj.IsNull());
FfiTrampolineData::Cast(obj).set_callback_id(value);
}
FunctionPtr Function::FfiCallbackTarget() const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(data());
ASSERT(!obj.IsNull());
return FfiTrampolineData::Cast(obj).callback_target();
}
void Function::SetFfiCallbackTarget(const Function& target) const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(data());
ASSERT(!obj.IsNull());
FfiTrampolineData::Cast(obj).set_callback_target(target);
}
InstancePtr Function::FfiCallbackExceptionalReturn() const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(data());
ASSERT(!obj.IsNull());
return FfiTrampolineData::Cast(obj).callback_exceptional_return();
}
void Function::SetFfiCallbackExceptionalReturn(const Instance& value) const {
ASSERT(IsFfiTrampoline());
const Object& obj = Object::Handle(data());
ASSERT(!obj.IsNull());
FfiTrampolineData::Cast(obj).set_callback_exceptional_return(value);
}
const char* Function::KindToCString(UntaggedFunction::Kind kind) {
return UntaggedFunction::KindToCString(kind);
}
FunctionPtr Function::ForwardingTarget() const {
ASSERT(kind() == UntaggedFunction::kDynamicInvocationForwarder);
Array& checks = Array::Handle();
checks ^= data();
return Function::RawCast(checks.At(0));
}
void Function::SetForwardingChecks(const Array& checks) const {
ASSERT(kind() == UntaggedFunction::kDynamicInvocationForwarder);
ASSERT(checks.Length() >= 1);
ASSERT(Object::Handle(checks.At(0)).IsFunction());
set_data(checks);
}
// This field is heavily overloaded:
// kernel eval function: Array[0] = Script
// Array[1] = Kernel data
// Array[2] = Kernel offset of enclosing library
// method extractor: Function extracted closure function
// implicit getter: Field
// implicit setter: Field
// impl. static final gttr: Field
// field initializer: Field
// noSuchMethod dispatcher: Array arguments descriptor
// invoke-field dispatcher: Array arguments descriptor
// closure function: ClosureData
// irregexp function: Array[0] = RegExp
// Array[1] = Smi string specialization cid
// native function: Array[0] = String native name
// Array[1] = Function implicit closure function
// regular function: Function for implicit closure function
// ffi trampoline function: FfiTrampolineData (Dart->C)
// dyn inv forwarder: Array[0] = Function target
// Array[1] = TypeArguments default type args
void Function::set_data(const Object& value) const {
untag()->set_data(value.ptr());
}
void Function::set_name(const String& value) const {
ASSERT(value.IsSymbol());
untag()->set_name(value.ptr());
}
void Function::set_owner(const Object& value) const {
ASSERT(!value.IsNull());
untag()->set_owner(value.ptr());
}
RegExpPtr Function::regexp() const {
ASSERT(kind() == UntaggedFunction::kIrregexpFunction);
const Array& pair = Array::Cast(Object::Handle(data()));
return RegExp::RawCast(pair.At(0));
}
class StickySpecialization : public BitField<intptr_t, bool, 0, 1> {};
class StringSpecializationCid
: public BitField<intptr_t, intptr_t, 1, UntaggedObject::kClassIdTagSize> {
};
intptr_t Function::string_specialization_cid() const {
ASSERT(kind() == UntaggedFunction::kIrregexpFunction);
const Array& pair = Array::Cast(Object::Handle(data()));
return StringSpecializationCid::decode(Smi::Value(Smi::RawCast(pair.At(1))));
}
bool Function::is_sticky_specialization() const {
ASSERT(kind() == UntaggedFunction::kIrregexpFunction);
const Array& pair = Array::Cast(Object::Handle(data()));
return StickySpecialization::decode(Smi::Value(Smi::RawCast(pair.At(1))));
}
void Function::SetRegExpData(const RegExp& regexp,
intptr_t string_specialization_cid,
bool sticky) const {
ASSERT(kind() == UntaggedFunction::kIrregexpFunction);
ASSERT(IsStringClassId(string_specialization_cid));
ASSERT(data() == Object::null());
const Array& pair = Array::Handle(Array::New(2, Heap::kOld));
pair.SetAt(0, regexp);
pair.SetAt(1, Smi::Handle(Smi::New(StickySpecialization::encode(sticky) |
StringSpecializationCid::encode(
string_specialization_cid))));
set_data(pair);
}
StringPtr Function::native_name() const {
ASSERT(is_native());
const Object& obj = Object::Handle(data());
ASSERT(obj.IsArray());
return String::RawCast(Array::Cast(obj).At(0));
}
void Function::set_native_name(const String& value) const {
Zone* zone = Thread::Current()->zone();
ASSERT(is_native());
// Due to the fact that kernel needs to read in the constant table before the
// annotation data is available, we don't know at function creation time
// whether the function is a native or not.
//
// Reading the constant table can cause a static function to get an implicit
// closure function.
//
// We therefore handle both cases.
const Object& old_data = Object::Handle(zone, data());
ASSERT(old_data.IsNull() ||
(old_data.IsFunction() &&
Function::Handle(zone, Function::RawCast(old_data.ptr()))
.IsImplicitClosureFunction()));
const Array& pair = Array::Handle(zone, Array::New(2, Heap::kOld));
pair.SetAt(0, value);
pair.SetAt(1, old_data); // will be the implicit closure function if needed.
set_data(pair);
}
void Function::set_signature(const FunctionType& value) const {
// Signature may be reset to null in aot to save space.
untag()->set_signature(value.ptr());
if (!value.IsNull()) {
ASSERT(NumImplicitParameters() == value.num_implicit_parameters());
UpdateCachedDefaultTypeArguments(Thread::Current());
}
}
void FunctionType::set_result_type(const AbstractType& value) const {
ASSERT(!value.IsNull());
untag()->set_result_type(value.ptr());
}
AbstractTypePtr Function::ParameterTypeAt(intptr_t index) const {
const Array& parameter_types =
Array::Handle(untag()->signature()->untag()->parameter_types());
return AbstractType::RawCast(parameter_types.At(index));
}
AbstractTypePtr FunctionType::ParameterTypeAt(intptr_t index) const {
const Array& parameter_types = Array::Handle(untag()->parameter_types());
return AbstractType::RawCast(parameter_types.At(index));
}
void FunctionType::SetParameterTypeAt(intptr_t index,
const AbstractType& value) const {
ASSERT(!value.IsNull());
const Array& parameter_types = Array::Handle(untag()->parameter_types());
parameter_types.SetAt(index, value);
}
void Function::set_parameter_types(const Array& value) const {
ASSERT(value.IsNull() || value.Length() > 0);
untag()->signature()->untag()->set_parameter_types(value.ptr());
}
void FunctionType::set_parameter_types(const Array& value) const {
ASSERT(value.IsNull() || value.Length() > 0);
untag()->set_parameter_types(value.ptr());
}
StringPtr Function::ParameterNameAt(intptr_t index) const {
const Array& parameter_names = Array::Handle(untag()->parameter_names());
return String::RawCast(parameter_names.At(index));
}
void Function::SetParameterNamesFrom(const FunctionType& signature) const {
untag()->set_parameter_names(signature.parameter_names());
}
StringPtr FunctionType::ParameterNameAt(intptr_t index) const {
const Array& parameter_names = Array::Handle(untag()->parameter_names());
return String::RawCast(parameter_names.At(index));
}
void FunctionType::SetParameterNameAt(intptr_t index,
const String& value) const {
ASSERT(!value.IsNull() && value.IsSymbol());
const Array& parameter_names = Array::Handle(untag()->parameter_names());
parameter_names.SetAt(index, value);
}
void Function::set_parameter_names(const Array& value) const {
ASSERT(value.IsNull() || value.Length() > 0);
untag()->set_parameter_names(value.ptr());
}
void FunctionType::set_parameter_names(const Array& value) const {
ASSERT(value.IsNull() || value.Length() > 0);
untag()->set_parameter_names(value.ptr());
}
void FunctionType::CreateNameArrayIncludingFlags(Heap::Space space) const {
// Currently, we only store flags for named parameters that are required.
const intptr_t num_parameters = NumParameters();
if (num_parameters == 0) return;
intptr_t num_total_slots = num_parameters;
if (HasOptionalNamedParameters()) {
const intptr_t last_index = (NumOptionalNamedParameters() - 1) /
compiler::target::kNumParameterFlagsPerElement;
const intptr_t num_flag_slots = last_index + 1;
num_total_slots += num_flag_slots;
}
auto& array = Array::Handle(Array::New(num_total_slots, space));
if (num_total_slots > num_parameters) {
// Set flag slots to Smi 0 before handing off.
auto& empty_flags_smi = Smi::Handle(Smi::New(0));
for (intptr_t i = num_parameters; i < num_total_slots; i++) {
array.SetAt(i, empty_flags_smi);
}
}
set_parameter_names(array);
}
intptr_t FunctionType::GetRequiredFlagIndex(intptr_t index,
intptr_t* flag_mask) const {
// If these calculations change, also change
// FlowGraphBuilder::BuildClosureCallHasRequiredNamedArgumentsCheck.
ASSERT(flag_mask != nullptr);
ASSERT(index >= num_fixed_parameters());
index -= num_fixed_parameters();
*flag_mask = (1 << compiler::target::kRequiredNamedParameterFlag)
<< ((static_cast<uintptr_t>(index) %
compiler::target::kNumParameterFlagsPerElement) *
compiler::target::kNumParameterFlags);
return NumParameters() +
index / compiler::target::kNumParameterFlagsPerElement;
}
bool Function::HasRequiredNamedParameters() const {
const FunctionType& sig = FunctionType::Handle(signature());
#if defined(DART_PRECOMPILED_RUNTIME)
if (sig.IsNull()) {
// Signature is not dropped in aot when any named parameter is required.
return false;
}
#else
ASSERT(!sig.IsNull());
#endif
const Array& parameter_names = Array::Handle(sig.parameter_names());
return parameter_names.Length() > NumParameters();
}
bool Function::IsRequiredAt(intptr_t index) const {
if (index < num_fixed_parameters() + NumOptionalPositionalParameters()) {
return false;
}
const FunctionType& sig = FunctionType::Handle(signature());
#if defined(DART_PRECOMPILED_RUNTIME)
if (sig.IsNull()) {
// Signature is not dropped in aot when any named parameter is required.
return false;
}
#else
ASSERT(!sig.IsNull());
#endif
return sig.IsRequiredAt(index);
}
bool FunctionType::IsRequiredAt(intptr_t index) const {
if (index < num_fixed_parameters() + NumOptionalPositionalParameters()) {
return false;
}
intptr_t flag_mask;
const intptr_t flag_index = GetRequiredFlagIndex(index, &flag_mask);
const Array& parameter_names = Array::Handle(untag()->parameter_names());
if (flag_index >= parameter_names.Length()) {
return false;
}
const intptr_t flags =
Smi::Value(Smi::RawCast(parameter_names.At(flag_index)));
return (flags & flag_mask) != 0;
}
void FunctionType::SetIsRequiredAt(intptr_t index) const {
intptr_t flag_mask;
const intptr_t flag_index = GetRequiredFlagIndex(index, &flag_mask);
const Array& parameter_names = Array::Handle(untag()->parameter_names());
ASSERT(flag_index < parameter_names.Length());
const intptr_t flags =
Smi::Value(Smi::RawCast(parameter_names.At(flag_index)));
parameter_names.SetAt(flag_index, Smi::Handle(Smi::New(flags | flag_mask)));
}
void FunctionType::TruncateUnusedParameterFlags() const {
const intptr_t num_params = NumParameters();
if (num_params == 0) return;
const Array& parameter_names = Array::Handle(untag()->parameter_names());
if (parameter_names.Length() == num_params) {
// No flag slots to truncate.
return;
}
// Truncate the parameter names array to remove unused flags from the end.
intptr_t last_used = parameter_names.Length() - 1;
for (; last_used >= num_params; --last_used) {
if (Smi::Value(Smi::RawCast(parameter_names.At(last_used))) != 0) {
break;
}
}
parameter_names.Truncate(last_used + 1);
}
void FunctionType::FinalizeNameArrays(const Function& function) const {
TruncateUnusedParameterFlags();
if (!function.IsNull()) {
function.SetParameterNamesFrom(*this);
// Unless the function is a dispatcher, its number of type parameters
// must match the number of type parameters in its signature.
ASSERT(function.kind() == UntaggedFunction::kNoSuchMethodDispatcher ||
function.kind() == UntaggedFunction::kInvokeFieldDispatcher ||
function.kind() == UntaggedFunction::kDynamicInvocationForwarder ||
function.NumTypeParameters() == NumTypeParameters());
}
}
void FunctionType::set_type_parameters(const TypeArguments& value) const {
untag()->set_type_parameters(value.ptr());
}
static void ReportTooManyTypeParameters(const Function& function) {
Report::MessageF(Report::kError, Script::Handle(), TokenPosition::kNoSource,
Report::AtLocation,
"too many type parameters declared in function '%s'",
function.UserVisibleNameCString());
UNREACHABLE();
}
static void ReportTooManyTypeParameters(const FunctionType& sig) {
Report::MessageF(Report::kError, Script::Handle(), TokenPosition::kNoSource,
Report::AtLocation,
"too many type parameters declared in signature '%s' or in "
"its enclosing signatures",
sig.ToUserVisibleCString());
UNREACHABLE();
}
void FunctionType::SetNumParentTypeArguments(intptr_t value) const {
ASSERT(value >= 0);
if (!Utils::IsUint(UntaggedFunctionType::kMaxParentTypeArgumentsBits,
value)) {
ReportTooManyTypeParameters(*this);
}
const uint32_t* original = &untag()->packed_fields_;
StoreNonPointer(original,
UntaggedFunctionType::PackedNumParentTypeArguments::update(
value, *original));
}
void Function::SetNumTypeParameters(intptr_t value) const {
ASSERT(value >= 0);
if (!Utils::IsUint(UntaggedFunction::kMaxTypeParametersBits, value)) {
ReportTooManyTypeParameters(*this);
}
const uint32_t* original = &untag()->packed_fields_;
StoreNonPointer(original, UntaggedFunction::PackedNumTypeParameters::update(
value, *original));
}
intptr_t FunctionType::NumTypeParameters(Thread* thread) const {
if (type_parameters() == TypeArguments::null()) {
return 0;
}
REUSABLE_TYPE_ARGUMENTS_HANDLESCOPE(thread);
TypeArguments& type_params = thread->TypeArgumentsHandle();
type_params = type_parameters();
// We require null to represent a non-generic signature.
ASSERT(type_params.Length() != 0);
return type_params.Length();
}
intptr_t Function::NumParentTypeArguments() const {
// Don't allocate handle in cases where we know it is 0.
if (!IsClosureFunction()) return 0;
return FunctionType::Handle(signature()).NumParentTypeArguments();
}
TypeParameterPtr Function::LookupTypeParameter(const String& type_name,
intptr_t* function_level) const {
ASSERT(!type_name.IsNull());
Thread* thread = Thread::Current();
REUSABLE_TYPE_ARGUMENTS_HANDLESCOPE(thread);
REUSABLE_TYPE_PARAMETER_HANDLESCOPE(thread);
REUSABLE_STRING_HANDLESCOPE(thread);
REUSABLE_FUNCTION_HANDLESCOPE(thread);
TypeArguments& type_params = thread->TypeArgumentsHandle();
TypeParameter& type_param = thread->TypeParameterHandle();
String& type_param_name = thread->StringHandle();
Function& function = thread->FunctionHandle();
function = this->ptr();
while (!function.IsNull()) {
if (function.signature() != FunctionType::null()) {
type_params = function.type_parameters();
if (!type_params.IsNull()) {
const intptr_t num_type_params = type_params.Length();
for (intptr_t i = 0; i < num_type_params; i++) {
type_param ^= type_params.TypeAt(i);
type_param_name = type_param.name();
if (type_param_name.Equals(type_name)) {
return type_param.ptr();
}
}
}
}
if (function.IsImplicitClosureFunction()) {
// The parent function is not the enclosing function, but the closurized
// function with identical type parameters.
break;
}
function = function.parent_function();
if (function_level != NULL) {
(*function_level)--;
}
}
return TypeParameter::null();
}
void Function::set_kind(UntaggedFunction::Kind value) const {
untag()->kind_tag_.Update<KindBits>(value);
}
void Function::set_modifier(UntaggedFunction::AsyncModifier value) const {
untag()->kind_tag_.Update<ModifierBits>(value);
}
void Function::set_recognized_kind(MethodRecognizer::Kind value) const {
// Prevent multiple settings of kind.
ASSERT((value == MethodRecognizer::kUnknown) || !IsRecognized());
untag()->kind_tag_.Update<RecognizedBits>(value);
}
void Function::set_token_pos(TokenPosition token_pos) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
ASSERT(!token_pos.IsClassifying() || IsMethodExtractor());
StoreNonPointer(&untag()->token_pos_, token_pos);
#endif
}
void Function::set_kind_tag(uint32_t value) const {
untag()->kind_tag_ = value;
}
void Function::set_packed_fields(uint32_t packed_fields) const {
StoreNonPointer(&untag()->packed_fields_, packed_fields);
}
bool Function::IsOptimizable() const {
if (FLAG_precompiled_mode) {
return true;
}
if (ForceOptimize()) return true;
if (is_native()) {
// Native methods don't need to be optimized.
return false;
}
if (is_optimizable() && (script() != Script::null()) &&
SourceSize() < FLAG_huge_method_cutoff_in_tokens) {
// Additional check needed for implicit getters.
return (unoptimized_code() == Object::null()) ||
(Code::Handle(unoptimized_code()).Size() <
FLAG_huge_method_cutoff_in_code_size);
}
return false;
}
void Function::SetIsOptimizable(bool value) const {
ASSERT(!is_native());
set_is_optimizable(value);
if (!value) {
set_is_inlinable(false);
set_usage_counter(INT32_MIN);
}
}
#if !defined(DART_PRECOMPILED_RUNTIME)
bool Function::CanBeInlined() const {
// Our force-optimized functions cannot deoptimize to an unoptimized frame.
// If the instructions of the force-optimized function body get moved via
// code motion, we might attempt do deoptimize a frame where the force-
// optimized function has only partially finished. Since force-optimized
// functions cannot deoptimize to unoptimized frames we prevent them from
// being inlined (for now).
if (ForceOptimize()) {
if (IsFfiTrampoline()) {
// The CallSiteInliner::InlineCall asserts in PrepareGraphs that
// GraphEntryInstr::SuccessorCount() == 1, but FFI trampoline has two
// entries (a normal and a catch entry).
return false;
}
return CompilerState::Current().is_aot();
}
if (HasBreakpoint()) {
return false;
}
return is_inlinable() && !is_external() && !is_generated_body();
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
intptr_t Function::NumParameters() const {
return num_fixed_parameters() + NumOptionalParameters();
}
intptr_t Function::NumImplicitParameters() const {
const UntaggedFunction::Kind k = kind();
if (k == UntaggedFunction::kConstructor) {
// Type arguments for factory; instance for generative constructor.
return 1;
}
if ((k == UntaggedFunction::kClosureFunction) ||
(k == UntaggedFunction::kImplicitClosureFunction) ||
(k == UntaggedFunction::kFfiTrampoline)) {
return 1; // Closure object.
}
if (!is_static()) {
// Closure functions defined inside instance (i.e. non-static) functions are
// marked as non-static, but they do not have a receiver.
// Closures are handled above.
ASSERT((k != UntaggedFunction::kClosureFunction) &&
(k != UntaggedFunction::kImplicitClosureFunction));
return 1; // Receiver.
}
return 0; // No implicit parameters.
}
bool Function::AreValidArgumentCounts(intptr_t num_type_arguments,
intptr_t num_arguments,
intptr_t num_named_arguments,
String* error_message) const {
if ((num_type_arguments != 0) &&
(num_type_arguments != NumTypeParameters())) {
if (error_message != NULL) {
const intptr_t kMessageBufferSize = 64;
char message_buffer[kMessageBufferSize];
Utils::SNPrint(message_buffer, kMessageBufferSize,
"%" Pd " type arguments passed, but %" Pd " expected",
num_type_arguments, NumTypeParameters());
// Allocate in old space because it can be invoked in background
// optimizing compilation.
*error_message = String::New(message_buffer, Heap::kOld);
}
return false; // Too many type arguments.
}
if (num_named_arguments > NumOptionalNamedParameters()) {
if (error_message != NULL) {
const intptr_t kMessageBufferSize = 64;
char message_buffer[kMessageBufferSize];
Utils::SNPrint(message_buffer, kMessageBufferSize,
"%" Pd " named passed, at most %" Pd " expected",
num_named_arguments, NumOptionalNamedParameters());
// Allocate in old space because it can be invoked in background
// optimizing compilation.
*error_message = String::New(message_buffer, Heap::kOld);
}
return false; // Too many named arguments.
}
const intptr_t num_pos_args = num_arguments - num_named_arguments;
const intptr_t num_opt_pos_params = NumOptionalPositionalParameters();
const intptr_t num_pos_params = num_fixed_parameters() + num_opt_pos_params;
if (num_pos_args > num_pos_params) {
if (error_message != NULL) {
const intptr_t kMessageBufferSize = 64;
char message_buffer[kMessageBufferSize];
// Hide implicit parameters to the user.
const intptr_t num_hidden_params = NumImplicitParameters();
Utils::SNPrint(message_buffer, kMessageBufferSize,
"%" Pd "%s passed, %s%" Pd " expected",
num_pos_args - num_hidden_params,
num_opt_pos_params > 0 ? " positional" : "",
num_opt_pos_params > 0 ? "at most " : "",
num_pos_params - num_hidden_params);
// Allocate in old space because it can be invoked in background
// optimizing compilation.
*error_message = String::New(message_buffer, Heap::kOld);
}
return false; // Too many fixed and/or positional arguments.
}
if (num_pos_args < num_fixed_parameters()) {
if (error_message != NULL) {
const intptr_t kMessageBufferSize = 64;
char message_buffer[kMessageBufferSize];
// Hide implicit parameters to the user.
const intptr_t num_hidden_params = NumImplicitParameters();
Utils::SNPrint(message_buffer, kMessageBufferSize,
"%" Pd "%s passed, %s%" Pd " expected",
num_pos_args - num_hidden_params,
num_opt_pos_params > 0 ? " positional" : "",
num_opt_pos_params > 0 ? "at least " : "",
num_fixed_parameters() - num_hidden_params);
// Allocate in old space because it can be invoked in background
// optimizing compilation.
*error_message = String::New(message_buffer, Heap::kOld);
}
return false; // Too few fixed and/or positional arguments.
}
return true;
}
bool Function::AreValidArguments(intptr_t num_type_arguments,
intptr_t num_arguments,
const Array& argument_names,
String* error_message) const {
const Array& args_desc_array = Array::Handle(ArgumentsDescriptor::NewBoxed(
num_type_arguments, num_arguments, argument_names, Heap::kNew));
ArgumentsDescriptor args_desc(args_desc_array);
return AreValidArguments(args_desc, error_message);
}
bool Function::AreValidArguments(const ArgumentsDescriptor& args_desc,
String* error_message) const {
const intptr_t num_type_arguments = args_desc.TypeArgsLen();
const intptr_t num_arguments = args_desc.Count();
const intptr_t num_named_arguments = args_desc.NamedCount();
if (!AreValidArgumentCounts(num_type_arguments, num_arguments,
num_named_arguments, error_message)) {
return false;
}
// Verify that all argument names are valid parameter names.
Thread* thread = Thread::Current();
auto isolate_group = thread->isolate_group();
Zone* zone = thread->zone();
String& argument_name = String::Handle(zone);
String& parameter_name = String::Handle(zone);
const intptr_t num_positional_args = num_arguments - num_named_arguments;
const intptr_t num_parameters = NumParameters();
for (intptr_t i = 0; i < num_named_arguments; i++) {
argument_name = args_desc.NameAt(i);
ASSERT(argument_name.IsSymbol());
bool found = false;
for (intptr_t j = num_positional_args; j < num_parameters; j++) {
parameter_name = ParameterNameAt(j);
ASSERT(parameter_name.IsSymbol());
if (argument_name.Equals(parameter_name)) {
found = true;
break;
}
}
if (!found) {
if (error_message != nullptr) {
const intptr_t kMessageBufferSize = 64;
char message_buffer[kMessageBufferSize];
Utils::SNPrint(message_buffer, kMessageBufferSize,
"no optional formal parameter named '%s'",
argument_name.ToCString());
*error_message = String::New(message_buffer);
}
return false;
}
}
if (isolate_group->use_strict_null_safety_checks()) {
// Verify that all required named parameters are filled.
for (intptr_t j = num_parameters - NumOptionalNamedParameters();
j < num_parameters; j++) {
if (IsRequiredAt(j)) {
parameter_name = ParameterNameAt(j);
ASSERT(parameter_name.IsSymbol());
bool found = false;
for (intptr_t i = 0; i < num_named_arguments; i++) {
argument_name = args_desc.NameAt(i);
ASSERT(argument_name.IsSymbol());
if (argument_name.Equals(parameter_name)) {
found = true;
break;
}
}
if (!found) {
if (error_message != nullptr) {
const intptr_t kMessageBufferSize = 64;
char message_buffer[kMessageBufferSize];
Utils::SNPrint(message_buffer, kMessageBufferSize,
"missing required named parameter '%s'",
parameter_name.ToCString());
*error_message = String::New(message_buffer);
}
return false;
}
}
}
}
return true;
}
// Retrieves the function type arguments, if any. This could be explicitly
// passed type from the arguments array, delayed type arguments in closures,
// or instantiated bounds for the type parameters if no other source for
// function type arguments are found.
static TypeArgumentsPtr RetrieveFunctionTypeArguments(
Thread* thread,
Zone* zone,
const Function& function,
const Instance& receiver,
const TypeArguments& instantiator_type_args,
const TypeArguments& type_params,
const Array& args,
const ArgumentsDescriptor& args_desc) {
ASSERT(!function.IsNull());
const intptr_t kNumCurrentTypeArgs = function.NumTypeParameters();
const intptr_t kNumParentTypeArgs = function.NumParentTypeArguments();
const intptr_t kNumTypeArgs = kNumCurrentTypeArgs + kNumParentTypeArgs;
// Non-generic functions don't receive type arguments.
if (kNumTypeArgs == 0) return Object::empty_type_arguments().ptr();
// Closure functions require that the receiver be provided (and is a closure).
ASSERT(!function.IsClosureFunction() || receiver.IsClosure());
// Only closure functions should have possibly generic parents.
ASSERT(function.IsClosureFunction() || kNumParentTypeArgs == 0);
const auto& parent_type_args =
function.IsClosureFunction()
? TypeArguments::Handle(
zone, Closure::Cast(receiver).function_type_arguments())
: Object::empty_type_arguments();
// We don't try to instantiate the parent type parameters to their bounds
// if not provided or check any closed-over type arguments against the parent
// type parameter bounds (since they have been type checked already).
if (kNumCurrentTypeArgs == 0) return parent_type_args.ptr();
auto& function_type_args = TypeArguments::Handle(zone);
// First check for delayed type arguments before using either provided or
// default type arguments.
bool has_delayed_type_args = false;
if (function.IsClosureFunction()) {
const auto& closure = Closure::Cast(receiver);
function_type_args = closure.delayed_type_arguments();
has_delayed_type_args =
function_type_args.ptr() != Object::empty_type_arguments().ptr();
}
if (args_desc.TypeArgsLen() > 0) {
// We should never end up here when the receiver is a closure with delayed
// type arguments unless this dynamically called closure function was
// retrieved directly from the closure instead of going through
// DartEntry::ResolveCallable, which appropriately checks for this case.
ASSERT(!has_delayed_type_args);
function_type_args ^= args.At(0);
} else if (!has_delayed_type_args) {
// We have no explicitly provided function type arguments, so instantiate
// the type parameters to bounds or replace as appropriate.
Function::DefaultTypeArgumentsKind kind;
function_type_args = function.InstantiateToBounds(thread, &kind);
switch (kind) {
case Function::DefaultTypeArgumentsKind::kInvalid:
// We shouldn't hit the invalid case.
UNREACHABLE();
break;
case Function::DefaultTypeArgumentsKind::kIsInstantiated:
// Nothing left to do.
break;
case Function::DefaultTypeArgumentsKind::kNeedsInstantiation:
function_type_args = function_type_args.InstantiateAndCanonicalizeFrom(
instantiator_type_args, parent_type_args);
break;
case Function::DefaultTypeArgumentsKind::kSharesInstantiatorTypeArguments:
function_type_args = instantiator_type_args.ptr();
break;
case Function::DefaultTypeArgumentsKind::kSharesFunctionTypeArguments:
function_type_args = parent_type_args.ptr();
break;
}
}
return function_type_args.Prepend(zone, parent_type_args, kNumParentTypeArgs,
kNumTypeArgs);
}
// Retrieves the instantiator type arguments, if any, from the receiver.
static TypeArgumentsPtr RetrieveInstantiatorTypeArguments(
Zone* zone,
const Function& function,
const Instance& receiver) {
if (function.IsClosureFunction()) {
ASSERT(receiver.IsClosure());
const auto& closure = Closure::Cast(receiver);
return closure.instantiator_type_arguments();
}
if (!receiver.IsNull()) {
const auto& cls = Class::Handle(zone, receiver.clazz());
if (cls.NumTypeArguments() > 0) {
return receiver.GetTypeArguments();
}
}
return Object::empty_type_arguments().ptr();
}
ObjectPtr Function::DoArgumentTypesMatch(
const Array& args,
const ArgumentsDescriptor& args_desc) const {
#if defined(DART_PRECOMPILED_RUNTIME)
if (signature() == FunctionType::null()) {
// Precompiler deleted signature because of missing entry point pragma.
return EntryPointMemberInvocationError(*this);
}
#endif
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
auto& receiver = Instance::Handle(zone);
if (IsClosureFunction() || HasThisParameter()) {
receiver ^= args.At(args_desc.FirstArgIndex());
}
const auto& instantiator_type_arguments = TypeArguments::Handle(
zone, RetrieveInstantiatorTypeArguments(zone, *this, receiver));
return Function::DoArgumentTypesMatch(args, args_desc,
instantiator_type_arguments);
}
ObjectPtr Function::DoArgumentTypesMatch(
const Array& args,
const ArgumentsDescriptor& args_desc,
const TypeArguments& instantiator_type_arguments) const {
#if defined(DART_PRECOMPILED_RUNTIME)
if (signature() == FunctionType::null()) {
// Precompiler deleted signature because of missing entry point pragma.
return EntryPointMemberInvocationError(*this);
}
#endif
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
auto& receiver = Instance::Handle(zone);
if (IsClosureFunction() || HasThisParameter()) {
receiver ^= args.At(args_desc.FirstArgIndex());
}
const auto& params = TypeArguments::Handle(zone, type_parameters());
const auto& function_type_arguments = TypeArguments::Handle(
zone, RetrieveFunctionTypeArguments(thread, zone, *this, receiver,
instantiator_type_arguments, params,
args, args_desc));
return Function::DoArgumentTypesMatch(
args, args_desc, instantiator_type_arguments, function_type_arguments);
}
ObjectPtr Function::DoArgumentTypesMatch(
const Array& args,
const ArgumentsDescriptor& args_desc,
const TypeArguments& instantiator_type_arguments,
const TypeArguments& function_type_arguments) const {
#if defined(DART_PRECOMPILED_RUNTIME)
if (signature() == FunctionType::null()) {
// Precompiler deleted signature because of missing entry point pragma.
return EntryPointMemberInvocationError(*this);
}
#endif
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
// Perform any non-covariant bounds checks on the provided function type
// arguments to make sure they are appropriate subtypes of the bounds.
const intptr_t kNumLocalTypeArgs = NumTypeParameters();
if (kNumLocalTypeArgs > 0) {
const intptr_t kNumParentTypeArgs = NumParentTypeArguments();
ASSERT(function_type_arguments.HasCount(kNumParentTypeArgs +
kNumLocalTypeArgs));
const auto& params = TypeArguments::Handle(zone, type_parameters());
auto& parameter = TypeParameter::Handle(zone);
auto& type = AbstractType::Handle(zone);
auto& bound = AbstractType::Handle(zone);
for (intptr_t i = 0; i < kNumLocalTypeArgs; i++) {
parameter ^= params.TypeAt(i);
type = parameter.ptr();
bound = parameter.bound();
// Only perform non-covariant checks where the bound is not the top type.
if (parameter.IsGenericCovariantImpl() || bound.IsTopTypeForSubtyping()) {
continue;
}
if (!AbstractType::InstantiateAndTestSubtype(&type, &bound,
instantiator_type_arguments,
function_type_arguments)) {
const auto& name = String::Handle(zone, parameter.name());
return Error::RawCast(ThrowTypeError(token_pos(), type, bound, name));
}
}
} else {
ASSERT(function_type_arguments.HasCount(NumParentTypeArguments()));
}
AbstractType& type = AbstractType::Handle(zone);
Instance& argument = Instance::Handle(zone);
auto check_argument = [](const Instance& argument, const AbstractType& type,
const TypeArguments& instantiator_type_args,
const TypeArguments& function_type_args) -> bool {
// If the argument type is the top type, no need to check.
if (type.IsTopTypeForSubtyping()) return true;
if (argument.IsNull()) {
return Instance::NullIsAssignableTo(type, instantiator_type_args,
function_type_args);
}
return argument.IsAssignableTo(type, instantiator_type_args,
function_type_args);
};
// Check types of the provided arguments against the expected parameter types.
const intptr_t arg_offset = args_desc.FirstArgIndex();
// Only check explicit arguments.
const intptr_t arg_start = arg_offset + NumImplicitParameters();
const intptr_t end_positional_args = arg_offset + args_desc.PositionalCount();
for (intptr_t arg_index = arg_start; arg_index < end_positional_args;
++arg_index) {
argument ^= args.At(arg_index);
// Adjust for type arguments when they're present.
const intptr_t param_index = arg_index - arg_offset;
type = ParameterTypeAt(param_index);
if (!check_argument(argument, type, instantiator_type_arguments,
function_type_arguments)) {
auto& name = String::Handle(zone, ParameterNameAt(param_index));
if (!type.IsInstantiated()) {
type =
type.InstantiateFrom(instantiator_type_arguments,
function_type_arguments, kAllFree, Heap::kNew);
}
return ThrowTypeError(token_pos(), argument, type, name);
}
}
const intptr_t num_named_arguments = args_desc.NamedCount();
if (num_named_arguments == 0) {
return Error::null();
}
const int num_parameters = NumParameters();
const int num_fixed_params = num_fixed_parameters();
String& argument_name = String::Handle(zone);
String& parameter_name = String::Handle(zone);
// Check types of named arguments against expected parameter type.
for (intptr_t named_index = 0; named_index < num_named_arguments;
named_index++) {
argument_name = args_desc.NameAt(named_index);
ASSERT(argument_name.IsSymbol());
argument ^= args.At(arg_offset + args_desc.PositionAt(named_index));
// Try to find the named parameter that matches the provided argument.
// Even when annotated with @required, named parameters are still stored
// as if they were optional and so come after the fixed parameters.
// Currently O(n^2) as there's no guarantee from either the CFE or the
// VM that named parameters and named arguments are sorted in the same way.
intptr_t param_index = num_fixed_params;
for (; param_index < num_parameters; param_index++) {
parameter_name = ParameterNameAt(param_index);
ASSERT(parameter_name.IsSymbol());
if (!parameter_name.Equals(argument_name)) continue;
type = ParameterTypeAt(param_index);
if (!check_argument(argument, type, instantiator_type_arguments,
function_type_arguments)) {
auto& name = String::Handle(zone, ParameterNameAt(param_index));
if (!type.IsInstantiated()) {
type = type.InstantiateFrom(instantiator_type_arguments,
function_type_arguments, kAllFree,
Heap::kNew);
}
return ThrowTypeError(token_pos(), argument, type, name);
}
break;
}
// Only should fail if AreValidArguments returns a false positive.
ASSERT(param_index < num_parameters);
}
return Error::null();
}
// Helper allocating a C string buffer in the zone, printing the fully qualified
// name of a function in it, and replacing ':' by '_' to make sure the
// constructed name is a valid C++ identifier for debugging purpose.
// Set 'chars' to allocated buffer and return number of written characters.
enum QualifiedFunctionLibKind {
kQualifiedFunctionLibKindLibUrl,
kQualifiedFunctionLibKindLibName
};
static intptr_t ConstructFunctionFullyQualifiedCString(
const Function& function,
char** chars,
intptr_t reserve_len,
bool with_lib,
QualifiedFunctionLibKind lib_kind) {
Zone* zone = Thread::Current()->zone();
const char* name = String::Handle(zone, function.name()).ToCString();
const char* function_format = (reserve_len == 0) ? "%s" : "%s_";
reserve_len += Utils::SNPrint(NULL, 0, function_format, name);
const Function& parent = Function::Handle(zone, function.parent_function());
intptr_t written = 0;
if (parent.IsNull()) {
const Class& function_class = Class::Handle(zone, function.Owner());
ASSERT(!function_class.IsNull());
const char* class_name =
String::Handle(zone, function_class.Name()).ToCString();
ASSERT(class_name != NULL);
const char* library_name = NULL;
const char* lib_class_format = NULL;
if (with_lib) {
const Library& library = Library::Handle(zone, function_class.library());
ASSERT(!library.IsNull());
switch (lib_kind) {
case kQualifiedFunctionLibKindLibUrl:
library_name = String::Handle(zone, library.url()).ToCString();
break;
case kQualifiedFunctionLibKindLibName:
library_name = String::Handle(zone, library.name()).ToCString();
break;
default:
UNREACHABLE();
}
ASSERT(library_name != NULL);
lib_class_format = (library_name[0] == '\0') ? "%s%s_" : "%s_%s_";
} else {
library_name = "";
lib_class_format = "%s%s.";
}
reserve_len +=
Utils::SNPrint(NULL, 0, lib_class_format, library_name, class_name);
ASSERT(chars != NULL);
*chars = zone->Alloc<char>(reserve_len + 1);
written = Utils::SNPrint(*chars, reserve_len + 1, lib_class_format,
library_name, class_name);
} else {
written = ConstructFunctionFullyQualifiedCString(parent, chars, reserve_len,
with_lib, lib_kind);
}
ASSERT(*chars != NULL);
char* next = *chars + written;
written += Utils::SNPrint(next, reserve_len + 1, function_format, name);
// Replace ":" with "_".
while (true) {
next = strchr(next, ':');
if (next == NULL) break;
*next = '_';
}
return written;
}
const char* Function::ToFullyQualifiedCString() const {
char* chars = NULL;
ConstructFunctionFullyQualifiedCString(*this, &chars, 0, true,
kQualifiedFunctionLibKindLibUrl);
return chars;
}
const char* Function::ToLibNamePrefixedQualifiedCString() const {
char* chars = NULL;
ConstructFunctionFullyQualifiedCString(*this, &chars, 0, true,
kQualifiedFunctionLibKindLibName);
return chars;
}
const char* Function::ToQualifiedCString() const {
char* chars = NULL;
ConstructFunctionFullyQualifiedCString(*this, &chars, 0, false,
kQualifiedFunctionLibKindLibUrl);
return chars;
}
AbstractTypePtr FunctionType::InstantiateFrom(
const TypeArguments& instantiator_type_arguments,
const TypeArguments& function_type_arguments,
intptr_t num_free_fun_type_params,
Heap::Space space,
TrailPtr trail) const {
ASSERT(IsFinalized() || IsBeingFinalized());
Zone* zone = Thread::Current()->zone();
const intptr_t num_parent_type_args = NumParentTypeArguments();
bool delete_type_parameters = false;
if (num_free_fun_type_params == kCurrentAndEnclosingFree) {
// See the comment on kCurrentAndEnclosingFree to understand why we don't
// adjust 'num_free_fun_type_params' downward in this case.
num_free_fun_type_params = kAllFree;
delete_type_parameters = true;
} else {
ASSERT(!IsInstantiated(kAny, num_free_fun_type_params));
// We only consider the function type parameters declared by the parents
// of this signature function as free.
if (num_parent_type_args < num_free_fun_type_params) {
num_free_fun_type_params = num_parent_type_args;
}
}
// The number of parent type parameters that remain uninstantiated.
const intptr_t remaining_parent_type_params =
num_free_fun_type_params < num_parent_type_args
? num_parent_type_args - num_free_fun_type_params
: 0;
FunctionType& sig = FunctionType::Handle(
FunctionType::New(remaining_parent_type_params, nullability(), space));
AbstractType& type = AbstractType::Handle(zone);
// Copy the type parameters and instantiate their bounds (if necessary).
if (!delete_type_parameters) {
const TypeArguments& type_params =
TypeArguments::Handle(zone, type_parameters());
if (!type_params.IsNull()) {
TypeArguments& instantiated_type_params = TypeArguments::Handle(zone);
TypeParameter& type_param = TypeParameter::Handle(zone);
String& param_name = String::Handle(zone);
for (intptr_t i = 0; i < type_params.Length(); ++i) {
type_param ^= type_params.TypeAt(i);
ASSERT(type_param.index() == num_parent_type_args + i);
type = type_param.bound();
if (!type.IsInstantiated(kAny, num_free_fun_type_params)) {
type = type.InstantiateFrom(instantiator_type_arguments,
function_type_arguments,
num_free_fun_type_params, space, trail);
// A returned null type indicates a failed instantiation in dead code
// that must be propagated up to the caller, the optimizing compiler.
if (type.IsNull()) {
return FunctionType::null();
}
ASSERT(type_param.IsFinalized());
param_name = type_param.name();
type_param = TypeParameter::New(
Object::null_class(), type_param.base(), type_param.index(),
param_name, type, type_param.IsGenericCovariantImpl(),
type_param.nullability());
type_param.SetIsFinalized();
if (instantiated_type_params.IsNull()) {
instantiated_type_params = TypeArguments::New(type_params.Length());
for (intptr_t j = 0; j < i; ++j) {
type = type_params.TypeAt(j);
instantiated_type_params.SetTypeAt(j, type);
}
}
instantiated_type_params.SetTypeAt(i, type_param);
} else if (!instantiated_type_params.IsNull()) {
instantiated_type_params.SetTypeAt(i, type_param);
}
}
sig.set_type_parameters(instantiated_type_params.IsNull()
? type_params
: instantiated_type_params);
}
}
type = result_type();
if (!type.IsInstantiated(kAny, num_free_fun_type_params)) {
type = type.InstantiateFrom(instantiator_type_arguments,
function_type_arguments,
num_free_fun_type_params, space, trail);
// A returned null type indicates a failed instantiation in dead code that
// must be propagated up to the caller, the optimizing compiler.
if (type.IsNull()) {
return FunctionType::null();
}
}
sig.set_result_type(type);
const intptr_t num_params = NumParameters();
sig.set_num_implicit_parameters(num_implicit_parameters());
sig.set_num_fixed_parameters(num_fixed_parameters());
sig.SetNumOptionalParameters(NumOptionalParameters(),
HasOptionalPositionalParameters());
sig.set_parameter_types(Array::Handle(Array::New(num_params, space)));
for (intptr_t i = 0; i < num_params; i++) {
type = ParameterTypeAt(i);
if (!type.IsInstantiated(kAny, num_free_fun_type_params)) {
type = type.InstantiateFrom(instantiator_type_arguments,
function_type_arguments,
num_free_fun_type_params, space, trail);
// A returned null type indicates a failed instantiation in dead code that
// must be propagated up to the caller, the optimizing compiler.
if (type.IsNull()) {
return FunctionType::null();
}
}
sig.SetParameterTypeAt(i, type);
}
sig.set_parameter_names(Array::Handle(zone, parameter_names()));
if (delete_type_parameters) {
ASSERT(sig.IsInstantiated(kFunctions));
}
if (IsFinalized()) {
sig.SetIsFinalized();
} else {
if (IsBeingFinalized()) {
sig.SetIsBeingFinalized();
}
}
// Canonicalization is not part of instantiation.
return sig.ptr();
}
// Checks if the type of the specified parameter of this signature is a
// supertype of the type of the specified parameter of the other signature
// (i.e. check parameter contravariance).
// Note that types marked as covariant are already dealt with in the front-end.
bool FunctionType::IsContravariantParameter(intptr_t parameter_position,
const FunctionType& other,
intptr_t other_parameter_position,
Heap::Space space) const {
const AbstractType& param_type =
AbstractType::Handle(ParameterTypeAt(parameter_position));
if (param_type.IsTopTypeForSubtyping()) {
return true;
}
const AbstractType& other_param_type =
AbstractType::Handle(other.ParameterTypeAt(other_parameter_position));
return other_param_type.IsSubtypeOf(param_type, space);
}
bool FunctionType::HasSameTypeParametersAndBounds(const FunctionType& other,
TypeEquality kind,
TrailPtr trail) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const intptr_t num_type_params = NumTypeParameters(thread);
if (num_type_params != other.NumTypeParameters(thread)) {
return false;
}
if (num_type_params > 0) {
const TypeArguments& type_params =
TypeArguments::Handle(zone, type_parameters());
ASSERT(!type_params.IsNull());
const TypeArguments& other_type_params =
TypeArguments::Handle(zone, other.type_parameters());
ASSERT(!other_type_params.IsNull());
TypeParameter& type_param = TypeParameter::Handle(zone);
TypeParameter& other_type_param = TypeParameter::Handle(zone);
for (intptr_t i = 0; i < num_type_params; i++) {
type_param ^= type_params.TypeAt(i);
other_type_param ^= other_type_params.TypeAt(i);
if (!type_param.IsEquivalent(other_type_param, kind, trail)) {
return false;
}
}
}
return true;
}
bool FunctionType::IsSubtypeOf(const FunctionType& other,
Heap::Space space) const {
const intptr_t num_fixed_params = num_fixed_parameters();
const intptr_t num_opt_pos_params = NumOptionalPositionalParameters();
const intptr_t num_opt_named_params = NumOptionalNamedParameters();
const intptr_t other_num_fixed_params = other.num_fixed_parameters();
const intptr_t other_num_opt_pos_params =
other.NumOptionalPositionalParameters();
const intptr_t other_num_opt_named_params =
other.NumOptionalNamedParameters();
// This signature requires the same arguments or less and accepts the same
// arguments or more. We can ignore implicit parameters.
const intptr_t num_ignored_params = num_implicit_parameters();
const intptr_t other_num_ignored_params = other.num_implicit_parameters();
if (((num_fixed_params - num_ignored_params) >
(other_num_fixed_params - other_num_ignored_params)) ||
((num_fixed_params - num_ignored_params + num_opt_pos_params) <
(other_num_fixed_params - other_num_ignored_params +
other_num_opt_pos_params)) ||
(num_opt_named_params < other_num_opt_named_params)) {
return false;
}
// Check the type parameters and bounds of generic functions.
if (!HasSameTypeParametersAndBounds(other, TypeEquality::kInSubtypeTest)) {
return false;
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
// Check the result type.
const AbstractType& other_res_type =
AbstractType::Handle(zone, other.result_type());
// 'void Function()' is a subtype of 'Object Function()'.
if (!other_res_type.IsTopTypeForSubtyping()) {
const AbstractType& res_type = AbstractType::Handle(zone, result_type());
if (!res_type.IsSubtypeOf(other_res_type, space)) {
return false;
}
}
// Check the types of fixed and optional positional parameters.
for (intptr_t i = 0; i < (other_num_fixed_params - other_num_ignored_params +
other_num_opt_pos_params);
i++) {
if (!IsContravariantParameter(i + num_ignored_params, other,
i + other_num_ignored_params, space)) {
return false;
}
}
// Check that for each optional named parameter of type T of the other
// function type, there exists an optional named parameter of this function
// type with an identical name and with a type S that is a supertype of T.
// Note that SetParameterNameAt() guarantees that names are symbols, so we
// can compare their raw pointers.
const int num_params = num_fixed_params + num_opt_named_params;
const int other_num_params =
other_num_fixed_params + other_num_opt_named_params;
bool found_param_name;
String& other_param_name = String::Handle(zone);
for (intptr_t i = other_num_fixed_params; i < other_num_params; i++) {
other_param_name = other.ParameterNameAt(i);
ASSERT(other_param_name.IsSymbol());
found_param_name = false;
for (intptr_t j = num_fixed_params; j < num_params; j++) {
ASSERT(String::Handle(zone, ParameterNameAt(j)).IsSymbol());
if (ParameterNameAt(j) == other_param_name.ptr()) {
found_param_name = true;
if (!IsContravariantParameter(j, other, i, space)) {
return false;
}
break;
}
}
if (!found_param_name) {
return false;
}
}
if (isolate_group->use_strict_null_safety_checks()) {
// Check that for each required named parameter in this function, there's a
// corresponding required named parameter in the other function.
String& param_name = other_param_name;
for (intptr_t j = num_params - num_opt_named_params; j < num_params; j++) {
if (IsRequiredAt(j)) {
param_name = ParameterNameAt(j);
ASSERT(param_name.IsSymbol());
bool found = false;
for (intptr_t i = other_num_fixed_params; i < other_num_params; i++) {
ASSERT(String::Handle(zone, other.ParameterNameAt(i)).IsSymbol());
if (other.ParameterNameAt(i) == param_name.ptr()) {
found = true;
if (!other.IsRequiredAt(i)) {
return false;
}
}
}
if (!found) {
return false;
}
}
}
}
return true;
}
// The compiler generates an implicit constructor if a class definition
// does not contain an explicit constructor or factory. The implicit
// constructor has the same token position as the owner class.
bool Function::IsImplicitConstructor() const {
return IsGenerativeConstructor() && (token_pos() == end_token_pos());
}
bool Function::IsImplicitStaticClosureFunction(FunctionPtr func) {
NoSafepointScope no_safepoint;
uint32_t kind_tag = func->untag()->kind_tag_.load(std::memory_order_relaxed);
return (KindBits::decode(kind_tag) ==
UntaggedFunction::kImplicitClosureFunction) &&
StaticBit::decode(kind_tag);
}
FunctionPtr Function::New(Heap::Space space) {
ASSERT(Object::function_class() != Class::null());
ObjectPtr raw = Object::Allocate(Function::kClassId, Function::InstanceSize(),
space, /*compressed*/ true);
return static_cast<FunctionPtr>(raw);
}
FunctionPtr Function::New(const FunctionType& signature,
const String& name,
UntaggedFunction::Kind kind,
bool is_static,
bool is_const,
bool is_abstract,
bool is_external,
bool is_native,
const Object& owner,
TokenPosition token_pos,
Heap::Space space) {
ASSERT(!owner.IsNull());
const Function& result = Function::Handle(Function::New(space));
result.set_kind_tag(0);
result.set_packed_fields(0);
result.set_name(name);
result.set_kind_tag(0); // Ensure determinism of uninitialized bits.
result.set_kind(kind);
result.set_recognized_kind(MethodRecognizer::kUnknown);
result.set_modifier(UntaggedFunction::kNoModifier);
result.set_is_static(is_static);
result.set_is_const(is_const);
result.set_is_abstract(is_abstract);
result.set_is_external(is_external);
result.set_is_native(is_native);
result.set_is_reflectable(true); // Will be computed later.
result.set_is_visible(true); // Will be computed later.
result.set_is_debuggable(true); // Will be computed later.
result.set_is_intrinsic(false);
result.set_is_generated_body(false);
result.set_has_pragma(false);
result.set_is_polymorphic_target(false);
result.set_is_synthetic(false);
NOT_IN_PRECOMPILED(result.set_state_bits(0));
result.set_owner(owner);
NOT_IN_PRECOMPILED(result.set_token_pos(token_pos));
NOT_IN_PRECOMPILED(result.set_end_token_pos(token_pos));
NOT_IN_PRECOMPILED(result.set_usage_counter(0));
NOT_IN_PRECOMPILED(result.set_deoptimization_counter(0));
NOT_IN_PRECOMPILED(result.set_optimized_instruction_count(0));
NOT_IN_PRECOMPILED(result.set_optimized_call_site_count(0));
NOT_IN_PRECOMPILED(result.set_inlining_depth(0));
NOT_IN_PRECOMPILED(result.set_kernel_offset(0));
result.set_is_optimizable(is_native ? false : true);
result.set_is_inlinable(true);
result.reset_unboxed_parameters_and_return();
result.SetInstructionsSafe(StubCode::LazyCompile());
if (kind == UntaggedFunction::kClosureFunction ||
kind == UntaggedFunction::kImplicitClosureFunction) {
ASSERT(space == Heap::kOld);
const ClosureData& data = ClosureData::Handle(ClosureData::New());
result.set_data(data);
} else if (kind == UntaggedFunction::kFfiTrampoline) {
const FfiTrampolineData& data =
FfiTrampolineData::Handle(FfiTrampolineData::New());
result.set_data(data);
} else {
// Functions other than signature functions have no reason to be allocated
// in new space.
ASSERT(space == Heap::kOld);
}
if (result.CachesDefaultTypeArguments()) {
// Make sure the default type arguments are set consistently with the
// function type parameters (currently null).
result.set_default_type_arguments(Object::empty_type_arguments());
}
// Force-optimized functions are not debuggable because they cannot
// deoptimize.
if (result.ForceOptimize()) {
result.set_is_debuggable(false);
}
if (!signature.IsNull()) {
signature.set_num_implicit_parameters(result.NumImplicitParameters());
result.set_signature(signature);
} else {
ASSERT(kind == UntaggedFunction::kFfiTrampoline);
}
return result.ptr();
}
FunctionPtr Function::NewClosureFunctionWithKind(UntaggedFunction::Kind kind,
const String& name,
const Function& parent,
TokenPosition token_pos,
const Object& owner) {
ASSERT((kind == UntaggedFunction::kClosureFunction) ||
(kind == UntaggedFunction::kImplicitClosureFunction));
ASSERT(!parent.IsNull());
ASSERT(!owner.IsNull());
const FunctionType& signature = FunctionType::Handle(FunctionType::New(
kind == UntaggedFunction::kClosureFunction ? parent.NumTypeArguments()
: 0));
const Function& result = Function::Handle(
Function::New(signature, name, kind,
/* is_static = */ parent.is_static(),
/* is_const = */ false,
/* is_abstract = */ false,
/* is_external = */ false,
/* is_native = */ false, owner, token_pos));
result.set_parent_function(parent);
return result.ptr();
}
FunctionPtr Function::NewClosureFunction(const String& name,
const Function& parent,
TokenPosition token_pos) {
// Use the owner defining the parent function and not the class containing it.
const Object& parent_owner = Object::Handle(parent.RawOwner());
return NewClosureFunctionWithKind(UntaggedFunction::kClosureFunction, name,
parent, token_pos, parent_owner);
}
FunctionPtr Function::NewImplicitClosureFunction(const String& name,
const Function& parent,
TokenPosition token_pos) {
// Use the owner defining the parent function and not the class containing it.
const Object& parent_owner = Object::Handle(parent.RawOwner());
return NewClosureFunctionWithKind(UntaggedFunction::kImplicitClosureFunction,
name, parent, token_pos, parent_owner);
}
bool Function::SafeToClosurize() const {
#if defined(DART_PRECOMPILED_RUNTIME)
return HasImplicitClosureFunction();
#else
return true;
#endif
}
bool Function::IsDynamicClosureCallDispatcher(Thread* thread) const {
if (!IsInvokeFieldDispatcher()) return false;
if (thread->isolate_group()->object_store()->closure_class() != Owner()) {
return false;
}
const auto& handle = String::Handle(thread->zone(), name());
return handle.Equals(Symbols::DynamicCall());
}
FunctionPtr Function::ImplicitClosureFunction() const {
// Return the existing implicit closure function if any.
if (implicit_closure_function() != Function::null()) {
return implicit_closure_function();
}
#if defined(DART_PRECOMPILED_RUNTIME)
// In AOT mode all implicit closures are pre-created.
FATAL("Cannot create implicit closure in AOT!");
return Function::null();
#else
ASSERT(!IsClosureFunction());
Thread* thread = Thread::Current();
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
if (implicit_closure_function() != Function::null()) {
return implicit_closure_function();
}
// Create closure function.
Zone* zone = thread->zone();
const String& closure_name = String::Handle(zone, name());
const Function& closure_function = Function::Handle(
zone, NewImplicitClosureFunction(closure_name, *this, token_pos()));
// Set closure function's context scope.
if (is_static()) {
closure_function.set_context_scope(Object::empty_context_scope());
} else {
const ContextScope& context_scope = ContextScope::Handle(
zone, LocalScope::CreateImplicitClosureScope(*this));
closure_function.set_context_scope(context_scope);
}
FunctionType& closure_signature =
FunctionType::Handle(zone, closure_function.signature());
// Set closure function's type parameters.
// This function cannot be local, therefore it has no generic parent.
// Its implicit closure function therefore has no generic parent function
// either. That is why it is safe to simply copy the type parameters.
closure_signature.set_type_parameters(
TypeArguments::Handle(zone, type_parameters()));
closure_function.SetNumTypeParameters(NumTypeParameters());
closure_function.UpdateCachedDefaultTypeArguments(thread);
// Set closure function's result type to this result type.
closure_signature.set_result_type(AbstractType::Handle(zone, result_type()));
// Set closure function's end token to this end token.
closure_function.set_end_token_pos(end_token_pos());
// The closurized method stub just calls into the original method and should
// therefore be skipped by the debugger and in stack traces.
closure_function.set_is_debuggable(false);
closure_function.set_is_visible(false);
// Set closure function's formal parameters to this formal parameters,
// removing the receiver if this is an instance method and adding the closure
// object as first parameter.
const int kClosure = 1;
const int has_receiver = is_static() ? 0 : 1;
const int num_fixed_params = kClosure - has_receiver + num_fixed_parameters();
const int num_opt_params = NumOptionalParameters();
const bool has_opt_pos_params = HasOptionalPositionalParameters();
const int num_params = num_fixed_params + num_opt_params;
closure_function.set_num_fixed_parameters(num_fixed_params);
closure_function.SetNumOptionalParameters(num_opt_params, has_opt_pos_params);
closure_signature.set_parameter_types(
Array::Handle(zone, Array::New(num_params, Heap::kOld)));
closure_signature.CreateNameArrayIncludingFlags(Heap::kOld);
AbstractType& param_type = AbstractType::Handle(zone);
String& param_name = String::Handle(zone);
// Add implicit closure object parameter.
param_type = Type::DynamicType();
closure_signature.SetParameterTypeAt(0, param_type);
closure_signature.SetParameterNameAt(0, Symbols::ClosureParameter());
for (int i = kClosure; i < num_params; i++) {
param_type = ParameterTypeAt(has_receiver - kClosure + i);
closure_signature.SetParameterTypeAt(i, param_type);
param_name = ParameterNameAt(has_receiver - kClosure + i);
closure_signature.SetParameterNameAt(i, param_name);
if (IsRequiredAt(has_receiver - kClosure + i)) {
closure_signature.SetIsRequiredAt(i);
}
}
closure_signature.FinalizeNameArrays(closure_function);
closure_function.InheritKernelOffsetFrom(*this);
// Change covariant parameter types to either Object? for an opted-in implicit
// closure or to Object* for a legacy implicit closure.
if (!is_static()) {
BitVector is_covariant(zone, NumParameters());
BitVector is_generic_covariant_impl(zone, NumParameters());
kernel::ReadParameterCovariance(*this, &is_covariant,
&is_generic_covariant_impl);
Type& object_type = Type::Handle(zone, Type::ObjectType());
ObjectStore* object_store = IsolateGroup::Current()->object_store();
object_type = nnbd_mode() == NNBDMode::kOptedInLib
? object_store->nullable_object_type()
: object_store->legacy_object_type();
ASSERT(object_type.IsCanonical());
for (intptr_t i = kClosure; i < num_params; ++i) {
const intptr_t original_param_index = has_receiver - kClosure + i;
if (is_covariant.Contains(original_param_index) ||
is_generic_covariant_impl.Contains(original_param_index)) {
closure_signature.SetParameterTypeAt(i, object_type);
}
}
}
ASSERT(!closure_signature.IsFinalized());
closure_signature ^= ClassFinalizer::FinalizeType(closure_signature);
closure_function.set_signature(closure_signature);
set_implicit_closure_function(closure_function);
ASSERT(closure_function.IsImplicitClosureFunction());
return closure_function.ptr();
#endif // defined(DART_PRECOMPILED_RUNTIME)
}
void Function::DropUncompiledImplicitClosureFunction() const {
if (implicit_closure_function() != Function::null()) {
const Function& func = Function::Handle(implicit_closure_function());
if (!func.HasCode()) {
set_implicit_closure_function(Function::Handle());
}
}
}
StringPtr Function::InternalSignature() const {
Thread* thread = Thread::Current();
ZoneTextBuffer printer(thread->zone());
const FunctionType& sig = FunctionType::Handle(signature());
sig.Print(kInternalName, &printer);
return Symbols::New(thread, printer.buffer());
}
StringPtr Function::UserVisibleSignature() const {
Thread* thread = Thread::Current();
ZoneTextBuffer printer(thread->zone());
const FunctionType& sig = FunctionType::Handle(signature());
sig.Print(kUserVisibleName, &printer);
return Symbols::New(thread, printer.buffer());
}
void FunctionType::PrintParameters(Thread* thread,
Zone* zone,
NameVisibility name_visibility,
BaseTextBuffer* printer) const {
AbstractType& param_type = AbstractType::Handle(zone);
const intptr_t num_params = NumParameters();
const intptr_t num_fixed_params = num_fixed_parameters();
const intptr_t num_opt_pos_params = NumOptionalPositionalParameters();
const intptr_t num_opt_named_params = NumOptionalNamedParameters();
const intptr_t num_opt_params = num_opt_pos_params + num_opt_named_params;
ASSERT((num_fixed_params + num_opt_params) == num_params);
intptr_t i = 0;
if (name_visibility == kUserVisibleName) {
// Hide implicit parameters.
i = num_implicit_parameters();
}
String& name = String::Handle(zone);
while (i < num_fixed_params) {
param_type = ParameterTypeAt(i);
ASSERT(!param_type.IsNull());
param_type.PrintName(name_visibility, printer);
if (i != (num_params - 1)) {
printer->AddString(", ");
}
i++;
}
if (num_opt_params > 0) {
if (num_opt_pos_params > 0) {
printer->AddString("[");
} else {
printer->AddString("{");
}
for (intptr_t i = num_fixed_params; i < num_params; i++) {
if (num_opt_named_params > 0 && IsRequiredAt(i)) {
printer->AddString("required ");
}
param_type = ParameterTypeAt(i);
ASSERT(!param_type.IsNull());
param_type.PrintName(name_visibility, printer);
// The parameter name of an optional positional parameter does not need
// to be part of the signature, since it is not used.
if (num_opt_named_params > 0) {
name = ParameterNameAt(i);
printer->AddString(" ");
printer->AddString(name.ToCString());
}
if (i != (num_params - 1)) {
printer->AddString(", ");
}
}
if (num_opt_pos_params > 0) {
printer->AddString("]");
} else {
printer->AddString("}");
}
}
}
InstancePtr Function::ImplicitStaticClosure() const {
ASSERT(IsImplicitStaticClosureFunction());
if (implicit_static_closure() != Instance::null()) {
return implicit_static_closure();
}
auto thread = Thread::Current();
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
if (implicit_static_closure() != Instance::null()) {
return implicit_static_closure();
}
Zone* zone = thread->zone();
const auto& null_context = Context::Handle(zone);
const auto& closure =
Instance::Handle(zone, Closure::New(Object::null_type_arguments(),
Object::null_type_arguments(), *this,
null_context, Heap::kOld));
set_implicit_static_closure(closure);
return implicit_static_closure();
}
InstancePtr Function::ImplicitInstanceClosure(const Instance& receiver) const {
ASSERT(IsImplicitClosureFunction());
Zone* zone = Thread::Current()->zone();
const Context& context = Context::Handle(zone, Context::New(1));
context.SetAt(0, receiver);
TypeArguments& instantiator_type_arguments = TypeArguments::Handle(zone);
if (!HasInstantiatedSignature(kCurrentClass)) {
instantiator_type_arguments = receiver.GetTypeArguments();
}
ASSERT(!HasGenericParent()); // No generic parent function.
return Closure::New(instantiator_type_arguments,
Object::null_type_arguments(), *this, context);
}
FunctionPtr Function::ImplicitClosureTarget(Zone* zone) const {
const auto& parent = Function::Handle(zone, parent_function());
const auto& func_name = String::Handle(zone, parent.name());
const auto& owner = Class::Handle(zone, parent.Owner());
Thread* thread = Thread::Current();
const auto& error = owner.EnsureIsFinalized(thread);
ASSERT(error == Error::null());
auto& target =
Function::Handle(zone, Resolver::ResolveFunction(zone, owner, func_name));
if (!target.IsNull() && (target.ptr() != parent.ptr())) {
DEBUG_ASSERT(IsolateGroup::Current()->HasAttemptedReload());
if ((target.is_static() != parent.is_static()) ||
(target.kind() != parent.kind())) {
target = Function::null();
}
}
return target.ptr();
}
intptr_t Function::ComputeClosureHash() const {
ASSERT(IsClosureFunction());
const Class& cls = Class::Handle(Owner());
uintptr_t result = String::Handle(name()).Hash();
result += String::Handle(InternalSignature()).Hash();
result += String::Handle(cls.Name()).Hash();
return result;
}
void FunctionType::Print(NameVisibility name_visibility,
BaseTextBuffer* printer) const {
if (IsNull()) {
printer->AddString("null"); // Signature optimized out in precompiler.
return;
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
const TypeArguments& type_params =
TypeArguments::Handle(zone, type_parameters());
if (!type_params.IsNull()) {
const intptr_t num_type_params = type_params.Length();
ASSERT(num_type_params > 0);
TypeParameter& type_param = TypeParameter::Handle(zone);
String& name = String::Handle(zone);
AbstractType& bound = AbstractType::Handle(zone);
printer->AddString("<");
for (intptr_t i = 0; i < num_type_params; i++) {
type_param ^= type_params.TypeAt(i);
name = type_param.name();
printer->AddString(name.ToCString());
bound = type_param.bound();
// Do not print default bound or non-nullable Object bound in weak mode.
if (!bound.IsNull() &&
(!bound.IsObjectType() ||
(isolate_group->null_safety() && bound.IsNonNullable()))) {
printer->AddString(" extends ");
bound.PrintName(name_visibility, printer);
if (FLAG_show_internal_names) {
bound = type_param.default_argument();
if (!bound.IsNull() && !bound.IsDynamicType()) {
printer->AddString(" defaults to ");
bound.PrintName(name_visibility, printer);
}
}
}
if (i < num_type_params - 1) {
printer->AddString(", ");
}
}
printer->AddString(">");
}
printer->AddString("(");
PrintParameters(thread, zone, name_visibility, printer);
printer->AddString(") => ");
const AbstractType& res_type = AbstractType::Handle(zone, result_type());
res_type.PrintName(name_visibility, printer);
}
bool Function::HasInstantiatedSignature(Genericity genericity,
intptr_t num_free_fun_type_params,
TrailPtr trail) const {
return FunctionType::Handle(signature())
.IsInstantiated(genericity, num_free_fun_type_params, trail);
}
bool FunctionType::IsInstantiated(Genericity genericity,
intptr_t num_free_fun_type_params,
TrailPtr trail) const {
if (num_free_fun_type_params == kCurrentAndEnclosingFree) {
num_free_fun_type_params = kAllFree;
} else if (genericity != kCurrentClass) {
const intptr_t num_parent_type_args = NumParentTypeArguments();
if (num_parent_type_args > 0 && num_free_fun_type_params > 0) {
// The number of parent type arguments is cached in the FunctionType, so
// we can't consider any FunctionType with free parent type arguments as
// fully instantiated. Instead, the FunctionType must be instantiated to
// reduce the number of parent type arguments, even if they're unused in
// its component types.
return false;
}
// Don't consider local function type parameters as free.
if (num_free_fun_type_params > num_parent_type_args) {
num_free_fun_type_params = num_parent_type_args;
}
}
AbstractType& type = AbstractType::Handle(result_type());
if (!type.IsInstantiated(genericity, num_free_fun_type_params, trail)) {
return false;
}
const intptr_t num_parameters = NumParameters();
for (intptr_t i = 0; i < num_parameters; i++) {
type = ParameterTypeAt(i);
if (!type.IsInstantiated(genericity, num_free_fun_type_params, trail)) {
return false;
}
}
TypeArguments& type_params = TypeArguments::Handle(type_parameters());
TypeParameter& type_param = TypeParameter::Handle();
for (intptr_t i = 0; i < type_params.Length(); ++i) {
type_param ^= type_params.TypeAt(i);
type = type_param.bound();
if (!type.IsInstantiated(genericity, num_free_fun_type_params, trail)) {
return false;
}
}
return true;
}
ClassPtr Function::Owner() const {
ASSERT(untag()->owner() != Object::null());
if (untag()->owner()->IsClass()) {
return Class::RawCast(untag()->owner());
}
const Object& obj = Object::Handle(untag()->owner());
ASSERT(obj.IsPatchClass());
return PatchClass::Cast(obj).patched_class();
}
ClassPtr Function::origin() const {
ASSERT(untag()->owner() != Object::null());
if (untag()->owner()->IsClass()) {
return Class::RawCast(untag()->owner());
}
const Object& obj = Object::Handle(untag()->owner());
ASSERT(obj.IsPatchClass());
return PatchClass::Cast(obj).origin_class();
}
void Function::InheritKernelOffsetFrom(const Function& src) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
StoreNonPointer(&untag()->kernel_offset_, src.untag()->kernel_offset_);
#endif
}
void Function::InheritKernelOffsetFrom(const Field& src) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
set_kernel_offset(src.kernel_offset());
#endif
}
void Function::SetKernelDataAndScript(const Script& script,
const ExternalTypedData& data,
intptr_t offset) const {
Array& data_field = Array::Handle(Array::New(3));
data_field.SetAt(0, script);
data_field.SetAt(1, data);
data_field.SetAt(2, Smi::Handle(Smi::New(offset)));
set_data(data_field);
}
ScriptPtr Function::script() const {
// NOTE(turnidge): If you update this function, you probably want to
// update Class::PatchFieldsAndFunctions() at the same time.
const Object& data = Object::Handle(this->data());
if (IsDynamicInvocationForwarder()) {
const auto& forwarding_target = Function::Handle(ForwardingTarget());
return forwarding_target.script();
}
if (IsImplicitGetterOrSetter()) {
const auto& field = Field::Handle(accessor_field());
return field.Script();
}
if (data.IsArray()) {
Object& script = Object::Handle(Array::Cast(data).At(0));
if (script.IsScript()) {
return Script::Cast(script).ptr();
}
}
if (token_pos() == TokenPosition::kMinSource) {
// Testing for position 0 is an optimization that relies on temporary
// eval functions having token position 0.
const Script& script = Script::Handle(eval_script());
if (!script.IsNull()) {
return script.ptr();
}
}
const Object& obj = Object::Handle(untag()->owner());
if (obj.IsPatchClass()) {
return PatchClass::Cast(obj).script();
}
if (IsClosureFunction()) {
const Function& function = Function::Handle(parent_function());
#if defined(DART_PRECOMPILED_RUNTIME)
if (function.IsNull()) return Script::null();
#endif
return function.script();
}
ASSERT(obj.IsClass());
return Class::Cast(obj).script();
}
ExternalTypedDataPtr Function::KernelData() const {
Object& data = Object::Handle(this->data());
if (data.IsArray()) {
Object& script = Object::Handle(Array::Cast(data).At(0));
if (script.IsScript()) {
return ExternalTypedData::RawCast(Array::Cast(data).At(1));
}
}
if (IsClosureFunction()) {
Function& parent = Function::Handle(parent_function());
ASSERT(!parent.IsNull());
return parent.KernelData();
}
const Object& obj = Object::Handle(untag()->owner());
if (obj.IsClass()) {
Library& lib = Library::Handle(Class::Cast(obj).library());
return lib.kernel_data();
}
ASSERT(obj.IsPatchClass());
return PatchClass::Cast(obj).library_kernel_data();
}
intptr_t Function::KernelDataProgramOffset() const {
if (IsNoSuchMethodDispatcher() || IsInvokeFieldDispatcher() ||
IsFfiTrampoline()) {
return 0;
}
Object& data = Object::Handle(this->data());
if (data.IsArray()) {
Object& script = Object::Handle(Array::Cast(data).At(0));
if (script.IsScript()) {
return Smi::Value(Smi::RawCast(Array::Cast(data).At(2)));
}
}
if (IsClosureFunction()) {
Function& parent = Function::Handle(parent_function());
ASSERT(!parent.IsNull());
return parent.KernelDataProgramOffset();
}
const Object& obj = Object::Handle(untag()->owner());
if (obj.IsClass()) {
Library& lib = Library::Handle(Class::Cast(obj).library());
return lib.kernel_offset();
}
ASSERT(obj.IsPatchClass());
return PatchClass::Cast(obj).library_kernel_offset();
}
bool Function::HasOptimizedCode() const {
return HasCode() && Code::Handle(CurrentCode()).is_optimized();
}
const char* Function::NameCString(NameVisibility name_visibility) const {
switch (name_visibility) {
case kInternalName:
return String::Handle(name()).ToCString();
case kScrubbedName:
case kUserVisibleName:
return UserVisibleNameCString();
}
UNREACHABLE();
return nullptr;
}
const char* Function::UserVisibleNameCString() const {
if (FLAG_show_internal_names) {
return String::Handle(name()).ToCString();
}
return String::ScrubName(String::Handle(name()), is_extension_member());
}
StringPtr Function::UserVisibleName() const {
if (FLAG_show_internal_names) {
return name();
}
return Symbols::New(
Thread::Current(),
String::ScrubName(String::Handle(name()), is_extension_member()));
}
StringPtr Function::QualifiedScrubbedName() const {
Thread* thread = Thread::Current();
ZoneTextBuffer printer(thread->zone());
PrintName(NameFormattingParams(kScrubbedName), &printer);
return Symbols::New(thread, printer.buffer());
}
StringPtr Function::QualifiedUserVisibleName() const {
Thread* thread = Thread::Current();
ZoneTextBuffer printer(thread->zone());
PrintName(NameFormattingParams(kUserVisibleName), &printer);
return Symbols::New(thread, printer.buffer());
}
const char* Function::QualifiedUserVisibleNameCString() const {
Thread* thread = Thread::Current();
ZoneTextBuffer printer(thread->zone());
PrintName(NameFormattingParams(kUserVisibleName), &printer);
return printer.buffer();
}
static void FunctionPrintNameHelper(const Function& fun,
const NameFormattingParams& params,
BaseTextBuffer* printer) {
if (fun.IsNonImplicitClosureFunction()) {
if (params.include_parent_name) {
const auto& parent = Function::Handle(fun.parent_function());
if (parent.IsNull()) {
printer->AddString(Symbols::OptimizedOut().ToCString());
} else {
parent.PrintName(params, printer);
}
// A function's scrubbed name and its user visible name are identical.
printer->AddString(".");
}
if (params.disambiguate_names &&
fun.name() == Symbols::AnonymousClosure().ptr()) {
printer->Printf("<anonymous closure @%" Pd ">", fun.token_pos().Pos());
} else {
printer->AddString(fun.NameCString(params.name_visibility));
}
return;
}
if (params.disambiguate_names) {
if (fun.IsInvokeFieldDispatcher()) {
printer->AddString("[invoke-field] ");
}
if (fun.IsNoSuchMethodDispatcher()) {
printer->AddString("[no-such-method] ");
}
if (fun.IsImplicitClosureFunction()) {
printer->AddString("[tear-off] ");
}
if (fun.IsMethodExtractor()) {
printer->AddString("[tear-off-extractor] ");
}
}
if (fun.kind() == UntaggedFunction::kConstructor) {
printer->AddString("new ");
} else if (params.include_class_name) {
const Class& cls = Class::Handle(fun.Owner());
if (!cls.IsTopLevel()) {
const Class& mixin = Class::Handle(cls.Mixin());
printer->AddString(params.name_visibility == Object::kUserVisibleName
? mixin.UserVisibleNameCString()
: cls.NameCString(params.name_visibility));
printer->AddString(".");
}
}
printer->AddString(fun.NameCString(params.name_visibility));
// Dispatchers that are created with an arguments descriptor need both the
// name and the saved arguments descriptor to disambiguate.
if (params.disambiguate_names && fun.HasSavedArgumentsDescriptor()) {
const auto& args_desc_array = Array::Handle(fun.saved_args_desc());
const ArgumentsDescriptor args_desc(args_desc_array);
args_desc.PrintTo(printer);
}
}
void Function::PrintName(const NameFormattingParams& params,
BaseTextBuffer* printer) const {
if (!IsLocalFunction()) {
FunctionPrintNameHelper(*this, params, printer);
return;
}
auto& fun = Function::Handle(ptr());
intptr_t fun_depth = 0;
// If |this| is a generated body closure, start with the closest
// non-generated parent function.
while (fun.is_generated_body()) {
fun = fun.parent_function();
fun_depth++;
}
FunctionPrintNameHelper(fun, params, printer);
// If we skipped generated bodies then append a suffix to the end.
if (fun_depth > 0 && params.disambiguate_names) {
printer->AddString("{body");
if (fun_depth > 1) {
printer->Printf(" depth %" Pd "", fun_depth);
}
printer->AddString("}");
}
}
StringPtr Function::GetSource() const {
if (IsImplicitConstructor() || is_synthetic()) {
// We may need to handle more cases when the restrictions on mixins are
// relaxed. In particular we might start associating some source with the
// forwarding constructors when it becomes possible to specify a particular
// constructor from the mixin to use.
return String::null();
}
Zone* zone = Thread::Current()->zone();
const Script& func_script = Script::Handle(zone, script());
intptr_t from_line, from_col;
if (!func_script.GetTokenLocation(token_pos(), &from_line, &from_col)) {
return String::null();
}
intptr_t to_line, to_col;
if (!func_script.GetTokenLocation(end_token_pos(), &to_line, &to_col)) {
return String::null();
}
intptr_t to_length = func_script.GetTokenLength(end_token_pos());
if (to_length < 0) {
return String::null();
}
if (to_length == 1) {
// Handle special cases for end tokens of closures (where we exclude the
// last token):
// (1) "foo(() => null, bar);": End token is `,', but we don't print it.
// (2) "foo(() => null);": End token is ')`, but we don't print it.
// (3) "var foo = () => null;": End token is `;', but in this case the
// token semicolon belongs to the assignment so we skip it.
const String& src = String::Handle(func_script.Source());
if (src.IsNull() || src.Length() == 0) {
return Symbols::OptimizedOut().ptr();
}
uint16_t end_char = src.CharAt(end_token_pos().Pos());
if ((end_char == ',') || // Case 1.
(end_char == ')') || // Case 2.
(end_char == ';' && String::Handle(zone, name())
.Equals("<anonymous closure>"))) { // Case 3.
to_length = 0;
}
}
return func_script.GetSnippet(from_line, from_col, to_line,
to_col + to_length);
}
// Construct fingerprint from token stream. The token stream contains also
// arguments.
int32_t Function::SourceFingerprint() const {
#if !defined(DART_PRECOMPILED_RUNTIME)
return kernel::KernelSourceFingerprintHelper::CalculateFunctionFingerprint(
*this);
#else
return 0;
#endif // !defined(DART_PRECOMPILED_RUNTIME)
}
void Function::SaveICDataMap(
const ZoneGrowableArray<const ICData*>& deopt_id_to_ic_data,
const Array& edge_counters_array) const {
#if !defined(DART_PRECOMPILED_RUNTIME)
// Compute number of ICData objects to save.
// Store edge counter array in the first slot.
intptr_t count = 1;
for (intptr_t i = 0; i < deopt_id_to_ic_data.length(); i++) {
if (deopt_id_to_ic_data[i] != NULL) {
count++;
}
}
const Array& array = Array::Handle(Array::New(count, Heap::kOld));
count = 1;
for (intptr_t i = 0; i < deopt_id_to_ic_data.length(); i++) {
if (deopt_id_to_ic_data[i] != NULL) {
ASSERT(i == deopt_id_to_ic_data[i]->deopt_id());
array.SetAt(count++, *deopt_id_to_ic_data[i]);
}
}
array.SetAt(0, edge_counters_array);
set_ic_data_array(array);
#else // DART_PRECOMPILED_RUNTIME
UNREACHABLE();
#endif // DART_PRECOMPILED_RUNTIME
}
void Function::RestoreICDataMap(
ZoneGrowableArray<const ICData*>* deopt_id_to_ic_data,
bool clone_ic_data) const {
#if !defined(DART_PRECOMPILED_RUNTIME)
if (FLAG_force_clone_compiler_objects) {
clone_ic_data = true;
}
ASSERT(deopt_id_to_ic_data->is_empty());
Zone* zone = Thread::Current()->zone();
const Array& saved_ic_data = Array::Handle(zone, ic_data_array());
if (saved_ic_data.IsNull()) {
// Could happen with not-yet compiled unoptimized code or force-optimized
// functions.
return;
}
const intptr_t saved_length = saved_ic_data.Length();
ASSERT(saved_length > 0);
if (saved_length > 1) {
const intptr_t restored_length =
ICData::Cast(Object::Handle(zone, saved_ic_data.At(saved_length - 1)))
.deopt_id() +
1;
deopt_id_to_ic_data->SetLength(restored_length);
for (intptr_t i = 0; i < restored_length; i++) {
(*deopt_id_to_ic_data)[i] = NULL;
}
for (intptr_t i = 1; i < saved_length; i++) {
ICData& ic_data = ICData::ZoneHandle(zone);
ic_data ^= saved_ic_data.At(i);
if (clone_ic_data) {
const ICData& original_ic_data = ICData::Handle(zone, ic_data.ptr());
ic_data = ICData::Clone(ic_data);
ic_data.SetOriginal(original_ic_data);
}
ASSERT(deopt_id_to_ic_data->At(ic_data.deopt_id()) == nullptr);
(*deopt_id_to_ic_data)[ic_data.deopt_id()] = &ic_data;
}
}
#else // DART_PRECOMPILED_RUNTIME
UNREACHABLE();
#endif // DART_PRECOMPILED_RUNTIME
}
void Function::set_ic_data_array(const Array& value) const {
untag()->set_ic_data_array<std::memory_order_release>(value.ptr());
}
ArrayPtr Function::ic_data_array() const {
return untag()->ic_data_array<std::memory_order_acquire>();
}
void Function::ClearICDataArray() const {
set_ic_data_array(Array::null_array());
}
ICDataPtr Function::FindICData(intptr_t deopt_id) const {
const Array& array = Array::Handle(ic_data_array());
ICData& ic_data = ICData::Handle();
for (intptr_t i = 1; i < array.Length(); i++) {
ic_data ^= array.At(i);
if (ic_data.deopt_id() == deopt_id) {
return ic_data.ptr();
}
}
return ICData::null();
}
void Function::SetDeoptReasonForAll(intptr_t deopt_id,
ICData::DeoptReasonId reason) {
const Array& array = Array::Handle(ic_data_array());
ICData& ic_data = ICData::Handle();
for (intptr_t i = 1; i < array.Length(); i++) {
ic_data ^= array.At(i);
if (ic_data.deopt_id() == deopt_id) {
ic_data.AddDeoptReason(reason);
}
}
}
bool Function::CheckSourceFingerprint(int32_t fp, const char* kind) const {
#if !defined(DEBUG)
return true; // Only check on debug.
#endif
#if !defined(DART_PRECOMPILED_RUNTIME)
// Check that the function is marked as recognized via the vm:recognized
// pragma. This is so that optimizations that change the signature will know
// not to touch it.
if (kind != nullptr && !MethodRecognizer::IsMarkedAsRecognized(*this, kind)) {
OS::PrintErr(
"Recognized method %s should be marked with: "
"@pragma(\"vm:recognized\", \"%s\")\n",
ToQualifiedCString(), kind);
return false;
}
#endif
if (IsolateGroup::Current()->obfuscate() || FLAG_precompiled_mode ||
(Dart::vm_snapshot_kind() != Snapshot::kNone)) {
return true; // The kernel structure has been altered, skip checking.
}
if (SourceFingerprint() != fp) {
// This output can be copied into a file, then used with sed
// to replace the old values.
// sed -i.bak -f /tmp/newkeys \
// runtime/vm/compiler/recognized_methods_list.h
THR_Print("s/0x%08x/0x%08x/\n", fp, SourceFingerprint());
return false;
}
return true;
}
CodePtr Function::EnsureHasCode() const {
if (HasCode()) return CurrentCode();
Thread* thread = Thread::Current();
ASSERT(thread->IsMutatorThread());
DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame());
Zone* zone = thread->zone();
const Object& result =
Object::Handle(zone, Compiler::CompileFunction(thread, *this));
if (result.IsError()) {
if (result.IsLanguageError()) {
Exceptions::ThrowCompileTimeError(LanguageError::Cast(result));
UNREACHABLE();
}
Exceptions::PropagateError(Error::Cast(result));
UNREACHABLE();
}
// Compiling in unoptimized mode should never fail if there are no errors.
ASSERT(HasCode());
ASSERT(ForceOptimize() || unoptimized_code() == result.ptr());
return CurrentCode();
}
bool Function::NeedsMonomorphicCheckedEntry(Zone* zone) const {
#if !defined(DART_PRECOMPILED_RUNTIME)
if (!IsDynamicFunction()) {
return false;
}
// For functions which need an args descriptor the switchable call sites will
// transition directly to calling via a stub (and therefore never call the
// monomorphic entry).
//
// See runtime_entry.cc:DEFINE_RUNTIME_ENTRY(UnlinkedCall)
if (PrologueNeedsArgumentsDescriptor()) {
return false;
}
// All dyn:* forwarders are called via SwitchableCalls and all except the ones
// with `PrologueNeedsArgumentsDescriptor()` transition into monomorphic
// state.
if (Function::IsDynamicInvocationForwarderName(name())) {
return true;
}
// If table dispatch is disabled, all instance calls use switchable calls.
if (!(FLAG_precompiled_mode && FLAG_use_bare_instructions &&
FLAG_use_table_dispatch)) {
return true;
}
// Only if there are dynamic callers and if we didn't create a dyn:* forwarder
// for it do we need the monomorphic checked entry.
return HasDynamicCallers(zone) &&
!kernel::NeedsDynamicInvocationForwarder(*this);
#else
UNREACHABLE();
return true;
#endif
}
bool Function::HasDynamicCallers(Zone* zone) const {
#if !defined(DART_PRECOMPILED_RUNTIME)
// Issue(dartbug.com/42719):
// Right now the metadata of _Closure.call says there are no dynamic callers -
// even though there can be. To be conservative we return true.
if ((name() == Symbols::GetCall().ptr() || name() == Symbols::Call().ptr()) &&
Class::IsClosureClass(Owner())) {
return true;
}
// Use the results of TFA to determine whether this function is ever
// called dynamically, i.e. using switchable calls.
kernel::ProcedureAttributesMetadata metadata;
metadata = kernel::ProcedureAttributesOf(*this, zone);
if (IsGetterFunction() || IsImplicitGetterFunction() || IsMethodExtractor()) {
return metadata.getter_called_dynamically;
} else {
return metadata.method_or_setter_called_dynamically;
}
#else
UNREACHABLE();
return true;
#endif
}
bool Function::PrologueNeedsArgumentsDescriptor() const {
// These functions have a saved compile-time arguments descriptor that is
// used in lieu of the runtime arguments descriptor in generated IL.
if (HasSavedArgumentsDescriptor()) {
return false;
}
// The prologue of those functions need to examine the arg descriptor for
// various purposes.
return IsGeneric() || HasOptionalParameters();
}
bool Function::MayHaveUncheckedEntryPoint() const {
return FLAG_enable_multiple_entrypoints &&
(NeedsTypeArgumentTypeChecks() || NeedsArgumentTypeChecks());
}
intptr_t Function::SourceSize() const {
const TokenPosition& start = token_pos();
const TokenPosition& end = end_token_pos();
if (!end.IsReal() || start.IsNoSource() || start.IsClassifying()) {
// No source information, so just return 0.
return 0;
}
if (start.IsSynthetic()) {
// Try and approximate the source size using the parent's source size.
const auto& parent = Function::Handle(parent_function());
ASSERT(!parent.IsNull());
const intptr_t parent_size = parent.SourceSize();
if (parent_size == 0) {
return parent_size;
}
// Parent must have a real ending position.
return parent_size - (parent.end_token_pos().Pos() - end.Pos());
}
return end.Pos() - start.Pos();
}
const char* Function::ToCString() const {
if (IsNull()) {
return "Function: null";
}
Zone* zone = Thread::Current()->zone();
ZoneTextBuffer buffer(zone);
buffer.Printf("Function '%s':", String::Handle(zone, name()).ToCString());
if (is_static()) {
buffer.AddString(" static");
}
if (is_abstract()) {
buffer.AddString(" abstract");
}
switch (kind()) {
case UntaggedFunction::kRegularFunction:
case UntaggedFunction::kClosureFunction:
case UntaggedFunction::kImplicitClosureFunction:
case UntaggedFunction::kGetterFunction:
case UntaggedFunction::kSetterFunction:
break;
case UntaggedFunction::kConstructor:
buffer.AddString(is_static() ? " factory" : " constructor");
break;
case UntaggedFunction::kImplicitGetter:
buffer.AddString(" getter");
break;
case UntaggedFunction::kImplicitSetter:
buffer.AddString(" setter");
break;
case UntaggedFunction::kImplicitStaticGetter:
buffer.AddString(" static-getter");
break;
case UntaggedFunction::kFieldInitializer:
buffer.AddString(" field-initializer");
break;
case UntaggedFunction::kMethodExtractor:
buffer.AddString(" method-extractor");
break;
case UntaggedFunction::kNoSuchMethodDispatcher:
buffer.AddString(" no-such-method-dispatcher");
break;
case UntaggedFunction::kDynamicInvocationForwarder:
buffer.AddString(" dynamic-invocation-forwarder");
break;
case UntaggedFunction::kInvokeFieldDispatcher:
buffer.AddString(" invoke-field-dispatcher");
break;
case UntaggedFunction::kIrregexpFunction:
buffer.AddString(" irregexp-function");
break;
case UntaggedFunction::kFfiTrampoline:
buffer.AddString(" ffi-trampoline-function");
break;
default:
UNREACHABLE();
}
if (HasSavedArgumentsDescriptor()) {
const auto& args_desc_array = Array::Handle(zone, saved_args_desc());
const ArgumentsDescriptor args_desc(args_desc_array);
buffer.AddChar('[');
args_desc.PrintTo(&buffer);
buffer.AddChar(']');
}
if (is_const()) {
buffer.AddString(" const");
}
buffer.AddChar('.');
return buffer.buffer();
}
void FunctionType::set_packed_fields(uint32_t packed_fields) const {
StoreNonPointer(&untag()->packed_fields_, packed_fields);
}
intptr_t FunctionType::NumParameters() const {
return num_fixed_parameters() + NumOptionalParameters();
}
void FunctionType::set_num_implicit_parameters(intptr_t value) const {
ASSERT(value >= 0);
ASSERT(
Utils::IsUint(UntaggedFunctionType::kMaxImplicitParametersBits, value));
const uint32_t* original = &untag()->packed_fields_;
StoreNonPointer(original,
UntaggedFunctionType::PackedNumImplicitParameters::update(
value, *original));
}
void ClosureData::set_default_type_arguments(const TypeArguments& value) const {
untag()->set_default_type_arguments(value.ptr());
}
ClosureData::DefaultTypeArgumentsKind ClosureData::default_type_arguments_kind()
const {
return LoadNonPointer(&untag()->default_type_arguments_kind_);
}
void ClosureData::set_default_type_arguments_kind(
DefaultTypeArgumentsKind value) const {
StoreNonPointer(&untag()->default_type_arguments_kind_, value);
}
ClosureDataPtr ClosureData::New() {
ASSERT(Object::closure_data_class() != Class::null());
ObjectPtr raw =
Object::Allocate(ClosureData::kClassId, ClosureData::InstanceSize(),
Heap::kOld, /*compressed*/ true);
return static_cast<ClosureDataPtr>(raw);
}
const char* ClosureData::ToCString() const {
if (IsNull()) {
return "ClosureData: null";
}
auto const zone = Thread::Current()->zone();
ZoneTextBuffer buffer(zone);
buffer.Printf("ClosureData: context_scope: 0x%" Px "",
static_cast<uword>(context_scope()));
buffer.AddString(" parent_function: ");
if (parent_function() == Object::null()) {
buffer.AddString("null");
} else {
buffer.AddString(Object::Handle(parent_function()).ToCString());
}
buffer.Printf(" implicit_static_closure: 0x%" Px "",
static_cast<uword>(implicit_static_closure()));
buffer.AddString(" default_type_arguments: ");
if (default_type_arguments() == TypeArguments::null()) {
buffer.AddString("null");
} else {
buffer.AddString(
TypeArguments::Handle(zone, default_type_arguments()).ToCString());
}
return buffer.buffer();
}
void Function::set_num_fixed_parameters(intptr_t value) const {
ASSERT(value >= 0);
ASSERT(Utils::IsUint(UntaggedFunction::kMaxFixedParametersBits, value));
const uint32_t* original = &untag()->packed_fields_;
StoreNonPointer(original, UntaggedFunction::PackedNumFixedParameters::update(
value, *original));
// Also store in signature.
FunctionType::Handle(signature()).set_num_fixed_parameters(value);
}
void FunctionType::set_num_fixed_parameters(intptr_t value) const {
ASSERT(value >= 0);
ASSERT(Utils::IsUint(UntaggedFunctionType::kMaxFixedParametersBits, value));
const uint32_t* original = &untag()->packed_fields_;
StoreNonPointer(
original,
UntaggedFunctionType::PackedNumFixedParameters::update(value, *original));
}
void Function::SetNumOptionalParameters(intptr_t value,
bool are_optional_positional) const {
ASSERT(Utils::IsUint(UntaggedFunction::kMaxOptionalParametersBits, value));
uint32_t packed_fields = untag()->packed_fields_;
packed_fields = UntaggedFunction::PackedHasNamedOptionalParameters::update(
(value > 0) && !are_optional_positional, packed_fields);
packed_fields = UntaggedFunction::PackedNumOptionalParameters::update(
value, packed_fields);
StoreNonPointer(&untag()->packed_fields_, packed_fields);
// Also store in signature.
FunctionType::Handle(signature())
.SetNumOptionalParameters(value, are_optional_positional);
}
void FfiTrampolineData::set_callback_target(const Function& value) const {
untag()->set_callback_target(value.ptr());
}
void FunctionType::SetNumOptionalParameters(
intptr_t value,
bool are_optional_positional) const {
ASSERT(
Utils::IsUint(UntaggedFunctionType::kMaxOptionalParametersBits, value));
uint32_t packed_fields = untag()->packed_fields_;
packed_fields =
UntaggedFunctionType::PackedHasNamedOptionalParameters::update(
(value > 0) && !are_optional_positional, packed_fields);
packed_fields = UntaggedFunctionType::PackedNumOptionalParameters::update(
value, packed_fields);
StoreNonPointer(&untag()->packed_fields_, packed_fields);
}
FunctionTypePtr FunctionType::New(Heap::Space space) {
ObjectPtr raw =
Object::Allocate(FunctionType::kClassId, FunctionType::InstanceSize(),
space, /*compressed*/ true);
return static_cast<FunctionTypePtr>(raw);
}
FunctionTypePtr FunctionType::New(intptr_t num_parent_type_arguments,
Nullability nullability,
Heap::Space space) {
Zone* Z = Thread::Current()->zone();
const FunctionType& result =
FunctionType::Handle(Z, FunctionType::New(space));
result.set_packed_fields(0);
result.SetNumParentTypeArguments(num_parent_type_arguments);
result.set_num_fixed_parameters(0);
result.SetNumOptionalParameters(0, false);
result.set_nullability(nullability);
result.SetHash(0);
result.StoreNonPointer(&result.untag()->type_state_,
UntaggedType::kAllocated);
result.SetTypeTestingStub(
Code::Handle(Z, TypeTestingStubGenerator::DefaultCodeForType(result)));
return result.ptr();
}
void FunctionType::set_type_state(uint8_t state) const {
ASSERT((state >= UntaggedFunctionType::kAllocated) &&
(state <= UntaggedFunctionType::kFinalizedUninstantiated));
StoreNonPointer(&untag()->type_state_, state);
}
const char* FunctionType::ToUserVisibleCString() const {
Zone* zone = Thread::Current()->zone();
ZoneTextBuffer printer(zone);
Print(kUserVisibleName, &printer);
return printer.buffer();
}
StringPtr FunctionType::ToUserVisibleString() const {
Thread* thread = Thread::Current();
ZoneTextBuffer printer(thread->zone());
Print(kUserVisibleName, &printer);
return Symbols::New(thread, printer.buffer());
}
const char* FunctionType::ToCString() const {
if (IsNull()) {
return "FunctionType: null";
}
Zone* zone = Thread::Current()->zone();
ZoneTextBuffer printer(zone);
const char* suffix = NullabilitySuffix(kInternalName);
if (suffix[0] != '\0') {
printer.AddString("(");
}
Print(kInternalName, &printer);
if (suffix[0] != '\0') {
printer.AddString(")");
printer.AddString(suffix);
}
return printer.buffer();
}
void ClosureData::set_context_scope(const ContextScope& value) const {
untag()->set_context_scope(value.ptr());
}
void ClosureData::set_implicit_static_closure(const Instance& closure) const {
ASSERT(!closure.IsNull());
ASSERT(untag()->closure() == Instance::null());
untag()->set_closure<std::memory_order_release>(closure.ptr());
}
#if defined(DART_PRECOMPILER)
void ClosureData::set_parent_function(const Object& value) const {
untag()->set_parent_function(value.ptr());
}
#else
void ClosureData::set_parent_function(const Function& value) const {
untag()->set_parent_function(value.ptr());
}
#endif
void FfiTrampolineData::set_c_signature(const FunctionType& value) const {
untag()->set_c_signature(value.ptr());
}
void FfiTrampolineData::set_callback_id(int32_t callback_id) const {
StoreNonPointer(&untag()->callback_id_, callback_id);
}
void FfiTrampolineData::set_callback_exceptional_return(
const Instance& value) const {
untag()->set_callback_exceptional_return(value.ptr());
}
FfiTrampolineDataPtr FfiTrampolineData::New() {
ASSERT(Object::ffi_trampoline_data_class() != Class::null());
ObjectPtr raw = Object::Allocate(FfiTrampolineData::kClassId,
FfiTrampolineData::InstanceSize(),
Heap::kOld, /*compressed*/ true);
FfiTrampolineDataPtr data = static_cast<FfiTrampolineDataPtr>(raw);
data->untag()->callback_id_ = 0;
return data;
}
const char* FfiTrampolineData::ToCString() const {
const FunctionType& c_sig = FunctionType::Handle(c_signature());
return OS::SCreate(Thread::Current()->zone(),
"TrampolineData: c_signature=%s",
c_sig.ToUserVisibleCString());
}
FieldPtr Field::CloneFromOriginal() const {
return this->Clone(*this);
}
FieldPtr Field::Original() const {
if (IsNull()) {
return Field::null();
}
Object& obj = Object::Handle(untag()->owner());
if (obj.IsField()) {
return Field::RawCast(obj.ptr());
} else {
return this->ptr();
}
}
const Object* Field::CloneForUnboxed(const Object& value) const {
if (is_unboxing_candidate() && !is_nullable()) {
switch (guarded_cid()) {
case kDoubleCid:
case kFloat32x4Cid:
case kFloat64x2Cid:
return &Object::Handle(Object::Clone(value, Heap::kNew));
default:
// Not a supported unboxed field type.
return &value;
}
}
return &value;
}
void Field::DisableFieldUnboxing() const {
Thread* thread = Thread::Current();
ASSERT(!IsOriginal());
const Field& original = Field::Handle(Original());
if (!original.is_unboxing_candidate()) {
return;
}
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
if (!original.is_unboxing_candidate()) {
return;
}
original.set_is_unboxing_candidate(false);
set_is_unboxing_candidate(false);
original.DeoptimizeDependentCode();
}
intptr_t Field::guarded_cid() const {
#if defined(DEBUG)
// This assertion ensures that the cid seen by the background compiler is
// consistent. So the assertion passes if the field is a clone. It also
// passes if the field is static, because we don't use field guards on
// static fields. It also passes if we're compiling unoptimized
// code (in which case the caller might get different answers if it obtains
// the guarded cid multiple times).
Thread* thread = Thread::Current();
ASSERT(!thread->IsInsideCompiler() ||
#if !defined(DART_PRECOMPILED_RUNTIME)
((CompilerState::Current().should_clone_fields() == !IsOriginal())) ||
#endif
is_static());
#endif
return LoadNonPointer<ClassIdTagType, std::memory_order_relaxed>(
&untag()->guarded_cid_);
}
bool Field::is_nullable(bool silence_assert) const {
#if defined(DEBUG)
if (!silence_assert) {
// Same assert as guarded_cid(), because is_nullable() also needs to be
// consistent for the background compiler.
Thread* thread = Thread::Current();
ASSERT(
!thread->IsInsideCompiler() ||
#if !defined(DART_PRECOMPILED_RUNTIME)
((CompilerState::Current().should_clone_fields() == !IsOriginal())) ||
#endif
is_static());
}
#endif
return untag()->is_nullable_ == kNullCid;
}
void Field::SetOriginal(const Field& value) const {
ASSERT(value.IsOriginal());
ASSERT(!value.IsNull());
untag()->set_owner(static_cast<ObjectPtr>(value.ptr()));
}
StringPtr Field::GetterName(const String& field_name) {
return String::Concat(Symbols::GetterPrefix(), field_name);
}
StringPtr Field::GetterSymbol(const String& field_name) {
return Symbols::FromGet(Thread::Current(), field_name);
}
StringPtr Field::LookupGetterSymbol(const String& field_name) {
return Symbols::LookupFromGet(Thread::Current(), field_name);
}
StringPtr Field::SetterName(const String& field_name) {
return String::Concat(Symbols::SetterPrefix(), field_name);
}
StringPtr Field::SetterSymbol(const String& field_name) {
return Symbols::FromSet(Thread::Current(), field_name);
}
StringPtr Field::LookupSetterSymbol(const String& field_name) {
return Symbols::LookupFromSet(Thread::Current(), field_name);
}
StringPtr Field::NameFromGetter(const String& getter_name) {
return Symbols::New(Thread::Current(), getter_name, kGetterPrefixLength,
getter_name.Length() - kGetterPrefixLength);
}
StringPtr Field::NameFromSetter(const String& setter_name) {
return Symbols::New(Thread::Current(), setter_name, kSetterPrefixLength,
setter_name.Length() - kSetterPrefixLength);
}
StringPtr Field::NameFromInit(const String& init_name) {
return Symbols::New(Thread::Current(), init_name, kInitPrefixLength,
init_name.Length() - kInitPrefixLength);
}
bool Field::IsGetterName(const String& function_name) {
return function_name.StartsWith(Symbols::GetterPrefix());
}
bool Field::IsSetterName(const String& function_name) {
return function_name.StartsWith(Symbols::SetterPrefix());
}
bool Field::IsInitName(const String& function_name) {
return function_name.StartsWith(Symbols::InitPrefix());
}
void Field::set_name(const String& value) const {
ASSERT(value.IsSymbol());
ASSERT(IsOriginal());
untag()->set_name(value.ptr());
}
ObjectPtr Field::RawOwner() const {
if (IsOriginal()) {
return untag()->owner();
} else {
const Field& field = Field::Handle(Original());
ASSERT(field.IsOriginal());
ASSERT(!Object::Handle(field.untag()->owner()).IsField());
return field.untag()->owner();
}
}
ClassPtr Field::Owner() const {
const Field& field = Field::Handle(Original());
ASSERT(field.IsOriginal());
const Object& obj = Object::Handle(field.untag()->owner());
if (obj.IsClass()) {
return Class::Cast(obj).ptr();
}
ASSERT(obj.IsPatchClass());
return PatchClass::Cast(obj).patched_class();
}
ClassPtr Field::Origin() const {
const Field& field = Field::Handle(Original());
ASSERT(field.IsOriginal());
const Object& obj = Object::Handle(field.untag()->owner());
if (obj.IsClass()) {
return Class::Cast(obj).ptr();
}
ASSERT(obj.IsPatchClass());
return PatchClass::Cast(obj).origin_class();
}
ScriptPtr Field::Script() const {
// NOTE(turnidge): If you update this function, you probably want to
// update Class::PatchFieldsAndFunctions() at the same time.
const Field& field = Field::Handle(Original());
ASSERT(field.IsOriginal());
const Object& obj = Object::Handle(field.untag()->owner());
if (obj.IsClass()) {
return Class::Cast(obj).script();
}
ASSERT(obj.IsPatchClass());
return PatchClass::Cast(obj).script();
}
ExternalTypedDataPtr Field::KernelData() const {
const Object& obj = Object::Handle(this->untag()->owner());
// During background JIT compilation field objects are copied
// and copy points to the original field via the owner field.
if (obj.IsField()) {
return Field::Cast(obj).KernelData();
} else if (obj.IsClass()) {
Library& library = Library::Handle(Class::Cast(obj).library());
return library.kernel_data();
}
ASSERT(obj.IsPatchClass());
return PatchClass::Cast(obj).library_kernel_data();
}
void Field::InheritKernelOffsetFrom(const Field& src) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
StoreNonPointer(&untag()->kernel_offset_, src.untag()->kernel_offset_);
#endif
}
intptr_t Field::KernelDataProgramOffset() const {
const Object& obj = Object::Handle(untag()->owner());
// During background JIT compilation field objects are copied
// and copy points to the original field via the owner field.
if (obj.IsField()) {
return Field::Cast(obj).KernelDataProgramOffset();
} else if (obj.IsClass()) {
Library& lib = Library::Handle(Class::Cast(obj).library());
return lib.kernel_offset();
}
ASSERT(obj.IsPatchClass());
return PatchClass::Cast(obj).library_kernel_offset();
}
void Field::SetFieldTypeSafe(const AbstractType& value) const {
ASSERT(IsOriginal());
ASSERT(!value.IsNull());
if (value.ptr() != type()) {
untag()->set_type(value.ptr());
}
}
// Called at finalization time
void Field::SetFieldType(const AbstractType& value) const {
DEBUG_ASSERT(
IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
SetFieldTypeSafe(value);
}
FieldPtr Field::New() {
ASSERT(Object::field_class() != Class::null());
ObjectPtr raw = Object::Allocate(Field::kClassId, Field::InstanceSize(),
Heap::kOld, /*compressed*/ true);
return static_cast<FieldPtr>(raw);
}
void Field::InitializeNew(const Field& result,
const String& name,
bool is_static,
bool is_final,
bool is_const,
bool is_reflectable,
bool is_late,
const Object& owner,
TokenPosition token_pos,
TokenPosition end_token_pos) {
result.set_kind_bits(0);
result.set_name(name);
result.set_is_static(is_static);
if (is_static) {
result.set_field_id_unsafe(-1);
} else {
result.SetOffset(0, 0);
}
result.set_is_final(is_final);
result.set_is_const(is_const);
result.set_is_reflectable(is_reflectable);
result.set_is_late(is_late);
result.set_is_double_initialized_unsafe(false);
result.set_owner(owner);
result.set_token_pos(token_pos);
result.set_end_token_pos(end_token_pos);
result.set_has_nontrivial_initializer_unsafe(false);
result.set_has_initializer_unsafe(false);
if (FLAG_precompiled_mode) {
// May be updated by KernelLoader::ReadInferredType
result.set_is_unboxing_candidate_unsafe(false);
} else {
result.set_is_unboxing_candidate_unsafe(!is_final && !is_late &&
!is_static);
}
result.set_initializer_changed_after_initialization(false);
NOT_IN_PRECOMPILED(result.set_kernel_offset(0));
result.set_has_pragma(false);
result.set_static_type_exactness_state(
StaticTypeExactnessState::NotTracking());
auto isolate_group = IsolateGroup::Current();
// Use field guards if they are enabled and the isolate has never reloaded.
// TODO(johnmccutchan): The reload case assumes the worst case (everything is
// dynamic and possibly null). Attempt to relax this later.
#if defined(PRODUCT)
const bool use_guarded_cid =
FLAG_precompiled_mode || isolate_group->use_field_guards();
#else
const bool use_guarded_cid =
FLAG_precompiled_mode || (isolate_group->use_field_guards() &&
!isolate_group->HasAttemptedReload());
#endif // !defined(PRODUCT)
result.set_guarded_cid_unsafe(use_guarded_cid ? kIllegalCid : kDynamicCid);
result.set_is_nullable_unsafe(use_guarded_cid ? false : true);
result.set_guarded_list_length_in_object_offset_unsafe(
Field::kUnknownLengthOffset);
// Presently, we only attempt to remember the list length for final fields.
if (is_final && use_guarded_cid) {
result.set_guarded_list_length_unsafe(Field::kUnknownFixedLength);
} else {
result.set_guarded_list_length_unsafe(Field::kNoFixedLength);
}
}
FieldPtr Field::New(const String& name,
bool is_static,
bool is_final,
bool is_const,
bool is_reflectable,
bool is_late,
const Object& owner,
const AbstractType& type,
TokenPosition token_pos,
TokenPosition end_token_pos) {
ASSERT(!owner.IsNull());
const Field& result = Field::Handle(Field::New());
InitializeNew(result, name, is_static, is_final, is_const, is_reflectable,
is_late, owner, token_pos, end_token_pos);
result.SetFieldTypeSafe(type);
return result.ptr();
}
FieldPtr Field::NewTopLevel(const String& name,
bool is_final,
bool is_const,
bool is_late,
const Object& owner,
TokenPosition token_pos,
TokenPosition end_token_pos) {
ASSERT(!owner.IsNull());
const Field& result = Field::Handle(Field::New());
InitializeNew(result, name, true, /* is_static */
is_final, is_const, true, /* is_reflectable */
is_late, owner, token_pos, end_token_pos);
return result.ptr();
}
FieldPtr Field::Clone(const Field& original) const {
if (original.IsNull()) {
return Field::null();
}
ASSERT(original.IsOriginal());
Field& clone = Field::Handle();
clone ^= Object::Clone(*this, Heap::kOld);
clone.SetOriginal(original);
clone.InheritKernelOffsetFrom(original);
return clone.ptr();
}
int32_t Field::SourceFingerprint() const {
#if !defined(DART_PRECOMPILED_RUNTIME)
return kernel::KernelSourceFingerprintHelper::CalculateFieldFingerprint(
*this);
#else
return 0;
#endif // !defined(DART_PRECOMPILED_RUNTIME)
}
StringPtr Field::InitializingExpression() const {
UNREACHABLE();
return String::null();
}
const char* Field::UserVisibleNameCString() const {
NoSafepointScope no_safepoint;
if (FLAG_show_internal_names) {
return String::Handle(name()).ToCString();
}
return String::ScrubName(String::Handle(name()), is_extension_member());
}
StringPtr Field::UserVisibleName() const {
if (FLAG_show_internal_names) {
return name();
}
return Symbols::New(
Thread::Current(),
String::ScrubName(String::Handle(name()), is_extension_member()));
}
intptr_t Field::guarded_list_length() const {
return Smi::Value(untag()->guarded_list_length());
}
void Field::set_guarded_list_length_unsafe(intptr_t list_length) const {
ASSERT(IsOriginal());
untag()->set_guarded_list_length(Smi::New(list_length));
}
intptr_t Field::guarded_list_length_in_object_offset() const {
return untag()->guarded_list_length_in_object_offset_ + kHeapObjectTag;
}
void Field::set_guarded_list_length_in_object_offset_unsafe(
intptr_t list_length_offset) const {
ASSERT(IsOriginal());
StoreNonPointer(&untag()->guarded_list_length_in_object_offset_,
static_cast<int8_t>(list_length_offset - kHeapObjectTag));
ASSERT(guarded_list_length_in_object_offset() == list_length_offset);
}
bool Field::NeedsSetter() const {
// Late fields always need a setter, unless they're static and non-final, or
// final with an initializer.
if (is_late()) {
if (is_static() && !is_final()) {
return false;
}
if (is_final() && has_initializer()) {
return false;
}
return true;
}
// Non-late static fields never need a setter.
if (is_static()) {
return false;
}
// Otherwise, the field only needs a setter if it isn't final.
return !is_final();
}
bool Field::NeedsGetter() const {
// All instance fields need a getter.
if (!is_static()) return true;
// Static fields also need a getter if they have a non-trivial initializer,
// because it needs to be initialized lazily.
if (has_nontrivial_initializer()) return true;
// Static late fields with no initializer also need a getter, to check if it's
// been initialized.
return is_late() && !has_initializer();
}
const char* Field::ToCString() const {
NoSafepointScope no_safepoint;
if (IsNull()) {
return "Field: null";
}
const char* kF0 = is_static() ? " static" : "";
const char* kF1 = is_late() ? " late" : "";
const char* kF2 = is_final() ? " final" : "";
const char* kF3 = is_const() ? " const" : "";
const char* field_name = String::Handle(name()).ToCString();
const Class& cls = Class::Handle(Owner());
const char* cls_name = String::Handle(cls.Name()).ToCString();
return OS::SCreate(Thread::Current()->zone(), "Field <%s.%s>:%s%s%s%s",
cls_name, field_name, kF0, kF1, kF2, kF3);
}
// Build a closure object that gets (or sets) the contents of a static
// field f and cache the closure in a newly created static field
// named #f (or #f= in case of a setter).
InstancePtr Field::AccessorClosure(bool make_setter) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
ASSERT(is_static());
const Class& field_owner = Class::Handle(zone, Owner());
String& closure_name = String::Handle(zone, this->name());
closure_name = Symbols::FromConcat(thread, Symbols::HashMark(), closure_name);
if (make_setter) {
closure_name =
Symbols::FromConcat(thread, Symbols::HashMark(), closure_name);
}
Field& closure_field = Field::Handle(zone);
closure_field = field_owner.LookupStaticField(closure_name);
if (!closure_field.IsNull()) {
ASSERT(closure_field.is_static());
const Instance& closure =
Instance::Handle(zone, closure_field.StaticValue());
ASSERT(!closure.IsNull());
ASSERT(closure.IsClosure());
return closure.ptr();
}
UNREACHABLE();
return Instance::null();
}
InstancePtr Field::GetterClosure() const {
return AccessorClosure(false);
}
InstancePtr Field::SetterClosure() const {
return AccessorClosure(true);
}
ArrayPtr Field::dependent_code() const {
DEBUG_ASSERT(
IsolateGroup::Current()->program_lock()->IsCurrentThreadReader());
return untag()->dependent_code();
}
void Field::set_dependent_code(const Array& array) const {
ASSERT(IsOriginal());
DEBUG_ASSERT(
IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
untag()->set_dependent_code(array.ptr());
}
class FieldDependentArray : public WeakCodeReferences {
public:
explicit FieldDependentArray(const Field& field)
: WeakCodeReferences(Array::Handle(field.dependent_code())),
field_(field) {}
virtual void UpdateArrayTo(const Array& value) {
field_.set_dependent_code(value);
}
virtual void ReportDeoptimization(const Code& code) {
if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) {
Function& function = Function::Handle(code.function());
THR_Print("Deoptimizing %s because guard on field %s failed.\n",
function.ToFullyQualifiedCString(), field_.ToCString());
}
}
virtual void ReportSwitchingCode(const Code& code) {
if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) {
Function& function = Function::Handle(code.function());
THR_Print(
"Switching '%s' to unoptimized code because guard"
" on field '%s' was violated.\n",
function.ToFullyQualifiedCString(), field_.ToCString());
}
}
private:
const Field& field_;
DISALLOW_COPY_AND_ASSIGN(FieldDependentArray);
};
void Field::RegisterDependentCode(const Code& code) const {
ASSERT(IsOriginal());
DEBUG_ASSERT(IsMutatorOrAtSafepoint());
ASSERT(code.is_optimized());
FieldDependentArray a(*this);
a.Register(code);
}
void Field::DeoptimizeDependentCode() const {
DEBUG_ASSERT(
IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
ASSERT(IsOriginal());
FieldDependentArray a(*this);
if (FLAG_trace_deoptimization && a.HasCodes()) {
THR_Print("Deopt for field guard (field %s)\n", ToCString());
}
a.DisableCode();
}
bool Field::IsConsistentWith(const Field& other) const {
return (untag()->guarded_cid_ == other.untag()->guarded_cid_) &&
(untag()->is_nullable_ == other.untag()->is_nullable_) &&
(untag()->guarded_list_length() ==
other.untag()->guarded_list_length()) &&
(is_unboxing_candidate() == other.is_unboxing_candidate()) &&
(static_type_exactness_state().Encode() ==
other.static_type_exactness_state().Encode());
}
bool Field::IsUninitialized() const {
Thread* thread = Thread::Current();
const FieldTable* field_table = thread->isolate()->field_table();
const InstancePtr raw_value = field_table->At(field_id());
ASSERT(raw_value != Object::transition_sentinel().ptr());
return raw_value == Object::sentinel().ptr();
}
FunctionPtr Field::EnsureInitializerFunction() const {
ASSERT(has_nontrivial_initializer());
ASSERT(IsOriginal());
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Function& initializer = Function::Handle(zone, InitializerFunction());
if (initializer.IsNull()) {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
SafepointMutexLocker ml(
thread->isolate_group()->initializer_functions_mutex());
// Double check after grabbing the lock.
initializer = InitializerFunction();
if (initializer.IsNull()) {
initializer = kernel::CreateFieldInitializerFunction(thread, zone, *this);
}
#endif
}
return initializer.ptr();
}
void Field::SetInitializerFunction(const Function& initializer) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
ASSERT(IsOriginal());
ASSERT(IsolateGroup::Current()
->initializer_functions_mutex()
->IsOwnedByCurrentThread());
// We have to ensure that all stores into the initializer function object
// happen before releasing the pointer to the initializer as it may be
// accessed without grabbing the lock.
untag()->set_initializer_function<std::memory_order_release>(
initializer.ptr());
#endif
}
bool Field::HasInitializerFunction() const {
return untag()->initializer_function() != Function::null();
}
ErrorPtr Field::InitializeInstance(const Instance& instance) const {
ASSERT(IsOriginal());
ASSERT(is_instance());
ASSERT(instance.GetField(*this) == Object::sentinel().ptr());
Object& value = Object::Handle();
if (has_nontrivial_initializer()) {
const Function& initializer = Function::Handle(EnsureInitializerFunction());
const Array& args = Array::Handle(Array::New(1));
args.SetAt(0, instance);
value = DartEntry::InvokeFunction(initializer, args);
if (!value.IsNull() && value.IsError()) {
return Error::Cast(value).ptr();
}
} else {
if (is_late() && !has_initializer()) {
Exceptions::ThrowLateFieldNotInitialized(String::Handle(name()));
UNREACHABLE();
}
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
// Our trivial initializer is `null`. Any non-`null` initializer is
// non-trivial (see `KernelLoader::CheckForInitializer()`).
value = Object::null();
#endif
}
ASSERT(value.IsNull() || value.IsInstance());
if (is_late() && is_final() &&
(instance.GetField(*this) != Object::sentinel().ptr())) {
Exceptions::ThrowLateFieldAssignedDuringInitialization(
String::Handle(name()));
UNREACHABLE();
}
instance.SetField(*this, value);
return Error::null();
}
ErrorPtr Field::InitializeStatic() const {
ASSERT(IsOriginal());
ASSERT(is_static());
if (StaticValue() == Object::sentinel().ptr()) {
auto& value = Object::Handle();
if (is_late()) {
if (!has_initializer()) {
Exceptions::ThrowLateFieldNotInitialized(String::Handle(name()));
UNREACHABLE();
}
value = EvaluateInitializer();
if (value.IsError()) {
return Error::Cast(value).ptr();
}
if (is_final() && (StaticValue() != Object::sentinel().ptr())) {
Exceptions::ThrowLateFieldAssignedDuringInitialization(
String::Handle(name()));
UNREACHABLE();
}
} else {
SetStaticValue(Object::transition_sentinel());
value = EvaluateInitializer();
if (value.IsError()) {
SetStaticValue(Object::null_instance());
return Error::Cast(value).ptr();
}
}
ASSERT(value.IsNull() || value.IsInstance());
SetStaticValue(value.IsNull() ? Instance::null_instance()
: Instance::Cast(value));
return Error::null();
} else if (StaticValue() == Object::transition_sentinel().ptr()) {
ASSERT(!is_late());
const Array& ctor_args = Array::Handle(Array::New(1));
const String& field_name = String::Handle(name());
ctor_args.SetAt(0, field_name);
Exceptions::ThrowByType(Exceptions::kCyclicInitializationError, ctor_args);
UNREACHABLE();
}
return Error::null();
}
ObjectPtr Field::StaticConstFieldValue() const {
ASSERT(is_static() && is_const());
auto thread = Thread::Current();
auto zone = thread->zone();
auto initial_field_table = thread->isolate_group()->initial_field_table();
// We can safely cache the value of the static const field in the initial
// field table.
auto& value = Object::Handle(zone, initial_field_table->At(field_id()));
if (value.ptr() == Object::sentinel().ptr()) {
ASSERT(has_initializer());
value = EvaluateInitializer();
if (!value.IsError()) {
ASSERT(value.IsNull() || value.IsInstance());
SetStaticConstFieldValue(value.IsNull() ? Instance::null_instance()
: Instance::Cast(value));
}
}
return value.ptr();
}
void Field::SetStaticConstFieldValue(const Instance& value,
bool assert_initializing_store) const {
auto thread = Thread::Current();
auto initial_field_table = thread->isolate_group()->initial_field_table();
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
ASSERT(initial_field_table->At(field_id()) == Object::sentinel().ptr() ||
initial_field_table->At(field_id()) == value.ptr() ||
!assert_initializing_store);
initial_field_table->SetAt(field_id(), value.IsNull()
? Instance::null_instance().ptr()
: Instance::Cast(value).ptr());
}
ObjectPtr Field::EvaluateInitializer() const {
Thread* const thread = Thread::Current();
ASSERT(thread->IsMutatorThread());
#if !defined(DART_PRECOMPILED_RUNTIME)
if (is_static() && is_const()) {
return kernel::EvaluateStaticConstFieldInitializer(*this);
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
NoOOBMessageScope no_msg_scope(thread);
NoReloadScope no_reload_scope(thread);
const Function& initializer = Function::Handle(EnsureInitializerFunction());
return DartEntry::InvokeFunction(initializer, Object::empty_array());
}
static intptr_t GetListLength(const Object& value) {
if (value.IsTypedData() || value.IsTypedDataView() ||
value.IsExternalTypedData()) {
return TypedDataBase::Cast(value).Length();
} else if (value.IsArray()) {
return Array::Cast(value).Length();
} else if (value.IsGrowableObjectArray()) {
// List length is variable.
return Field::kNoFixedLength;
}
return Field::kNoFixedLength;
}
static intptr_t GetListLengthOffset(intptr_t cid) {
if (IsTypedDataClassId(cid) || IsTypedDataViewClassId(cid) ||
IsExternalTypedDataClassId(cid)) {
return TypedData::length_offset();
} else if (cid == kArrayCid || cid == kImmutableArrayCid) {
return Array::length_offset();
} else if (cid == kGrowableObjectArrayCid) {
// List length is variable.
return Field::kUnknownLengthOffset;
}
return Field::kUnknownLengthOffset;
}
const char* Field::GuardedPropertiesAsCString() const {
if (guarded_cid() == kIllegalCid) {
return "<?>";
} else if (guarded_cid() == kDynamicCid) {
ASSERT(!static_type_exactness_state().IsExactOrUninitialized());
return "<*>";
}
Zone* zone = Thread::Current()->zone();
const char* exactness = "";
if (static_type_exactness_state().IsTracking()) {
exactness =
zone->PrintToString(" {%s}", static_type_exactness_state().ToCString());
}
const Class& cls =
Class::Handle(IsolateGroup::Current()->class_table()->At(guarded_cid()));
const char* class_name = String::Handle(cls.Name()).ToCString();
if (IsBuiltinListClassId(guarded_cid()) && !is_nullable() && is_final()) {
ASSERT(guarded_list_length() != kUnknownFixedLength);
if (guarded_list_length() == kNoFixedLength) {
return zone->PrintToString("<%s [*]%s>", class_name, exactness);
} else {
return zone->PrintToString(
"<%s [%" Pd " @%" Pd "]%s>", class_name, guarded_list_length(),
guarded_list_length_in_object_offset(), exactness);
}
}
return zone->PrintToString("<%s %s%s>",
is_nullable() ? "nullable" : "not-nullable",
class_name, exactness);
}
void Field::InitializeGuardedListLengthInObjectOffset(bool unsafe) const {
auto setter = unsafe ? &Field::set_guarded_list_length_in_object_offset_unsafe
: &Field::set_guarded_list_length_in_object_offset;
ASSERT(IsOriginal());
if (needs_length_check() &&
(guarded_list_length() != Field::kUnknownFixedLength)) {
const intptr_t offset = GetListLengthOffset(guarded_cid());
(this->*setter)(offset);
ASSERT(offset != Field::kUnknownLengthOffset);
} else {
(this->*setter)(Field::kUnknownLengthOffset);
}
}
bool Field::UpdateGuardedCidAndLength(const Object& value) const {
ASSERT(IsOriginal());
const intptr_t cid = value.GetClassId();
if (guarded_cid() == kIllegalCid) {
// Field is assigned first time.
set_guarded_cid(cid);
set_is_nullable(cid == kNullCid);
// Start tracking length if needed.
ASSERT((guarded_list_length() == Field::kUnknownFixedLength) ||
(guarded_list_length() == Field::kNoFixedLength));
if (needs_length_check()) {
ASSERT(guarded_list_length() == Field::kUnknownFixedLength);
set_guarded_list_length(GetListLength(value));
InitializeGuardedListLengthInObjectOffset();
}
if (FLAG_trace_field_guards) {
THR_Print(" => %s\n", GuardedPropertiesAsCString());
}
return false;
}
if ((cid == guarded_cid()) || ((cid == kNullCid) && is_nullable())) {
// Class id of the assigned value matches expected class id and nullability.
// If we are tracking length check if it has matches.
if (needs_length_check() &&
(guarded_list_length() != GetListLength(value))) {
ASSERT(guarded_list_length() != Field::kUnknownFixedLength);
set_guarded_list_length(Field::kNoFixedLength);
set_guarded_list_length_in_object_offset(Field::kUnknownLengthOffset);
return true;
}
// Everything matches.
return false;
}
if ((cid == kNullCid) && !is_nullable()) {
// Assigning null value to a non-nullable field makes it nullable.
set_is_nullable(true);
} else if ((cid != kNullCid) && (guarded_cid() == kNullCid)) {
// Assigning non-null value to a field that previously contained only null
// turns it into a nullable field with the given class id.
ASSERT(is_nullable());
set_guarded_cid(cid);
} else {
// Give up on tracking class id of values contained in this field.
ASSERT(guarded_cid() != cid);
set_guarded_cid(kDynamicCid);
set_is_nullable(true);
}
// If we were tracking length drop collected feedback.
if (needs_length_check()) {
ASSERT(guarded_list_length() != Field::kUnknownFixedLength);
set_guarded_list_length(Field::kNoFixedLength);
set_guarded_list_length_in_object_offset(Field::kUnknownLengthOffset);
}
// Expected class id or nullability of the field changed.
return true;
}
// Given the type G<T0, ..., Tn> and class C<U0, ..., Un> find path to C at G.
// This path can be used to compute type arguments of C at G.
//
// Note: we are relying on the restriction that the same class can only occur
// once among the supertype.
static bool FindInstantiationOf(const Type& type,
const Class& cls,
GrowableArray<const AbstractType*>* path,
bool consider_only_super_classes) {
if (type.type_class() == cls.ptr()) {
return true; // Found instantiation.
}
Class& cls2 = Class::Handle();
AbstractType& super_type = AbstractType::Handle();
super_type = cls.super_type();
if (!super_type.IsNull() && !super_type.IsObjectType()) {
cls2 = super_type.type_class();
path->Add(&super_type);
if (FindInstantiationOf(type, cls2, path, consider_only_super_classes)) {
return true; // Found instantiation.
}
path->RemoveLast();
}
if (!consider_only_super_classes) {
Array& super_interfaces = Array::Handle(cls.interfaces());
for (intptr_t i = 0; i < super_interfaces.Length(); i++) {
super_type ^= super_interfaces.At(i);
cls2 = super_type.type_class();
path->Add(&super_type);
if (FindInstantiationOf(type, cls2, path,
/*consider_only_supertypes=*/false)) {
return true; // Found instantiation.
}
path->RemoveLast();
}
}
return false; // Not found.
}
void Field::SetStaticValue(const Instance& value) const {
auto thread = Thread::Current();
ASSERT(thread->IsMutatorThread());
ASSERT(is_static()); // Valid only for static dart fields.
const intptr_t id = field_id();
ASSERT(id >= 0);
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
thread->isolate()->field_table()->SetAt(id, value.ptr());
}
static StaticTypeExactnessState TrivialTypeExactnessFor(const Class& cls) {
const intptr_t type_arguments_offset = cls.host_type_arguments_field_offset();
ASSERT(type_arguments_offset != Class::kNoTypeArguments);
if (StaticTypeExactnessState::CanRepresentAsTriviallyExact(
type_arguments_offset / kWordSize)) {
return StaticTypeExactnessState::TriviallyExact(type_arguments_offset /
kWordSize);
} else {
return StaticTypeExactnessState::NotExact();
}
}
static const char* SafeTypeArgumentsToCString(const TypeArguments& args) {
return (args.ptr() == TypeArguments::null()) ? "<null>" : args.ToCString();
}
StaticTypeExactnessState StaticTypeExactnessState::Compute(
const Type& static_type,
const Instance& value,
bool print_trace /* = false */) {
ASSERT(!value.IsNull()); // Should be handled by the caller.
ASSERT(value.ptr() != Object::sentinel().ptr());
ASSERT(value.ptr() != Object::transition_sentinel().ptr());
const TypeArguments& static_type_args =
TypeArguments::Handle(static_type.arguments());
TypeArguments& args = TypeArguments::Handle();
ASSERT(static_type.IsFinalized());
const Class& cls = Class::Handle(value.clazz());
GrowableArray<const AbstractType*> path(10);
bool is_super_class = true;
if (!FindInstantiationOf(static_type, cls, &path,
/*consider_only_super_classes=*/true)) {
is_super_class = false;
bool found_super_interface = FindInstantiationOf(
static_type, cls, &path, /*consider_only_super_classes=*/false);
ASSERT(found_super_interface);
}
// Trivial case: field has type G<T0, ..., Tn> and value has type
// G<U0, ..., Un>. Check if type arguments match.
if (path.is_empty()) {
ASSERT(cls.ptr() == static_type.type_class());
args = value.GetTypeArguments();
// TODO(dartbug.com/34170) Evaluate if comparing relevant subvectors (that
// disregards superclass own arguments) improves precision of the
// tracking.
if (args.ptr() == static_type_args.ptr()) {
return TrivialTypeExactnessFor(cls);
}
if (print_trace) {
THR_Print(" expected %s got %s type arguments\n",
SafeTypeArgumentsToCString(static_type_args),
SafeTypeArgumentsToCString(args));
}
return StaticTypeExactnessState::NotExact();
}
// Value has type C<U0, ..., Un> and field has type G<T0, ..., Tn> and G != C.
// Compute C<X0, ..., Xn> at G (Xi are free type arguments).
// Path array contains a chain of immediate supertypes S0 <: S1 <: ... Sn,
// such that S0 is an immediate supertype of C and Sn is G<...>.
// Each Si might depend on type parameters of the previous supertype S{i-1}.
// To compute C<X0, ..., Xn> at G we walk the chain backwards and
// instantiate Si using type parameters of S{i-1} which gives us a type
// depending on type parameters of S{i-2}.
AbstractType& type = AbstractType::Handle(path.Last()->ptr());
for (intptr_t i = path.length() - 2; (i >= 0) && !type.IsInstantiated();
i--) {
args = path[i]->arguments();
type = type.InstantiateFrom(args, TypeArguments::null_type_arguments(),
kAllFree, Heap::kNew);
}
if (type.IsInstantiated()) {
// C<X0, ..., Xn> at G is fully instantiated and does not depend on
// Xi. In this case just check if type arguments match.
args = type.arguments();
if (args.Equals(static_type_args)) {
return is_super_class ? StaticTypeExactnessState::HasExactSuperClass()
: StaticTypeExactnessState::HasExactSuperType();
}
if (print_trace) {
THR_Print(" expected %s got %s type arguments\n",
SafeTypeArgumentsToCString(static_type_args),
SafeTypeArgumentsToCString(args));
}
return StaticTypeExactnessState::NotExact();
}
// The most complicated case: C<X0, ..., Xn> at G depends on
// Xi values. To compare type arguments we would need to instantiate
// it fully from value's type arguments and compare with <U0, ..., Un>.
// However this would complicate fast path in the native code. To avoid this
// complication we would optimize for the trivial case: we check if
// C<X0, ..., Xn> at G is exactly G<X0, ..., Xn> which means we can simply
// compare values type arguements (<T0, ..., Tn>) to fields type arguments
// (<U0, ..., Un>) to establish if field type is exact.
ASSERT(cls.IsGeneric());
const intptr_t num_type_params = cls.NumTypeParameters();
bool trivial_case =
(num_type_params ==
Class::Handle(static_type.type_class()).NumTypeParameters()) &&
(value.GetTypeArguments() == static_type.arguments());
if (!trivial_case && FLAG_trace_field_guards) {
THR_Print("Not a simple case: %" Pd " vs %" Pd
" type parameters, %s vs %s type arguments\n",
num_type_params,
Class::Handle(static_type.type_class()).NumTypeParameters(),
SafeTypeArgumentsToCString(
TypeArguments::Handle(value.GetTypeArguments())),
SafeTypeArgumentsToCString(static_type_args));
}
AbstractType& type_arg = AbstractType::Handle();
args = type.arguments();
for (intptr_t i = 0; (i < num_type_params) && trivial_case; i++) {
type_arg = args.TypeAt(i);
if (!type_arg.IsTypeParameter() ||
(TypeParameter::Cast(type_arg).index() != i)) {
if (FLAG_trace_field_guards) {
THR_Print(" => encountered %s at index % " Pd "\n",
type_arg.ToCString(), i);
}
trivial_case = false;
}
}
return trivial_case ? TrivialTypeExactnessFor(cls)
: StaticTypeExactnessState::NotExact();
}
const char* StaticTypeExactnessState::ToCString() const {
if (!IsTracking()) {
return "not-tracking";
} else if (!IsExactOrUninitialized()) {
return "not-exact";
} else if (IsTriviallyExact()) {
return Thread::Current()->zone()->PrintToString(
"trivially-exact(%hhu)", GetTypeArgumentsOffsetInWords());
} else if (IsHasExactSuperType()) {
return "has-exact-super-type";
} else if (IsHasExactSuperClass()) {
return "has-exact-super-class";
} else {
ASSERT(IsUninitialized());
return "uninitialized-exactness";
}
}
bool Field::UpdateGuardedExactnessState(const Object& value) const {
if (!static_type_exactness_state().IsExactOrUninitialized()) {
// Nothing to update.
return false;
}
if (guarded_cid() == kDynamicCid) {
if (FLAG_trace_field_guards) {
THR_Print(
" => switching off exactness tracking because guarded cid is "
"dynamic\n");
}
set_static_type_exactness_state(StaticTypeExactnessState::NotExact());
return true; // Invalidate.
}
// If we are storing null into a field or we have an exact super type
// then there is nothing to do.
if (value.IsNull() || static_type_exactness_state().IsHasExactSuperType() ||
static_type_exactness_state().IsHasExactSuperClass()) {
return false;
}
// If we are storing a non-null value into a field that is considered
// to be trivially exact then we need to check if value has an appropriate
// type.
ASSERT(guarded_cid() != kNullCid);
const Type& field_type = Type::Cast(AbstractType::Handle(type()));
const TypeArguments& field_type_args =
TypeArguments::Handle(field_type.arguments());
const Instance& instance = Instance::Cast(value);
TypeArguments& args = TypeArguments::Handle();
if (static_type_exactness_state().IsTriviallyExact()) {
args = instance.GetTypeArguments();
if (args.ptr() == field_type_args.ptr()) {
return false;
}
if (FLAG_trace_field_guards) {
THR_Print(" expected %s got %s type arguments\n",
field_type_args.ToCString(), args.ToCString());
}
set_static_type_exactness_state(StaticTypeExactnessState::NotExact());
return true;
}
ASSERT(static_type_exactness_state().IsUninitialized());
set_static_type_exactness_state(StaticTypeExactnessState::Compute(
field_type, instance, FLAG_trace_field_guards));
return true;
}
void Field::RecordStore(const Object& value) const {
ASSERT(IsOriginal());
if (!IsolateGroup::Current()->use_field_guards()) {
return;
}
// We should never try to record a sentinel.
ASSERT(value.ptr() != Object::sentinel().ptr());
Thread* const thread = Thread::Current();
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
if ((guarded_cid() == kDynamicCid) ||
(is_nullable() && value.ptr() == Object::null())) {
// Nothing to do: the field is not guarded or we are storing null into
// a nullable field.
return;
}
if (FLAG_trace_field_guards) {
THR_Print("Store %s %s <- %s\n", ToCString(), GuardedPropertiesAsCString(),
value.ToCString());
}
bool invalidate = false;
if (UpdateGuardedCidAndLength(value)) {
invalidate = true;
}
if (UpdateGuardedExactnessState(value)) {
invalidate = true;
}
if (invalidate) {
if (FLAG_trace_field_guards) {
THR_Print(" => %s\n", GuardedPropertiesAsCString());
}
DeoptimizeDependentCode();
}
}
void Field::ForceDynamicGuardedCidAndLength() const {
// Assume nothing about this field.
set_is_unboxing_candidate(false);
set_guarded_cid(kDynamicCid);
set_is_nullable(true);
set_guarded_list_length(Field::kNoFixedLength);
set_guarded_list_length_in_object_offset(Field::kUnknownLengthOffset);
if (static_type_exactness_state().IsTracking()) {
set_static_type_exactness_state(StaticTypeExactnessState::NotExact());
}
// Drop any code that relied on the above assumptions.
DeoptimizeDependentCode();
}
#if !defined(DART_PRECOMPILED_RUNTIME)
void Field::set_type_test_cache(const SubtypeTestCache& cache) const {
untag()->set_type_test_cache(cache.ptr());
}
#endif
bool Script::HasSource() const {
return untag()->source() != String::null();
}
StringPtr Script::Source() const {
return untag()->source();
}
bool Script::IsPartOfDartColonLibrary() const {
const String& script_url = String::Handle(url());
return (script_url.StartsWith(Symbols::DartScheme()) ||
script_url.StartsWith(Symbols::DartSchemePrivate()));
}
#if !defined(DART_PRECOMPILED_RUNTIME)
void Script::LoadSourceFromKernel(const uint8_t* kernel_buffer,
intptr_t kernel_buffer_len) const {
String& uri = String::Handle(resolved_url());
String& source = String::Handle(kernel::KernelLoader::FindSourceForScript(
kernel_buffer, kernel_buffer_len, uri));
set_source(source);
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
void Script::set_compile_time_constants(const Array& value) const {
untag()->set_compile_time_constants(value.ptr());
}
void Script::set_kernel_program_info(const KernelProgramInfo& info) const {
untag()->set_kernel_program_info(info.ptr());
}
void Script::set_kernel_script_index(const intptr_t kernel_script_index) const {
StoreNonPointer(&untag()->kernel_script_index_, kernel_script_index);
}
TypedDataPtr Script::kernel_string_offsets() const {
KernelProgramInfo& program_info =
KernelProgramInfo::Handle(kernel_program_info());
ASSERT(!program_info.IsNull());
return program_info.string_offsets();
}
void Script::LookupSourceAndLineStarts(Zone* zone) const {
#if !defined(DART_PRECOMPILED_RUNTIME)
if (!IsLazyLookupSourceAndLineStarts()) {
return;
}
const String& uri = String::Handle(zone, resolved_url());
ASSERT(uri.IsSymbol());
if (uri.Length() > 0) {
// Entry included only to provide URI - actual source should already exist
// in the VM, so try to find it.
Library& lib = Library::Handle(zone);
Script& script = Script::Handle(zone);
const GrowableObjectArray& libs = GrowableObjectArray::Handle(
zone, IsolateGroup::Current()->object_store()->libraries());
for (intptr_t i = 0; i < libs.Length(); i++) {
lib ^= libs.At(i);
script = lib.LookupScript(uri, /* useResolvedUri = */ true);
if (!script.IsNull()) {
const auto& source = String::Handle(zone, script.Source());
const auto& starts = TypedData::Handle(zone, script.line_starts());
if (!source.IsNull() || !starts.IsNull()) {
set_source(source);
set_line_starts(starts);
break;
}
}
}
}
SetLazyLookupSourceAndLineStarts(false);
#endif // !defined(DART_PRECOMPILED_RUNTIME)
}
GrowableObjectArrayPtr Script::GenerateLineNumberArray() const {
Zone* zone = Thread::Current()->zone();
const GrowableObjectArray& info =
GrowableObjectArray::Handle(zone, GrowableObjectArray::New());
const Object& line_separator = Object::Handle(zone);
LookupSourceAndLineStarts(zone);
if (line_starts() == TypedData::null()) {
// Scripts in the AOT snapshot do not have a line starts array.
// A well-formed line number array has a leading null.
info.Add(line_separator); // New line.
return info.ptr();
}
#if !defined(DART_PRECOMPILED_RUNTIME)
Smi& value = Smi::Handle(zone);
const TypedData& line_starts_data = TypedData::Handle(zone, line_starts());
intptr_t line_count = line_starts_data.Length();
const Array& debug_positions_array = Array::Handle(debug_positions());
intptr_t token_count = debug_positions_array.Length();
int token_index = 0;
kernel::KernelLineStartsReader line_starts_reader(line_starts_data, zone);
intptr_t previous_start = 0;
for (int line_index = 0; line_index < line_count; ++line_index) {
intptr_t start = previous_start + line_starts_reader.DeltaAt(line_index);
// Output the rest of the tokens if we have no next line.
intptr_t end = TokenPosition::kMaxSourcePos;
if (line_index + 1 < line_count) {
end = start + line_starts_reader.DeltaAt(line_index + 1);
}
bool first = true;
while (token_index < token_count) {
value ^= debug_positions_array.At(token_index);
intptr_t debug_position = value.Value();
if (debug_position >= end) break;
if (first) {
info.Add(line_separator); // New line.
value = Smi::New(line_index + 1); // Line number.
info.Add(value);
first = false;
}
value ^= debug_positions_array.At(token_index);
info.Add(value); // Token position.
value = Smi::New(debug_position - start + 1); // Column.
info.Add(value);
++token_index;
}
previous_start = start;
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
return info.ptr();
}
TokenPosition Script::MaxPosition() const {
#if !defined(DART_PRECOMPILED_RUNTIME)
if (HasCachedMaxPosition()) {
return TokenPosition::Deserialize(
UntaggedScript::CachedMaxPositionBitField::decode(
untag()->flags_and_max_position_));
}
auto const zone = Thread::Current()->zone();
LookupSourceAndLineStarts(zone);
if (!HasCachedMaxPosition() && line_starts() != TypedData::null()) {
const auto& starts = TypedData::Handle(zone, line_starts());
kernel::KernelLineStartsReader reader(starts, zone);
const intptr_t max_position = reader.MaxPosition();
SetCachedMaxPosition(max_position);
SetHasCachedMaxPosition(true);
return TokenPosition::Deserialize(max_position);
}
#endif
return TokenPosition::kNoSource;
}
void Script::set_url(const String& value) const {
untag()->set_url(value.ptr());
}
void Script::set_resolved_url(const String& value) const {
untag()->set_resolved_url(value.ptr());
}
void Script::set_source(const String& value) const {
untag()->set_source(value.ptr());
}
void Script::set_line_starts(const TypedData& value) const {
untag()->set_line_starts(value.ptr());
}
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
void Script::set_constant_coverage(const ExternalTypedData& value) const {
untag()->set_constant_coverage(value.ptr());
}
ExternalTypedDataPtr Script::constant_coverage() const {
return untag()->constant_coverage();
}
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
void Script::set_debug_positions(const Array& value) const {
untag()->set_debug_positions(value.ptr());
}
TypedDataPtr Script::line_starts() const {
return untag()->line_starts();
}
ArrayPtr Script::debug_positions() const {
#if !defined(DART_PRECOMPILED_RUNTIME)
Array& debug_positions_array = Array::Handle(untag()->debug_positions());
if (debug_positions_array.IsNull()) {
// This is created lazily. Now we need it.
kernel::CollectTokenPositionsFor(*this);
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
return untag()->debug_positions();
}
#if !defined(DART_PRECOMPILED_RUNTIME)
void Script::SetLazyLookupSourceAndLineStarts(bool value) const {
StoreNonPointer(&untag()->flags_and_max_position_,
UntaggedScript::LazyLookupSourceAndLineStartsBit::update(
value, untag()->flags_and_max_position_));
}
bool Script::IsLazyLookupSourceAndLineStarts() const {
return UntaggedScript::LazyLookupSourceAndLineStartsBit::decode(
untag()->flags_and_max_position_);
}
bool Script::HasCachedMaxPosition() const {
return UntaggedScript::HasCachedMaxPositionBit::decode(
untag()->flags_and_max_position_);
}
void Script::SetHasCachedMaxPosition(bool value) const {
StoreNonPointer(&untag()->flags_and_max_position_,
UntaggedScript::HasCachedMaxPositionBit::update(
value, untag()->flags_and_max_position_));
}
void Script::SetCachedMaxPosition(intptr_t value) const {
StoreNonPointer(&untag()->flags_and_max_position_,
UntaggedScript::CachedMaxPositionBitField::update(
value, untag()->flags_and_max_position_));
}
#endif
void Script::set_load_timestamp(int64_t value) const {
StoreNonPointer(&untag()->load_timestamp_, value);
}
void Script::SetLocationOffset(intptr_t line_offset,
intptr_t col_offset) const {
ASSERT(line_offset >= 0);
ASSERT(col_offset >= 0);
StoreNonPointer(&untag()->line_offset_, line_offset);
StoreNonPointer(&untag()->col_offset_, col_offset);
}
bool Script::IsValidTokenPosition(TokenPosition token_pos) const {
const TokenPosition& max_position = MaxPosition();
// We may end up with scripts that have the empty string as a source file
// in testing and the like, so allow any token position when the max position
// is 0 as well as when it is kNoSource.
return !max_position.IsReal() || !token_pos.IsReal() ||
max_position.Pos() == 0 || token_pos <= max_position;
}
#if !defined(DART_PRECOMPILED_RUNTIME)
static bool IsLetter(int32_t c) {
return (('A' <= c) && (c <= 'Z')) || (('a' <= c) && (c <= 'z'));
}
static bool IsDecimalDigit(int32_t c) {
return '0' <= c && c <= '9';
}
static bool IsIdentStartChar(int32_t c) {
return IsLetter(c) || (c == '_') || (c == '$');
}
static bool IsIdentChar(int32_t c) {
return IsLetter(c) || IsDecimalDigit(c) || (c == '_') || (c == '$');
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
bool Script::GetTokenLocation(const TokenPosition& token_pos,
intptr_t* line,
intptr_t* column) const {
ASSERT(line != nullptr);
#if defined(DART_PRECOMPILED_RUNTIME)
// Scripts in the AOT snapshot do not have a line starts array.
return false;
#else
if (!token_pos.IsReal()) return false;
auto const zone = Thread::Current()->zone();
LookupSourceAndLineStarts(zone);
const TypedData& line_starts_data = TypedData::Handle(zone, line_starts());
if (line_starts_data.IsNull()) return false;
kernel::KernelLineStartsReader line_starts_reader(line_starts_data, zone);
return line_starts_reader.LocationForPosition(token_pos.Pos(), line, column);
#endif // defined(DART_PRECOMPILED_RUNTIME)
}
intptr_t Script::GetTokenLength(const TokenPosition& token_pos) const {
#if defined(DART_PRECOMPILED_RUNTIME)
// Scripts in the AOT snapshot do not have their source.
return -1;
#else
if (!HasSource() || !token_pos.IsReal()) return -1;
auto const zone = Thread::Current()->zone();
LookupSourceAndLineStarts(zone);
// We don't explicitly save this data: Load the source and find it from there.
const String& source = String::Handle(zone, Source());
const intptr_t start = token_pos.Pos();
if (start >= source.Length()) return -1; // Can't determine token_len.
intptr_t end = start;
if (IsIdentStartChar(source.CharAt(end++))) {
for (; end < source.Length(); ++end) {
if (!IsIdentChar(source.CharAt(end))) break;
}
}
return end - start;
#endif
}
bool Script::TokenRangeAtLine(intptr_t line_number,
TokenPosition* first_token_index,
TokenPosition* last_token_index) const {
ASSERT(first_token_index != nullptr && last_token_index != nullptr);
#if defined(DART_PRECOMPILED_RUNTIME)
// Scripts in the AOT snapshot do not have a line starts array.
return false;
#else
// Line numbers are 1-indexed.
if (line_number <= 0) return false;
Zone* zone = Thread::Current()->zone();
LookupSourceAndLineStarts(zone);
const TypedData& line_starts_data = TypedData::Handle(zone, line_starts());
kernel::KernelLineStartsReader line_starts_reader(line_starts_data, zone);
if (!line_starts_reader.TokenRangeAtLine(line_number, first_token_index,
last_token_index)) {
return false;
}
#if defined(DEBUG)
intptr_t source_length;
if (!HasSource()) {
Smi& value = Smi::Handle(zone);
const Array& debug_positions_array = Array::Handle(zone, debug_positions());
value ^= debug_positions_array.At(debug_positions_array.Length() - 1);
source_length = value.Value();
} else {
const String& source = String::Handle(zone, Source());
source_length = source.Length();
}
ASSERT(last_token_index->Serialize() <= source_length);
#endif
return true;
#endif // !defined(DART_PRECOMPILED_RUNTIME)
}
// Returns the index in the given source string for the given (1-based) absolute
// line and column numbers. The line and column offsets are used to calculate
// the absolute line and column number for the starting index in the source.
//
// If the given line number is outside the range of lines represented by the
// source, the given column number invalid for the given line, or a negative
// starting index is given, a negative value is returned to indicate failure.
static intptr_t GetRelativeSourceIndex(const String& src,
intptr_t line,
intptr_t line_offset = 0,
intptr_t column = 1,
intptr_t column_offset = 0,
intptr_t starting_index = 0) {
if (starting_index < 0 || line < 1 || column < 1 || line <= line_offset ||
(line == line_offset + 1 && column <= column_offset)) {
return -1;
}
intptr_t len = src.Length();
intptr_t current_line = line_offset + 1;
intptr_t current_index = starting_index;
for (; current_index < len; current_index++) {
if (current_line == line) {
break;
}
const uint16_t c = src.CharAt(current_index);
if (c == '\n' || c == '\r') {
current_line++;
}
if (c == '\r' && current_index + 1 < len &&
src.CharAt(current_index + 1) == '\n') {
// \r\n is treated as a single line terminator.
current_index++;
}
}
if (current_line != line) {
return -1;
}
// Only adjust with column offset when still on the first line.
intptr_t current_column = 1 + (line == line_offset + 1 ? column_offset : 0);
for (; current_index < len; current_index++, current_column++) {
if (current_column == column) {
return current_index;
}
const uint16_t c = src.CharAt(current_index);
if (c == '\n' || c == '\r') {
break;
}
}
// Check for a column value representing the source's end.
if (current_column == column) {
return current_index;
}
return -1;
}
StringPtr Script::GetLine(intptr_t line_number, Heap::Space space) const {
if (!HasSource()) {
return Symbols::OptimizedOut().ptr();
}
const String& src = String::Handle(Source());
const intptr_t start =
GetRelativeSourceIndex(src, line_number, line_offset());
if (start < 0) {
return Symbols::Empty().ptr();
}
intptr_t end = start;
for (; end < src.Length(); end++) {
const uint16_t c = src.CharAt(end);
if (c == '\n' || c == '\r') {
break;
}
}
return String::SubString(src, start, end - start, space);
}
StringPtr Script::GetSnippet(intptr_t from_line,
intptr_t from_column,
intptr_t to_line,
intptr_t to_column) const {
if (!HasSource()) {
return Symbols::OptimizedOut().ptr();
}
const String& src = String::Handle(Source());
const intptr_t start = GetRelativeSourceIndex(src, from_line, line_offset(),
from_column, col_offset());
// Lines and columns are 1-based, so need to subtract one to get offsets.
const intptr_t end = GetRelativeSourceIndex(
src, to_line, from_line - 1, to_column, from_column - 1, start);
// Only need to check end, because a negative start results in a negative end.
if (end < 0) {
return String::null();
}
return String::SubString(src, start, end - start);
}
ScriptPtr Script::New() {
ASSERT(Object::script_class() != Class::null());
ObjectPtr raw = Object::Allocate(Script::kClassId, Script::InstanceSize(),
Heap::kOld, /*compressed*/ true);
return static_cast<ScriptPtr>(raw);
}
ScriptPtr Script::New(const String& url, const String& source) {
return Script::New(url, url, source);
}
ScriptPtr Script::New(const String& url,
const String& resolved_url,
const String& source) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const Script& result = Script::Handle(zone, Script::New());
result.set_url(String::Handle(zone, Symbols::New(thread, url)));
result.set_resolved_url(
String::Handle(zone, Symbols::New(thread, resolved_url)));
result.set_source(source);
result.SetLocationOffset(0, 0);
NOT_IN_PRECOMPILED(result.SetLazyLookupSourceAndLineStarts(false));
NOT_IN_PRECOMPILED(result.SetHasCachedMaxPosition(false));
result.set_kernel_script_index(0);
result.set_load_timestamp(
FLAG_remove_script_timestamps_for_test ? 0 : OS::GetCurrentTimeMillis());
return result.ptr();
}
const char* Script::ToCString() const {
const String& name = String::Handle(url());
return OS::SCreate(Thread::Current()->zone(), "Script(%s)", name.ToCString());
}
LibraryPtr Script::FindLibrary() const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
const GrowableObjectArray& libs = GrowableObjectArray::Handle(
zone, isolate_group->object_store()->libraries());
Library& lib = Library::Handle(zone);
Array& scripts = Array::Handle(zone);
for (intptr_t i = 0; i < libs.Length(); i++) {
lib ^= libs.At(i);
scripts = lib.LoadedScripts();
for (intptr_t j = 0; j < scripts.Length(); j++) {
if (scripts.At(j) == ptr()) {
return lib.ptr();
}
}
}
return Library::null();
}
DictionaryIterator::DictionaryIterator(const Library& library)
: array_(Array::Handle(library.dictionary())),
// Last element in array is a Smi indicating the number of entries used.
size_(Array::Handle(library.dictionary()).Length() - 1),
next_ix_(0) {
MoveToNextObject();
}
ObjectPtr DictionaryIterator::GetNext() {
ASSERT(HasNext());
int ix = next_ix_++;
MoveToNextObject();
ASSERT(array_.At(ix) != Object::null());
return array_.At(ix);
}
void DictionaryIterator::MoveToNextObject() {
Object& obj = Object::Handle(array_.At(next_ix_));
while (obj.IsNull() && HasNext()) {
next_ix_++;
obj = array_.At(next_ix_);
}
}
ClassDictionaryIterator::ClassDictionaryIterator(const Library& library,
IterationKind kind)
: DictionaryIterator(library),
toplevel_class_(Class::Handle((kind == kIteratePrivate)
? library.toplevel_class()
: Class::null())) {
MoveToNextClass();
}
ClassPtr ClassDictionaryIterator::GetNextClass() {
ASSERT(HasNext());
Class& cls = Class::Handle();
if (next_ix_ < size_) {
int ix = next_ix_++;
cls ^= array_.At(ix);
MoveToNextClass();
return cls.ptr();
}
ASSERT(!toplevel_class_.IsNull());
cls = toplevel_class_.ptr();
toplevel_class_ = Class::null();
return cls.ptr();
}
void ClassDictionaryIterator::MoveToNextClass() {
Object& obj = Object::Handle();
while (next_ix_ < size_) {
obj = array_.At(next_ix_);
if (obj.IsClass()) {
return;
}
next_ix_++;
}
}
static void ReportTooManyImports(const Library& lib) {
const String& url = String::Handle(lib.url());
Report::MessageF(Report::kError, Script::Handle(lib.LookupScript(url)),
TokenPosition::kNoSource, Report::AtLocation,
"too many imports in library '%s'", url.ToCString());
UNREACHABLE();
}
bool Library::IsAnyCoreLibrary() const {
String& url_str = Thread::Current()->StringHandle();
url_str = url();
return url_str.StartsWith(Symbols::DartScheme()) ||
url_str.StartsWith(Symbols::DartSchemePrivate());
}
void Library::set_num_imports(intptr_t value) const {
if (!Utils::IsUint(16, value)) {
ReportTooManyImports(*this);
}
StoreNonPointer(&untag()->num_imports_, value);
}
void Library::set_name(const String& name) const {
ASSERT(name.IsSymbol());
untag()->set_name(name.ptr());
}
void Library::set_url(const String& name) const {
untag()->set_url(name.ptr());
}
void Library::set_kernel_data(const ExternalTypedData& data) const {
untag()->set_kernel_data(data.ptr());
}
void Library::set_loading_unit(const LoadingUnit& value) const {
untag()->set_loading_unit(value.ptr());
}
void Library::SetName(const String& name) const {
// Only set name once.
ASSERT(!Loaded());
set_name(name);
}
void Library::SetLoadInProgress() const {
// Must not already be in the process of being loaded.
ASSERT(untag()->load_state_ <= UntaggedLibrary::kLoadRequested);
StoreNonPointer(&untag()->load_state_, UntaggedLibrary::kLoadInProgress);
}
void Library::SetLoadRequested() const {
// Must not be already loaded.
ASSERT(untag()->load_state_ == UntaggedLibrary::kAllocated);
StoreNonPointer(&untag()->load_state_, UntaggedLibrary::kLoadRequested);
}
void Library::SetLoaded() const {
// Should not be already loaded or just allocated.
ASSERT(LoadInProgress() || LoadRequested());
StoreNonPointer(&untag()->load_state_, UntaggedLibrary::kLoaded);
}
void Library::AddMetadata(const Object& declaration,
intptr_t kernel_offset) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
Thread* thread = Thread::Current();
ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
MetadataMap map(metadata());
map.UpdateOrInsert(declaration, Smi::Handle(Smi::New(kernel_offset)));
set_metadata(map.Release());
#endif // defined(DART_PRECOMPILED_RUNTIME)
}
ObjectPtr Library::GetMetadata(const Object& declaration) const {
#if defined(DART_PRECOMPILED_RUNTIME)
return Object::empty_array().ptr();
#else
RELEASE_ASSERT(declaration.IsClass() || declaration.IsField() ||
declaration.IsFunction() || declaration.IsLibrary() ||
declaration.IsTypeParameter() || declaration.IsNamespace());
auto thread = Thread::Current();
auto zone = thread->zone();
if (declaration.IsLibrary()) {
// Ensure top-level class is loaded as it may contain annotations of
// a library.
const auto& cls = Class::Handle(zone, toplevel_class());
if (!cls.IsNull()) {
cls.EnsureDeclarationLoaded();
}
}
Object& value = Object::Handle(zone);
{
SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
MetadataMap map(metadata());
value = map.GetOrNull(declaration);
set_metadata(map.Release());
}
if (value.IsNull()) {
// There is no metadata for this object.
return Object::empty_array().ptr();
}
if (!value.IsSmi()) {
// Metadata is already evaluated.
ASSERT(value.IsArray());
return value.ptr();
}
const auto& smi_value = Smi::Cast(value);
intptr_t kernel_offset = smi_value.Value();
ASSERT(kernel_offset > 0);
const auto& evaluated_value = Object::Handle(
zone, kernel::EvaluateMetadata(
*this, kernel_offset,
/* is_annotations_offset = */ declaration.IsLibrary() ||
declaration.IsNamespace()));
if (evaluated_value.IsArray() || evaluated_value.IsNull()) {
ASSERT(evaluated_value.ptr() != Object::empty_array().ptr());
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
MetadataMap map(metadata());
if (map.GetOrNull(declaration) == smi_value.ptr()) {
map.UpdateOrInsert(declaration, evaluated_value);
} else {
ASSERT(map.GetOrNull(declaration) == evaluated_value.ptr());
}
set_metadata(map.Release());
}
return evaluated_value.ptr();
#endif // defined(DART_PRECOMPILED_RUNTIME)
}
static bool ShouldBePrivate(const String& name) {
return (name.Length() >= 1 && name.CharAt(0) == '_') ||
(name.Length() >= 5 &&
(name.CharAt(4) == '_' &&
(name.CharAt(0) == 'g' || name.CharAt(0) == 's') &&
name.CharAt(1) == 'e' && name.CharAt(2) == 't' &&
name.CharAt(3) == ':'));
}
ObjectPtr Library::ResolveName(const String& name) const {
Object& obj = Object::Handle();
if (FLAG_use_lib_cache && LookupResolvedNamesCache(name, &obj)) {
return obj.ptr();
}
EnsureTopLevelClassIsFinalized();
obj = LookupLocalObject(name);
if (!obj.IsNull()) {
// Names that are in this library's dictionary and are unmangled
// are not cached. This reduces the size of the cache.
return obj.ptr();
}
String& accessor_name = String::Handle(Field::LookupGetterSymbol(name));
if (!accessor_name.IsNull()) {
obj = LookupLocalObject(accessor_name);
}
if (obj.IsNull()) {
accessor_name = Field::LookupSetterSymbol(name);
if (!accessor_name.IsNull()) {
obj = LookupLocalObject(accessor_name);
}
if (obj.IsNull() && !ShouldBePrivate(name)) {
obj = LookupImportedObject(name);
}
}
AddToResolvedNamesCache(name, obj);
return obj.ptr();
}
class StringEqualsTraits {
public:
static const char* Name() { return "StringEqualsTraits"; }
static bool ReportStats() { return false; }
static bool IsMatch(const Object& a, const Object& b) {
return String::Cast(a).Equals(String::Cast(b));
}
static uword Hash(const Object& obj) { return String::Cast(obj).Hash(); }
};
typedef UnorderedHashMap<StringEqualsTraits> ResolvedNamesMap;
// Returns true if the name is found in the cache, false no cache hit.
// obj is set to the cached entry. It may be null, indicating that the
// name does not resolve to anything in this library.
bool Library::LookupResolvedNamesCache(const String& name, Object* obj) const {
if (resolved_names() == Array::null()) {
return false;
}
ResolvedNamesMap cache(resolved_names());
bool present = false;
*obj = cache.GetOrNull(name, &present);
// Mutator compiler thread may add entries and therefore
// change 'resolved_names()' while running a background compilation;
// ASSERT that 'resolved_names()' has not changed only in mutator.
#if defined(DEBUG)
if (Thread::Current()->IsMutatorThread()) {
ASSERT(cache.Release().ptr() == resolved_names());
} else {
// Release must be called in debug mode.
cache.Release();
}
#endif
return present;
}
// Add a name to the resolved name cache. This name resolves to the
// given object in this library scope. obj may be null, which means
// the name does not resolve to anything in this library scope.
void Library::AddToResolvedNamesCache(const String& name,
const Object& obj) const {
if (!FLAG_use_lib_cache || Compiler::IsBackgroundCompilation()) {
return;
}
if (resolved_names() == Array::null()) {
InitResolvedNamesCache();
}
ResolvedNamesMap cache(resolved_names());
cache.UpdateOrInsert(name, obj);
untag()->set_resolved_names(cache.Release().ptr());
}
bool Library::LookupExportedNamesCache(const String& name, Object* obj) const {
ASSERT(FLAG_use_exp_cache);
if (exported_names() == Array::null()) {
return false;
}
ResolvedNamesMap cache(exported_names());
bool present = false;
*obj = cache.GetOrNull(name, &present);
// Mutator compiler thread may add entries and therefore
// change 'exported_names()' while running a background compilation;
// do not ASSERT that 'exported_names()' has not changed.
#if defined(DEBUG)
if (Thread::Current()->IsMutatorThread()) {
ASSERT(cache.Release().ptr() == exported_names());
} else {
// Release must be called in debug mode.
cache.Release();
}
#endif
return present;
}
void Library::AddToExportedNamesCache(const String& name,
const Object& obj) const {
if (!FLAG_use_exp_cache || Compiler::IsBackgroundCompilation()) {
return;
}
if (exported_names() == Array::null()) {
InitExportedNamesCache();
}
ResolvedNamesMap cache(exported_names());
cache.UpdateOrInsert(name, obj);
untag()->set_exported_names(cache.Release().ptr());
}
void Library::InvalidateResolvedName(const String& name) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Object& entry = Object::Handle(zone);
if (FLAG_use_lib_cache && LookupResolvedNamesCache(name, &entry)) {
// TODO(koda): Support deleted sentinel in snapshots and remove only 'name'.
ClearResolvedNamesCache();
}
if (!FLAG_use_exp_cache) {
return;
}
// When a new name is added to a library, we need to invalidate all
// caches that contain an entry for this name. If the name was previously
// looked up but could not be resolved, the cache contains a null entry.
GrowableObjectArray& libs = GrowableObjectArray::Handle(
zone, thread->isolate_group()->object_store()->libraries());
Library& lib = Library::Handle(zone);
intptr_t num_libs = libs.Length();
for (intptr_t i = 0; i < num_libs; i++) {
lib ^= libs.At(i);
if (lib.LookupExportedNamesCache(name, &entry)) {
lib.ClearExportedNamesCache();
}
}
}
// Invalidate all exported names caches in the isolate.
void Library::InvalidateExportedNamesCaches() {
GrowableObjectArray& libs = GrowableObjectArray::Handle(
IsolateGroup::Current()->object_store()->libraries());
Library& lib = Library::Handle();
intptr_t num_libs = libs.Length();
for (intptr_t i = 0; i < num_libs; i++) {
lib ^= libs.At(i);
lib.ClearExportedNamesCache();
}
}
void Library::RehashDictionary(const Array& old_dict,
intptr_t new_dict_size) const {
intptr_t old_dict_size = old_dict.Length() - 1;
const Array& new_dict =
Array::Handle(Array::New(new_dict_size + 1, Heap::kOld));
// Rehash all elements from the original dictionary
// to the newly allocated array.
Object& entry = Class::Handle();
String& entry_name = String::Handle();
Object& new_entry = Object::Handle();
intptr_t used = 0;
for (intptr_t i = 0; i < old_dict_size; i++) {
entry = old_dict.At(i);
if (!entry.IsNull()) {
entry_name = entry.DictionaryName();
ASSERT(!entry_name.IsNull());
const intptr_t hash = entry_name.Hash();
intptr_t index = hash % new_dict_size;
new_entry = new_dict.At(index);
while (!new_entry.IsNull()) {
index = (index + 1) % new_dict_size; // Move to next element.
new_entry = new_dict.At(index);
}
new_dict.SetAt(index, entry);
used++;
}
}
// Set used count.
ASSERT(used < new_dict_size); // Need at least one empty slot.
new_entry = Smi::New(used);
new_dict.SetAt(new_dict_size, new_entry);
// Remember the new dictionary now.
untag()->set_dictionary(new_dict.ptr());
}
void Library::AddObject(const Object& obj, const String& name) const {
ASSERT(Thread::Current()->IsMutatorThread());
ASSERT(obj.IsClass() || obj.IsFunction() || obj.IsField() ||
obj.IsLibraryPrefix());
ASSERT(name.Equals(String::Handle(obj.DictionaryName())));
ASSERT(LookupLocalObject(name) == Object::null());
const Array& dict = Array::Handle(dictionary());
intptr_t dict_size = dict.Length() - 1;
intptr_t index = name.Hash() % dict_size;
Object& entry = Object::Handle();
entry = dict.At(index);
// An empty spot will be found because we keep the hash set at most 75% full.
while (!entry.IsNull()) {
index = (index + 1) % dict_size;
entry = dict.At(index);
}
// Insert the object at the empty slot.
dict.SetAt(index, obj);
// One more element added.
intptr_t used_elements = Smi::Value(Smi::RawCast(dict.At(dict_size))) + 1;
const Smi& used = Smi::Handle(Smi::New(used_elements));
dict.SetAt(dict_size, used); // Update used count.
// Rehash if symbol_table is 75% full.
if (used_elements > ((dict_size / 4) * 3)) {
// TODO(iposva): Avoid exponential growth.
RehashDictionary(dict, 2 * dict_size);
}
// Invalidate the cache of loaded scripts.
if (loaded_scripts() != Array::null()) {
untag()->set_loaded_scripts(Array::null());
}
}
// Lookup a name in the library's re-export namespace.
// This lookup can occur from two different threads: background compiler and
// mutator thread.
ObjectPtr Library::LookupReExport(const String& name,
ZoneGrowableArray<intptr_t>* trail) const {
if (!HasExports()) {
return Object::null();
}
if (trail == NULL) {
trail = new ZoneGrowableArray<intptr_t>();
}
Object& obj = Object::Handle();
if (FLAG_use_exp_cache && LookupExportedNamesCache(name, &obj)) {
return obj.ptr();
}
const intptr_t lib_id = this->index();
ASSERT(lib_id >= 0); // We use -1 to indicate that a cycle was found.
trail->Add(lib_id);
const Array& exports = Array::Handle(this->exports());
Namespace& ns = Namespace::Handle();
for (int i = 0; i < exports.Length(); i++) {
ns ^= exports.At(i);
obj = ns.Lookup(name, trail);
if (!obj.IsNull()) {
// The Lookup call above may return a setter x= when we are looking
// for the name x. Make sure we only return when a matching name
// is found.
String& obj_name = String::Handle(obj.DictionaryName());
if (Field::IsSetterName(obj_name) == Field::IsSetterName(name)) {
break;
}
}
}
bool in_cycle = (trail->RemoveLast() < 0);
if (FLAG_use_exp_cache && !in_cycle && !Compiler::IsBackgroundCompilation()) {
AddToExportedNamesCache(name, obj);
}
return obj.ptr();
}
ObjectPtr Library::LookupEntry(const String& name, intptr_t* index) const {
ASSERT(!IsNull());
Thread* thread = Thread::Current();
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_OBJECT_HANDLESCOPE(thread);
REUSABLE_STRING_HANDLESCOPE(thread);
Array& dict = thread->ArrayHandle();
dict = dictionary();
intptr_t dict_size = dict.Length() - 1;
*index = name.Hash() % dict_size;
Object& entry = thread->ObjectHandle();
String& entry_name = thread->StringHandle();
entry = dict.At(*index);
// Search the entry in the hash set.
while (!entry.IsNull()) {
entry_name = entry.DictionaryName();
ASSERT(!entry_name.IsNull());
if (entry_name.Equals(name)) {
return entry.ptr();
}
*index = (*index + 1) % dict_size;
entry = dict.At(*index);
}
return Object::null();
}
void Library::AddClass(const Class& cls) const {
ASSERT(!Compiler::IsBackgroundCompilation());
const String& class_name = String::Handle(cls.Name());
AddObject(cls, class_name);
// Link class to this library.
cls.set_library(*this);
InvalidateResolvedName(class_name);
}
static void AddScriptIfUnique(const GrowableObjectArray& scripts,
const Script& candidate) {
if (candidate.IsNull()) {
return;
}
Script& script_obj = Script::Handle();
for (int i = 0; i < scripts.Length(); i++) {
script_obj ^= scripts.At(i);
if (script_obj.ptr() == candidate.ptr()) {
// We already have a reference to this script.
return;
}
}
// Add script to the list of scripts.
scripts.Add(candidate);
}
ArrayPtr Library::LoadedScripts() const {
ASSERT(Thread::Current()->IsMutatorThread());
// We compute the list of loaded scripts lazily. The result is
// cached in loaded_scripts_.
if (loaded_scripts() == Array::null()) {
// TODO(jensj): This can be cleaned up.
// It really should just return the content of `used_scripts`, and there
// should be no need to do the O(n) call to `AddScriptIfUnique` per script.
// Iterate over the library dictionary and collect all scripts.
const GrowableObjectArray& scripts =
GrowableObjectArray::Handle(GrowableObjectArray::New(8));
Object& entry = Object::Handle();
Class& cls = Class::Handle();
Script& owner_script = Script::Handle();
DictionaryIterator it(*this);
while (it.HasNext()) {
entry = it.GetNext();
if (entry.IsClass()) {
owner_script = Class::Cast(entry).script();
} else if (entry.IsFunction()) {
owner_script = Function::Cast(entry).script();
} else if (entry.IsField()) {
owner_script = Field::Cast(entry).Script();
} else {
continue;
}
AddScriptIfUnique(scripts, owner_script);
}
// Add all scripts from patch classes.
GrowableObjectArray& patches = GrowableObjectArray::Handle(used_scripts());
for (intptr_t i = 0; i < patches.Length(); i++) {
entry = patches.At(i);
if (entry.IsClass()) {
owner_script = Class::Cast(entry).script();
} else {
ASSERT(entry.IsScript());
owner_script = Script::Cast(entry).ptr();
}
AddScriptIfUnique(scripts, owner_script);
}
cls = toplevel_class();
if (!cls.IsNull()) {
owner_script = cls.script();
AddScriptIfUnique(scripts, owner_script);
// Special case: Scripts that only contain external top-level functions
// are not included above, but can be referenced through a library's
// anonymous classes. Example: dart-core:identical.dart.
Function& func = Function::Handle();
Array& functions = Array::Handle(cls.current_functions());
for (intptr_t j = 0; j < functions.Length(); j++) {
func ^= functions.At(j);
if (func.is_external()) {
owner_script = func.script();
AddScriptIfUnique(scripts, owner_script);
}
}
}
// Create the array of scripts and cache it in loaded_scripts_.
const Array& scripts_array = Array::Handle(Array::MakeFixedLength(scripts));
untag()->set_loaded_scripts(scripts_array.ptr());
}
return loaded_scripts();
}
// TODO(hausner): we might want to add a script dictionary to the
// library class to make this lookup faster.
ScriptPtr Library::LookupScript(const String& url,
bool useResolvedUri /* = false */) const {
const intptr_t url_length = url.Length();
if (url_length == 0) {
return Script::null();
}
const Array& scripts = Array::Handle(LoadedScripts());
Script& script = Script::Handle();
String& script_url = String::Handle();
const intptr_t num_scripts = scripts.Length();
for (int i = 0; i < num_scripts; i++) {
script ^= scripts.At(i);
if (useResolvedUri) {
// Use for urls with 'org-dartlang-sdk:' or 'file:' schemes
script_url = script.resolved_url();
} else {
// Use for urls with 'dart:', 'package:', or 'file:' schemes
script_url = script.url();
}
const intptr_t start_idx = script_url.Length() - url_length;
if ((start_idx == 0) && url.Equals(script_url)) {
return script.ptr();
} else if (start_idx > 0) {
// If we do a suffix match, only match if the partial path
// starts at or immediately after the path separator.
if (((url.CharAt(0) == '/') ||
(script_url.CharAt(start_idx - 1) == '/')) &&
url.Equals(script_url, start_idx, url_length)) {
return script.ptr();
}
}
}
return Script::null();
}
void Library::EnsureTopLevelClassIsFinalized() const {
if (toplevel_class() == Object::null()) {
return;
}
Thread* thread = Thread::Current();
const Class& cls = Class::Handle(thread->zone(), toplevel_class());
if (cls.is_finalized()) {
return;
}
const Error& error =
Error::Handle(thread->zone(), cls.EnsureIsFinalized(thread));
if (!error.IsNull()) {
Exceptions::PropagateError(error);
}
}
ObjectPtr Library::LookupLocalObject(const String& name) const {
intptr_t index;
return LookupEntry(name, &index);
}
ObjectPtr Library::LookupLocalOrReExportObject(const String& name) const {
intptr_t index;
EnsureTopLevelClassIsFinalized();
const Object& result = Object::Handle(LookupEntry(name, &index));
if (!result.IsNull() && !result.IsLibraryPrefix()) {
return result.ptr();
}
return LookupReExport(name);
}
FieldPtr Library::LookupFieldAllowPrivate(const String& name) const {
EnsureTopLevelClassIsFinalized();
Object& obj = Object::Handle(LookupObjectAllowPrivate(name));
if (obj.IsField()) {
return Field::Cast(obj).ptr();
}
return Field::null();
}
FieldPtr Library::LookupLocalField(const String& name) const {
EnsureTopLevelClassIsFinalized();
Object& obj = Object::Handle(LookupLocalObjectAllowPrivate(name));
if (obj.IsField()) {
return Field::Cast(obj).ptr();
}
return Field::null();
}
FunctionPtr Library::LookupFunctionAllowPrivate(const String& name) const {
EnsureTopLevelClassIsFinalized();
Object& obj = Object::Handle(LookupObjectAllowPrivate(name));
if (obj.IsFunction()) {
return Function::Cast(obj).ptr();
}
return Function::null();
}
FunctionPtr Library::LookupLocalFunction(const String& name) const {
EnsureTopLevelClassIsFinalized();
Object& obj = Object::Handle(LookupLocalObjectAllowPrivate(name));
if (obj.IsFunction()) {
return Function::Cast(obj).ptr();
}
return Function::null();
}
ObjectPtr Library::LookupLocalObjectAllowPrivate(const String& name) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Object& obj = Object::Handle(zone, Object::null());
obj = LookupLocalObject(name);
if (obj.IsNull() && ShouldBePrivate(name)) {
String& private_name = String::Handle(zone, PrivateName(name));
obj = LookupLocalObject(private_name);
}
return obj.ptr();
}
ObjectPtr Library::LookupObjectAllowPrivate(const String& name) const {
// First check if name is found in the local scope of the library.
Object& obj = Object::Handle(LookupLocalObjectAllowPrivate(name));
if (!obj.IsNull()) {
return obj.ptr();
}
// Do not look up private names in imported libraries.
if (ShouldBePrivate(name)) {
return Object::null();
}
// Now check if name is found in any imported libs.
return LookupImportedObject(name);
}
ObjectPtr Library::LookupImportedObject(const String& name) const {
Object& obj = Object::Handle();
Namespace& import = Namespace::Handle();
Library& import_lib = Library::Handle();
String& import_lib_url = String::Handle();
String& first_import_lib_url = String::Handle();
Object& found_obj = Object::Handle();
String& found_obj_name = String::Handle();
ASSERT(!ShouldBePrivate(name));
for (intptr_t i = 0; i < num_imports(); i++) {
import = ImportAt(i);
obj = import.Lookup(name);
if (!obj.IsNull()) {
import_lib = import.target();
import_lib_url = import_lib.url();
if (found_obj.ptr() != obj.ptr()) {
if (first_import_lib_url.IsNull() ||
first_import_lib_url.StartsWith(Symbols::DartScheme())) {
// This is the first object we found, or the
// previously found object is exported from a Dart
// system library. The newly found object hides the one
// from the Dart library.
first_import_lib_url = import_lib.url();
found_obj = obj.ptr();
found_obj_name = obj.DictionaryName();
} else if (import_lib_url.StartsWith(Symbols::DartScheme())) {
// The newly found object is exported from a Dart system
// library. It is hidden by the previously found object.
// We continue to search.
} else if (Field::IsSetterName(found_obj_name) &&
!Field::IsSetterName(name)) {
// We are looking for an unmangled name or a getter, but
// the first object we found is a setter. Replace the first
// object with the one we just found.
first_import_lib_url = import_lib.url();
found_obj = obj.ptr();
found_obj_name = found_obj.DictionaryName();
} else {
// We found two different objects with the same name.
// Note that we need to compare the names again because
// looking up an unmangled name can return a getter or a
// setter. A getter name is the same as the unmangled name,
// but a setter name is different from an unmangled name or a
// getter name.
if (Field::IsGetterName(found_obj_name)) {
found_obj_name = Field::NameFromGetter(found_obj_name);
}
String& second_obj_name = String::Handle(obj.DictionaryName());
if (Field::IsGetterName(second_obj_name)) {
second_obj_name = Field::NameFromGetter(second_obj_name);
}
if (found_obj_name.Equals(second_obj_name)) {
return Object::null();
}
}
}
}
}
return found_obj.ptr();
}
ClassPtr Library::LookupClass(const String& name) const {
Object& obj = Object::Handle(LookupLocalObject(name));
if (obj.IsNull() && !ShouldBePrivate(name)) {
obj = LookupImportedObject(name);
}
if (obj.IsClass()) {
return Class::Cast(obj).ptr();
}
return Class::null();
}
ClassPtr Library::LookupLocalClass(const String& name) const {
Object& obj = Object::Handle(LookupLocalObject(name));
if (obj.IsClass()) {
return Class::Cast(obj).ptr();
}
return Class::null();
}
ClassPtr Library::LookupClassAllowPrivate(const String& name) const {
// See if the class is available in this library or in the top level
// scope of any imported library.
Zone* zone = Thread::Current()->zone();
const Class& cls = Class::Handle(zone, LookupClass(name));
if (!cls.IsNull()) {
return cls.ptr();
}
// Now try to lookup the class using its private name, but only in
// this library (not in imported libraries).
if (ShouldBePrivate(name)) {
String& private_name = String::Handle(zone, PrivateName(name));
const Object& obj = Object::Handle(LookupLocalObject(private_name));
if (obj.IsClass()) {
return Class::Cast(obj).ptr();
}
}
return Class::null();
}
// Mixin applications can have multiple private keys from different libraries.
ClassPtr Library::SlowLookupClassAllowMultiPartPrivate(
const String& name) const {
Array& dict = Array::Handle(dictionary());
Object& entry = Object::Handle();
String& cls_name = String::Handle();
for (intptr_t i = 0; i < dict.Length(); i++) {
entry = dict.At(i);
if (entry.IsClass()) {
cls_name = Class::Cast(entry).Name();
// Warning: comparison is not symmetric.
if (String::EqualsIgnoringPrivateKey(cls_name, name)) {
return Class::Cast(entry).ptr();
}
}
}
return Class::null();
}
LibraryPrefixPtr Library::LookupLocalLibraryPrefix(const String& name) const {
const Object& obj = Object::Handle(LookupLocalObject(name));
if (obj.IsLibraryPrefix()) {
return LibraryPrefix::Cast(obj).ptr();
}
return LibraryPrefix::null();
}
void Library::set_toplevel_class(const Class& value) const {
ASSERT(untag()->toplevel_class() == Class::null());
untag()->set_toplevel_class(value.ptr());
}
void Library::set_dependencies(const Array& deps) const {
untag()->set_dependencies(deps.ptr());
}
void Library::set_metadata(const Array& value) const {
if (untag()->metadata() != value.ptr()) {
DEBUG_ASSERT(
IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
untag()->set_metadata(value.ptr());
}
}
LibraryPtr Library::ImportLibraryAt(intptr_t index) const {
Namespace& import = Namespace::Handle(ImportAt(index));
if (import.IsNull()) {
return Library::null();
}
return import.target();
}
NamespacePtr Library::ImportAt(intptr_t index) const {
if ((index < 0) || index >= num_imports()) {
return Namespace::null();
}
const Array& import_list = Array::Handle(imports());
return Namespace::RawCast(import_list.At(index));
}
void Library::DropDependenciesAndCaches() const {
// We need to preserve the "dart-ext:" imports because they are used by
// Loader::ReloadNativeExtensions().
intptr_t native_import_count = 0;
Array& imports = Array::Handle(untag()->imports());
Namespace& ns = Namespace::Handle();
Library& lib = Library::Handle();
String& url = String::Handle();
for (int i = 0; i < imports.Length(); ++i) {
ns = Namespace::RawCast(imports.At(i));
if (ns.IsNull()) continue;
lib = ns.target();
url = lib.url();
if (url.StartsWith(Symbols::DartExtensionScheme())) {
native_import_count++;
}
}
Array& new_imports =
Array::Handle(Array::New(native_import_count, Heap::kOld));
for (int i = 0, j = 0; i < imports.Length(); ++i) {
ns = Namespace::RawCast(imports.At(i));
if (ns.IsNull()) continue;
lib = ns.target();
url = lib.url();
if (url.StartsWith(Symbols::DartExtensionScheme())) {
new_imports.SetAt(j++, ns);
}
}
untag()->set_imports(new_imports.ptr());
untag()->set_exports(Object::empty_array().ptr());
StoreNonPointer(&untag()->num_imports_, 0);
untag()->set_resolved_names(Array::null());
untag()->set_exported_names(Array::null());
untag()->set_loaded_scripts(Array::null());
untag()->set_dependencies(Array::null());
}
void Library::AddImport(const Namespace& ns) const {
Array& imports = Array::Handle(this->imports());
intptr_t capacity = imports.Length();
if (num_imports() == capacity) {
capacity = capacity + kImportsCapacityIncrement + (capacity >> 2);
imports = Array::Grow(imports, capacity);
untag()->set_imports(imports.ptr());
}
intptr_t index = num_imports();
imports.SetAt(index, ns);
set_num_imports(index + 1);
}
// Convenience function to determine whether the export list is
// non-empty.
bool Library::HasExports() const {
return exports() != Object::empty_array().ptr();
}
// We add one namespace at a time to the exports array and don't
// pre-allocate any unused capacity. The assumption is that
// re-exports are quite rare.
void Library::AddExport(const Namespace& ns) const {
Array& exports = Array::Handle(this->exports());
intptr_t num_exports = exports.Length();
exports = Array::Grow(exports, num_exports + 1);
untag()->set_exports(exports.ptr());
exports.SetAt(num_exports, ns);
}
static ArrayPtr NewDictionary(intptr_t initial_size) {
const Array& dict = Array::Handle(Array::New(initial_size + 1, Heap::kOld));
// The last element of the dictionary specifies the number of in use slots.
dict.SetAt(initial_size, Object::smi_zero());
return dict.ptr();
}
void Library::InitResolvedNamesCache() const {
Thread* thread = Thread::Current();
ASSERT(thread->IsMutatorThread());
REUSABLE_FUNCTION_HANDLESCOPE(thread);
Array& cache = thread->ArrayHandle();
cache = HashTables::New<ResolvedNamesMap>(64);
untag()->set_resolved_names(cache.ptr());
}
void Library::ClearResolvedNamesCache() const {
ASSERT(Thread::Current()->IsMutatorThread());
untag()->set_resolved_names(Array::null());
}
void Library::InitExportedNamesCache() const {
Thread* thread = Thread::Current();
ASSERT(thread->IsMutatorThread());
REUSABLE_FUNCTION_HANDLESCOPE(thread);
Array& cache = thread->ArrayHandle();
cache = HashTables::New<ResolvedNamesMap>(16);
untag()->set_exported_names(cache.ptr());
}
void Library::ClearExportedNamesCache() const {
untag()->set_exported_names(Array::null());
}
void Library::InitClassDictionary() const {
Thread* thread = Thread::Current();
ASSERT(thread->IsMutatorThread());
REUSABLE_FUNCTION_HANDLESCOPE(thread);
Array& dictionary = thread->ArrayHandle();
// TODO(iposva): Find reasonable initial size.
const int kInitialElementCount = 16;
dictionary = NewDictionary(kInitialElementCount);
untag()->set_dictionary(dictionary.ptr());
}
void Library::InitImportList() const {
const Array& imports =
Array::Handle(Array::New(kInitialImportsCapacity, Heap::kOld));
untag()->set_imports(imports.ptr());
StoreNonPointer(&untag()->num_imports_, 0);
}
LibraryPtr Library::New() {
ASSERT(Object::library_class() != Class::null());
ObjectPtr raw = Object::Allocate(Library::kClassId, Library::InstanceSize(),
Heap::kOld, /*compressed*/ true);
return static_cast<LibraryPtr>(raw);
}
LibraryPtr Library::NewLibraryHelper(const String& url, bool import_core_lib) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
ASSERT(thread->IsMutatorThread());
// Force the url to have a hash code.
url.Hash();
const bool dart_scheme = url.StartsWith(Symbols::DartScheme());
const Library& result = Library::Handle(zone, Library::New());
result.untag()->set_name(Symbols::Empty().ptr());
result.untag()->set_url(url.ptr());
result.untag()->set_resolved_names(Array::null());
result.untag()->set_exported_names(Array::null());
result.untag()->set_dictionary(Object::empty_array().ptr());
Array& array = Array::Handle(zone);
array = HashTables::New<MetadataMap>(4, Heap::kOld);
result.untag()->set_metadata(array.ptr());
result.untag()->set_toplevel_class(Class::null());
GrowableObjectArray& list = GrowableObjectArray::Handle(zone);
list = GrowableObjectArray::New(Object::empty_array(), Heap::kOld);
result.untag()->set_used_scripts(list.ptr());
result.untag()->set_imports(Object::empty_array().ptr());
result.untag()->set_exports(Object::empty_array().ptr());
result.untag()->set_loaded_scripts(Array::null());
result.set_native_entry_resolver(NULL);
result.set_native_entry_symbol_resolver(NULL);
result.set_flags(0);
result.set_is_in_fullsnapshot(false);
result.set_is_nnbd(false);
if (dart_scheme) {
// Only debug dart: libraries if we have been requested to show invisible
// frames.
result.set_debuggable(FLAG_show_invisible_frames);
} else {
// Default to debuggable for all other libraries.
result.set_debuggable(true);
}
result.set_is_dart_scheme(dart_scheme);
NOT_IN_PRECOMPILED(result.set_kernel_offset(0));
result.StoreNonPointer(&result.untag()->load_state_,
UntaggedLibrary::kAllocated);
result.StoreNonPointer(&result.untag()->index_, -1);
result.InitClassDictionary();
result.InitImportList();
result.AllocatePrivateKey();
if (import_core_lib) {
const Library& core_lib = Library::Handle(zone, Library::CoreLibrary());
ASSERT(!core_lib.IsNull());
const Namespace& ns =
Namespace::Handle(zone, Namespace::New(core_lib, Object::null_array(),
Object::null_array(), result));
result.AddImport(ns);
}
return result.ptr();
}
LibraryPtr Library::New(const String& url) {
return NewLibraryHelper(url, false);
}
void Library::set_flags(uint8_t flags) const {
StoreNonPointer(&untag()->flags_, flags);
}
void Library::InitCoreLibrary(IsolateGroup* isolate_group) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const String& core_lib_url = Symbols::DartCore();
const Library& core_lib =
Library::Handle(zone, Library::NewLibraryHelper(core_lib_url, false));
core_lib.SetLoadRequested();
core_lib.Register(thread);
isolate_group->object_store()->set_bootstrap_library(ObjectStore::kCore,
core_lib);
isolate_group->object_store()->set_root_library(Library::Handle());
}
// Invoke the function, or noSuchMethod if it is null.
static ObjectPtr InvokeInstanceFunction(
Thread* thread,
const Instance& receiver,
const Function& function,
const String& target_name,
const Array& args,
const Array& args_descriptor_array,
bool respect_reflectable,
const TypeArguments& instantiator_type_args) {
// Note "args" is already the internal arguments with the receiver as the
// first element.
ArgumentsDescriptor args_descriptor(args_descriptor_array);
if (function.IsNull() ||
!function.AreValidArguments(args_descriptor, nullptr) ||
(respect_reflectable && !function.is_reflectable())) {
return DartEntry::InvokeNoSuchMethod(thread, receiver, target_name, args,
args_descriptor_array);
}
ObjectPtr type_error = function.DoArgumentTypesMatch(args, args_descriptor,
instantiator_type_args);
if (type_error != Error::null()) {
return type_error;
}
return DartEntry::InvokeFunction(function, args, args_descriptor_array);
}
ObjectPtr Library::InvokeGetter(const String& getter_name,
bool throw_nsm_if_absent,
bool respect_reflectable,
bool check_is_entrypoint) const {
Object& obj = Object::Handle(LookupLocalOrReExportObject(getter_name));
Function& getter = Function::Handle();
if (obj.IsField()) {
const Field& field = Field::Cast(obj);
if (check_is_entrypoint) {
CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
}
if (!field.IsUninitialized()) {
return field.StaticValue();
}
// An uninitialized field was found. Check for a getter in the field's
// owner class.
const Class& klass = Class::Handle(field.Owner());
const String& internal_getter_name =
String::Handle(Field::GetterName(getter_name));
getter = klass.LookupStaticFunction(internal_getter_name);
} else {
// No field found. Check for a getter in the lib.
const String& internal_getter_name =
String::Handle(Field::GetterName(getter_name));
obj = LookupLocalOrReExportObject(internal_getter_name);
if (obj.IsFunction()) {
getter = Function::Cast(obj).ptr();
if (check_is_entrypoint) {
CHECK_ERROR(getter.VerifyCallEntryPoint());
}
} else {
obj = LookupLocalOrReExportObject(getter_name);
// Normally static top-level methods cannot be closurized through the
// native API even if they are marked as entry-points, with the one
// exception of "main".
if (obj.IsFunction() && check_is_entrypoint) {
if (!getter_name.Equals(String::Handle(String::New("main"))) ||
ptr() != IsolateGroup::Current()->object_store()->root_library()) {
CHECK_ERROR(Function::Cast(obj).VerifyClosurizedEntryPoint());
}
}
if (obj.IsFunction() && Function::Cast(obj).SafeToClosurize()) {
// Looking for a getter but found a regular method: closurize it.
const Function& closure_function =
Function::Handle(Function::Cast(obj).ImplicitClosureFunction());
return closure_function.ImplicitStaticClosure();
}
}
}
if (getter.IsNull() || (respect_reflectable && !getter.is_reflectable())) {
if (throw_nsm_if_absent) {
return ThrowNoSuchMethod(
AbstractType::Handle(Class::Handle(toplevel_class()).RareType()),
getter_name, Object::null_array(), Object::null_array(),
InvocationMirror::kTopLevel, InvocationMirror::kGetter);
}
// Fall through case: Indicate that we didn't find any function or field
// using a special null instance. This is different from a field being null.
// Callers make sure that this null does not leak into Dartland.
return Object::sentinel().ptr();
}
// Invoke the getter and return the result.
return DartEntry::InvokeFunction(getter, Object::empty_array());
}
ObjectPtr Library::InvokeSetter(const String& setter_name,
const Instance& value,
bool respect_reflectable,
bool check_is_entrypoint) const {
Object& obj = Object::Handle(LookupLocalOrReExportObject(setter_name));
const String& internal_setter_name =
String::Handle(Field::SetterName(setter_name));
AbstractType& setter_type = AbstractType::Handle();
AbstractType& argument_type = AbstractType::Handle(value.GetType(Heap::kOld));
if (obj.IsField()) {
const Field& field = Field::Cast(obj);
if (check_is_entrypoint) {
CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kSetterOnly));
}
setter_type = field.type();
if (!argument_type.IsNullType() && !setter_type.IsDynamicType() &&
!value.IsInstanceOf(setter_type, Object::null_type_arguments(),
Object::null_type_arguments())) {
return ThrowTypeError(field.token_pos(), value, setter_type, setter_name);
}
if (field.is_final() || (respect_reflectable && !field.is_reflectable())) {
const int kNumArgs = 1;
const Array& args = Array::Handle(Array::New(kNumArgs));
args.SetAt(0, value);
return ThrowNoSuchMethod(
AbstractType::Handle(Class::Handle(toplevel_class()).RareType()),
internal_setter_name, args, Object::null_array(),
InvocationMirror::kTopLevel, InvocationMirror::kSetter);
}
field.SetStaticValue(value);
return value.ptr();
}
Function& setter = Function::Handle();
obj = LookupLocalOrReExportObject(internal_setter_name);
if (obj.IsFunction()) {
setter ^= obj.ptr();
}
if (!setter.IsNull() && check_is_entrypoint) {
CHECK_ERROR(setter.VerifyCallEntryPoint());
}
const int kNumArgs = 1;
const Array& args = Array::Handle(Array::New(kNumArgs));
args.SetAt(0, value);
if (setter.IsNull() || (respect_reflectable && !setter.is_reflectable())) {
return ThrowNoSuchMethod(
AbstractType::Handle(Class::Handle(toplevel_class()).RareType()),
internal_setter_name, args, Object::null_array(),
InvocationMirror::kTopLevel, InvocationMirror::kSetter);
}
setter_type = setter.ParameterTypeAt(0);
if (!argument_type.IsNullType() && !setter_type.IsDynamicType() &&
!value.IsInstanceOf(setter_type, Object::null_type_arguments(),
Object::null_type_arguments())) {
return ThrowTypeError(setter.token_pos(), value, setter_type, setter_name);
}
return DartEntry::InvokeFunction(setter, args);
}
ObjectPtr Library::Invoke(const String& function_name,
const Array& args,
const Array& arg_names,
bool respect_reflectable,
bool check_is_entrypoint) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
// We don't pass any explicit type arguments, which will be understood as
// using dynamic for any function type arguments by lower layers.
const int kTypeArgsLen = 0;
const Array& args_descriptor_array = Array::Handle(
zone, ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(),
arg_names, Heap::kNew));
ArgumentsDescriptor args_descriptor(args_descriptor_array);
auto& function = Function::Handle(zone);
auto& result =
Object::Handle(zone, LookupLocalOrReExportObject(function_name));
if (result.IsFunction()) {
function ^= result.ptr();
}
if (!function.IsNull() && check_is_entrypoint) {
CHECK_ERROR(function.VerifyCallEntryPoint());
}
if (function.IsNull()) {
// Didn't find a method: try to find a getter and invoke call on its result.
const Object& getter_result = Object::Handle(
zone, InvokeGetter(function_name, false, respect_reflectable,
check_is_entrypoint));
if (getter_result.ptr() != Object::sentinel().ptr()) {
if (check_is_entrypoint) {
CHECK_ERROR(EntryPointFieldInvocationError(function_name));
}
const auto& call_args_descriptor_array = Array::Handle(
zone, ArgumentsDescriptor::NewBoxed(args_descriptor.TypeArgsLen(),
args_descriptor.Count() + 1,
arg_names, Heap::kNew));
const auto& call_args = Array::Handle(
zone,
CreateCallableArgumentsFromStatic(zone, Instance::Cast(getter_result),
args, arg_names, args_descriptor));
return DartEntry::InvokeClosure(thread, call_args,
call_args_descriptor_array);
}
}
if (function.IsNull() ||
!function.AreValidArguments(args_descriptor, nullptr) ||
(respect_reflectable && !function.is_reflectable())) {
return ThrowNoSuchMethod(
AbstractType::Handle(zone,
Class::Handle(zone, toplevel_class()).RareType()),
function_name, args, arg_names, InvocationMirror::kTopLevel,
InvocationMirror::kMethod);
}
// This is a static function, so we pass an empty instantiator tav.
ASSERT(function.is_static());
ObjectPtr type_error = function.DoArgumentTypesMatch(
args, args_descriptor, Object::empty_type_arguments());
if (type_error != Error::null()) {
return type_error;
}
return DartEntry::InvokeFunction(function, args, args_descriptor_array);
}
ObjectPtr Library::EvaluateCompiledExpression(
const ExternalTypedData& kernel_buffer,
const Array& type_definitions,
const Array& arguments,
const TypeArguments& type_arguments) const {
return EvaluateCompiledExpressionHelper(
kernel_buffer, type_definitions, String::Handle(url()), String::Handle(),
arguments, type_arguments);
}
void Library::InitNativeWrappersLibrary(IsolateGroup* isolate_group,
bool is_kernel) {
static const int kNumNativeWrappersClasses = 4;
COMPILE_ASSERT((kNumNativeWrappersClasses > 0) &&
(kNumNativeWrappersClasses < 10));
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const String& native_flds_lib_url = Symbols::DartNativeWrappers();
const Library& native_flds_lib = Library::Handle(
zone, Library::NewLibraryHelper(native_flds_lib_url, false));
const String& native_flds_lib_name = Symbols::DartNativeWrappersLibName();
native_flds_lib.SetName(native_flds_lib_name);
native_flds_lib.SetLoadRequested();
native_flds_lib.Register(thread);
native_flds_lib.SetLoadInProgress();
isolate_group->object_store()->set_native_wrappers_library(native_flds_lib);
static const char* const kNativeWrappersClass = "NativeFieldWrapperClass";
static const int kNameLength = 25;
ASSERT(kNameLength == (strlen(kNativeWrappersClass) + 1 + 1));
char name_buffer[kNameLength];
String& cls_name = String::Handle(zone);
for (int fld_cnt = 1; fld_cnt <= kNumNativeWrappersClasses; fld_cnt++) {
Utils::SNPrint(name_buffer, kNameLength, "%s%d", kNativeWrappersClass,
fld_cnt);
cls_name = Symbols::New(thread, name_buffer);
Class::NewNativeWrapper(native_flds_lib, cls_name, fld_cnt);
}
// NOTE: If we bootstrap from a Kernel IR file we want to generate the
// synthetic constructors for the native wrapper classes. We leave this up to
// the [KernelLoader] who will take care of it later.
if (!is_kernel) {
native_flds_lib.SetLoaded();
}
}
// LibraryLookupSet maps URIs to libraries.
class LibraryLookupTraits {
public:
static const char* Name() { return "LibraryLookupTraits"; }
static bool ReportStats() { return false; }
static bool IsMatch(const Object& a, const Object& b) {
const String& a_str = String::Cast(a);
const String& b_str = String::Cast(b);
ASSERT(a_str.HasHash() && b_str.HasHash());
return a_str.Equals(b_str);
}
static uword Hash(const Object& key) { return String::Cast(key).Hash(); }
static ObjectPtr NewKey(const String& str) { return str.ptr(); }
};
typedef UnorderedHashMap<LibraryLookupTraits> LibraryLookupMap;
static ObjectPtr EvaluateCompiledExpressionHelper(
const ExternalTypedData& kernel_buffer,
const Array& type_definitions,
const String& library_url,
const String& klass,
const Array& arguments,
const TypeArguments& type_arguments) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
#if defined(DART_PRECOMPILED_RUNTIME)
const String& error_str = String::Handle(
zone,
String::New("Expression evaluation not available in precompiled mode."));
return ApiError::New(error_str);
#else
std::unique_ptr<kernel::Program> kernel_pgm =
kernel::Program::ReadFromTypedData(kernel_buffer);
if (kernel_pgm == NULL) {
return ApiError::New(String::Handle(
zone, String::New("Kernel isolate returned ill-formed kernel.")));
}
kernel::KernelLoader loader(kernel_pgm.get(),
/*uri_to_source_table=*/nullptr);
auto& result = Object::Handle(
zone, loader.LoadExpressionEvaluationFunction(library_url, klass));
kernel_pgm.reset();
if (result.IsError()) return result.ptr();
const auto& callee = Function::CheckedHandle(zone, result.ptr());
// type_arguments is null if all type arguments are dynamic.
if (type_definitions.Length() == 0 || type_arguments.IsNull()) {
result = DartEntry::InvokeFunction(callee, arguments);
} else {
intptr_t num_type_args = type_arguments.Length();
Array& real_arguments =
Array::Handle(zone, Array::New(arguments.Length() + 1));
real_arguments.SetAt(0, type_arguments);
Object& arg = Object::Handle(zone);
for (intptr_t i = 0; i < arguments.Length(); ++i) {
arg = arguments.At(i);
real_arguments.SetAt(i + 1, arg);
}
const Array& args_desc =
Array::Handle(zone, ArgumentsDescriptor::NewBoxed(
num_type_args, arguments.Length(), Heap::kNew));
result = DartEntry::InvokeFunction(callee, real_arguments, args_desc);
}
return result.ptr();
#endif
}
// Returns library with given url in current isolate, or NULL.
LibraryPtr Library::LookupLibrary(Thread* thread, const String& url) {
Zone* zone = thread->zone();
ObjectStore* object_store = thread->isolate_group()->object_store();
// Make sure the URL string has an associated hash code
// to speed up the repeated equality checks.
url.Hash();
// Use the libraries map to lookup the library by URL.
Library& lib = Library::Handle(zone);
if (object_store->libraries_map() == Array::null()) {
return Library::null();
} else {
LibraryLookupMap map(object_store->libraries_map());
lib ^= map.GetOrNull(url);
ASSERT(map.Release().ptr() == object_store->libraries_map());
}
return lib.ptr();
}
bool Library::IsPrivate(const String& name) {
if (ShouldBePrivate(name)) return true;
// Factory names: List._fromLiteral.
for (intptr_t i = 1; i < name.Length() - 1; i++) {
if (name.CharAt(i) == '.') {
if (name.CharAt(i + 1) == '_') {
return true;
}
}
}
return false;
}
// Create a private key for this library. It is based on the hash of the
// library URI and the sequence number of the library to guarantee unique
// private keys without having to verify.
void Library::AllocatePrivateKey() const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
if (isolate_group->IsReloading()) {
// When reloading, we need to make sure we use the original private key
// if this library previously existed.
ProgramReloadContext* program_reload_context =
isolate_group->program_reload_context();
const String& original_key =
String::Handle(program_reload_context->FindLibraryPrivateKey(*this));
if (!original_key.IsNull()) {
untag()->set_private_key(original_key.ptr());
return;
}
}
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
// Format of the private key is: "@<sequence number><6 digits of hash>
const intptr_t hash_mask = 0x7FFFF;
const String& url = String::Handle(zone, this->url());
intptr_t hash_value = url.Hash() & hash_mask;
const GrowableObjectArray& libs = GrowableObjectArray::Handle(
zone, isolate_group->object_store()->libraries());
intptr_t sequence_value = libs.Length();
char private_key[32];
Utils::SNPrint(private_key, sizeof(private_key), "%c%" Pd "%06" Pd "",
kPrivateKeySeparator, sequence_value, hash_value);
const String& key =
String::Handle(zone, String::New(private_key, Heap::kOld));
key.Hash(); // This string may end up in the VM isolate.
untag()->set_private_key(key.ptr());
}
const String& Library::PrivateCoreLibName(const String& member) {
const Library& core_lib = Library::Handle(Library::CoreLibrary());
const String& private_name = String::ZoneHandle(core_lib.PrivateName(member));
return private_name;
}
bool Library::IsPrivateCoreLibName(const String& name, const String& member) {
Zone* zone = Thread::Current()->zone();
const auto& core_lib = Library::Handle(zone, Library::CoreLibrary());
const auto& private_key = String::Handle(zone, core_lib.private_key());
ASSERT(core_lib.IsPrivate(member));
return name.EqualsConcat(member, private_key);
}
ClassPtr Library::LookupCoreClass(const String& class_name) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const Library& core_lib = Library::Handle(zone, Library::CoreLibrary());
String& name = String::Handle(zone, class_name.ptr());
if (class_name.CharAt(0) == kPrivateIdentifierStart) {
// Private identifiers are mangled on a per library basis.
name = Symbols::FromConcat(thread, name,
String::Handle(zone, core_lib.private_key()));
}
return core_lib.LookupClass(name);
}
// Cannot handle qualified names properly as it only appends private key to
// the end (e.g. _Alfa.foo -> _Alfa.foo@...).
StringPtr Library::PrivateName(const String& name) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
ASSERT(IsPrivate(name));
// ASSERT(strchr(name, '@') == NULL);
String& str = String::Handle(zone);
str = name.ptr();
str = Symbols::FromConcat(thread, str,
String::Handle(zone, this->private_key()));
return str.ptr();
}
LibraryPtr Library::GetLibrary(intptr_t index) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
const GrowableObjectArray& libs = GrowableObjectArray::Handle(
zone, isolate_group->object_store()->libraries());
ASSERT(!libs.IsNull());
if ((0 <= index) && (index < libs.Length())) {
Library& lib = Library::Handle(zone);
lib ^= libs.At(index);
return lib.ptr();
}
return Library::null();
}
void Library::Register(Thread* thread) const {
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
ObjectStore* object_store = isolate_group->object_store();
// A library is "registered" in two places:
// - A growable array mapping from index to library.
const String& lib_url = String::Handle(zone, url());
ASSERT(Library::LookupLibrary(thread, lib_url) == Library::null());
ASSERT(lib_url.HasHash());
GrowableObjectArray& libs =
GrowableObjectArray::Handle(zone, object_store->libraries());
ASSERT(!libs.IsNull());
set_index(libs.Length());
libs.Add(*this);
// - A map from URL string to library.
if (object_store->libraries_map() == Array::null()) {
LibraryLookupMap map(HashTables::New<LibraryLookupMap>(16, Heap::kOld));
object_store->set_libraries_map(map.Release());
}
LibraryLookupMap map(object_store->libraries_map());
bool present = map.UpdateOrInsert(lib_url, *this);
ASSERT(!present);
object_store->set_libraries_map(map.Release());
}
void Library::RegisterLibraries(Thread* thread,
const GrowableObjectArray& libs) {
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
Library& lib = Library::Handle(zone);
String& lib_url = String::Handle(zone);
LibraryLookupMap map(HashTables::New<LibraryLookupMap>(16, Heap::kOld));
intptr_t len = libs.Length();
for (intptr_t i = 0; i < len; i++) {
lib ^= libs.At(i);
lib_url = lib.url();
map.InsertNewOrGetValue(lib_url, lib);
}
// Now remember these in the isolate's object store.
isolate_group->object_store()->set_libraries(libs);
isolate_group->object_store()->set_libraries_map(map.Release());
}
LibraryPtr Library::AsyncLibrary() {
return IsolateGroup::Current()->object_store()->async_library();
}
LibraryPtr Library::ConvertLibrary() {
return IsolateGroup::Current()->object_store()->convert_library();
}
LibraryPtr Library::CoreLibrary() {
return IsolateGroup::Current()->object_store()->core_library();
}
LibraryPtr Library::CollectionLibrary() {
return IsolateGroup::Current()->object_store()->collection_library();
}
LibraryPtr Library::DeveloperLibrary() {
return IsolateGroup::Current()->object_store()->developer_library();
}
LibraryPtr Library::FfiLibrary() {
return IsolateGroup::Current()->object_store()->ffi_library();
}
LibraryPtr Library::InternalLibrary() {
return IsolateGroup::Current()->object_store()->_internal_library();
}
LibraryPtr Library::IsolateLibrary() {
return IsolateGroup::Current()->object_store()->isolate_library();
}
LibraryPtr Library::MathLibrary() {
return IsolateGroup::Current()->object_store()->math_library();
}
#if !defined(DART_PRECOMPILED_RUNTIME)
LibraryPtr Library::MirrorsLibrary() {
return IsolateGroup::Current()->object_store()->mirrors_library();
}
#endif
LibraryPtr Library::NativeWrappersLibrary() {
return IsolateGroup::Current()->object_store()->native_wrappers_library();
}
LibraryPtr Library::ProfilerLibrary() {
return IsolateGroup::Current()->object_store()->profiler_library();
}
LibraryPtr Library::TypedDataLibrary() {
return IsolateGroup::Current()->object_store()->typed_data_library();
}
LibraryPtr Library::VMServiceLibrary() {
return IsolateGroup::Current()->object_store()->_vmservice_library();
}
const char* Library::ToCString() const {
NoSafepointScope no_safepoint;
const String& name = String::Handle(url());
return OS::SCreate(Thread::Current()->zone(), "Library:'%s'",
name.ToCString());
}
LibraryPtr LibraryPrefix::GetLibrary(int index) const {
if ((index >= 0) || (index < num_imports())) {
const Array& imports = Array::Handle(this->imports());
Namespace& import = Namespace::Handle();
import ^= imports.At(index);
return import.target();
}
return Library::null();
}
void LibraryPrefix::AddImport(const Namespace& import) const {
intptr_t num_current_imports = num_imports();
// Prefixes with deferred libraries can only contain one library.
ASSERT((num_current_imports == 0) || !is_deferred_load());
// The library needs to be added to the list.
Array& imports = Array::Handle(this->imports());
const intptr_t length = (imports.IsNull()) ? 0 : imports.Length();
// Grow the list if it is full.
if (num_current_imports >= length) {
const intptr_t new_length = length + kIncrementSize + (length >> 2);
imports = Array::Grow(imports, new_length, Heap::kOld);
set_imports(imports);
}
imports.SetAt(num_current_imports, import);
set_num_imports(num_current_imports + 1);
}
LibraryPrefixPtr LibraryPrefix::New() {
ObjectPtr raw =
Object::Allocate(LibraryPrefix::kClassId, LibraryPrefix::InstanceSize(),
Heap::kOld, /*compressed*/ false);
return static_cast<LibraryPrefixPtr>(raw);
}
LibraryPrefixPtr LibraryPrefix::New(const String& name,
const Namespace& import,
bool deferred_load,
const Library& importer) {
const LibraryPrefix& result = LibraryPrefix::Handle(LibraryPrefix::New());
result.set_name(name);
result.set_num_imports(0);
result.set_importer(importer);
result.StoreNonPointer(&result.untag()->is_deferred_load_, deferred_load);
result.set_imports(Array::Handle(Array::New(kInitialSize)));
result.AddImport(import);
return result.ptr();
}
void LibraryPrefix::set_name(const String& value) const {
ASSERT(value.IsSymbol());
untag()->set_name(value.ptr());
}
void LibraryPrefix::set_imports(const Array& value) const {
untag()->set_imports(value.ptr());
}
void LibraryPrefix::set_num_imports(intptr_t value) const {
if (!Utils::IsUint(16, value)) {
ReportTooManyImports(Library::Handle(importer()));
}
StoreNonPointer(&untag()->num_imports_, value);
}
void LibraryPrefix::set_importer(const Library& value) const {
untag()->set_importer(value.ptr());
}
const char* LibraryPrefix::ToCString() const {
const String& prefix = String::Handle(name());
return prefix.ToCString();
}
const char* Namespace::ToCString() const {
const Library& lib = Library::Handle(target());
return OS::SCreate(Thread::Current()->zone(), "Namespace for library '%s'",
lib.ToCString());
}
bool Namespace::HidesName(const String& name) const {
// Quick check for common case with no combinators.
if (hide_names() == show_names()) {
ASSERT(hide_names() == Array::null());
return false;
}
const String* plain_name = &name;
if (Field::IsGetterName(name)) {
plain_name = &String::Handle(Field::NameFromGetter(name));
} else if (Field::IsSetterName(name)) {
plain_name = &String::Handle(Field::NameFromSetter(name));
}
// Check whether the name is in the list of explicitly hidden names.
if (hide_names() != Array::null()) {
const Array& names = Array::Handle(hide_names());
String& hidden = String::Handle();
intptr_t num_names = names.Length();
for (intptr_t i = 0; i < num_names; i++) {
hidden ^= names.At(i);
if (plain_name->Equals(hidden)) {
return true;
}
}
}
// The name is not explicitly hidden. Now check whether it is in the
// list of explicitly visible names, if there is one.
if (show_names() != Array::null()) {
const Array& names = Array::Handle(show_names());
String& shown = String::Handle();
intptr_t num_names = names.Length();
for (intptr_t i = 0; i < num_names; i++) {
shown ^= names.At(i);
if (plain_name->Equals(shown)) {
return false;
}
}
// There is a list of visible names. The name we're looking for is not
// contained in the list, so it is hidden.
return true;
}
// The name is not filtered out.
return false;
}
// Look up object with given name in library and filter out hidden
// names. Also look up getters and setters.
ObjectPtr Namespace::Lookup(const String& name,
ZoneGrowableArray<intptr_t>* trail) const {
Zone* zone = Thread::Current()->zone();
const Library& lib = Library::Handle(zone, target());
if (trail != NULL) {
// Look for cycle in reexport graph.
for (int i = 0; i < trail->length(); i++) {
if (trail->At(i) == lib.index()) {
for (int j = i + 1; j < trail->length(); j++) {
(*trail)[j] = -1;
}
return Object::null();
}
}
}
lib.EnsureTopLevelClassIsFinalized();
intptr_t ignore = 0;
// Lookup the name in the library's symbols.
Object& obj = Object::Handle(zone, lib.LookupEntry(name, &ignore));
if (!Field::IsGetterName(name) && !Field::IsSetterName(name) &&
(obj.IsNull() || obj.IsLibraryPrefix())) {
String& accessor_name = String::Handle(zone);
accessor_name = Field::LookupGetterSymbol(name);
if (!accessor_name.IsNull()) {
obj = lib.LookupEntry(accessor_name, &ignore);
}
if (obj.IsNull()) {
accessor_name = Field::LookupSetterSymbol(name);
if (!accessor_name.IsNull()) {
obj = lib.LookupEntry(accessor_name, &ignore);
}
}
}
// Library prefixes are not exported.
if (obj.IsNull() || obj.IsLibraryPrefix()) {
// Lookup in the re-exported symbols.
obj = lib.LookupReExport(name, trail);
if (obj.IsNull() && !Field::IsSetterName(name)) {
// LookupReExport() only returns objects that match the given name.
// If there is no field/func/getter, try finding a setter.
const String& setter_name =
String::Handle(zone, Field::LookupSetterSymbol(name));
if (!setter_name.IsNull()) {
obj = lib.LookupReExport(setter_name, trail);
}
}
}
if (obj.IsNull() || HidesName(name) || obj.IsLibraryPrefix()) {
return Object::null();
}
return obj.ptr();
}
NamespacePtr Namespace::New() {
ASSERT(Object::namespace_class() != Class::null());
ObjectPtr raw =
Object::Allocate(Namespace::kClassId, Namespace::InstanceSize(),
Heap::kOld, /*compressed*/ true);
return static_cast<NamespacePtr>(raw);
}
NamespacePtr Namespace::New(const Library& target,
const Array& show_names,
const Array& hide_names,
const Library& owner) {
ASSERT(show_names.IsNull() || (show_names.Length() > 0));
ASSERT(hide_names.IsNull() || (hide_names.Length() > 0));
const Namespace& result = Namespace::Handle(Namespace::New());
result.untag()->set_target(target.ptr());
result.untag()->set_show_names(show_names.ptr());
result.untag()->set_hide_names(hide_names.ptr());
result.untag()->set_owner(owner.ptr());
return result.ptr();
}
KernelProgramInfoPtr KernelProgramInfo::New() {
ObjectPtr raw = Object::Allocate(KernelProgramInfo::kClassId,
KernelProgramInfo::InstanceSize(),
Heap::kOld, /*compressed*/ true);
return static_cast<KernelProgramInfoPtr>(raw);
}
KernelProgramInfoPtr KernelProgramInfo::New(
const TypedData& string_offsets,
const ExternalTypedData& string_data,
const TypedData& canonical_names,
const ExternalTypedData& metadata_payloads,
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) {
const KernelProgramInfo& info =
KernelProgramInfo::Handle(KernelProgramInfo::New());
info.untag()->set_string_offsets(string_offsets.ptr());
info.untag()->set_string_data(string_data.ptr());
info.untag()->set_canonical_names(canonical_names.ptr());
info.untag()->set_metadata_payloads(metadata_payloads.ptr());
info.untag()->set_metadata_mappings(metadata_mappings.ptr());
info.untag()->set_scripts(scripts.ptr());
info.untag()->set_constants_table(constants_table.ptr());
info.untag()->set_libraries_cache(libraries_cache.ptr());
info.untag()->set_classes_cache(classes_cache.ptr());
info.untag()->set_retained_kernel_blob(retained_kernel_blob.ptr());
info.set_kernel_binary_version(binary_version);
return info.ptr();
}
const char* KernelProgramInfo::ToCString() const {
return "[KernelProgramInfo]";
}
ScriptPtr KernelProgramInfo::ScriptAt(intptr_t index) const {
const Array& all_scripts = Array::Handle(scripts());
ObjectPtr script = all_scripts.At(index);
return Script::RawCast(script);
}
void KernelProgramInfo::set_scripts(const Array& scripts) const {
untag()->set_scripts(scripts.ptr());
}
void KernelProgramInfo::set_constants(const Array& constants) const {
untag()->set_constants(constants.ptr());
}
void KernelProgramInfo::set_kernel_binary_version(uint32_t version) const {
StoreNonPointer(&untag()->kernel_binary_version_, version);
}
void KernelProgramInfo::set_constants_table(
const ExternalTypedData& value) const {
untag()->set_constants_table(value.ptr());
}
void KernelProgramInfo::set_potential_natives(
const GrowableObjectArray& candidates) const {
untag()->set_potential_natives(candidates.ptr());
}
void KernelProgramInfo::set_potential_pragma_functions(
const GrowableObjectArray& candidates) const {
untag()->set_potential_pragma_functions(candidates.ptr());
}
void KernelProgramInfo::set_libraries_cache(const Array& cache) const {
untag()->set_libraries_cache(cache.ptr());
}
typedef UnorderedHashMap<SmiTraits> IntHashMap;
LibraryPtr KernelProgramInfo::LookupLibrary(Thread* thread,
const Smi& name_index) const {
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_LIBRARY_HANDLESCOPE(thread);
REUSABLE_OBJECT_HANDLESCOPE(thread);
REUSABLE_SMI_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
Library& result = thread->LibraryHandle();
Object& key = thread->ObjectHandle();
Smi& value = thread->SmiHandle();
{
SafepointMutexLocker ml(
thread->isolate_group()->kernel_data_lib_cache_mutex());
data = libraries_cache();
ASSERT(!data.IsNull());
IntHashMap table(&key, &value, &data);
result ^= table.GetOrNull(name_index);
table.Release();
}
return result.ptr();
}
LibraryPtr KernelProgramInfo::InsertLibrary(Thread* thread,
const Smi& name_index,
const Library& lib) const {
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_LIBRARY_HANDLESCOPE(thread);
REUSABLE_OBJECT_HANDLESCOPE(thread);
REUSABLE_SMI_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
Library& result = thread->LibraryHandle();
Object& key = thread->ObjectHandle();
Smi& value = thread->SmiHandle();
{
SafepointMutexLocker ml(
thread->isolate_group()->kernel_data_lib_cache_mutex());
data = libraries_cache();
ASSERT(!data.IsNull());
IntHashMap table(&key, &value, &data);
result ^= table.InsertOrGetValue(name_index, lib);
set_libraries_cache(table.Release());
}
return result.ptr();
}
void KernelProgramInfo::set_classes_cache(const Array& cache) const {
untag()->set_classes_cache(cache.ptr());
}
ClassPtr KernelProgramInfo::LookupClass(Thread* thread,
const Smi& name_index) const {
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_CLASS_HANDLESCOPE(thread);
REUSABLE_OBJECT_HANDLESCOPE(thread);
REUSABLE_SMI_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
Class& result = thread->ClassHandle();
Object& key = thread->ObjectHandle();
Smi& value = thread->SmiHandle();
{
SafepointMutexLocker ml(
thread->isolate_group()->kernel_data_class_cache_mutex());
data = classes_cache();
ASSERT(!data.IsNull());
IntHashMap table(&key, &value, &data);
result ^= table.GetOrNull(name_index);
table.Release();
}
return result.ptr();
}
ClassPtr KernelProgramInfo::InsertClass(Thread* thread,
const Smi& name_index,
const Class& klass) const {
REUSABLE_ARRAY_HANDLESCOPE(thread);
REUSABLE_CLASS_HANDLESCOPE(thread);
REUSABLE_OBJECT_HANDLESCOPE(thread);
REUSABLE_SMI_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
Class& result = thread->ClassHandle();
Object& key = thread->ObjectHandle();
Smi& value = thread->SmiHandle();
{
SafepointMutexLocker ml(
thread->isolate_group()->kernel_data_class_cache_mutex());
data = classes_cache();
ASSERT(!data.IsNull());
IntHashMap table(&key, &value, &data);
result ^= table.InsertOrGetValue(name_index, klass);
set_classes_cache(table.Release());
}
return result.ptr();
}
ErrorPtr Library::CompileAll(bool ignore_error /* = false */) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Error& error = Error::Handle(zone);
const GrowableObjectArray& libs = GrowableObjectArray::Handle(
IsolateGroup::Current()->object_store()->libraries());
Library& lib = Library::Handle(zone);
Class& cls = Class::Handle(zone);
for (int i = 0; i < libs.Length(); i++) {
lib ^= libs.At(i);
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
while (it.HasNext()) {
cls = it.GetNextClass();
error = cls.EnsureIsFinalized(thread);
if (!error.IsNull()) {
if (ignore_error) continue;
return error.ptr();
}
error = Compiler::CompileAllFunctions(cls);
if (!error.IsNull()) {
if (ignore_error) continue;
return error.ptr();
}
}
}
Object& result = Object::Handle(zone);
ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& func) {
if (!func.HasCode()) {
result = Compiler::CompileFunction(thread, func);
if (result.IsError()) {
error = Error::Cast(result).ptr();
return false; // Stop iteration.
}
}
return true; // Continue iteration.
});
return error.ptr();
}
#if !defined(DART_PRECOMPILED_RUNTIME)
ErrorPtr Library::FinalizeAllClasses() {
Thread* thread = Thread::Current();
ASSERT(thread->IsMutatorThread());
Zone* zone = thread->zone();
Error& error = Error::Handle(zone);
const GrowableObjectArray& libs = GrowableObjectArray::Handle(
IsolateGroup::Current()->object_store()->libraries());
Library& lib = Library::Handle(zone);
Class& cls = Class::Handle(zone);
for (int i = 0; i < libs.Length(); i++) {
lib ^= libs.At(i);
if (!lib.Loaded()) {
String& uri = String::Handle(zone, lib.url());
String& msg = String::Handle(
zone,
String::NewFormatted("Library '%s' is not loaded. "
"Did you forget to call Dart_FinalizeLoading?",
uri.ToCString()));
return ApiError::New(msg);
}
ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate);
while (it.HasNext()) {
cls = it.GetNextClass();
error = cls.EnsureIsFinalized(thread);
if (!error.IsNull()) {
return error.ptr();
}
}
}
return Error::null();
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
// Return Function::null() if function does not exist in libs.
FunctionPtr Library::GetFunction(const GrowableArray<Library*>& libs,
const char* class_name,
const char* function_name) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Function& func = Function::Handle(zone);
String& class_str = String::Handle(zone);
String& func_str = String::Handle(zone);
Class& cls = Class::Handle(zone);
for (intptr_t l = 0; l < libs.length(); l++) {
const Library& lib = *libs[l];
if (strcmp(class_name, "::") == 0) {
func_str = Symbols::New(thread, function_name);
func = lib.LookupFunctionAllowPrivate(func_str);
} else {
class_str = String::New(class_name);
cls = lib.LookupClassAllowPrivate(class_str);
if (!cls.IsNull()) {
if (cls.EnsureIsFinalized(thread) == Error::null()) {
func_str = String::New(function_name);
if (function_name[0] == '.') {
func_str = String::Concat(class_str, func_str);
}
func = cls.LookupFunctionAllowPrivate(func_str);
}
}
}
if (!func.IsNull()) {
return func.ptr();
}
}
return Function::null();
}
ObjectPtr Library::GetFunctionClosure(const String& name) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Function& func = Function::Handle(zone, LookupFunctionAllowPrivate(name));
if (func.IsNull()) {
// Check whether the function is reexported into the library.
const Object& obj = Object::Handle(zone, LookupReExport(name));
if (obj.IsFunction()) {
func ^= obj.ptr();
} else {
// Check if there is a getter of 'name', in which case invoke it
// and return the result.
const String& getter_name = String::Handle(zone, Field::GetterName(name));
func = LookupFunctionAllowPrivate(getter_name);
if (func.IsNull()) {
return Closure::null();
}
// Invoke the getter and return the result.
return DartEntry::InvokeFunction(func, Object::empty_array());
}
}
func = func.ImplicitClosureFunction();
return func.ImplicitStaticClosure();
}
#if defined(DEBUG) && !defined(DART_PRECOMPILED_RUNTIME)
void Library::CheckFunctionFingerprints() {
GrowableArray<Library*> all_libs;
Function& func = Function::Handle();
bool fingerprints_match = true;
#define CHECK_FINGERPRINTS_INNER(class_name, function_name, dest, fp, kind) \
func = GetFunction(all_libs, #class_name, #function_name); \
if (func.IsNull()) { \
fingerprints_match = false; \
OS::PrintErr("Function not found %s.%s\n", #class_name, #function_name); \
} else { \
fingerprints_match = \
func.CheckSourceFingerprint(fp, kind) && fingerprints_match; \
}
#define CHECK_FINGERPRINTS(class_name, function_name, dest, fp) \
CHECK_FINGERPRINTS_INNER(class_name, function_name, dest, fp, nullptr)
#define CHECK_FINGERPRINTS_ASM_INTRINSIC(class_name, function_name, dest, fp) \
CHECK_FINGERPRINTS_INNER(class_name, function_name, dest, fp, "asm-intrinsic")
#define CHECK_FINGERPRINTS_GRAPH_INTRINSIC(class_name, function_name, dest, \
fp) \
CHECK_FINGERPRINTS_INNER(class_name, function_name, dest, fp, \
"graph-intrinsic")
#define CHECK_FINGERPRINTS_OTHER(class_name, function_name, dest, fp) \
CHECK_FINGERPRINTS_INNER(class_name, function_name, dest, fp, "other")
all_libs.Add(&Library::ZoneHandle(Library::CoreLibrary()));
CORE_LIB_INTRINSIC_LIST(CHECK_FINGERPRINTS_ASM_INTRINSIC);
CORE_INTEGER_LIB_INTRINSIC_LIST(CHECK_FINGERPRINTS_ASM_INTRINSIC);
GRAPH_CORE_INTRINSICS_LIST(CHECK_FINGERPRINTS_GRAPH_INTRINSIC);
all_libs.Add(&Library::ZoneHandle(Library::AsyncLibrary()));
all_libs.Add(&Library::ZoneHandle(Library::MathLibrary()));
all_libs.Add(&Library::ZoneHandle(Library::TypedDataLibrary()));
all_libs.Add(&Library::ZoneHandle(Library::CollectionLibrary()));
all_libs.Add(&Library::ZoneHandle(Library::ConvertLibrary()));
all_libs.Add(&Library::ZoneHandle(Library::InternalLibrary()));
all_libs.Add(&Library::ZoneHandle(Library::FfiLibrary()));
INTERNAL_LIB_INTRINSIC_LIST(CHECK_FINGERPRINTS_ASM_INTRINSIC);
OTHER_RECOGNIZED_LIST(CHECK_FINGERPRINTS_OTHER);
POLYMORPHIC_TARGET_LIST(CHECK_FINGERPRINTS);
GRAPH_TYPED_DATA_INTRINSICS_LIST(CHECK_FINGERPRINTS_GRAPH_INTRINSIC);
all_libs.Clear();
all_libs.Add(&Library::ZoneHandle(Library::DeveloperLibrary()));
DEVELOPER_LIB_INTRINSIC_LIST(CHECK_FINGERPRINTS_ASM_INTRINSIC);
all_libs.Clear();
all_libs.Add(&Library::ZoneHandle(Library::MathLibrary()));
MATH_LIB_INTRINSIC_LIST(CHECK_FINGERPRINTS_ASM_INTRINSIC);
GRAPH_MATH_LIB_INTRINSIC_LIST(CHECK_FINGERPRINTS_GRAPH_INTRINSIC);
#undef CHECK_FINGERPRINTS_INNER
#undef CHECK_FINGERPRINTS
#undef CHECK_FINGERPRINTS_ASM_INTRINSIC
#undef CHECK_FINGERPRINTS_GRAPH_INTRINSIC
#undef CHECK_FINGERPRINTS_OTHER
#define CHECK_FACTORY_FINGERPRINTS(symbol, class_name, factory_name, cid, fp) \
func = GetFunction(all_libs, #class_name, #factory_name); \
if (func.IsNull()) { \
fingerprints_match = false; \
OS::PrintErr("Function not found %s.%s\n", #class_name, #factory_name); \
} else { \
fingerprints_match = \
func.CheckSourceFingerprint(fp) && fingerprints_match; \
}
all_libs.Clear();
all_libs.Add(&Library::ZoneHandle(Library::CoreLibrary()));
all_libs.Add(&Library::ZoneHandle(Library::TypedDataLibrary()));
RECOGNIZED_LIST_FACTORY_LIST(CHECK_FACTORY_FINGERPRINTS);
#undef CHECK_FACTORY_FINGERPRINTS
if (!fingerprints_match) {
FATAL(
"FP mismatch while recognizing methods. If the behavior of "
"these functions has changed, then changes are also needed in "
"the VM's compiler. Otherwise the fingerprint can simply be "
"updated in recognized_methods_list.h\n");
}
}
#endif // defined(DEBUG) && !defined(DART_PRECOMPILED_RUNTIME).
InstructionsPtr Instructions::New(intptr_t size, bool has_monomorphic_entry) {
ASSERT(size >= 0);
ASSERT(Object::instructions_class() != Class::null());
if (size < 0 || size > kMaxElements) {
// This should be caught before we reach here.
FATAL1("Fatal error in Instructions::New: invalid size %" Pd "\n", size);
}
Instructions& result = Instructions::Handle();
{
uword aligned_size = Instructions::InstanceSize(size);
ObjectPtr raw = Object::Allocate(Instructions::kClassId, aligned_size,
Heap::kCode, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
result.SetSize(size);
result.SetHasMonomorphicEntry(has_monomorphic_entry);
result.set_stats(nullptr);
}
return result.ptr();
}
const char* Instructions::ToCString() const {
return "Instructions";
}
CodeStatistics* Instructions::stats() const {
#if defined(DART_PRECOMPILER)
return reinterpret_cast<CodeStatistics*>(
Thread::Current()->heap()->GetPeer(ptr()));
#else
return nullptr;
#endif
}
void Instructions::set_stats(CodeStatistics* stats) const {
#if defined(DART_PRECOMPILER)
Thread::Current()->heap()->SetPeer(ptr(), stats);
#endif
}
const char* InstructionsSection::ToCString() const {
return "InstructionsSection";
}
ObjectPoolPtr ObjectPool::New(intptr_t len) {
ASSERT(Object::object_pool_class() != Class::null());
if (len < 0 || len > kMaxElements) {
// This should be caught before we reach here.
FATAL1("Fatal error in ObjectPool::New: invalid length %" Pd "\n", len);
}
ObjectPool& result = ObjectPool::Handle();
{
uword size = ObjectPool::InstanceSize(len);
ObjectPtr raw = Object::Allocate(ObjectPool::kClassId, size, Heap::kOld,
/*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
result.SetLength(len);
for (intptr_t i = 0; i < len; i++) {
result.SetTypeAt(i, ObjectPool::EntryType::kImmediate,
ObjectPool::Patchability::kPatchable);
}
}
return result.ptr();
}
#if !defined(DART_PRECOMPILED_RUNTIME)
ObjectPoolPtr ObjectPool::NewFromBuilder(
const compiler::ObjectPoolBuilder& builder) {
const intptr_t len = builder.CurrentLength();
if (len == 0) {
return Object::empty_object_pool().ptr();
}
const ObjectPool& result = ObjectPool::Handle(ObjectPool::New(len));
for (intptr_t i = 0; i < len; i++) {
auto entry = builder.EntryAt(i);
auto type = entry.type();
auto patchable = entry.patchable();
result.SetTypeAt(i, type, patchable);
if (type == EntryType::kTaggedObject) {
result.SetObjectAt(i, *entry.obj_);
} else {
result.SetRawValueAt(i, entry.raw_value_);
}
}
return result.ptr();
}
void ObjectPool::CopyInto(compiler::ObjectPoolBuilder* builder) const {
ASSERT(builder->CurrentLength() == 0);
for (intptr_t i = 0; i < Length(); i++) {
auto type = TypeAt(i);
auto patchable = PatchableAt(i);
switch (type) {
case compiler::ObjectPoolBuilderEntry::kTaggedObject: {
compiler::ObjectPoolBuilderEntry entry(&Object::ZoneHandle(ObjectAt(i)),
patchable);
builder->AddObject(entry);
break;
}
case compiler::ObjectPoolBuilderEntry::kImmediate:
case compiler::ObjectPoolBuilderEntry::kNativeFunction:
case compiler::ObjectPoolBuilderEntry::kNativeFunctionWrapper: {
compiler::ObjectPoolBuilderEntry entry(RawValueAt(i), type, patchable);
builder->AddObject(entry);
break;
}
default:
UNREACHABLE();
}
}
ASSERT(builder->CurrentLength() == Length());
}
#endif
const char* ObjectPool::ToCString() const {
Zone* zone = Thread::Current()->zone();
return zone->PrintToString("ObjectPool len:%" Pd, Length());
}
void ObjectPool::DebugPrint() const {
THR_Print("ObjectPool len:%" Pd " {\n", Length());
for (intptr_t i = 0; i < Length(); i++) {
intptr_t offset = OffsetFromIndex(i);
THR_Print(" [pp+0x%" Px "] ", offset);
if (TypeAt(i) == EntryType::kTaggedObject) {
const Object& obj = Object::Handle(ObjectAt(i));
THR_Print("%s (obj)\n", obj.ToCString());
} else if (TypeAt(i) == EntryType::kNativeFunction) {
uword pc = RawValueAt(i);
uintptr_t start = 0;
char* name = NativeSymbolResolver::LookupSymbolName(pc, &start);
if (name != NULL) {
THR_Print("%s (native function)\n", name);
NativeSymbolResolver::FreeSymbolName(name);
} else {
THR_Print("0x%" Px " (native function)\n", pc);
}
} else if (TypeAt(i) == EntryType::kNativeFunctionWrapper) {
THR_Print("0x%" Px " (native function wrapper)\n", RawValueAt(i));
} else {
THR_Print("0x%" Px " (raw)\n", RawValueAt(i));
}
}
THR_Print("}\n");
}
intptr_t PcDescriptors::Length() const {
return untag()->length_;
}
void PcDescriptors::SetLength(intptr_t value) const {
StoreNonPointer(&untag()->length_, value);
}
void PcDescriptors::CopyData(const void* bytes, intptr_t size) {
NoSafepointScope no_safepoint;
uint8_t* data = UnsafeMutableNonPointer(&untag()->data()[0]);
// We're guaranted these memory spaces do not overlap.
memcpy(data, bytes, size); // NOLINT
}
PcDescriptorsPtr PcDescriptors::New(const void* delta_encoded_data,
intptr_t size) {
ASSERT(Object::pc_descriptors_class() != Class::null());
Thread* thread = Thread::Current();
PcDescriptors& result = PcDescriptors::Handle(thread->zone());
{
ObjectPtr raw = Object::Allocate(PcDescriptors::kClassId,
PcDescriptors::InstanceSize(size),
Heap::kOld, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.SetLength(size);
result.CopyData(delta_encoded_data, size);
}
return result.ptr();
}
PcDescriptorsPtr PcDescriptors::New(intptr_t length) {
ASSERT(Object::pc_descriptors_class() != Class::null());
Thread* thread = Thread::Current();
PcDescriptors& result = PcDescriptors::Handle(thread->zone());
{
uword size = PcDescriptors::InstanceSize(length);
ObjectPtr raw = Object::Allocate(PcDescriptors::kClassId, size, Heap::kOld,
/*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.SetLength(length);
}
return result.ptr();
}
const char* PcDescriptors::KindAsStr(UntaggedPcDescriptors::Kind kind) {
switch (kind) {
case UntaggedPcDescriptors::kDeopt:
return "deopt ";
case UntaggedPcDescriptors::kIcCall:
return "ic-call ";
case UntaggedPcDescriptors::kUnoptStaticCall:
return "unopt-call ";
case UntaggedPcDescriptors::kRuntimeCall:
return "runtime-call ";
case UntaggedPcDescriptors::kOsrEntry:
return "osr-entry ";
case UntaggedPcDescriptors::kRewind:
return "rewind ";
case UntaggedPcDescriptors::kBSSRelocation:
return "bss reloc ";
case UntaggedPcDescriptors::kOther:
return "other ";
case UntaggedPcDescriptors::kAnyKind:
UNREACHABLE();
break;
}
UNREACHABLE();
return "";
}
void PcDescriptors::PrintHeaderString() {
// 4 bits per hex digit + 2 for "0x".
const int addr_width = (kBitsPerWord / 4) + 2;
// "*" in a printf format specifier tells it to read the field width from
// the printf argument list.
THR_Print("%-*s\tkind \tdeopt-id\ttok-ix\ttry-ix\tyield-idx\n", addr_width,
"pc");
}
const char* PcDescriptors::ToCString() const {
// "*" in a printf format specifier tells it to read the field width from
// the printf argument list.
#define FORMAT "%#-*" Px "\t%s\t%" Pd "\t\t%s\t%" Pd "\t%" Pd "\n"
if (Length() == 0) {
return "empty PcDescriptors\n";
}
// 4 bits per hex digit.
const int addr_width = kBitsPerWord / 4;
// First compute the buffer size required.
intptr_t len = 1; // Trailing '\0'.
{
Iterator iter(*this, UntaggedPcDescriptors::kAnyKind);
while (iter.MoveNext()) {
len += Utils::SNPrint(NULL, 0, FORMAT, addr_width, iter.PcOffset(),
KindAsStr(iter.Kind()), iter.DeoptId(),
iter.TokenPos().ToCString(), iter.TryIndex(),
iter.YieldIndex());
}
}
// Allocate the buffer.
char* buffer = Thread::Current()->zone()->Alloc<char>(len);
// Layout the fields in the buffer.
intptr_t index = 0;
Iterator iter(*this, UntaggedPcDescriptors::kAnyKind);
while (iter.MoveNext()) {
index += Utils::SNPrint((buffer + index), (len - index), FORMAT, addr_width,
iter.PcOffset(), KindAsStr(iter.Kind()),
iter.DeoptId(), iter.TokenPos().ToCString(),
iter.TryIndex(), iter.YieldIndex());
}
return buffer;
#undef FORMAT
}
// Verify assumptions (in debug mode only).
// - No two deopt descriptors have the same deoptimization id.
// - No two ic-call descriptors have the same deoptimization id (type feedback).
// A function without unique ids is marked as non-optimizable (e.g., because of
// finally blocks).
void PcDescriptors::Verify(const Function& function) const {
#if defined(DEBUG)
// Only check ids for unoptimized code that is optimizable.
if (!function.IsOptimizable()) {
return;
}
intptr_t max_deopt_id = 0;
Iterator max_iter(
*this, UntaggedPcDescriptors::kDeopt | UntaggedPcDescriptors::kIcCall);
while (max_iter.MoveNext()) {
if (max_iter.DeoptId() > max_deopt_id) {
max_deopt_id = max_iter.DeoptId();
}
}
Zone* zone = Thread::Current()->zone();
BitVector* deopt_ids = new (zone) BitVector(zone, max_deopt_id + 1);
BitVector* iccall_ids = new (zone) BitVector(zone, max_deopt_id + 1);
Iterator iter(*this,
UntaggedPcDescriptors::kDeopt | UntaggedPcDescriptors::kIcCall);
while (iter.MoveNext()) {
// 'deopt_id' is set for kDeopt and kIcCall and must be unique for one kind.
if (DeoptId::IsDeoptAfter(iter.DeoptId())) {
// TODO(vegorov): some instructions contain multiple calls and have
// multiple "after" targets recorded. Right now it is benign but might
// lead to issues in the future. Fix that and enable verification.
continue;
}
if (iter.Kind() == UntaggedPcDescriptors::kDeopt) {
ASSERT(!deopt_ids->Contains(iter.DeoptId()));
deopt_ids->Add(iter.DeoptId());
} else {
ASSERT(!iccall_ids->Contains(iter.DeoptId()));
iccall_ids->Add(iter.DeoptId());
}
}
#endif // DEBUG
}
void CodeSourceMap::SetLength(intptr_t value) const {
StoreNonPointer(&untag()->length_, value);
}
CodeSourceMapPtr CodeSourceMap::New(intptr_t length) {
ASSERT(Object::code_source_map_class() != Class::null());
Thread* thread = Thread::Current();
CodeSourceMap& result = CodeSourceMap::Handle(thread->zone());
{
uword size = CodeSourceMap::InstanceSize(length);
ObjectPtr raw = Object::Allocate(CodeSourceMap::kClassId, size, Heap::kOld,
/*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.SetLength(length);
}
return result.ptr();
}
const char* CodeSourceMap::ToCString() const {
return "CodeSourceMap";
}
intptr_t CompressedStackMaps::Hashcode() const {
NoSafepointScope scope;
uint8_t* data = UnsafeMutableNonPointer(&untag()->data()[0]);
uint8_t* end = data + payload_size();
uint32_t hash = payload_size();
for (uint8_t* cursor = data; cursor < end; cursor++) {
hash = CombineHashes(hash, *cursor);
}
return FinalizeHash(hash, kHashBits);
}
CompressedStackMaps::Iterator::Iterator(const CompressedStackMaps& maps,
const CompressedStackMaps& 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());
}
CompressedStackMaps::Iterator::Iterator(Thread* thread,
const CompressedStackMaps& maps)
: CompressedStackMaps::Iterator(
maps,
// Only look up the global table if the map will end up using it.
maps.UsesGlobalTable() ? CompressedStackMaps::Handle(
thread->zone(),
thread->isolate_group()
->object_store()
->canonicalized_stack_map_entries())
: Object::null_compressed_stackmaps()) {}
CompressedStackMaps::Iterator::Iterator(const CompressedStackMaps::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_) {}
bool CompressedStackMaps::Iterator::MoveNext() {
if (next_offset_ >= maps_.payload_size()) {
return false;
}
NoSafepointScope scope;
ReadStream stream(maps_.untag()->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.
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;
}
intptr_t CompressedStackMaps::Iterator::Length() const {
EnsureFullyLoadedEntry();
return current_spill_slot_bit_count_ + current_non_spill_slot_bit_count_;
}
intptr_t CompressedStackMaps::Iterator::SpillSlotBitCount() const {
EnsureFullyLoadedEntry();
return current_spill_slot_bit_count_;
}
bool CompressedStackMaps::Iterator::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_.untag()->data()[byte_offset] & byte_mask) != 0;
}
void CompressedStackMaps::Iterator::LazyLoadGlobalTableEntry() const {
ASSERT(maps_.UsesGlobalTable());
ASSERT(HasLoadedEntry());
ASSERT(current_global_table_offset_ < bits_container_.payload_size());
NoSafepointScope scope;
ReadStream stream(bits_container_.untag()->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 CompressedStackMaps::Iterator::WriteToBuffer(BaseTextBuffer* buffer,
const char* separator) const {
CompressedStackMaps::Iterator it(*this);
// If we haven't loaded an entry yet, do so (but don't skip the current
// one if we have!)
if (!it.HasLoadedEntry()) {
if (!it.MoveNext()) return;
}
bool first_entry = true;
do {
if (!first_entry) {
buffer->AddString(separator);
}
buffer->Printf("0x%.8" Px32 ": ", it.pc_offset());
for (intptr_t i = 0, n = it.Length(); i < n; i++) {
buffer->AddString(it.IsObject(i) ? "1" : "0");
}
first_entry = false;
} while (it.MoveNext());
}
CompressedStackMapsPtr CompressedStackMaps::New(const void* payload,
intptr_t size,
bool is_global_table,
bool uses_global_table) {
ASSERT(Object::compressed_stackmaps_class() != Class::null());
// We don't currently allow both flags to be true.
ASSERT(!is_global_table || !uses_global_table);
// The canonical empty instance should be used instead.
ASSERT(size != 0);
if (!UntaggedCompressedStackMaps::SizeField::is_valid(size)) {
FATAL1(
"Fatal error in CompressedStackMaps::New: "
"invalid payload size %" Pu "\n",
size);
}
auto& result = CompressedStackMaps::Handle();
{
// CompressedStackMaps data objects are associated with a code object,
// allocate them in old generation.
ObjectPtr raw = Object::Allocate(CompressedStackMaps::kClassId,
CompressedStackMaps::InstanceSize(size),
Heap::kOld, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.StoreNonPointer(
&result.untag()->flags_and_size_,
UntaggedCompressedStackMaps::GlobalTableBit::encode(is_global_table) |
UntaggedCompressedStackMaps::UsesTableBit::encode(
uses_global_table) |
UntaggedCompressedStackMaps::SizeField::encode(size));
auto cursor = result.UnsafeMutableNonPointer(result.untag()->data());
memcpy(cursor, payload, size); // NOLINT
}
ASSERT(!result.IsGlobalTable() || !result.UsesGlobalTable());
return result.ptr();
}
const char* CompressedStackMaps::ToCString() const {
ASSERT(!IsGlobalTable());
if (payload_size() == 0) {
return "CompressedStackMaps()";
}
auto const t = Thread::Current();
CompressedStackMaps::Iterator it(t, *this);
ZoneTextBuffer buffer(t->zone(), 100);
buffer.AddString("CompressedStackMaps(");
it.WriteToBuffer(&buffer, ", ");
buffer.AddString(")");
return buffer.buffer();
}
StringPtr LocalVarDescriptors::GetName(intptr_t var_index) const {
ASSERT(var_index < Length());
ASSERT(Object::Handle(ptr()->untag()->name(var_index)).IsString());
return ptr()->untag()->name(var_index);
}
void LocalVarDescriptors::SetVar(
intptr_t var_index,
const String& name,
UntaggedLocalVarDescriptors::VarInfo* info) const {
ASSERT(var_index < Length());
ASSERT(!name.IsNull());
ptr()->untag()->set_name(var_index, name.ptr());
ptr()->untag()->data()[var_index] = *info;
}
void LocalVarDescriptors::GetInfo(
intptr_t var_index,
UntaggedLocalVarDescriptors::VarInfo* info) const {
ASSERT(var_index < Length());
*info = ptr()->untag()->data()[var_index];
}
static int PrintVarInfo(char* buffer,
int len,
intptr_t i,
const String& var_name,
const UntaggedLocalVarDescriptors::VarInfo& info) {
const UntaggedLocalVarDescriptors::VarInfoKind kind = info.kind();
const int32_t index = info.index();
if (kind == UntaggedLocalVarDescriptors::kContextLevel) {
return Utils::SNPrint(buffer, len,
"%2" Pd
" %-13s level=%-3d"
" begin=%-3d end=%d\n",
i, LocalVarDescriptors::KindToCString(kind), index,
static_cast<int>(info.begin_pos.Pos()),
static_cast<int>(info.end_pos.Pos()));
} else if (kind == UntaggedLocalVarDescriptors::kContextVar) {
return Utils::SNPrint(
buffer, len,
"%2" Pd
" %-13s level=%-3d index=%-3d"
" begin=%-3d end=%-3d name=%s\n",
i, LocalVarDescriptors::KindToCString(kind), info.scope_id, index,
static_cast<int>(info.begin_pos.Pos()),
static_cast<int>(info.end_pos.Pos()), var_name.ToCString());
} else {
return Utils::SNPrint(
buffer, len,
"%2" Pd
" %-13s scope=%-3d index=%-3d"
" begin=%-3d end=%-3d name=%s\n",
i, LocalVarDescriptors::KindToCString(kind), info.scope_id, index,
static_cast<int>(info.begin_pos.Pos()),
static_cast<int>(info.end_pos.Pos()), var_name.ToCString());
}
}
const char* LocalVarDescriptors::ToCString() const {
if (IsNull()) {
return "LocalVarDescriptors: null";
}
if (Length() == 0) {
return "empty LocalVarDescriptors";
}
intptr_t len = 1; // Trailing '\0'.
String& var_name = String::Handle();
for (intptr_t i = 0; i < Length(); i++) {
UntaggedLocalVarDescriptors::VarInfo info;
var_name = GetName(i);
GetInfo(i, &info);
len += PrintVarInfo(NULL, 0, i, var_name, info);
}
char* buffer = Thread::Current()->zone()->Alloc<char>(len + 1);
buffer[0] = '\0';
intptr_t num_chars = 0;
for (intptr_t i = 0; i < Length(); i++) {
UntaggedLocalVarDescriptors::VarInfo info;
var_name = GetName(i);
GetInfo(i, &info);
num_chars += PrintVarInfo((buffer + num_chars), (len - num_chars), i,
var_name, info);
}
return buffer;
}
const char* LocalVarDescriptors::KindToCString(
UntaggedLocalVarDescriptors::VarInfoKind kind) {
switch (kind) {
case UntaggedLocalVarDescriptors::kStackVar:
return "StackVar";
case UntaggedLocalVarDescriptors::kContextVar:
return "ContextVar";
case UntaggedLocalVarDescriptors::kContextLevel:
return "ContextLevel";
case UntaggedLocalVarDescriptors::kSavedCurrentContext:
return "CurrentCtx";
default:
UNIMPLEMENTED();
return NULL;
}
}
LocalVarDescriptorsPtr LocalVarDescriptors::New(intptr_t num_variables) {
ASSERT(Object::var_descriptors_class() != Class::null());
if (num_variables < 0 || num_variables > kMaxElements) {
// This should be caught before we reach here.
FATAL2(
"Fatal error in LocalVarDescriptors::New: "
"invalid num_variables %" Pd ". Maximum is: %d\n",
num_variables, UntaggedLocalVarDescriptors::kMaxIndex);
}
LocalVarDescriptors& result = LocalVarDescriptors::Handle();
{
uword size = LocalVarDescriptors::InstanceSize(num_variables);
ObjectPtr raw = Object::Allocate(LocalVarDescriptors::kClassId, size,
Heap::kOld, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.StoreNonPointer(&result.untag()->num_entries_, num_variables);
}
return result.ptr();
}
intptr_t LocalVarDescriptors::Length() const {
return untag()->num_entries_;
}
intptr_t ExceptionHandlers::num_entries() const {
return untag()->num_entries_;
}
void ExceptionHandlers::SetHandlerInfo(intptr_t try_index,
intptr_t outer_try_index,
uword handler_pc_offset,
bool needs_stacktrace,
bool has_catch_all,
bool is_generated) const {
ASSERT((try_index >= 0) && (try_index < num_entries()));
NoSafepointScope no_safepoint;
ExceptionHandlerInfo* info =
UnsafeMutableNonPointer(&untag()->data()[try_index]);
info->outer_try_index = outer_try_index;
// Some C compilers warn about the comparison always being true when using <=
// due to limited range of data type.
ASSERT((handler_pc_offset == static_cast<uword>(kMaxUint32)) ||
(handler_pc_offset < static_cast<uword>(kMaxUint32)));
info->handler_pc_offset = handler_pc_offset;
info->needs_stacktrace = static_cast<int8_t>(needs_stacktrace);
info->has_catch_all = static_cast<int8_t>(has_catch_all);
info->is_generated = static_cast<int8_t>(is_generated);
}
void ExceptionHandlers::GetHandlerInfo(intptr_t try_index,
ExceptionHandlerInfo* info) const {
ASSERT((try_index >= 0) && (try_index < num_entries()));
ASSERT(info != NULL);
*info = untag()->data()[try_index];
}
uword ExceptionHandlers::HandlerPCOffset(intptr_t try_index) const {
ASSERT((try_index >= 0) && (try_index < num_entries()));
return untag()->data()[try_index].handler_pc_offset;
}
intptr_t ExceptionHandlers::OuterTryIndex(intptr_t try_index) const {
ASSERT((try_index >= 0) && (try_index < num_entries()));
return untag()->data()[try_index].outer_try_index;
}
bool ExceptionHandlers::NeedsStackTrace(intptr_t try_index) const {
ASSERT((try_index >= 0) && (try_index < num_entries()));
return untag()->data()[try_index].needs_stacktrace != 0;
}
bool ExceptionHandlers::IsGenerated(intptr_t try_index) const {
ASSERT((try_index >= 0) && (try_index < num_entries()));
return untag()->data()[try_index].is_generated != 0;
}
bool ExceptionHandlers::HasCatchAll(intptr_t try_index) const {
ASSERT((try_index >= 0) && (try_index < num_entries()));
return untag()->data()[try_index].has_catch_all != 0;
}
void ExceptionHandlers::SetHandledTypes(intptr_t try_index,
const Array& handled_types) const {
ASSERT((try_index >= 0) && (try_index < num_entries()));
ASSERT(!handled_types.IsNull());
const Array& handled_types_data =
Array::Handle(untag()->handled_types_data());
handled_types_data.SetAt(try_index, handled_types);
}
ArrayPtr ExceptionHandlers::GetHandledTypes(intptr_t try_index) const {
ASSERT((try_index >= 0) && (try_index < num_entries()));
Array& array = Array::Handle(untag()->handled_types_data());
array ^= array.At(try_index);
return array.ptr();
}
void ExceptionHandlers::set_handled_types_data(const Array& value) const {
untag()->set_handled_types_data(value.ptr());
}
ExceptionHandlersPtr ExceptionHandlers::New(intptr_t num_handlers) {
ASSERT(Object::exception_handlers_class() != Class::null());
if ((num_handlers < 0) || (num_handlers >= kMaxHandlers)) {
FATAL1(
"Fatal error in ExceptionHandlers::New(): "
"invalid num_handlers %" Pd "\n",
num_handlers);
}
ExceptionHandlers& result = ExceptionHandlers::Handle();
{
uword size = ExceptionHandlers::InstanceSize(num_handlers);
ObjectPtr raw = Object::Allocate(ExceptionHandlers::kClassId, size,
Heap::kOld, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.StoreNonPointer(&result.untag()->num_entries_, num_handlers);
}
const Array& handled_types_data =
(num_handlers == 0) ? Object::empty_array()
: Array::Handle(Array::New(num_handlers, Heap::kOld));
result.set_handled_types_data(handled_types_data);
return result.ptr();
}
ExceptionHandlersPtr ExceptionHandlers::New(const Array& handled_types_data) {
ASSERT(Object::exception_handlers_class() != Class::null());
const intptr_t num_handlers = handled_types_data.Length();
if ((num_handlers < 0) || (num_handlers >= kMaxHandlers)) {
FATAL1(
"Fatal error in ExceptionHandlers::New(): "
"invalid num_handlers %" Pd "\n",
num_handlers);
}
ExceptionHandlers& result = ExceptionHandlers::Handle();
{
uword size = ExceptionHandlers::InstanceSize(num_handlers);
ObjectPtr raw = Object::Allocate(ExceptionHandlers::kClassId, size,
Heap::kOld, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.StoreNonPointer(&result.untag()->num_entries_, num_handlers);
}
result.set_handled_types_data(handled_types_data);
return result.ptr();
}
const char* ExceptionHandlers::ToCString() const {
#define FORMAT1 "%" Pd " => %#x (%" Pd " types) (outer %d)%s%s\n"
#define FORMAT2 " %d. %s\n"
if (num_entries() == 0) {
return "empty ExceptionHandlers\n";
}
Array& handled_types = Array::Handle();
Type& type = Type::Handle();
ExceptionHandlerInfo info;
// First compute the buffer size required.
intptr_t len = 1; // Trailing '\0'.
for (intptr_t i = 0; i < num_entries(); i++) {
GetHandlerInfo(i, &info);
handled_types = GetHandledTypes(i);
const intptr_t num_types =
handled_types.IsNull() ? 0 : handled_types.Length();
len += Utils::SNPrint(
NULL, 0, FORMAT1, i, info.handler_pc_offset, num_types,
info.outer_try_index,
((info.needs_stacktrace != 0) ? " (needs stack trace)" : ""),
((info.is_generated != 0) ? " (generated)" : ""));
for (int k = 0; k < num_types; k++) {
type ^= handled_types.At(k);
ASSERT(!type.IsNull());
len += Utils::SNPrint(NULL, 0, FORMAT2, k, type.ToCString());
}
}
// Allocate the buffer.
char* buffer = Thread::Current()->zone()->Alloc<char>(len);
// Layout the fields in the buffer.
intptr_t num_chars = 0;
for (intptr_t i = 0; i < num_entries(); i++) {
GetHandlerInfo(i, &info);
handled_types = GetHandledTypes(i);
const intptr_t num_types =
handled_types.IsNull() ? 0 : handled_types.Length();
num_chars += Utils::SNPrint(
(buffer + num_chars), (len - num_chars), FORMAT1, i,
info.handler_pc_offset, num_types, info.outer_try_index,
((info.needs_stacktrace != 0) ? " (needs stack trace)" : ""),
((info.is_generated != 0) ? " (generated)" : ""));
for (int k = 0; k < num_types; k++) {
type ^= handled_types.At(k);
num_chars += Utils::SNPrint((buffer + num_chars), (len - num_chars),
FORMAT2, k, type.ToCString());
}
}
return buffer;
#undef FORMAT1
#undef FORMAT2
}
void SingleTargetCache::set_target(const Code& value) const {
untag()->set_target(value.ptr());
}
const char* SingleTargetCache::ToCString() const {
return "SingleTargetCache";
}
SingleTargetCachePtr SingleTargetCache::New() {
SingleTargetCache& result = SingleTargetCache::Handle();
{
// IC data objects are long living objects, allocate them in old generation.
ObjectPtr raw = Object::Allocate(SingleTargetCache::kClassId,
SingleTargetCache::InstanceSize(),
Heap::kOld, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_target(Code::Handle());
result.set_entry_point(0);
result.set_lower_limit(kIllegalCid);
result.set_upper_limit(kIllegalCid);
return result.ptr();
}
void UnlinkedCall::set_can_patch_to_monomorphic(bool value) const {
StoreNonPointer(&untag()->can_patch_to_monomorphic_, value);
}
intptr_t UnlinkedCall::Hashcode() const {
return String::Handle(target_name()).Hash();
}
bool UnlinkedCall::Equals(const UnlinkedCall& other) const {
return (target_name() == other.target_name()) &&
(arguments_descriptor() == other.arguments_descriptor()) &&
(can_patch_to_monomorphic() == other.can_patch_to_monomorphic());
}
const char* UnlinkedCall::ToCString() const {
return "UnlinkedCall";
}
UnlinkedCallPtr UnlinkedCall::New() {
UnlinkedCall& result = UnlinkedCall::Handle();
result ^=
Object::Allocate(UnlinkedCall::kClassId, UnlinkedCall::InstanceSize(),
Heap::kOld, /*compressed*/ false);
result.set_can_patch_to_monomorphic(!FLAG_precompiled_mode);
return result.ptr();
}
MonomorphicSmiableCallPtr MonomorphicSmiableCall::New(classid_t expected_cid,
const Code& target) {
auto& result = MonomorphicSmiableCall::Handle();
result ^= Object::Allocate(MonomorphicSmiableCall::kClassId,
MonomorphicSmiableCall::InstanceSize(), Heap::kOld,
/*compressed*/ false);
result.untag()->set_target(target.ptr());
result.StoreNonPointer(&result.untag()->expected_cid_, expected_cid);
result.StoreNonPointer(&result.untag()->entrypoint_, target.EntryPoint());
return result.ptr();
}
const char* MonomorphicSmiableCall::ToCString() const {
return "MonomorphicSmiableCall";
}
const char* CallSiteData::ToCString() const {
// CallSiteData is an abstract class. We should never reach here.
UNREACHABLE();
return "CallSiteData";
}
void CallSiteData::set_target_name(const String& value) const {
ASSERT(!value.IsNull());
untag()->set_target_name(value.ptr());
}
void CallSiteData::set_arguments_descriptor(const Array& value) const {
ASSERT(!value.IsNull());
untag()->set_args_descriptor(value.ptr());
}
#if !defined(DART_PRECOMPILED_RUNTIME)
void ICData::SetReceiversStaticType(const AbstractType& type) const {
untag()->set_receivers_static_type(type.ptr());
#if defined(TARGET_ARCH_X64)
if (!type.IsNull() && type.HasTypeClass() && (NumArgsTested() == 1) &&
type.IsInstantiated() && !type.IsFutureOrType()) {
const Class& cls = Class::Handle(type.type_class());
if (cls.IsGeneric()) {
set_tracking_exactness(true);
}
}
#endif // defined(TARGET_ARCH_X64)
}
#endif
void ICData::SetTargetAtPos(const Array& data,
intptr_t data_pos,
intptr_t num_args_tested,
const Function& target) {
#if !defined(DART_PRECOMPILED_RUNTIME)
// JIT
data.SetAt(data_pos + TargetIndexFor(num_args_tested), target);
#else
// AOT
ASSERT(target.HasCode());
const Code& code = Code::Handle(target.CurrentCode());
const Smi& entry_point =
Smi::Handle(Smi::FromAlignedAddress(code.EntryPoint()));
data.SetAt(data_pos + CodeIndexFor(num_args_tested), code);
data.SetAt(data_pos + EntryPointIndexFor(num_args_tested), entry_point);
#endif
}
const char* ICData::ToCString() const {
Zone* zone = Thread::Current()->zone();
const String& name = String::Handle(zone, target_name());
const intptr_t num_args = NumArgsTested();
const intptr_t num_checks = NumberOfChecks();
const intptr_t type_args_len = TypeArgsLen();
return zone->PrintToString(
"ICData(%s num-args: %" Pd " num-checks: %" Pd " type-args-len: %" Pd ")",
name.ToCString(), num_args, num_checks, type_args_len);
}
FunctionPtr ICData::Owner() const {
Object& obj = Object::Handle(untag()->owner());
if (obj.IsNull()) {
ASSERT(Dart::vm_snapshot_kind() == Snapshot::kFullAOT);
return Function::null();
} else if (obj.IsFunction()) {
return Function::Cast(obj).ptr();
} else {
ICData& original = ICData::Handle();
original ^= obj.ptr();
return original.Owner();
}
}
ICDataPtr ICData::Original() const {
if (IsNull()) {
return ICData::null();
}
Object& obj = Object::Handle(untag()->owner());
if (obj.IsFunction()) {
return this->ptr();
} else {
return ICData::RawCast(obj.ptr());
}
}
void ICData::SetOriginal(const ICData& value) const {
ASSERT(value.IsOriginal());
ASSERT(!value.IsNull());
untag()->set_owner(static_cast<ObjectPtr>(value.ptr()));
}
void ICData::set_owner(const Function& value) const {
untag()->set_owner(static_cast<ObjectPtr>(value.ptr()));
}
void ICData::set_deopt_id(intptr_t value) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
ASSERT(value <= kMaxInt32);
StoreNonPointer(&untag()->deopt_id_, value);
#endif
}
void ICData::set_entries(const Array& value) const {
ASSERT(!value.IsNull());
untag()->set_entries<std::memory_order_release>(value.ptr());
}
intptr_t ICData::NumArgsTested() const {
return NumArgsTestedBits::decode(untag()->state_bits_);
}
void ICData::SetNumArgsTested(intptr_t value) const {
ASSERT(Utils::IsUint(2, value));
StoreNonPointer(&untag()->state_bits_,
NumArgsTestedBits::update(value, untag()->state_bits_));
}
intptr_t CallSiteData::TypeArgsLen() const {
ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor()));
return args_desc.TypeArgsLen();
}
intptr_t CallSiteData::CountWithTypeArgs() const {
ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor()));
return args_desc.CountWithTypeArgs();
}
intptr_t CallSiteData::CountWithoutTypeArgs() const {
ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor()));
return args_desc.Count();
}
intptr_t CallSiteData::SizeWithoutTypeArgs() const {
ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor()));
return args_desc.Size();
}
intptr_t CallSiteData::SizeWithTypeArgs() const {
ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor()));
return args_desc.SizeWithTypeArgs();
}
uint32_t ICData::DeoptReasons() const {
return DeoptReasonBits::decode(untag()->state_bits_);
}
void ICData::SetDeoptReasons(uint32_t reasons) const {
StoreNonPointer(&untag()->state_bits_,
DeoptReasonBits::update(reasons, untag()->state_bits_));
}
bool ICData::HasDeoptReason(DeoptReasonId reason) const {
ASSERT(reason <= kLastRecordedDeoptReason);
return (DeoptReasons() & (1 << reason)) != 0;
}
void ICData::AddDeoptReason(DeoptReasonId reason) const {
if (reason <= kLastRecordedDeoptReason) {
SetDeoptReasons(DeoptReasons() | (1 << reason));
}
}
const char* ICData::RebindRuleToCString(RebindRule r) {
switch (r) {
#define RULE_CASE(Name) \
case RebindRule::k##Name: \
return #Name;
FOR_EACH_REBIND_RULE(RULE_CASE)
#undef RULE_CASE
default:
return nullptr;
}
}
bool ICData::ParseRebindRule(const char* str, RebindRule* out) {
#define RULE_CASE(Name) \
if (strcmp(str, #Name) == 0) { \
*out = RebindRule::k##Name; \
return true; \
}
FOR_EACH_REBIND_RULE(RULE_CASE)
#undef RULE_CASE
return false;
}
ICData::RebindRule ICData::rebind_rule() const {
return (ICData::RebindRule)RebindRuleBits::decode(untag()->state_bits_);
}
void ICData::set_rebind_rule(uint32_t rebind_rule) const {
StoreNonPointer(&untag()->state_bits_,
RebindRuleBits::update(rebind_rule, untag()->state_bits_));
}
bool ICData::is_static_call() const {
return rebind_rule() != kInstance;
}
void ICData::set_state_bits(uint32_t bits) const {
StoreNonPointer(&untag()->state_bits_, bits);
}
intptr_t ICData::TestEntryLengthFor(intptr_t num_args,
bool tracking_exactness) {
return num_args + 1 /* target function*/ + 1 /* frequency */ +
(tracking_exactness ? 1 : 0) /* exactness state */;
}
intptr_t ICData::TestEntryLength() const {
return TestEntryLengthFor(NumArgsTested(), is_tracking_exactness());
}
intptr_t ICData::Length() const {
return (Smi::Value(entries()->untag()->length()) / TestEntryLength());
}
intptr_t ICData::NumberOfChecks() const {
const intptr_t length = Length();
for (intptr_t i = 0; i < length; i++) {
if (IsSentinelAt(i)) {
return i;
}
}
UNREACHABLE();
return -1;
}
bool ICData::NumberOfChecksIs(intptr_t n) const {
const intptr_t length = Length();
for (intptr_t i = 0; i < length; i++) {
if (i == n) {
return IsSentinelAt(i);
} else {
if (IsSentinelAt(i)) return false;
}
}
return n == length;
}
// Discounts any checks with usage of zero.
intptr_t ICData::NumberOfUsedChecks() const {
intptr_t n = NumberOfChecks();
if (n == 0) {
return 0;
}
intptr_t count = 0;
for (intptr_t i = 0; i < n; i++) {
if (GetCountAt(i) > 0) {
count++;
}
}
return count;
}
void ICData::WriteSentinel(const Array& data, intptr_t test_entry_length) {
ASSERT(!data.IsNull());
RELEASE_ASSERT(smi_illegal_cid().Value() == kIllegalCid);
for (intptr_t i = 1; i <= test_entry_length; i++) {
data.SetAt(data.Length() - i, smi_illegal_cid());
}
}
#if defined(DEBUG)
// Used in asserts to verify that a check is not added twice.
bool ICData::HasCheck(const GrowableArray<intptr_t>& cids) const {
return FindCheck(cids) != -1;
}
#endif // DEBUG
intptr_t ICData::FindCheck(const GrowableArray<intptr_t>& cids) const {
const intptr_t len = NumberOfChecks();
GrowableArray<intptr_t> class_ids;
for (intptr_t i = 0; i < len; i++) {
GetClassIdsAt(i, &class_ids);
bool matches = true;
for (intptr_t k = 0; k < class_ids.length(); k++) {
ASSERT(class_ids[k] != kIllegalCid);
if (class_ids[k] != cids[k]) {
matches = false;
break;
}
}
if (matches) {
return i;
}
}
return -1;
}
void ICData::WriteSentinelAt(intptr_t index,
const CallSiteResetter& proof_of_reload) const {
USE(proof_of_reload); // This method can only be called during reload.
Thread* thread = Thread::Current();
const intptr_t len = Length();
ASSERT(index >= 0);
ASSERT(index < len);
REUSABLE_ARRAY_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
data = entries();
const intptr_t start = index * TestEntryLength();
const intptr_t end = start + TestEntryLength();
for (intptr_t i = start; i < end; i++) {
data.SetAt(i, smi_illegal_cid());
}
}
void ICData::ClearCountAt(intptr_t index,
const CallSiteResetter& proof_of_reload) const {
USE(proof_of_reload); // This method can only be called during reload.
ASSERT(index >= 0);
ASSERT(index < NumberOfChecks());
SetCountAt(index, 0);
}
void ICData::ClearAndSetStaticTarget(
const Function& func,
const CallSiteResetter& proof_of_reload) const {
USE(proof_of_reload); // This method can only be called during reload.
if (IsImmutable()) {
return;
}
const intptr_t len = Length();
if (len == 0) {
return;
}
Thread* thread = Thread::Current();
// The final entry is always the sentinel.
ASSERT(IsSentinelAt(len - 1));
const intptr_t num_args_tested = NumArgsTested();
if (num_args_tested == 0) {
// No type feedback is being collected.
REUSABLE_ARRAY_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
data = entries();
// Static calls with no argument checks hold only one target and the
// sentinel value.
ASSERT(len == 2);
// Static calls with no argument checks only need two words.
ASSERT(TestEntryLength() == 2);
// Set the target.
data.SetAt(TargetIndexFor(num_args_tested), func);
// Set count to 0 as this is called during compilation, before the
// call has been executed.
data.SetAt(CountIndexFor(num_args_tested), Object::smi_zero());
} else {
// Type feedback on arguments is being collected.
// Fill all but the first entry with the sentinel.
for (intptr_t i = len - 1; i > 0; i--) {
WriteSentinelAt(i, proof_of_reload);
}
REUSABLE_ARRAY_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
data = entries();
// Rewrite the dummy entry.
const Smi& object_cid = Smi::Handle(Smi::New(kObjectCid));
for (intptr_t i = 0; i < NumArgsTested(); i++) {
data.SetAt(i, object_cid);
}
data.SetAt(TargetIndexFor(num_args_tested), func);
data.SetAt(CountIndexFor(num_args_tested), Object::smi_zero());
}
}
bool ICData::ValidateInterceptor(const Function& target) const {
#if !defined(DART_PRECOMPILED_RUNTIME)
const String& name = String::Handle(target_name());
if (Function::IsDynamicInvocationForwarderName(name)) {
return Function::DemangleDynamicInvocationForwarderName(name) ==
target.name();
}
#endif
ObjectStore* store = IsolateGroup::Current()->object_store();
ASSERT((target.ptr() == store->simple_instance_of_true_function()) ||
(target.ptr() == store->simple_instance_of_false_function()));
const String& instance_of_name = String::Handle(
Library::PrivateCoreLibName(Symbols::_simpleInstanceOf()).ptr());
ASSERT(target_name() == instance_of_name.ptr());
return true;
}
void ICData::EnsureHasCheck(const GrowableArray<intptr_t>& class_ids,
const Function& target,
intptr_t count) const {
SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());
if (FindCheck(class_ids) != -1) return;
AddCheckInternal(class_ids, target, count);
}
void ICData::AddCheck(const GrowableArray<intptr_t>& class_ids,
const Function& target,
intptr_t count) const {
SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());
AddCheckInternal(class_ids, target, count);
}
void ICData::AddCheckInternal(const GrowableArray<intptr_t>& class_ids,
const Function& target,
intptr_t count) const {
ASSERT(
IsolateGroup::Current()->type_feedback_mutex()->IsOwnedByCurrentThread());
ASSERT(!is_tracking_exactness());
ASSERT(!target.IsNull());
ASSERT((target.name() == target_name()) || ValidateInterceptor(target));
DEBUG_ASSERT(!HasCheck(class_ids));
ASSERT(NumArgsTested() > 1); // Otherwise use 'AddReceiverCheck'.
const intptr_t num_args_tested = NumArgsTested();
ASSERT(class_ids.length() == num_args_tested);
const intptr_t old_num = NumberOfChecks();
Array& data = Array::Handle(entries());
// ICData of static calls with NumArgsTested() > 0 have initially a
// dummy set of cids entered (see ICData::NewForStaticCall). That entry is
// overwritten by first real type feedback data.
if (old_num == 1 && num_args_tested == 2) {
const bool has_dummy_entry =
Smi::Value(Smi::RawCast(data.At(0))) == kObjectCid &&
Smi::Value(Smi::RawCast(data.At(1))) == kObjectCid;
if (has_dummy_entry) {
ASSERT(target.ptr() == data.At(TargetIndexFor(num_args_tested)));
// Replace dummy entry.
Smi& value = Smi::Handle();
for (intptr_t i = 0; i < NumArgsTested(); i++) {
ASSERT(class_ids[i] != kIllegalCid);
value = Smi::New(class_ids[i]);
data.SetAt(i, value);
}
return;
}
}
intptr_t index = -1;
data = Grow(&index);
ASSERT(!data.IsNull());
intptr_t data_pos = index * TestEntryLength();
Smi& value = Smi::Handle();
for (intptr_t i = 0; i < class_ids.length(); i++) {
// kIllegalCid is used as terminating value, do not add it.
ASSERT(class_ids[i] != kIllegalCid);
value = Smi::New(class_ids[i]);
data.SetAt(data_pos + i, value);
}
ASSERT(!target.IsNull());
data.SetAt(data_pos + TargetIndexFor(num_args_tested), target);
value = Smi::New(count);
data.SetAt(data_pos + CountIndexFor(num_args_tested), value);
// Multithreaded access to ICData requires setting of array to be the last
// operation.
set_entries(data);
}
ArrayPtr ICData::Grow(intptr_t* index) const {
Array& data = Array::Handle(entries());
// Last entry in array should be a sentinel and will be the new entry
// that can be updated after growing.
*index = Length() - 1;
ASSERT(*index >= 0);
ASSERT(IsSentinelAt(*index));
// Grow the array and write the new final sentinel into place.
const intptr_t new_len = data.Length() + TestEntryLength();
data = Array::Grow(data, new_len, Heap::kOld);
WriteSentinel(data, TestEntryLength());
return data.ptr();
}
void ICData::DebugDump() const {
const Function& owner = Function::Handle(Owner());
THR_Print("ICData::DebugDump\n");
THR_Print("Owner = %s [deopt=%" Pd "]\n", owner.ToCString(), deopt_id());
THR_Print("NumArgsTested = %" Pd "\n", NumArgsTested());
THR_Print("Length = %" Pd "\n", Length());
THR_Print("NumberOfChecks = %" Pd "\n", NumberOfChecks());
GrowableArray<intptr_t> class_ids;
for (intptr_t i = 0; i < NumberOfChecks(); i++) {
THR_Print("Check[%" Pd "]:", i);
GetClassIdsAt(i, &class_ids);
for (intptr_t c = 0; c < class_ids.length(); c++) {
THR_Print(" %" Pd "", class_ids[c]);
}
THR_Print("--- %" Pd " hits\n", GetCountAt(i));
}
}
void ICData::EnsureHasReceiverCheck(intptr_t receiver_class_id,
const Function& target,
intptr_t count,
StaticTypeExactnessState exactness) const {
SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());
GrowableArray<intptr_t> class_ids(1);
class_ids.Add(receiver_class_id);
if (FindCheck(class_ids) != -1) return;
AddReceiverCheckInternal(receiver_class_id, target, count, exactness);
}
void ICData::AddReceiverCheck(intptr_t receiver_class_id,
const Function& target,
intptr_t count,
StaticTypeExactnessState exactness) const {
SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());
AddReceiverCheckInternal(receiver_class_id, target, count, exactness);
}
void ICData::AddReceiverCheckInternal(
intptr_t receiver_class_id,
const Function& target,
intptr_t count,
StaticTypeExactnessState exactness) const {
#if defined(DEBUG)
GrowableArray<intptr_t> class_ids(1);
class_ids.Add(receiver_class_id);
ASSERT(!HasCheck(class_ids));
#endif // DEBUG
ASSERT(!target.IsNull());
const intptr_t kNumArgsTested = 1;
ASSERT(NumArgsTested() == kNumArgsTested); // Otherwise use 'AddCheck'.
ASSERT(receiver_class_id != kIllegalCid);
intptr_t index = -1;
Array& data = Array::Handle(Grow(&index));
intptr_t data_pos = index * TestEntryLength();
if ((receiver_class_id == kSmiCid) && (data_pos > 0)) {
ASSERT(GetReceiverClassIdAt(0) != kSmiCid);
// Move class occupying position 0 to the data_pos.
for (intptr_t i = 0; i < TestEntryLength(); i++) {
data.SetAt(data_pos + i, Object::Handle(data.At(i)));
}
// Insert kSmiCid in position 0.
data_pos = 0;
}
data.SetAt(data_pos, Smi::Handle(Smi::New(receiver_class_id)));
SetTargetAtPos(data, data_pos, kNumArgsTested, target);
#if !defined(DART_PRECOMPILED_RUNTIME)
data.SetAt(data_pos + CountIndexFor(kNumArgsTested),
Smi::Handle(Smi::New(count)));
if (is_tracking_exactness()) {
data.SetAt(data_pos + ExactnessIndexFor(kNumArgsTested),
Smi::Handle(Smi::New(exactness.Encode())));
}
#endif
// Multithreaded access to ICData requires setting of array to be the last
// operation.
set_entries(data);
}
StaticTypeExactnessState ICData::GetExactnessAt(intptr_t index) const {
if (!is_tracking_exactness()) {
return StaticTypeExactnessState::NotTracking();
}
Thread* thread = Thread::Current();
REUSABLE_ARRAY_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
data = entries();
intptr_t data_pos =
index * TestEntryLength() + ExactnessIndexFor(NumArgsTested());
return StaticTypeExactnessState::Decode(
Smi::Value(Smi::RawCast(data.At(data_pos))));
}
void ICData::GetCheckAt(intptr_t index,
GrowableArray<intptr_t>* class_ids,
Function* target) const {
ASSERT(index < NumberOfChecks());
ASSERT(class_ids != NULL);
ASSERT(target != NULL);
class_ids->Clear();
Thread* thread = Thread::Current();
REUSABLE_ARRAY_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
data = entries();
intptr_t data_pos = index * TestEntryLength();
for (intptr_t i = 0; i < NumArgsTested(); i++) {
class_ids->Add(Smi::Value(Smi::RawCast(data.At(data_pos + i))));
}
(*target) ^= data.At(data_pos + TargetIndexFor(NumArgsTested()));
}
bool ICData::IsSentinelAt(intptr_t index) const {
ASSERT(index < Length());
Thread* thread = Thread::Current();
REUSABLE_ARRAY_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
data = entries();
const intptr_t entry_length = TestEntryLength();
intptr_t data_pos = index * TestEntryLength();
for (intptr_t i = 0; i < entry_length; i++) {
if (data.At(data_pos++) != smi_illegal_cid().ptr()) {
return false;
}
}
// The entry at |index| was filled with the value kIllegalCid.
return true;
}
void ICData::GetClassIdsAt(intptr_t index,
GrowableArray<intptr_t>* class_ids) const {
ASSERT(index < Length());
ASSERT(class_ids != NULL);
ASSERT(!IsSentinelAt(index));
class_ids->Clear();
Thread* thread = Thread::Current();
REUSABLE_ARRAY_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
data = entries();
intptr_t data_pos = index * TestEntryLength();
for (intptr_t i = 0; i < NumArgsTested(); i++) {
class_ids->Add(Smi::Value(Smi::RawCast(data.At(data_pos++))));
}
}
void ICData::GetOneClassCheckAt(intptr_t index,
intptr_t* class_id,
Function* target) const {
ASSERT(class_id != NULL);
ASSERT(target != NULL);
ASSERT(NumArgsTested() == 1);
Thread* thread = Thread::Current();
REUSABLE_ARRAY_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
data = entries();
const intptr_t data_pos = index * TestEntryLength();
*class_id = Smi::Value(Smi::RawCast(data.At(data_pos)));
*target ^= data.At(data_pos + TargetIndexFor(NumArgsTested()));
}
intptr_t ICData::GetCidAt(intptr_t index) const {
ASSERT(NumArgsTested() == 1);
Thread* thread = Thread::Current();
REUSABLE_ARRAY_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
data = entries();
const intptr_t data_pos = index * TestEntryLength();
return Smi::Value(Smi::RawCast(data.At(data_pos)));
}
intptr_t ICData::GetClassIdAt(intptr_t index, intptr_t arg_nr) const {
GrowableArray<intptr_t> class_ids;
GetClassIdsAt(index, &class_ids);
return class_ids[arg_nr];
}
intptr_t ICData::GetReceiverClassIdAt(intptr_t index) const {
ASSERT(index < Length());
ASSERT(!IsSentinelAt(index));
const intptr_t data_pos = index * TestEntryLength();
NoSafepointScope no_safepoint;
ArrayPtr raw_data = entries();
return Smi::Value(Smi::RawCast(raw_data->untag()->data()[data_pos]));
}
FunctionPtr ICData::GetTargetAt(intptr_t index) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
return nullptr;
#else
const intptr_t data_pos =
index * TestEntryLength() + TargetIndexFor(NumArgsTested());
ASSERT(Object::Handle(Array::Handle(entries()).At(data_pos)).IsFunction());
NoSafepointScope no_safepoint;
ArrayPtr raw_data = entries();
return static_cast<FunctionPtr>(raw_data->untag()->data()[data_pos]);
#endif
}
void ICData::IncrementCountAt(intptr_t index, intptr_t value) const {
ASSERT(0 <= value);
ASSERT(value <= Smi::kMaxValue);
SetCountAt(index, Utils::Minimum(GetCountAt(index) + value, Smi::kMaxValue));
}
void ICData::SetCountAt(intptr_t index, intptr_t value) const {
ASSERT(0 <= value);
ASSERT(value <= Smi::kMaxValue);
Thread* thread = Thread::Current();
REUSABLE_ARRAY_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
data = entries();
const intptr_t data_pos =
index * TestEntryLength() + CountIndexFor(NumArgsTested());
data.SetAt(data_pos, Smi::Handle(Smi::New(value)));
}
intptr_t ICData::GetCountAt(intptr_t index) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
return 0;
#else
Thread* thread = Thread::Current();
REUSABLE_ARRAY_HANDLESCOPE(thread);
Array& data = thread->ArrayHandle();
data = entries();
const intptr_t data_pos =
index * TestEntryLength() + CountIndexFor(NumArgsTested());
intptr_t value = Smi::Value(Smi::RawCast(data.At(data_pos)));
if (value >= 0) return value;
// The counter very rarely overflows to a negative value, but if it does, we
// would rather just reset it to zero.
SetCountAt(index, 0);
return 0;
#endif
}
intptr_t ICData::AggregateCount() const {
if (IsNull()) return 0;
const intptr_t len = NumberOfChecks();
intptr_t count = 0;
for (intptr_t i = 0; i < len; i++) {
count += GetCountAt(i);
}
return count;
}
#if !defined(DART_PRECOMPILED_RUNTIME)
ICDataPtr ICData::AsUnaryClassChecksForArgNr(intptr_t arg_nr) const {
ASSERT(!IsNull());
ASSERT(NumArgsTested() > arg_nr);
if ((arg_nr == 0) && (NumArgsTested() == 1)) {
// Frequent case.
return ptr();
}
const intptr_t kNumArgsTested = 1;
ICData& result = ICData::Handle(ICData::NewFrom(*this, kNumArgsTested));
const intptr_t len = NumberOfChecks();
for (intptr_t i = 0; i < len; i++) {
const intptr_t class_id = GetClassIdAt(i, arg_nr);
const intptr_t count = GetCountAt(i);
if (count == 0) {
continue;
}
intptr_t duplicate_class_id = -1;
const intptr_t result_len = result.NumberOfChecks();
for (intptr_t k = 0; k < result_len; k++) {
if (class_id == result.GetReceiverClassIdAt(k)) {
duplicate_class_id = k;
break;
}
}
if (duplicate_class_id >= 0) {
// This check is valid only when checking the receiver.
ASSERT((arg_nr != 0) ||
(result.GetTargetAt(duplicate_class_id) == GetTargetAt(i)));
result.IncrementCountAt(duplicate_class_id, count);
} else {
// This will make sure that Smi is first if it exists.
result.AddReceiverCheckInternal(class_id,
Function::Handle(GetTargetAt(i)), count,
StaticTypeExactnessState::NotTracking());
}
}
return result.ptr();
}
// (cid, count) tuple used to sort ICData by count.
struct CidCount {
CidCount(intptr_t cid_, intptr_t count_, Function* f_)
: cid(cid_), count(count_), function(f_) {}
static int HighestCountFirst(const CidCount* a, const CidCount* b);
intptr_t cid;
intptr_t count;
Function* function;
};
int CidCount::HighestCountFirst(const CidCount* a, const CidCount* b) {
if (a->count > b->count) {
return -1;
}
return (a->count < b->count) ? 1 : 0;
}
ICDataPtr ICData::AsUnaryClassChecksSortedByCount() const {
ASSERT(!IsNull());
const intptr_t kNumArgsTested = 1;
const intptr_t len = NumberOfChecks();
if (len <= 1) {
// No sorting needed.
return AsUnaryClassChecks();
}
GrowableArray<CidCount> aggregate;
for (intptr_t i = 0; i < len; i++) {
const intptr_t class_id = GetClassIdAt(i, 0);
const intptr_t count = GetCountAt(i);
if (count == 0) {
continue;
}
bool found = false;
for (intptr_t r = 0; r < aggregate.length(); r++) {
if (aggregate[r].cid == class_id) {
aggregate[r].count += count;
found = true;
break;
}
}
if (!found) {
aggregate.Add(
CidCount(class_id, count, &Function::ZoneHandle(GetTargetAt(i))));
}
}
aggregate.Sort(CidCount::HighestCountFirst);
ICData& result = ICData::Handle(ICData::NewFrom(*this, kNumArgsTested));
ASSERT(result.NumberOfChecksIs(0));
// Room for all entries and the sentinel.
const intptr_t data_len = result.TestEntryLength() * (aggregate.length() + 1);
// Allocate the array but do not assign it to result until we have populated
// it with the aggregate data and the terminating sentinel.
const Array& data = Array::Handle(Array::New(data_len, Heap::kOld));
intptr_t pos = 0;
for (intptr_t i = 0; i < aggregate.length(); i++) {
data.SetAt(pos + 0, Smi::Handle(Smi::New(aggregate[i].cid)));
data.SetAt(pos + TargetIndexFor(1), *aggregate[i].function);
data.SetAt(pos + CountIndexFor(1),
Smi::Handle(Smi::New(aggregate[i].count)));
pos += result.TestEntryLength();
}
WriteSentinel(data, result.TestEntryLength());
result.set_entries(data);
ASSERT(result.NumberOfChecksIs(aggregate.length()));
return result.ptr();
}
UnlinkedCallPtr ICData::AsUnlinkedCall() const {
ASSERT(NumArgsTested() == 1);
ASSERT(!is_tracking_exactness());
const UnlinkedCall& result = UnlinkedCall::Handle(UnlinkedCall::New());
result.set_target_name(String::Handle(target_name()));
result.set_arguments_descriptor(Array::Handle(arguments_descriptor()));
result.set_can_patch_to_monomorphic(!FLAG_precompiled_mode ||
receiver_cannot_be_smi());
return result.ptr();
}
bool ICData::HasReceiverClassId(intptr_t class_id) const {
ASSERT(NumArgsTested() > 0);
const intptr_t len = NumberOfChecks();
for (intptr_t i = 0; i < len; i++) {
if (IsUsedAt(i)) {
const intptr_t test_class_id = GetReceiverClassIdAt(i);
if (test_class_id == class_id) {
return true;
}
}
}
return false;
}
#endif
bool ICData::IsUsedAt(intptr_t i) const {
if (GetCountAt(i) <= 0) {
// Do not mistake unoptimized static call ICData for unused.
// See ICData::AddTarget.
// TODO(srdjan): Make this test more robust.
if (NumArgsTested() > 0) {
const intptr_t cid = GetReceiverClassIdAt(i);
if (cid == kObjectCid) {
return true;
}
}
return false;
}
return true;
}
void ICData::Init() {
for (int i = 0; i <= kCachedICDataMaxArgsTestedWithoutExactnessTracking;
i++) {
cached_icdata_arrays_
[kCachedICDataZeroArgTestedWithoutExactnessTrackingIdx + i] =
ICData::NewNonCachedEmptyICDataArray(i, false);
}
cached_icdata_arrays_[kCachedICDataOneArgWithExactnessTrackingIdx] =
ICData::NewNonCachedEmptyICDataArray(1, true);
}
void ICData::Cleanup() {
for (int i = 0; i < kCachedICDataArrayCount; ++i) {
cached_icdata_arrays_[i] = NULL;
}
}
ArrayPtr ICData::NewNonCachedEmptyICDataArray(intptr_t num_args_tested,
bool tracking_exactness) {
// IC data array must be null terminated (sentinel entry).
const intptr_t len = TestEntryLengthFor(num_args_tested, tracking_exactness);
const Array& array = Array::Handle(Array::New(len, Heap::kOld));
WriteSentinel(array, len);
array.MakeImmutable();
return array.ptr();
}
ArrayPtr ICData::CachedEmptyICDataArray(intptr_t num_args_tested,
bool tracking_exactness) {
if (tracking_exactness) {
ASSERT(num_args_tested == 1);
return cached_icdata_arrays_[kCachedICDataOneArgWithExactnessTrackingIdx];
} else {
ASSERT(num_args_tested >= 0);
ASSERT(num_args_tested <=
kCachedICDataMaxArgsTestedWithoutExactnessTracking);
return cached_icdata_arrays_
[kCachedICDataZeroArgTestedWithoutExactnessTrackingIdx +
num_args_tested];
}
}
// Does not initialize ICData array.
ICDataPtr ICData::NewDescriptor(Zone* zone,
const Function& owner,
const String& target_name,
const Array& arguments_descriptor,
intptr_t deopt_id,
intptr_t num_args_tested,
RebindRule rebind_rule,
const AbstractType& receivers_static_type) {
#if !defined(DART_PRECOMPILED_RUNTIME)
// We should only have null owners in the precompiled runtime, if the
// owning function for a Code object was optimized out.
ASSERT(!owner.IsNull());
#endif
ASSERT(!target_name.IsNull());
ASSERT(!arguments_descriptor.IsNull());
ASSERT(Object::icdata_class() != Class::null());
ASSERT(num_args_tested >= 0);
ICData& result = ICData::Handle(zone);
{
// IC data objects are long living objects, allocate them in old generation.
ObjectPtr raw = Object::Allocate(ICData::kClassId, ICData::InstanceSize(),
Heap::kOld, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_owner(owner);
result.set_target_name(target_name);
result.set_arguments_descriptor(arguments_descriptor);
NOT_IN_PRECOMPILED(result.set_deopt_id(deopt_id));
result.set_state_bits(0);
result.set_rebind_rule(rebind_rule);
result.SetNumArgsTested(num_args_tested);
NOT_IN_PRECOMPILED(result.SetReceiversStaticType(receivers_static_type));
return result.ptr();
}
bool ICData::IsImmutable() const {
return entries()->IsImmutableArray();
}
ICDataPtr ICData::New() {
ICData& result = ICData::Handle();
{
// IC data objects are long living objects, allocate them in old generation.
ObjectPtr raw = Object::Allocate(ICData::kClassId, ICData::InstanceSize(),
Heap::kOld, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_deopt_id(DeoptId::kNone);
result.set_state_bits(0);
return result.ptr();
}
ICDataPtr ICData::New(const Function& owner,
const String& target_name,
const Array& arguments_descriptor,
intptr_t deopt_id,
intptr_t num_args_tested,
RebindRule rebind_rule,
const AbstractType& receivers_static_type) {
Zone* zone = Thread::Current()->zone();
const ICData& result = ICData::Handle(
zone,
NewDescriptor(zone, owner, target_name, arguments_descriptor, deopt_id,
num_args_tested, rebind_rule, receivers_static_type));
result.set_entries(Array::Handle(
zone,
CachedEmptyICDataArray(num_args_tested, result.is_tracking_exactness())));
return result.ptr();
}
ICDataPtr ICData::NewWithCheck(const Function& owner,
const String& target_name,
const Array& arguments_descriptor,
intptr_t deopt_id,
intptr_t num_args_tested,
RebindRule rebind_rule,
GrowableArray<intptr_t>* cids,
const Function& target,
const AbstractType& receiver_type) {
ASSERT((cids != nullptr) && !target.IsNull());
ASSERT(cids->length() == num_args_tested);
Zone* zone = Thread::Current()->zone();
const auto& result = ICData::Handle(
zone,
NewDescriptor(zone, owner, target_name, arguments_descriptor, deopt_id,
num_args_tested, rebind_rule, receiver_type));
const intptr_t kNumEntries = 2; // 1 entry and a sentinel.
const intptr_t entry_len =
TestEntryLengthFor(num_args_tested, result.is_tracking_exactness());
const auto& array =
Array::Handle(zone, Array::New(kNumEntries * entry_len, Heap::kOld));
auto& cid = Smi::Handle(zone);
for (intptr_t i = 0; i < num_args_tested; ++i) {
cid = Smi::New((*cids)[i]);
array.SetAt(i, cid);
}
SetTargetAtPos(array, 0, num_args_tested, target);
#if !defined(DART_PRECOMPILED_RUNTIME)
array.SetAt(CountIndexFor(num_args_tested), Object::smi_zero());
#endif
WriteSentinel(array, entry_len);
result.set_entries(array);
return result.ptr();
}
ICDataPtr ICData::NewForStaticCall(const Function& owner,
const Function& target,
const Array& arguments_descriptor,
intptr_t deopt_id,
intptr_t num_args_tested,
RebindRule rebind_rule) {
// See `MethodRecognizer::NumArgsCheckedForStaticCall`.
ASSERT(num_args_tested == 0 || num_args_tested == 2);
ASSERT(!target.IsNull());
Zone* zone = Thread::Current()->zone();
const auto& target_name = String::Handle(zone, target.name());
GrowableArray<intptr_t> cids(num_args_tested);
if (num_args_tested == 2) {
cids.Add(kObjectCid);
cids.Add(kObjectCid);
}
return ICData::NewWithCheck(owner, target_name, arguments_descriptor,
deopt_id, num_args_tested, rebind_rule, &cids,
target, Object::null_abstract_type());
}
#if !defined(DART_PRECOMPILED_RUNTIME)
ICDataPtr ICData::NewFrom(const ICData& from, intptr_t num_args_tested) {
// See comment in [ICData::Clone] why we access the megamorphic bit first.
const bool is_megamorphic = from.is_megamorphic();
const ICData& result = ICData::Handle(ICData::New(
Function::Handle(from.Owner()), String::Handle(from.target_name()),
Array::Handle(from.arguments_descriptor()), from.deopt_id(),
num_args_tested, from.rebind_rule(),
AbstractType::Handle(from.receivers_static_type())));
// Copy deoptimization reasons.
result.SetDeoptReasons(from.DeoptReasons());
result.set_is_megamorphic(is_megamorphic);
return result.ptr();
}
ICDataPtr ICData::Clone(const ICData& from) {
Zone* zone = Thread::Current()->zone();
// We have to check the megamorphic bit before accessing the entries of the
// ICData to ensure all writes to the entries have been flushed and are
// visible at this point.
//
// This will allow us to maintain the invariant that if the megamorphic bit is
// set, the number of entries in the ICData have reached the limit.
const bool is_megamorphic = from.is_megamorphic();
const ICData& result = ICData::Handle(
zone, ICData::NewDescriptor(
zone, Function::Handle(zone, from.Owner()),
String::Handle(zone, from.target_name()),
Array::Handle(zone, from.arguments_descriptor()),
from.deopt_id(), from.NumArgsTested(), from.rebind_rule(),
AbstractType::Handle(zone, from.receivers_static_type())));
// Clone entry array.
const Array& from_array = Array::Handle(zone, from.entries());
const intptr_t len = from_array.Length();
const Array& cloned_array = Array::Handle(zone, Array::New(len, Heap::kOld));
Object& obj = Object::Handle(zone);
for (intptr_t i = 0; i < len; i++) {
obj = from_array.At(i);
cloned_array.SetAt(i, obj);
}
result.set_entries(cloned_array);
// Copy deoptimization reasons.
result.SetDeoptReasons(from.DeoptReasons());
result.set_is_megamorphic(is_megamorphic);
RELEASE_ASSERT(!is_megamorphic ||
result.NumberOfChecks() >= FLAG_max_polymorphic_checks);
return result.ptr();
}
#endif
const char* WeakSerializationReference::ToCString() const {
return Object::Handle(target()).ToCString();
}
WeakSerializationReferencePtr WeakSerializationReference::New(
const Object& target,
const Object& replacement) {
ASSERT(Object::weak_serialization_reference_class() != Class::null());
WeakSerializationReference& result = WeakSerializationReference::Handle();
{
ObjectPtr raw = Object::Allocate(WeakSerializationReference::kClassId,
WeakSerializationReference::InstanceSize(),
Heap::kOld, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.untag()->set_target(target.ptr());
result.untag()->set_replacement(replacement.ptr());
}
return result.ptr();
}
#if defined(INCLUDE_IL_PRINTER)
Code::Comments& Code::Comments::New(intptr_t count) {
Comments* comments;
if (count < 0 || count > (kIntptrMax / kNumberOfEntries)) {
// This should be caught before we reach here.
FATAL1("Fatal error in Code::Comments::New: invalid count %" Pd "\n",
count);
}
if (count == 0) {
comments = new Comments(Object::empty_array());
} else {
const Array& data =
Array::Handle(Array::New(count * kNumberOfEntries, Heap::kOld));
comments = new Comments(data);
}
return *comments;
}
intptr_t Code::Comments::Length() const {
if (comments_.IsNull()) {
return 0;
}
return comments_.Length() / kNumberOfEntries;
}
intptr_t Code::Comments::PCOffsetAt(intptr_t idx) const {
return Smi::Value(
Smi::RawCast(comments_.At(idx * kNumberOfEntries + kPCOffsetEntry)));
}
void Code::Comments::SetPCOffsetAt(intptr_t idx, intptr_t pc) {
comments_.SetAt(idx * kNumberOfEntries + kPCOffsetEntry,
Smi::Handle(Smi::New(pc)));
}
const char* Code::Comments::CommentAt(intptr_t idx) const {
string_ ^= comments_.At(idx * kNumberOfEntries + kCommentEntry);
return string_.ToCString();
}
void Code::Comments::SetCommentAt(intptr_t idx, const String& comment) {
comments_.SetAt(idx * kNumberOfEntries + kCommentEntry, comment);
}
Code::Comments::Comments(const Array& comments)
: comments_(comments), string_(String::Handle()) {}
#endif // defined(INCLUDE_IL_PRINTER)
const char* Code::EntryKindToCString(EntryKind kind) {
switch (kind) {
case EntryKind::kNormal:
return "Normal";
case EntryKind::kUnchecked:
return "Unchecked";
case EntryKind::kMonomorphic:
return "Monomorphic";
case EntryKind::kMonomorphicUnchecked:
return "MonomorphicUnchecked";
default:
UNREACHABLE();
return nullptr;
}
}
bool Code::ParseEntryKind(const char* str, EntryKind* out) {
if (strcmp(str, "Normal") == 0) {
*out = EntryKind::kNormal;
return true;
} else if (strcmp(str, "Unchecked") == 0) {
*out = EntryKind::kUnchecked;
return true;
} else if (strcmp(str, "Monomorphic") == 0) {
*out = EntryKind::kMonomorphic;
return true;
} else if (strcmp(str, "MonomorphicUnchecked") == 0) {
*out = EntryKind::kMonomorphicUnchecked;
return true;
}
return false;
}
LocalVarDescriptorsPtr Code::GetLocalVarDescriptors() const {
const LocalVarDescriptors& v = LocalVarDescriptors::Handle(var_descriptors());
if (v.IsNull()) {
ASSERT(!is_optimized());
const Function& f = Function::Handle(function());
ASSERT(!f.IsIrregexpFunction()); // Not yet implemented.
Compiler::ComputeLocalVarDescriptors(*this);
}
return var_descriptors();
}
void Code::set_owner(const Object& owner) const {
#if defined(DEBUG)
const auto& unwrapped_owner =
Object::Handle(WeakSerializationReference::Unwrap(owner));
ASSERT(unwrapped_owner.IsFunction() || unwrapped_owner.IsClass() ||
unwrapped_owner.IsAbstractType());
#endif
untag()->set_owner(owner.ptr());
}
void Code::set_state_bits(intptr_t bits) const {
StoreNonPointer(&untag()->state_bits_, bits);
}
void Code::set_is_optimized(bool value) const {
set_state_bits(OptimizedBit::update(value, untag()->state_bits_));
}
void Code::set_is_force_optimized(bool value) const {
set_state_bits(ForceOptimizedBit::update(value, untag()->state_bits_));
}
void Code::set_is_alive(bool value) const {
set_state_bits(AliveBit::update(value, untag()->state_bits_));
}
void Code::set_is_discarded(bool value) const {
set_state_bits(DiscardedBit::update(value, untag()->state_bits_));
}
void Code::set_compressed_stackmaps(const CompressedStackMaps& maps) const {
ASSERT(maps.IsOld());
untag()->set_compressed_stackmaps(maps.ptr());
}
#if !defined(DART_PRECOMPILED_RUNTIME)
intptr_t Code::num_variables() const {
ASSERT(!FLAG_precompiled_mode);
return Smi::Value(Smi::RawCast(untag()->catch_entry()));
}
void Code::set_num_variables(intptr_t num_variables) const {
ASSERT(!FLAG_precompiled_mode);
untag()->set_catch_entry(Smi::New(num_variables));
}
#endif
#if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER)
TypedDataPtr Code::catch_entry_moves_maps() const {
ASSERT(FLAG_precompiled_mode);
return TypedData::RawCast(untag()->catch_entry());
}
void Code::set_catch_entry_moves_maps(const TypedData& maps) const {
ASSERT(FLAG_precompiled_mode);
untag()->set_catch_entry(maps.ptr());
}
#endif
void Code::set_deopt_info_array(const Array& array) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
ASSERT(array.IsOld());
untag()->set_deopt_info_array(array.ptr());
#endif
}
void Code::set_static_calls_target_table(const Array& value) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
untag()->set_static_calls_target_table(value.ptr());
#endif
#if defined(DEBUG)
// Check that the table is sorted by pc offsets.
// FlowGraphCompiler::AddStaticCallTarget adds pc-offsets to the table while
// emitting assembly. This guarantees that every succeeding pc-offset is
// larger than the previously added one.
StaticCallsTable entries(value);
const intptr_t count = entries.Length();
for (intptr_t i = 0; i < count - 1; ++i) {
auto left = Smi::Value(entries[i].Get<kSCallTableKindAndOffset>());
auto right = Smi::Value(entries[i + 1].Get<kSCallTableKindAndOffset>());
ASSERT(OffsetField::decode(left) < OffsetField::decode(right));
}
#endif // DEBUG
}
ObjectPoolPtr Code::GetObjectPool() const {
#if defined(DART_PRECOMPILER) || defined(DART_PRECOMPILED_RUNTIME)
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
return IsolateGroup::Current()->object_store()->global_object_pool();
}
#endif
return object_pool();
}
bool Code::HasBreakpoint() const {
#if defined(PRODUCT)
return false;
#else
return IsolateGroup::Current()->debugger()->HasBreakpointInCode(*this);
#endif
}
TypedDataPtr Code::GetDeoptInfoAtPc(uword pc,
ICData::DeoptReasonId* deopt_reason,
uint32_t* deopt_flags) const {
#if defined(DART_PRECOMPILED_RUNTIME)
ASSERT(Dart::vm_snapshot_kind() == Snapshot::kFullAOT);
return TypedData::null();
#else
ASSERT(is_optimized());
const Instructions& instrs = Instructions::Handle(instructions());
uword code_entry = instrs.PayloadStart();
const Array& table = Array::Handle(deopt_info_array());
if (table.IsNull()) {
ASSERT(Dart::vm_snapshot_kind() == Snapshot::kFullAOT);
return TypedData::null();
}
// Linear search for the PC offset matching the target PC.
intptr_t length = DeoptTable::GetLength(table);
Smi& offset = Smi::Handle();
Smi& reason_and_flags = Smi::Handle();
TypedData& info = TypedData::Handle();
for (intptr_t i = 0; i < length; ++i) {
DeoptTable::GetEntry(table, i, &offset, &info, &reason_and_flags);
if (pc == (code_entry + offset.Value())) {
ASSERT(!info.IsNull());
*deopt_reason = DeoptTable::ReasonField::decode(reason_and_flags.Value());
*deopt_flags = DeoptTable::FlagsField::decode(reason_and_flags.Value());
return info.ptr();
}
}
*deopt_reason = ICData::kDeoptUnknown;
return TypedData::null();
#endif // defined(DART_PRECOMPILED_RUNTIME)
}
intptr_t Code::BinarySearchInSCallTable(uword pc) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
NoSafepointScope no_safepoint;
const Array& table = Array::Handle(untag()->static_calls_target_table());
StaticCallsTable entries(table);
const intptr_t pc_offset = pc - PayloadStart();
intptr_t imin = 0;
intptr_t imax = (table.Length() / kSCallTableEntryLength) - 1;
while (imax >= imin) {
const intptr_t imid = imin + (imax - imin) / 2;
const auto offset = OffsetField::decode(
Smi::Value(entries[imid].Get<kSCallTableKindAndOffset>()));
if (offset < pc_offset) {
imin = imid + 1;
} else if (offset > pc_offset) {
imax = imid - 1;
} else {
return imid;
}
}
#endif
return -1;
}
FunctionPtr Code::GetStaticCallTargetFunctionAt(uword pc) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
return Function::null();
#else
const intptr_t i = BinarySearchInSCallTable(pc);
if (i < 0) {
return Function::null();
}
const Array& array = Array::Handle(untag()->static_calls_target_table());
StaticCallsTable entries(array);
return entries[i].Get<kSCallTableFunctionTarget>();
#endif
}
void Code::SetStaticCallTargetCodeAt(uword pc, const Code& code) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
const intptr_t i = BinarySearchInSCallTable(pc);
ASSERT(i >= 0);
const Array& array = Array::Handle(untag()->static_calls_target_table());
StaticCallsTable entries(array);
ASSERT(code.IsNull() ||
(code.function() == entries[i].Get<kSCallTableFunctionTarget>()));
return entries[i].Set<kSCallTableCodeOrTypeTarget>(code);
#endif
}
void Code::SetStubCallTargetCodeAt(uword pc, const Code& code) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
const intptr_t i = BinarySearchInSCallTable(pc);
ASSERT(i >= 0);
const Array& array = Array::Handle(untag()->static_calls_target_table());
StaticCallsTable entries(array);
#if defined(DEBUG)
if (entries[i].Get<kSCallTableFunctionTarget>() == Function::null()) {
ASSERT(!code.IsNull() && Object::Handle(code.owner()).IsClass());
} else {
ASSERT(code.IsNull() ||
(code.function() == entries[i].Get<kSCallTableFunctionTarget>()));
}
#endif
return entries[i].Set<kSCallTableCodeOrTypeTarget>(code);
#endif
}
void Code::Disassemble(DisassemblyFormatter* formatter) const {
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
if (!FLAG_support_disassembler) {
return;
}
const uword start = PayloadStart();
if (formatter == NULL) {
Disassembler::Disassemble(start, start + Size(), *this);
} else {
Disassembler::Disassemble(start, start + Size(), formatter, *this);
}
#endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)
}
#if defined(INCLUDE_IL_PRINTER)
#if defined(PRODUCT)
// In PRODUCT builds we don't have space in Code object to store code comments
// so we move them into malloced heap (and leak them). This functionality
// is only indended to be used in AOT compiler so leaking is fine.
class MallocCodeComments final : public CodeComments {
public:
explicit MallocCodeComments(const CodeComments& comments)
: length_(comments.Length()), comments_(new Comment[comments.Length()]) {
for (intptr_t i = 0; i < length_; i++) {
comments_[i].pc_offset = comments.PCOffsetAt(i);
comments_[i].comment =
Utils::CreateCStringUniquePtr(Utils::StrDup(comments.CommentAt(i)));
}
}
intptr_t Length() const override { return length_; }
intptr_t PCOffsetAt(intptr_t i) const override {
return comments_[i].pc_offset;
}
const char* CommentAt(intptr_t i) const override {
return comments_[i].comment.get();
}
private:
struct Comment {
intptr_t pc_offset;
Utils::CStringUniquePtr comment{nullptr, std::free};
};
intptr_t length_;
std::unique_ptr<Comment[]> comments_;
};
#endif
const CodeComments& Code::comments() const {
#if defined(PRODUCT)
auto comments =
static_cast<CodeComments*>(Thread::Current()->heap()->GetPeer(ptr()));
return (comments != nullptr) ? *comments : Code::Comments::New(0);
#else
return *new Code::Comments(Array::Handle(untag()->comments()));
#endif
}
void Code::set_comments(const CodeComments& comments) const {
#if !defined(PRODUCT)
auto& wrapper = static_cast<const Code::Comments&>(comments);
ASSERT(wrapper.comments_.IsOld());
untag()->set_comments(wrapper.comments_.ptr());
#else
if (FLAG_code_comments && comments.Length() > 0) {
Thread::Current()->heap()->SetPeer(ptr(), new MallocCodeComments(comments));
} else {
Thread::Current()->heap()->SetPeer(ptr(), nullptr);
}
#endif
}
#endif // defined(INCLUDE_IL_PRINTER)
void Code::SetPrologueOffset(intptr_t offset) const {
#if defined(PRODUCT)
UNREACHABLE();
#else
ASSERT(offset >= 0);
untag()->set_return_address_metadata(Smi::New(offset));
#endif
}
intptr_t Code::GetPrologueOffset() const {
#if defined(PRODUCT)
UNREACHABLE();
return -1;
#else
const Object& object = Object::Handle(untag()->return_address_metadata());
// In the future we may put something other than a smi in
// |return_address_metadata_|.
if (object.IsNull() || !object.IsSmi()) {
return -1;
}
return Smi::Cast(object).Value();
#endif
}
ArrayPtr Code::inlined_id_to_function() const {
return untag()->inlined_id_to_function();
}
void Code::set_inlined_id_to_function(const Array& value) const {
ASSERT(value.IsOld());
untag()->set_inlined_id_to_function(value.ptr());
}
CodePtr Code::New(intptr_t pointer_offsets_length) {
if (pointer_offsets_length < 0 || pointer_offsets_length > kMaxElements) {
// This should be caught before we reach here.
FATAL1("Fatal error in Code::New: invalid pointer_offsets_length %" Pd "\n",
pointer_offsets_length);
}
ASSERT(Object::code_class() != Class::null());
Code& result = Code::Handle();
{
uword size = Code::InstanceSize(pointer_offsets_length);
ObjectPtr raw = Object::Allocate(Code::kClassId, size, Heap::kOld,
/*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
result.set_pointer_offsets_length(pointer_offsets_length);
result.set_is_optimized(false);
result.set_is_force_optimized(false);
result.set_is_alive(false);
#if defined(INCLUDE_IL_PRINTER)
result.set_comments(Comments::New(0));
#endif
NOT_IN_PRODUCT(result.set_compile_timestamp(0));
result.set_pc_descriptors(Object::empty_descriptors());
result.set_compressed_stackmaps(Object::empty_compressed_stackmaps());
}
return result.ptr();
}
#if !defined(DART_PRECOMPILED_RUNTIME)
CodePtr Code::FinalizeCodeAndNotify(const Function& function,
FlowGraphCompiler* compiler,
compiler::Assembler* assembler,
PoolAttachment pool_attachment,
bool optimized,
CodeStatistics* stats) {
auto thread = Thread::Current();
ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
const auto& code = Code::Handle(
FinalizeCode(compiler, assembler, pool_attachment, optimized, stats));
NotifyCodeObservers(function, code, optimized);
return code.ptr();
}
CodePtr Code::FinalizeCodeAndNotify(const char* name,
FlowGraphCompiler* compiler,
compiler::Assembler* assembler,
PoolAttachment pool_attachment,
bool optimized,
CodeStatistics* stats) {
auto thread = Thread::Current();
ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
const auto& code = Code::Handle(
FinalizeCode(compiler, assembler, pool_attachment, optimized, stats));
NotifyCodeObservers(name, code, optimized);
return code.ptr();
}
#if defined(DART_PRECOMPILER)
DECLARE_FLAG(charp, write_v8_snapshot_profile_to);
DECLARE_FLAG(charp, trace_precompiler_to);
#endif // defined(DART_PRECOMPILER)
CodePtr Code::FinalizeCode(FlowGraphCompiler* compiler,
compiler::Assembler* assembler,
PoolAttachment pool_attachment,
bool optimized,
CodeStatistics* stats /* = nullptr */) {
auto thread = Thread::Current();
ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter());
ASSERT(assembler != NULL);
ObjectPool& object_pool = ObjectPool::Handle();
if (pool_attachment == PoolAttachment::kAttachPool) {
if (assembler->HasObjectPoolBuilder()) {
object_pool =
ObjectPool::NewFromBuilder(assembler->object_pool_builder());
} else {
object_pool = ObjectPool::empty_object_pool().ptr();
}
} else {
#if defined(DART_PRECOMPILER)
if (assembler->HasObjectPoolBuilder() &&
assembler->object_pool_builder().HasParent()) {
// We are not going to write this pool into snapshot, but we will use
// it to emit references from this code object to other objects in the
// snapshot that it uses.
object_pool =
ObjectPool::NewFromBuilder(assembler->object_pool_builder());
}
#endif // defined(DART_PRECOMPILER)
}
// Allocate the Code and Instructions objects. Code is allocated first
// because a GC during allocation of the code will leave the instruction
// pages read-only.
intptr_t pointer_offset_count = assembler->CountPointerOffsets();
Code& code = Code::ZoneHandle(Code::New(pointer_offset_count));
#ifdef TARGET_ARCH_IA32
assembler->GetSelfHandle() = code.ptr();
#endif
Instructions& instrs = Instructions::ZoneHandle(Instructions::New(
assembler->CodeSize(), assembler->has_monomorphic_entry()));
{
// Important: if GC is triggerred at any point between Instructions::New
// and here it would write protect instructions object that we are trying
// to fill in.
NoSafepointScope no_safepoint;
// Copy the instructions into the instruction area and apply all fixups.
// Embedded pointers are still in handles at this point.
MemoryRegion region(reinterpret_cast<void*>(instrs.PayloadStart()),
instrs.Size());
assembler->FinalizeInstructions(region);
const auto& pointer_offsets = assembler->GetPointerOffsets();
ASSERT(pointer_offsets.length() == pointer_offset_count);
ASSERT(code.pointer_offsets_length() == pointer_offsets.length());
// Set pointer offsets list in Code object and resolve all handles in
// the instruction stream to raw objects.
for (intptr_t i = 0; i < pointer_offsets.length(); i++) {
intptr_t offset_in_instrs = pointer_offsets[i];
code.SetPointerOffsetAt(i, offset_in_instrs);
uword addr = region.start() + offset_in_instrs;
ASSERT(instrs.PayloadStart() <= addr);
ASSERT((instrs.PayloadStart() + instrs.Size()) > addr);
const Object* object = LoadUnaligned(reinterpret_cast<Object**>(addr));
ASSERT(object->IsOld());
// N.B. The pointer is embedded in the Instructions object, but visited
// through the Code object.
code.ptr()->untag()->StorePointerUnaligned(
reinterpret_cast<ObjectPtr*>(addr), object->ptr(), thread);
}
// Write protect instructions and, if supported by OS, use dual mapping
// for execution.
if (FLAG_write_protect_code) {
uword address = UntaggedObject::ToAddr(instrs.ptr());
// Check if a dual mapping exists.
instrs = Instructions::RawCast(OldPage::ToExecutable(instrs.ptr()));
uword exec_address = UntaggedObject::ToAddr(instrs.ptr());
const bool use_dual_mapping = exec_address != address;
ASSERT(use_dual_mapping == FLAG_dual_map_code);
// When dual mapping is enabled the executable mapping is RX from the
// point of allocation and never changes protection.
// Yet the writable mapping is still turned back from RW to R.
if (use_dual_mapping) {
VirtualMemory::Protect(reinterpret_cast<void*>(address),
instrs.ptr()->untag()->HeapSize(),
VirtualMemory::kReadOnly);
address = exec_address;
} else {
// If dual mapping is disabled and we write protect then we have to
// change the single mapping from RW -> RX.
VirtualMemory::Protect(reinterpret_cast<void*>(address),
instrs.ptr()->untag()->HeapSize(),
VirtualMemory::kReadExecute);
}
}
// Hook up Code and Instructions objects.
const uword unchecked_offset = assembler->UncheckedEntryOffset();
code.SetActiveInstructions(instrs, unchecked_offset);
code.set_instructions(instrs);
NOT_IN_PRECOMPILED(code.set_unchecked_offset(unchecked_offset));
code.set_is_alive(true);
// Set object pool in Instructions object.
if (!object_pool.IsNull()) {
code.set_object_pool(object_pool.ptr());
}
#if defined(DART_PRECOMPILER)
if (stats != nullptr) {
stats->Finalize();
instrs.set_stats(stats);
}
#endif
CPU::FlushICache(instrs.PayloadStart(), instrs.Size());
}
#if defined(INCLUDE_IL_PRINTER)
code.set_comments(CreateCommentsFrom(assembler));
#endif // defined(INCLUDE_IL_PRINTER)
#ifndef PRODUCT
code.set_compile_timestamp(OS::GetCurrentMonotonicMicros());
if (assembler->prologue_offset() >= 0) {
code.SetPrologueOffset(assembler->prologue_offset());
} else {
// No prologue was ever entered, optimistically assume nothing was ever
// pushed onto the stack.
code.SetPrologueOffset(assembler->CodeSize());
}
#endif
return code.ptr();
}
void Code::NotifyCodeObservers(const Code& code, bool optimized) {
#if !defined(PRODUCT)
ASSERT(!Thread::Current()->IsAtSafepoint());
if (CodeObservers::AreActive()) {
if (code.IsFunctionCode()) {
const auto& function = Function::Handle(code.function());
if (!function.IsNull()) {
return NotifyCodeObservers(function, code, optimized);
}
}
NotifyCodeObservers(code.Name(), code, optimized);
}
#endif
}
void Code::NotifyCodeObservers(const Function& function,
const Code& code,
bool optimized) {
#if !defined(PRODUCT)
ASSERT(!function.IsNull());
ASSERT(!Thread::Current()->IsAtSafepoint());
// Calling ToLibNamePrefixedQualifiedCString is very expensive,
// try to avoid it.
if (CodeObservers::AreActive()) {
const char* name = function.ToLibNamePrefixedQualifiedCString();
NotifyCodeObservers(name, code, optimized);
}
#endif
}
void Code::NotifyCodeObservers(const char* name,
const Code& code,
bool optimized) {
#if !defined(PRODUCT)
ASSERT(name != nullptr);
ASSERT(!code.IsNull());
ASSERT(!Thread::Current()->IsAtSafepoint());
if (CodeObservers::AreActive()) {
const auto& instrs = Instructions::Handle(code.instructions());
CodeObservers::NotifyAll(name, instrs.PayloadStart(),
code.GetPrologueOffset(), instrs.Size(), optimized,
&code.comments());
}
#endif
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
bool Code::SlowFindRawCodeVisitor::FindObject(ObjectPtr raw_obj) const {
return UntaggedCode::ContainsPC(raw_obj, pc_) &&
!Code::IsUnknownDartCode(Code::RawCast(raw_obj));
}
CodePtr Code::LookupCodeInIsolateGroup(IsolateGroup* isolate_group, uword pc) {
ASSERT((isolate_group == IsolateGroup::Current()) ||
(isolate_group == Dart::vm_isolate_group()));
if (isolate_group->heap() == NULL) {
return Code::null();
}
HeapIterationScope heap_iteration_scope(Thread::Current());
SlowFindRawCodeVisitor visitor(pc);
ObjectPtr needle = isolate_group->heap()->FindOldObject(&visitor);
if (needle != Code::null()) {
return static_cast<CodePtr>(needle);
}
return Code::null();
}
CodePtr Code::LookupCode(uword pc) {
return LookupCodeInIsolateGroup(IsolateGroup::Current(), pc);
}
CodePtr Code::LookupCodeInVmIsolate(uword pc) {
return LookupCodeInIsolateGroup(Dart::vm_isolate_group(), pc);
}
// Given a pc and a timestamp, lookup the code.
CodePtr Code::FindCode(uword pc, int64_t timestamp) {
Code& code = Code::Handle(Code::LookupCode(pc));
if (!code.IsNull() && (code.compile_timestamp() == timestamp) &&
(code.PayloadStart() == pc)) {
// Found code in isolate.
return code.ptr();
}
code = Code::LookupCodeInVmIsolate(pc);
if (!code.IsNull() && (code.compile_timestamp() == timestamp) &&
(code.PayloadStart() == pc)) {
// Found code in VM isolate.
return code.ptr();
}
return Code::null();
}
TokenPosition Code::GetTokenIndexOfPC(uword pc) const {
uword pc_offset = pc - PayloadStart();
const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors());
PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kAnyKind);
while (iter.MoveNext()) {
if (iter.PcOffset() == pc_offset) {
return iter.TokenPos();
}
}
return TokenPosition::kNoSource;
}
uword Code::GetPcForDeoptId(intptr_t deopt_id,
UntaggedPcDescriptors::Kind kind) const {
const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors());
PcDescriptors::Iterator iter(descriptors, kind);
while (iter.MoveNext()) {
if (iter.DeoptId() == deopt_id) {
uword pc_offset = iter.PcOffset();
uword pc = PayloadStart() + pc_offset;
ASSERT(ContainsInstructionAt(pc));
return pc;
}
}
return 0;
}
intptr_t Code::GetDeoptIdForOsr(uword pc) const {
uword pc_offset = pc - PayloadStart();
const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors());
PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kOsrEntry);
while (iter.MoveNext()) {
if (iter.PcOffset() == pc_offset) {
return iter.DeoptId();
}
}
return DeoptId::kNone;
}
const char* Code::ToCString() const {
return OS::SCreate(Thread::Current()->zone(), "Code(%s)",
QualifiedName(NameFormattingParams(
kScrubbedName, NameDisambiguation::kYes)));
}
const char* Code::Name() const {
Zone* zone = Thread::Current()->zone();
if (IsStubCode()) {
// Regular stub.
const char* name = StubCode::NameOfStub(EntryPoint());
if (name == NULL) {
return "[unknown stub]"; // Not yet recorded.
}
return OS::SCreate(zone, "[Stub] %s", name);
}
const auto& obj =
Object::Handle(zone, WeakSerializationReference::UnwrapIfTarget(owner()));
if (obj.IsClass()) {
// Allocation stub.
return OS::SCreate(zone, "[Stub] Allocate %s",
Class::Cast(obj).ScrubbedNameCString());
} else if (obj.IsAbstractType()) {
// Type test stub.
return OS::SCreate(zone, "[Stub] Type Test %s",
AbstractType::Cast(obj).ToCString());
} else {
ASSERT(IsFunctionCode());
// Dart function.
const char* opt = is_optimized() ? "[Optimized]" : "[Unoptimized]";
const char* function_name =
obj.IsFunction()
? String::Handle(zone, Function::Cast(obj).UserVisibleName())
.ToCString()
: WeakSerializationReference::Cast(obj).ToCString();
return OS::SCreate(zone, "%s %s", opt, function_name);
}
}
const char* Code::QualifiedName(const NameFormattingParams& params) const {
Zone* zone = Thread::Current()->zone();
const Object& obj =
Object::Handle(zone, WeakSerializationReference::UnwrapIfTarget(owner()));
if (obj.IsFunction()) {
ZoneTextBuffer printer(zone);
printer.AddString(is_optimized() ? "[Optimized] " : "[Unoptimized] ");
Function::Cast(obj).PrintName(params, &printer);
return printer.buffer();
}
return Name();
}
bool Code::IsStubCode() const {
// We should _not_ unwrap any possible WSRs here, as the null value is never
// wrapped by a WSR.
return owner() == Object::null();
}
bool Code::IsAllocationStubCode() const {
return OwnerClassId() == kClassCid;
}
bool Code::IsTypeTestStubCode() const {
auto const cid = OwnerClassId();
return cid == kAbstractTypeCid || cid == kTypeCid ||
cid == kFunctionTypeCid || cid == kTypeRefCid ||
cid == kTypeParameterCid;
}
bool Code::IsFunctionCode() const {
return OwnerClassId() == kFunctionCid;
}
bool Code::IsUnknownDartCode(CodePtr code) {
return code == StubCode::UnknownDartCode().ptr();
}
void Code::DisableDartCode() const {
SafepointOperationScope safepoint(Thread::Current());
ASSERT(IsFunctionCode());
ASSERT(instructions() == active_instructions());
const Code& new_code = StubCode::FixCallersTarget();
SetActiveInstructions(Instructions::Handle(new_code.instructions()),
new_code.UncheckedEntryPointOffset());
}
void Code::DisableStubCode() const {
SafepointOperationScope safepoint(Thread::Current());
ASSERT(IsAllocationStubCode());
ASSERT(instructions() == active_instructions());
const Code& new_code = StubCode::FixAllocationStubTarget();
SetActiveInstructions(Instructions::Handle(new_code.instructions()),
new_code.UncheckedEntryPointOffset());
}
void Code::InitializeCachedEntryPointsFrom(CodePtr code,
InstructionsPtr instructions,
uint32_t unchecked_offset) {
NoSafepointScope _;
const uword entry_point = Instructions::EntryPoint(instructions);
const uword monomorphic_entry_point =
Instructions::MonomorphicEntryPoint(instructions);
code->untag()->entry_point_ = entry_point;
code->untag()->monomorphic_entry_point_ = monomorphic_entry_point;
code->untag()->unchecked_entry_point_ = entry_point + unchecked_offset;
code->untag()->monomorphic_unchecked_entry_point_ =
monomorphic_entry_point + unchecked_offset;
}
void Code::SetActiveInstructions(const Instructions& instructions,
uint32_t unchecked_offset) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
SetActiveInstructionsSafe(instructions, unchecked_offset);
#endif
}
void Code::SetActiveInstructionsSafe(const Instructions& instructions,
uint32_t unchecked_offset) const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
// RawInstructions are never allocated in New space and hence a
// store buffer update is not needed here.
untag()->set_active_instructions(instructions.ptr());
Code::InitializeCachedEntryPointsFrom(ptr(), instructions.ptr(),
unchecked_offset);
#endif
}
void Code::ResetActiveInstructions() const {
#if defined(DART_PRECOMPILED_RUNTIME)
UNREACHABLE();
#else
SetActiveInstructions(Instructions::Handle(instructions()),
untag()->unchecked_offset_);
#endif
}
void Code::GetInlinedFunctionsAtInstruction(
intptr_t pc_offset,
GrowableArray<const Function*>* functions,
GrowableArray<TokenPosition>* token_positions) const {
const CodeSourceMap& map = CodeSourceMap::Handle(code_source_map());
if (map.IsNull()) {
ASSERT(!IsFunctionCode());
return; // VM stub, allocation stub, or type testing stub.
}
const Array& id_map = Array::Handle(inlined_id_to_function());
const Function& root = Function::Handle(function());
CodeSourceMapReader reader(map, id_map, root);
reader.GetInlinedFunctionsAt(pc_offset, functions, token_positions);
}
#ifndef PRODUCT
void Code::PrintJSONInlineIntervals(JSONObject* jsobj) const {
if (!is_optimized()) {
return; // No inlining.
}
const CodeSourceMap& map = CodeSourceMap::Handle(code_source_map());
const Array& id_map = Array::Handle(inlined_id_to_function());
const Function& root = Function::Handle(function());
CodeSourceMapReader reader(map, id_map, root);
reader.PrintJSONInlineIntervals(jsobj);
}
#endif
void Code::DumpInlineIntervals() const {
const CodeSourceMap& map = CodeSourceMap::Handle(code_source_map());
if (map.IsNull()) {
// Stub code.
return;
}
const Array& id_map = Array::Handle(inlined_id_to_function());
const Function& root = Function::Handle(function());
CodeSourceMapReader reader(map, id_map, root);
reader.DumpInlineIntervals(PayloadStart());
}
void Code::DumpSourcePositions(bool relative_addresses) const {
const CodeSourceMap& map = CodeSourceMap::Handle(code_source_map());
if (map.IsNull()) {
// Stub code.
return;
}
const Array& id_map = Array::Handle(inlined_id_to_function());
const Function& root = Function::Handle(function());
CodeSourceMapReader reader(map, id_map, root);
reader.DumpSourcePositions(relative_addresses ? 0 : PayloadStart());
}
intptr_t Context::GetLevel() const {
intptr_t level = 0;
Context& parent_ctx = Context::Handle(parent());
while (!parent_ctx.IsNull()) {
level++;
parent_ctx = parent_ctx.parent();
}
return level;
}
ContextPtr Context::New(intptr_t num_variables, Heap::Space space) {
ASSERT(num_variables >= 0);
ASSERT(Object::context_class() != Class::null());
if (!IsValidLength(num_variables)) {
// This should be caught before we reach here.
FATAL1("Fatal error in Context::New: invalid num_variables %" Pd "\n",
num_variables);
}
Context& result = Context::Handle();
{
ObjectPtr raw = Object::Allocate(Context::kClassId,
Context::InstanceSize(num_variables),
space, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
result.set_num_variables(num_variables);
}
return result.ptr();
}
const char* Context::ToCString() const {
if (IsNull()) {
return "Context: null";
}
Zone* zone = Thread::Current()->zone();
const Context& parent_ctx = Context::Handle(parent());
if (parent_ctx.IsNull()) {
return zone->PrintToString("Context num_variables: %" Pd "",
num_variables());
} else {
const char* parent_str = parent_ctx.ToCString();
return zone->PrintToString("Context num_variables: %" Pd " parent:{ %s }",
num_variables(), parent_str);
}
}
static void IndentN(int count) {
for (int i = 0; i < count; i++) {
THR_Print(" ");
}
}
void Context::Dump(int indent) const {
if (IsNull()) {
IndentN(indent);
THR_Print("Context@null\n");
return;
}
IndentN(indent);
THR_Print("Context vars(%" Pd ") {\n", num_variables());
Object& obj = Object::Handle();
for (intptr_t i = 0; i < num_variables(); i++) {
IndentN(indent + 2);
obj = At(i);
const char* s = obj.ToCString();
if (strlen(s) > 50) {
THR_Print("[%" Pd "] = [first 50 chars:] %.50s...\n", i, s);
} else {
THR_Print("[%" Pd "] = %s\n", i, s);
}
}
const Context& parent_ctx = Context::Handle(parent());
if (!parent_ctx.IsNull()) {
parent_ctx.Dump(indent + 2);
}
IndentN(indent);
THR_Print("}\n");
}
ContextScopePtr ContextScope::New(intptr_t num_variables, bool is_implicit) {
ASSERT(Object::context_scope_class() != Class::null());
if (num_variables < 0 || num_variables > kMaxElements) {
// This should be caught before we reach here.
FATAL1("Fatal error in ContextScope::New: invalid num_variables %" Pd "\n",
num_variables);
}
intptr_t size = ContextScope::InstanceSize(num_variables);
ContextScope& result = ContextScope::Handle();
{
ObjectPtr raw = Object::Allocate(ContextScope::kClassId, size, Heap::kOld,
/*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.set_num_variables(num_variables);
result.set_is_implicit(is_implicit);
}
return result.ptr();
}
TokenPosition ContextScope::TokenIndexAt(intptr_t scope_index) const {
return TokenPosition::Deserialize(
Smi::Value(untag()->token_pos_at(scope_index)));
}
void ContextScope::SetTokenIndexAt(intptr_t scope_index,
TokenPosition token_pos) const {
untag()->set_token_pos_at(scope_index, Smi::New(token_pos.Serialize()));
}
TokenPosition ContextScope::DeclarationTokenIndexAt(
intptr_t scope_index) const {
return TokenPosition::Deserialize(
Smi::Value(untag()->declaration_token_pos_at(scope_index)));
}
void ContextScope::SetDeclarationTokenIndexAt(
intptr_t scope_index,
TokenPosition declaration_token_pos) const {
untag()->set_declaration_token_pos_at(
scope_index, Smi::New(declaration_token_pos.Serialize()));
}
StringPtr ContextScope::NameAt(intptr_t scope_index) const {
return untag()->name_at(scope_index);
}
void ContextScope::SetNameAt(intptr_t scope_index, const String& name) const {
untag()->set_name_at(scope_index, name.ptr());
}
void ContextScope::ClearFlagsAt(intptr_t scope_index) const {
untag()->set_flags_at(scope_index, Smi::New(0));
}
bool ContextScope::GetFlagAt(intptr_t scope_index, intptr_t mask) const {
return (Smi::Value(untag()->flags_at(scope_index)) & mask) != 0;
}
void ContextScope::SetFlagAt(intptr_t scope_index,
intptr_t mask,
bool value) const {
intptr_t flags = Smi::Value(untag()->flags_at(scope_index));
untag()->set_flags_at(scope_index,
Smi::New(value ? flags | mask : flags & ~mask));
}
bool ContextScope::IsFinalAt(intptr_t scope_index) const {
return GetFlagAt(scope_index, UntaggedContextScope::VariableDesc::kIsFinal);
}
void ContextScope::SetIsFinalAt(intptr_t scope_index, bool is_final) const {
SetFlagAt(scope_index, UntaggedContextScope::VariableDesc::kIsFinal,
is_final);
}
bool ContextScope::IsLateAt(intptr_t scope_index) const {
return GetFlagAt(scope_index, UntaggedContextScope::VariableDesc::kIsLate);
}
void ContextScope::SetIsLateAt(intptr_t scope_index, bool is_late) const {
SetFlagAt(scope_index, UntaggedContextScope::VariableDesc::kIsLate, is_late);
}
bool ContextScope::IsConstAt(intptr_t scope_index) const {
return GetFlagAt(scope_index, UntaggedContextScope::VariableDesc::kIsConst);
}
void ContextScope::SetIsConstAt(intptr_t scope_index, bool is_const) const {
SetFlagAt(scope_index, UntaggedContextScope::VariableDesc::kIsConst,
is_const);
}
intptr_t ContextScope::LateInitOffsetAt(intptr_t scope_index) const {
return Smi::Value(untag()->late_init_offset_at(scope_index));
}
void ContextScope::SetLateInitOffsetAt(intptr_t scope_index,
intptr_t late_init_offset) const {
untag()->set_late_init_offset_at(scope_index, Smi::New(late_init_offset));
}
AbstractTypePtr ContextScope::TypeAt(intptr_t scope_index) const {
ASSERT(!IsConstAt(scope_index));
return untag()->type_at(scope_index);
}
void ContextScope::SetTypeAt(intptr_t scope_index,
const AbstractType& type) const {
untag()->set_type_at(scope_index, type.ptr());
}
InstancePtr ContextScope::ConstValueAt(intptr_t scope_index) const {
ASSERT(IsConstAt(scope_index));
return untag()->value_at(scope_index);
}
void ContextScope::SetConstValueAt(intptr_t scope_index,
const Instance& value) const {
ASSERT(IsConstAt(scope_index));
untag()->set_value_at(scope_index, value.ptr());
}
intptr_t ContextScope::ContextIndexAt(intptr_t scope_index) const {
return Smi::Value(untag()->context_index_at(scope_index));
}
void ContextScope::SetContextIndexAt(intptr_t scope_index,
intptr_t context_index) const {
untag()->set_context_index_at(scope_index, Smi::New(context_index));
}
intptr_t ContextScope::ContextLevelAt(intptr_t scope_index) const {
return Smi::Value(untag()->context_level_at(scope_index));
}
void ContextScope::SetContextLevelAt(intptr_t scope_index,
intptr_t context_level) const {
untag()->set_context_level_at(scope_index, Smi::New(context_level));
}
const char* ContextScope::ToCString() const {
const char* prev_cstr = "ContextScope:";
String& name = String::Handle();
for (int i = 0; i < num_variables(); i++) {
name = NameAt(i);
const char* cname = name.ToCString();
TokenPosition pos = TokenIndexAt(i);
intptr_t idx = ContextIndexAt(i);
intptr_t lvl = ContextLevelAt(i);
char* chars =
OS::SCreate(Thread::Current()->zone(),
"%s\nvar %s token-pos %s ctx lvl %" Pd " index %" Pd "",
prev_cstr, cname, pos.ToCString(), lvl, idx);
prev_cstr = chars;
}
return prev_cstr;
}
ArrayPtr MegamorphicCache::buckets() const {
return untag()->buckets();
}
void MegamorphicCache::set_buckets(const Array& buckets) const {
untag()->set_buckets(buckets.ptr());
}
// Class IDs in the table are smi-tagged, so we use a smi-tagged mask
// and target class ID to avoid untagging (on each iteration of the
// test loop) in generated code.
intptr_t MegamorphicCache::mask() const {
return Smi::Value(untag()->mask());
}
void MegamorphicCache::set_mask(intptr_t mask) const {
untag()->set_mask(Smi::New(mask));
}
intptr_t MegamorphicCache::filled_entry_count() const {
return untag()->filled_entry_count_;
}
void MegamorphicCache::set_filled_entry_count(intptr_t count) const {
StoreNonPointer(&untag()->filled_entry_count_, count);
}
MegamorphicCachePtr MegamorphicCache::New() {
MegamorphicCache& result = MegamorphicCache::Handle();
{
ObjectPtr raw = Object::Allocate(MegamorphicCache::kClassId,
MegamorphicCache::InstanceSize(),
Heap::kOld, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_filled_entry_count(0);
return result.ptr();
}
MegamorphicCachePtr MegamorphicCache::New(const String& target_name,
const Array& arguments_descriptor) {
MegamorphicCache& result = MegamorphicCache::Handle();
{
ObjectPtr raw = Object::Allocate(MegamorphicCache::kClassId,
MegamorphicCache::InstanceSize(),
Heap::kOld, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
const intptr_t capacity = kInitialCapacity;
const Array& buckets =
Array::Handle(Array::New(kEntryLength * capacity, Heap::kOld));
const Object& handler = Object::Handle();
for (intptr_t i = 0; i < capacity; ++i) {
SetEntry(buckets, i, smi_illegal_cid(), handler);
}
result.set_buckets(buckets);
result.set_mask(capacity - 1);
result.set_target_name(target_name);
result.set_arguments_descriptor(arguments_descriptor);
result.set_filled_entry_count(0);
return result.ptr();
}
void MegamorphicCache::EnsureContains(const Smi& class_id,
const Object& target) const {
SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());
if (LookupLocked(class_id) == Object::null()) {
InsertLocked(class_id, target);
}
#if defined(DEBUG)
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
if (target.IsFunction()) {
const auto& function = Function::Cast(target);
const auto& entry_point = Smi::Handle(
Smi::FromAlignedAddress(Code::EntryPointOf(function.CurrentCode())));
ASSERT(LookupLocked(class_id) == entry_point.ptr());
}
} else {
ASSERT(LookupLocked(class_id) == target.ptr());
}
#endif // define(DEBUG)
}
ObjectPtr MegamorphicCache::Lookup(const Smi& class_id) const {
SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex());
return LookupLocked(class_id);
}
ObjectPtr MegamorphicCache::LookupLocked(const Smi& class_id) const {
auto thread = Thread::Current();
auto isolate_group = thread->isolate_group();
auto zone = thread->zone();
ASSERT(thread->IsMutatorThread());
ASSERT(isolate_group->type_feedback_mutex()->IsOwnedByCurrentThread());
const auto& backing_array = Array::Handle(zone, buckets());
intptr_t id_mask = mask();
intptr_t index = (class_id.Value() * kSpreadFactor) & id_mask;
intptr_t i = index;
do {
const classid_t current_cid =
Smi::Value(Smi::RawCast(GetClassId(backing_array, i)));
if (current_cid == class_id.Value()) {
return GetTargetFunction(backing_array, i);
} else if (current_cid == kIllegalCid) {
return Object::null();
}
i = (i + 1) & id_mask;
} while (i != index);
UNREACHABLE();
}
void MegamorphicCache::InsertLocked(const Smi& class_id,
const Object& target) const {
auto isolate_group = IsolateGroup::Current();
ASSERT(isolate_group->type_feedback_mutex()->IsOwnedByCurrentThread());
// As opposed to ICData we are stopping mutator threads from other isolates
// while modifying the megamorphic cache, since updates are not atomic.
//
// NOTE: In the future we might change the megamorphic cache insertions to
// carefully use store-release barriers on the writer as well as
// load-acquire barriers on the reader, ...
isolate_group->RunWithStoppedMutators(
[&]() {
EnsureCapacityLocked();
InsertEntryLocked(class_id, target);
},
/*use_force_growth=*/true);
}
void MegamorphicCache::EnsureCapacityLocked() const {
auto thread = Thread::Current();
auto zone = thread->zone();
auto isolate_group = thread->isolate_group();
ASSERT(isolate_group->type_feedback_mutex()->IsOwnedByCurrentThread());
intptr_t old_capacity = mask() + 1;
double load_limit = kLoadFactor * static_cast<double>(old_capacity);
if (static_cast<double>(filled_entry_count() + 1) > load_limit) {
const Array& old_buckets = Array::Handle(zone, buckets());
intptr_t new_capacity = old_capacity * 2;
const Array& new_buckets =
Array::Handle(zone, Array::New(kEntryLength * new_capacity));
auto& target = Object::Handle(zone);
for (intptr_t i = 0; i < new_capacity; ++i) {
SetEntry(new_buckets, i, smi_illegal_cid(), target);
}
set_buckets(new_buckets);
set_mask(new_capacity - 1);
set_filled_entry_count(0);
// Rehash the valid entries.
Smi& class_id = Smi::Handle(zone);
for (intptr_t i = 0; i < old_capacity; ++i) {
class_id ^= GetClassId(old_buckets, i);
if (class_id.Value() != kIllegalCid) {
target = GetTargetFunction(old_buckets, i);
InsertEntryLocked(class_id, target);
}
}
}
}
void MegamorphicCache::InsertEntryLocked(const Smi& class_id,
const Object& target) const {
auto thread = Thread::Current();
auto isolate_group = thread->isolate_group();
ASSERT(isolate_group->type_feedback_mutex()->IsOwnedByCurrentThread());
ASSERT(Thread::Current()->IsMutatorThread());
ASSERT(static_cast<double>(filled_entry_count() + 1) <=
(kLoadFactor * static_cast<double>(mask() + 1)));
const Array& backing_array = Array::Handle(buckets());
intptr_t id_mask = mask();
intptr_t index = (class_id.Value() * kSpreadFactor) & id_mask;
intptr_t i = index;
do {
if (Smi::Value(Smi::RawCast(GetClassId(backing_array, i))) == kIllegalCid) {
SetEntry(backing_array, i, class_id, target);
set_filled_entry_count(filled_entry_count() + 1);
return;
}
i = (i + 1) & id_mask;
} while (i != index);
UNREACHABLE();
}
const char* MegamorphicCache::ToCString() const {
const String& name = String::Handle(target_name());
return OS::SCreate(Thread::Current()->zone(), "MegamorphicCache(%s)",
name.ToCString());
}
void MegamorphicCache::SwitchToBareInstructions() {
NoSafepointScope no_safepoint_scope;
intptr_t capacity = mask() + 1;
for (intptr_t i = 0; i < capacity; ++i) {
const intptr_t target_index = i * kEntryLength + kTargetFunctionIndex;
ObjectPtr* slot = &Array::DataOf(buckets())[target_index];
const intptr_t cid = (*slot)->GetClassIdMayBeSmi();
if (cid == kFunctionCid) {
CodePtr code = Function::CurrentCodeOf(Function::RawCast(*slot));
*slot = Smi::FromAlignedAddress(Code::EntryPointOf(code));
} else {
ASSERT(cid == kSmiCid || cid == kNullCid);
}
}
}
void SubtypeTestCache::Init() {
cached_array_ = Array::New(kTestEntryLength, Heap::kOld);
}
void SubtypeTestCache::Cleanup() {
cached_array_ = NULL;
}
SubtypeTestCachePtr SubtypeTestCache::New() {
ASSERT(Object::subtypetestcache_class() != Class::null());
SubtypeTestCache& result = SubtypeTestCache::Handle();
{
// SubtypeTestCache objects are long living objects, allocate them in the
// old generation.
ObjectPtr raw = Object::Allocate(SubtypeTestCache::kClassId,
SubtypeTestCache::InstanceSize(),
Heap::kOld, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_cache(Array::Handle(cached_array_));
return result.ptr();
}
ArrayPtr SubtypeTestCache::cache() const {
// We rely on the fact that any loads from the array are dependent loads and
// avoid the load-acquire barrier here.
return untag()->cache<std::memory_order_relaxed>();
}
void SubtypeTestCache::set_cache(const Array& value) const {
// We have to ensure that initializing stores to the array are available
// when releasing the pointer to the array pointer.
// => We have to use store-release here.
untag()->set_cache<std::memory_order_release>(value.ptr());
}
intptr_t SubtypeTestCache::NumberOfChecks() const {
NoSafepointScope no_safepoint;
// Do not count the sentinel;
return (Smi::Value(cache()->untag()->length()) / kTestEntryLength) - 1;
}
void SubtypeTestCache::AddCheck(
const Object& instance_class_id_or_function,
const AbstractType& destination_type,
const TypeArguments& instance_type_arguments,
const TypeArguments& instantiator_type_arguments,
const TypeArguments& function_type_arguments,
const TypeArguments& instance_parent_function_type_arguments,
const TypeArguments& instance_delayed_type_arguments,
const Bool& test_result) const {
ASSERT(Thread::Current()
->isolate_group()
->subtype_test_cache_mutex()
->IsOwnedByCurrentThread());
intptr_t old_num = NumberOfChecks();
Array& data = Array::Handle(cache());
intptr_t new_len = data.Length() + kTestEntryLength;
data = Array::Grow(data, new_len);
SubtypeTestCacheTable entries(data);
auto entry = entries[old_num];
ASSERT(entry.Get<kInstanceClassIdOrFunction>() == Object::null());
entry.Set<kInstanceClassIdOrFunction>(instance_class_id_or_function);
entry.Set<kDestinationType>(destination_type);
entry.Set<kInstanceTypeArguments>(instance_type_arguments);
entry.Set<kInstantiatorTypeArguments>(instantiator_type_arguments);
entry.Set<kFunctionTypeArguments>(function_type_arguments);
entry.Set<kInstanceParentFunctionTypeArguments>(
instance_parent_function_type_arguments);
entry.Set<kInstanceDelayedFunctionTypeArguments>(
instance_delayed_type_arguments);
entry.Set<kTestResult>(test_result);
// We let any concurrently running mutator thread now see the new entry (the
// `set_cache()` uses a store-release barrier).
set_cache(data);
}
void SubtypeTestCache::GetCheck(
intptr_t ix,
Object* instance_class_id_or_function,
AbstractType* destination_type,
TypeArguments* instance_type_arguments,
TypeArguments* instantiator_type_arguments,
TypeArguments* function_type_arguments,
TypeArguments* instance_parent_function_type_arguments,
TypeArguments* instance_delayed_type_arguments,
Bool* test_result) const {
ASSERT(Thread::Current()
->isolate_group()
->subtype_test_cache_mutex()
->IsOwnedByCurrentThread());
GetCurrentCheck(ix, instance_class_id_or_function, destination_type,
instance_type_arguments, instantiator_type_arguments,
function_type_arguments,
instance_parent_function_type_arguments,
instance_delayed_type_arguments, test_result);
}
void SubtypeTestCache::GetCurrentCheck(
intptr_t ix,
Object* instance_class_id_or_function,
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 {
Array& data = Array::Handle(cache());
SubtypeTestCacheTable entries(data);
auto entry = entries[ix];
*instance_class_id_or_function = entry.Get<kInstanceClassIdOrFunction>();
*destination_type = entry.Get<kDestinationType>();
*instance_type_arguments = entry.Get<kInstanceTypeArguments>();
*instantiator_type_arguments = entry.Get<kInstantiatorTypeArguments>();
*function_type_arguments = entry.Get<kFunctionTypeArguments>();
*instance_parent_function_type_arguments =
entry.Get<kInstanceParentFunctionTypeArguments>();
*instance_delayed_type_arguments =
entry.Get<kInstanceDelayedFunctionTypeArguments>();
*test_result ^= entry.Get<kTestResult>();
}
bool SubtypeTestCache::HasCheck(
const Object& instance_class_id_or_function,
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 {
ASSERT(Thread::Current()
->isolate_group()
->subtype_test_cache_mutex()
->IsOwnedByCurrentThread());
const intptr_t last_index = NumberOfChecks();
const auto& data = Array::Handle(cache());
SubtypeTestCacheTable entries(data);
for (intptr_t i = 0; i < last_index; i++) {
const auto entry = entries[i];
if (entry.Get<kInstanceClassIdOrFunction>() ==
instance_class_id_or_function.ptr() &&
entry.Get<kDestinationType>() == destination_type.ptr() &&
entry.Get<kInstanceTypeArguments>() == instance_type_arguments.ptr() &&
entry.Get<kInstantiatorTypeArguments>() ==
instantiator_type_arguments.ptr() &&
entry.Get<kFunctionTypeArguments>() == function_type_arguments.ptr() &&
entry.Get<kInstanceParentFunctionTypeArguments>() ==
instance_parent_function_type_arguments.ptr() &&
entry.Get<kInstanceDelayedFunctionTypeArguments>() ==
instance_delayed_type_arguments.ptr()) {
if (index != nullptr) {
*index = i;
}
if (result != nullptr) {
*result ^= entry.Get<kTestResult>();
}
return true;
}
}
return false;
}
void SubtypeTestCache::WriteEntryToBuffer(Zone* zone,
BaseTextBuffer* buffer,
intptr_t index,
const char* line_prefix) const {
ASSERT(Thread::Current()
->isolate_group()
->subtype_test_cache_mutex()
->IsOwnedByCurrentThread());
WriteCurrentEntryToBuffer(zone, buffer, index, line_prefix);
}
void SubtypeTestCache::WriteCurrentEntryToBuffer(
Zone* zone,
BaseTextBuffer* buffer,
intptr_t index,
const char* line_prefix) const {
const char* separator =
line_prefix == nullptr ? ", " : OS::SCreate(zone, "\n%s", line_prefix);
auto& instance_class_id_or_function = Object::Handle(zone);
auto& destination_type = AbstractType::Handle(zone);
auto& instance_type_arguments = TypeArguments::Handle(zone);
auto& instantiator_type_arguments = TypeArguments::Handle(zone);
auto& function_type_arguments = TypeArguments::Handle(zone);
auto& instance_parent_function_type_arguments = TypeArguments::Handle(zone);
auto& instance_delayed_type_arguments = TypeArguments::Handle(zone);
auto& result = Bool::Handle(zone);
GetCurrentCheck(index, &instance_class_id_or_function, &destination_type,
&instance_type_arguments, &instantiator_type_arguments,
&function_type_arguments,
&instance_parent_function_type_arguments,
&instance_delayed_type_arguments, &result);
ASSERT(!result.IsNull());
buffer->Printf(
"[ %#" Px ", %#" Px ", %#" Px ", %#" Px ", %#" Px ", %#" Px ", %#" Px
", %#" Px " ]",
static_cast<uword>(instance_class_id_or_function.ptr()),
static_cast<uword>(destination_type.ptr()),
static_cast<uword>(instance_type_arguments.ptr()),
static_cast<uword>(instantiator_type_arguments.ptr()),
static_cast<uword>(function_type_arguments.ptr()),
static_cast<uword>(instance_parent_function_type_arguments.ptr()),
static_cast<uword>(instance_delayed_type_arguments.ptr()),
static_cast<uword>(result.ptr()));
if (instance_class_id_or_function.IsSmi()) {
buffer->Printf("%sclass id: %" Pd "", separator,
Smi::Cast(instance_class_id_or_function).Value());
} else {
ASSERT(instance_class_id_or_function.IsFunction());
buffer->Printf("%sfunction: %s", separator,
Function::Cast(instance_class_id_or_function)
.ToFullyQualifiedCString());
}
if (!destination_type.IsNull()) {
buffer->Printf("%sdestination type: %s", separator,
destination_type.ToCString());
if (!destination_type.IsInstantiated()) {
AbstractType& test_type = AbstractType::Handle(
zone, destination_type.InstantiateFrom(instantiator_type_arguments,
function_type_arguments,
kAllFree, Heap::kNew));
const auto type_class_id = test_type.type_class_id();
buffer->Printf("%sinstantiated type: %s", separator,
test_type.ToCString());
buffer->Printf("%sinstantiated type class id: %d", separator,
type_class_id);
}
}
if (!instance_type_arguments.IsNull()) {
if (instance_class_id_or_function.IsSmi()) {
buffer->Printf("%sinstance type arguments: %s", separator,
instance_type_arguments.ToCString());
} else {
ASSERT(instance_class_id_or_function.IsFunction());
buffer->Printf("%sclosure instantiator function type arguments: %s",
separator, instance_type_arguments.ToCString());
}
}
if (!instantiator_type_arguments.IsNull()) {
buffer->Printf("%sinstantiator type arguments: %s", separator,
instantiator_type_arguments.ToCString());
}
if (!function_type_arguments.IsNull()) {
buffer->Printf("%sfunction type arguments: %s", separator,
function_type_arguments.ToCString());
}
if (!instance_parent_function_type_arguments.IsNull()) {
ASSERT(instance_class_id_or_function.IsFunction());
buffer->Printf("%sclosure parent function type arguments: %s", separator,
instance_parent_function_type_arguments.ToCString());
}
if (!instance_delayed_type_arguments.IsNull()) {
ASSERT(instance_class_id_or_function.IsFunction());
buffer->Printf("%sclosure delayed function type arguments: %s", separator,
instance_delayed_type_arguments.ToCString());
}
buffer->Printf("%sresult: %s", separator, result.ToCString());
}
void SubtypeTestCache::Reset() const {
set_cache(Array::Handle(cached_array_));
}
const char* SubtypeTestCache::ToCString() const {
auto const zone = Thread::Current()->zone();
ZoneTextBuffer buffer(zone);
const intptr_t num_checks = NumberOfChecks();
buffer.AddString("SubtypeTestCache(");
for (intptr_t i = 0; i < num_checks; i++) {
if (i != 0) {
buffer.AddString(",");
}
buffer.AddString("{ entry: ");
WriteCurrentEntryToBuffer(zone, &buffer, i);
buffer.AddString(" }");
}
buffer.AddString(")");
return buffer.buffer();
}
LoadingUnitPtr LoadingUnit::New() {
ASSERT(Object::loadingunit_class() != Class::null());
LoadingUnit& result = LoadingUnit::Handle();
{
// LoadingUnit objects are long living objects, allocate them in the
// old generation.
ObjectPtr raw =
Object::Allocate(LoadingUnit::kClassId, LoadingUnit::InstanceSize(),
Heap::kOld, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_id(kIllegalId);
result.set_loaded(false);
result.set_load_outstanding(false);
return result.ptr();
}
LoadingUnitPtr LoadingUnit::parent() const {
return untag()->parent();
}
void LoadingUnit::set_parent(const LoadingUnit& value) const {
untag()->set_parent(value.ptr());
}
ArrayPtr LoadingUnit::base_objects() const {
return untag()->base_objects();
}
void LoadingUnit::set_base_objects(const Array& value) const {
untag()->set_base_objects(value.ptr());
}
const char* LoadingUnit::ToCString() const {
return "LoadingUnit";
}
ObjectPtr LoadingUnit::IssueLoad() const {
ASSERT(!loaded());
ASSERT(!load_outstanding());
set_load_outstanding(true);
return Isolate::Current()->CallDeferredLoadHandler(id());
}
ObjectPtr LoadingUnit::CompleteLoad(const String& error_message,
bool transient_error) const {
ASSERT(!loaded());
ASSERT(load_outstanding());
set_loaded(error_message.IsNull());
set_load_outstanding(false);
const Library& lib = Library::Handle(Library::CoreLibrary());
const String& sel = String::Handle(String::New("_completeLoads"));
const Function& func = Function::Handle(lib.LookupFunctionAllowPrivate(sel));
ASSERT(!func.IsNull());
const Array& args = Array::Handle(Array::New(3));
args.SetAt(0, Smi::Handle(Smi::New(id())));
args.SetAt(1, error_message);
args.SetAt(2, Bool::Get(transient_error));
return DartEntry::InvokeFunction(func, args);
}
const char* Error::ToErrorCString() const {
if (IsNull()) {
return "Error: null";
}
UNREACHABLE();
return "Error";
}
const char* Error::ToCString() const {
if (IsNull()) {
return "Error: null";
}
// Error is an abstract class. We should never reach here.
UNREACHABLE();
return "Error";
}
ApiErrorPtr ApiError::New() {
ASSERT(Object::api_error_class() != Class::null());
ObjectPtr raw = Object::Allocate(ApiError::kClassId, ApiError::InstanceSize(),
Heap::kOld, /*compressed*/ true);
return static_cast<ApiErrorPtr>(raw);
}
ApiErrorPtr ApiError::New(const String& message, Heap::Space space) {
#ifndef PRODUCT
if (FLAG_print_stacktrace_at_api_error) {
OS::PrintErr("ApiError: %s\n", message.ToCString());
Profiler::DumpStackTrace(false /* for_crash */);
}
#endif // !PRODUCT
ASSERT(Object::api_error_class() != Class::null());
ApiError& result = ApiError::Handle();
{
ObjectPtr raw =
Object::Allocate(ApiError::kClassId, ApiError::InstanceSize(), space,
/*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_message(message);
return result.ptr();
}
void ApiError::set_message(const String& message) const {
untag()->set_message(message.ptr());
}
const char* ApiError::ToErrorCString() const {
const String& msg_str = String::Handle(message());
return msg_str.ToCString();
}
const char* ApiError::ToCString() const {
return "ApiError";
}
LanguageErrorPtr LanguageError::New() {
ASSERT(Object::language_error_class() != Class::null());
ObjectPtr raw =
Object::Allocate(LanguageError::kClassId, LanguageError::InstanceSize(),
Heap::kOld, /*compressed*/ true);
return static_cast<LanguageErrorPtr>(raw);
}
LanguageErrorPtr LanguageError::NewFormattedV(const Error& prev_error,
const Script& script,
TokenPosition token_pos,
bool report_after_token,
Report::Kind kind,
Heap::Space space,
const char* format,
va_list args) {
ASSERT(Object::language_error_class() != Class::null());
LanguageError& result = LanguageError::Handle();
{
ObjectPtr raw =
Object::Allocate(LanguageError::kClassId, LanguageError::InstanceSize(),
space, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_previous_error(prev_error);
result.set_script(script);
result.set_token_pos(token_pos);
result.set_report_after_token(report_after_token);
result.set_kind(kind);
result.set_message(
String::Handle(String::NewFormattedV(format, args, space)));
return result.ptr();
}
LanguageErrorPtr LanguageError::NewFormatted(const Error& prev_error,
const Script& script,
TokenPosition token_pos,
bool report_after_token,
Report::Kind kind,
Heap::Space space,
const char* format,
...) {
va_list args;
va_start(args, format);
LanguageErrorPtr result = LanguageError::NewFormattedV(
prev_error, script, token_pos, report_after_token, kind, space, format,
args);
NoSafepointScope no_safepoint;
va_end(args);
return result;
}
LanguageErrorPtr LanguageError::New(const String& formatted_message,
Report::Kind kind,
Heap::Space space) {
ASSERT(Object::language_error_class() != Class::null());
LanguageError& result = LanguageError::Handle();
{
ObjectPtr raw =
Object::Allocate(LanguageError::kClassId, LanguageError::InstanceSize(),
space, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_formatted_message(formatted_message);
result.set_kind(kind);
return result.ptr();
}
void LanguageError::set_previous_error(const Error& value) const {
untag()->set_previous_error(value.ptr());
}
void LanguageError::set_script(const Script& value) const {
untag()->set_script(value.ptr());
}
void LanguageError::set_token_pos(TokenPosition token_pos) const {
ASSERT(!token_pos.IsClassifying());
StoreNonPointer(&untag()->token_pos_, token_pos);
}
void LanguageError::set_report_after_token(bool value) {
StoreNonPointer(&untag()->report_after_token_, value);
}
void LanguageError::set_kind(uint8_t value) const {
StoreNonPointer(&untag()->kind_, value);
}
void LanguageError::set_message(const String& value) const {
untag()->set_message(value.ptr());
}
void LanguageError::set_formatted_message(const String& value) const {
untag()->set_formatted_message(value.ptr());
}
StringPtr LanguageError::FormatMessage() const {
if (formatted_message() != String::null()) {
return formatted_message();
}
String& result = String::Handle(
Report::PrependSnippet(kind(), Script::Handle(script()), token_pos(),
report_after_token(), String::Handle(message())));
// Prepend previous error message.
const Error& prev_error = Error::Handle(previous_error());
if (!prev_error.IsNull()) {
result = String::Concat(
String::Handle(String::New(prev_error.ToErrorCString())), result);
}
set_formatted_message(result);
return result.ptr();
}
const char* LanguageError::ToErrorCString() const {
const String& msg_str = String::Handle(FormatMessage());
return msg_str.ToCString();
}
const char* LanguageError::ToCString() const {
return "LanguageError";
}
UnhandledExceptionPtr UnhandledException::New(const Instance& exception,
const Instance& stacktrace,
Heap::Space space) {
ASSERT(Object::unhandled_exception_class() != Class::null());
UnhandledException& result = UnhandledException::Handle();
{
ObjectPtr raw = Object::Allocate(UnhandledException::kClassId,
UnhandledException::InstanceSize(), space,
/*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_exception(exception);
result.set_stacktrace(stacktrace);
return result.ptr();
}
UnhandledExceptionPtr UnhandledException::New(Heap::Space space) {
ASSERT(Object::unhandled_exception_class() != Class::null());
UnhandledException& result = UnhandledException::Handle();
{
ObjectPtr raw = Object::Allocate(UnhandledException::kClassId,
UnhandledException::InstanceSize(), space,
/*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_exception(Object::null_instance());
result.set_stacktrace(StackTrace::Handle());
return result.ptr();
}
void UnhandledException::set_exception(const Instance& exception) const {
untag()->set_exception(exception.ptr());
}
void UnhandledException::set_stacktrace(const Instance& stacktrace) const {
untag()->set_stacktrace(stacktrace.ptr());
}
const char* UnhandledException::ToErrorCString() const {
Thread* thread = Thread::Current();
auto isolate_group = thread->isolate_group();
NoReloadScope no_reload_scope(thread);
HANDLESCOPE(thread);
Object& strtmp = Object::Handle();
const char* exc_str;
if (exception() == isolate_group->object_store()->out_of_memory()) {
exc_str = "Out of Memory";
} else if (exception() == isolate_group->object_store()->stack_overflow()) {
exc_str = "Stack Overflow";
} else {
const Instance& exc = Instance::Handle(exception());
strtmp = DartLibraryCalls::ToString(exc);
if (!strtmp.IsError()) {
exc_str = strtmp.ToCString();
} else {
exc_str = "<Received error while converting exception to string>";
}
}
const Instance& stack = Instance::Handle(stacktrace());
strtmp = DartLibraryCalls::ToString(stack);
const char* stack_str =
"<Received error while converting stack trace to string>";
if (!strtmp.IsError()) {
stack_str = strtmp.ToCString();
}
return OS::SCreate(thread->zone(), "Unhandled exception:\n%s\n%s", exc_str,
stack_str);
}
const char* UnhandledException::ToCString() const {
return "UnhandledException";
}
UnwindErrorPtr UnwindError::New(const String& message, Heap::Space space) {
ASSERT(Object::unwind_error_class() != Class::null());
UnwindError& result = UnwindError::Handle();
{
ObjectPtr raw =
Object::Allocate(UnwindError::kClassId, UnwindError::InstanceSize(),
space, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_message(message);
result.set_is_user_initiated(false);
return result.ptr();
}
void UnwindError::set_message(const String& message) const {
untag()->set_message(message.ptr());
}
void UnwindError::set_is_user_initiated(bool value) const {
StoreNonPointer(&untag()->is_user_initiated_, value);
}
const char* UnwindError::ToErrorCString() const {
const String& msg_str = String::Handle(message());
return msg_str.ToCString();
}
const char* UnwindError::ToCString() const {
return "UnwindError";
}
ObjectPtr Instance::InvokeGetter(const String& getter_name,
bool respect_reflectable,
bool check_is_entrypoint) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Class& klass = Class::Handle(zone, clazz());
CHECK_ERROR(klass.EnsureIsFinalized(thread));
const auto& inst_type_args =
klass.NumTypeArguments() > 0
? TypeArguments::Handle(zone, GetTypeArguments())
: Object::null_type_arguments();
const String& internal_getter_name =
String::Handle(zone, Field::GetterName(getter_name));
Function& function = Function::Handle(
zone, Resolver::ResolveDynamicAnyArgs(zone, klass, internal_getter_name));
if (!function.IsNull() && check_is_entrypoint) {
// The getter must correspond to either an entry-point field or a getter
// method explicitly marked.
Field& field = Field::Handle(zone);
if (function.kind() == UntaggedFunction::kImplicitGetter) {
field = function.accessor_field();
}
if (!field.IsNull()) {
CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kGetterOnly));
} else {
CHECK_ERROR(function.VerifyCallEntryPoint());
}
}
// Check for method extraction when method extractors are not created.
if (function.IsNull() && !FLAG_lazy_dispatchers) {
function = Resolver::ResolveDynamicAnyArgs(zone, klass, getter_name);
if (!function.IsNull() && check_is_entrypoint) {
CHECK_ERROR(function.VerifyClosurizedEntryPoint());
}
if (!function.IsNull() && function.SafeToClosurize()) {
const Function& closure_function =
Function::Handle(zone, function.ImplicitClosureFunction());
return closure_function.ImplicitInstanceClosure(*this);
}
}
const int kTypeArgsLen = 0;
const int kNumArgs = 1;
const Array& args = Array::Handle(zone, Array::New(kNumArgs));
args.SetAt(0, *this);
const Array& args_descriptor = Array::Handle(
zone,
ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(), Heap::kNew));
return InvokeInstanceFunction(thread, *this, function, internal_getter_name,
args, args_descriptor, respect_reflectable,
inst_type_args);
}
ObjectPtr Instance::InvokeSetter(const String& setter_name,
const Instance& value,
bool respect_reflectable,
bool check_is_entrypoint) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const Class& klass = Class::Handle(zone, clazz());
CHECK_ERROR(klass.EnsureIsFinalized(thread));
const auto& inst_type_args =
klass.NumTypeArguments() > 0
? TypeArguments::Handle(zone, GetTypeArguments())
: Object::null_type_arguments();
const String& internal_setter_name =
String::Handle(zone, Field::SetterName(setter_name));
const Function& setter = Function::Handle(
zone, Resolver::ResolveDynamicAnyArgs(zone, klass, internal_setter_name));
if (check_is_entrypoint) {
// The setter must correspond to either an entry-point field or a setter
// method explicitly marked.
Field& field = Field::Handle(zone);
if (setter.kind() == UntaggedFunction::kImplicitSetter) {
field = setter.accessor_field();
}
if (!field.IsNull()) {
CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kSetterOnly));
} else if (!setter.IsNull()) {
CHECK_ERROR(setter.VerifyCallEntryPoint());
}
}
const int kTypeArgsLen = 0;
const int kNumArgs = 2;
const Array& args = Array::Handle(zone, Array::New(kNumArgs));
args.SetAt(0, *this);
args.SetAt(1, value);
const Array& args_descriptor = Array::Handle(
zone,
ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(), Heap::kNew));
return InvokeInstanceFunction(thread, *this, setter, internal_setter_name,
args, args_descriptor, respect_reflectable,
inst_type_args);
}
ObjectPtr Instance::Invoke(const String& function_name,
const Array& args,
const Array& arg_names,
bool respect_reflectable,
bool check_is_entrypoint) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Class& klass = Class::Handle(zone, clazz());
CHECK_ERROR(klass.EnsureIsFinalized(thread));
Function& function = Function::Handle(
zone, Resolver::ResolveDynamicAnyArgs(zone, klass, function_name));
if (!function.IsNull() && check_is_entrypoint) {
CHECK_ERROR(function.VerifyCallEntryPoint());
}
// We don't pass any explicit type arguments, which will be understood as
// using dynamic for any function type arguments by lower layers.
const int kTypeArgsLen = 0;
const Array& args_descriptor = Array::Handle(
zone, ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(),
arg_names, Heap::kNew));
const auto& inst_type_args =
klass.NumTypeArguments() > 0
? TypeArguments::Handle(zone, GetTypeArguments())
: Object::null_type_arguments();
if (function.IsNull()) {
// Didn't find a method: try to find a getter and invoke call on its result.
const String& getter_name =
String::Handle(zone, Field::GetterName(function_name));
function = Resolver::ResolveDynamicAnyArgs(zone, klass, getter_name);
if (!function.IsNull()) {
if (check_is_entrypoint) {
CHECK_ERROR(EntryPointFieldInvocationError(function_name));
}
ASSERT(function.kind() != UntaggedFunction::kMethodExtractor);
// Invoke the getter.
const int kNumArgs = 1;
const Array& getter_args = Array::Handle(zone, Array::New(kNumArgs));
getter_args.SetAt(0, *this);
const Array& getter_args_descriptor = Array::Handle(
zone, ArgumentsDescriptor::NewBoxed(
kTypeArgsLen, getter_args.Length(), Heap::kNew));
const Object& getter_result = Object::Handle(
zone, InvokeInstanceFunction(thread, *this, function, getter_name,
getter_args, getter_args_descriptor,
respect_reflectable, inst_type_args));
if (getter_result.IsError()) {
return getter_result.ptr();
}
// Replace the closure as the receiver in the arguments list.
args.SetAt(0, getter_result);
return DartEntry::InvokeClosure(thread, args, args_descriptor);
}
}
// Found an ordinary method.
return InvokeInstanceFunction(thread, *this, function, function_name, args,
args_descriptor, respect_reflectable,
inst_type_args);
}
ObjectPtr Instance::EvaluateCompiledExpression(
const Class& method_cls,
const ExternalTypedData& kernel_buffer,
const Array& type_definitions,
const Array& arguments,
const TypeArguments& type_arguments) const {
const Array& arguments_with_receiver =
Array::Handle(Array::New(1 + arguments.Length()));
PassiveObject& param = PassiveObject::Handle();
arguments_with_receiver.SetAt(0, *this);
for (intptr_t i = 0; i < arguments.Length(); i++) {
param = arguments.At(i);
arguments_with_receiver.SetAt(i + 1, param);
}
return EvaluateCompiledExpressionHelper(
kernel_buffer, type_definitions,
String::Handle(Library::Handle(method_cls.library()).url()),
String::Handle(method_cls.UserVisibleName()), arguments_with_receiver,
type_arguments);
}
ObjectPtr Instance::HashCode() const {
// TODO(koda): Optimize for all builtin classes and all classes
// that do not override hashCode.
return DartLibraryCalls::HashCode(*this);
}
ObjectPtr Instance::IdentityHashCode() const {
return DartLibraryCalls::IdentityHashCode(*this);
}
bool Instance::CanonicalizeEquals(const Instance& other) const {
if (this->ptr() == other.ptr()) {
return true; // "===".
}
if (other.IsNull() || (this->clazz() != other.clazz())) {
return false;
}
{
NoSafepointScope no_safepoint;
// Raw bits compare.
const intptr_t instance_size = SizeFromClass();
ASSERT(instance_size != 0);
const intptr_t other_instance_size = other.SizeFromClass();
ASSERT(other_instance_size != 0);
if (instance_size != other_instance_size) {
return false;
}
uword this_addr = reinterpret_cast<uword>(this->untag());
uword other_addr = reinterpret_cast<uword>(other.untag());
for (intptr_t offset = Instance::NextFieldOffset(); offset < instance_size;
offset += kWordSize) {
if ((*reinterpret_cast<ObjectPtr*>(this_addr + offset)) !=
(*reinterpret_cast<ObjectPtr*>(other_addr + offset))) {
return false;
}
}
}
return true;
}
uint32_t Instance::CanonicalizeHash() const {
if (GetClassId() == kNullCid) {
return 2011; // Matches null_patch.dart.
}
Thread* thread = Thread::Current();
uint32_t hash = thread->heap()->GetCanonicalHash(ptr());
if (hash != 0) {
return hash;
}
const Class& cls = Class::Handle(clazz());
NoSafepointScope no_safepoint(thread);
const intptr_t instance_size = SizeFromClass();
ASSERT(instance_size != 0);
hash = instance_size / kWordSize;
uword this_addr = reinterpret_cast<uword>(this->untag());
Instance& member = Instance::Handle();
const auto unboxed_fields_bitmap =
thread->isolate_group()->shared_class_table()->GetUnboxedFieldsMapAt(
GetClassId());
for (intptr_t offset = Instance::NextFieldOffset();
offset < cls.host_next_field_offset(); offset += kWordSize) {
if (unboxed_fields_bitmap.Get(offset / kWordSize)) {
if (kWordSize == 8) {
hash = CombineHashes(hash,
*reinterpret_cast<uint32_t*>(this_addr + offset));
hash = CombineHashes(
hash, *reinterpret_cast<uint32_t*>(this_addr + offset + 4));
} else {
hash = CombineHashes(hash,
*reinterpret_cast<uint32_t*>(this_addr + offset));
}
} else {
member ^= *reinterpret_cast<ObjectPtr*>(this_addr + offset);
hash = CombineHashes(hash, member.CanonicalizeHash());
}
}
hash = FinalizeHash(hash, String::kHashBits);
thread->heap()->SetCanonicalHash(ptr(), hash);
return hash;
}
#if defined(DEBUG)
class CheckForPointers : public ObjectPointerVisitor {
public:
explicit CheckForPointers(IsolateGroup* isolate_group)
: ObjectPointerVisitor(isolate_group), has_pointers_(false) {}
bool has_pointers() const { return has_pointers_; }
void VisitPointers(ObjectPtr* first, ObjectPtr* last) {
if (first != last) {
has_pointers_ = true;
}
}
void VisitCompressedPointers(uword heap_base,
CompressedObjectPtr* first,
CompressedObjectPtr* last) {
if (first != last) {
has_pointers_ = true;
}
}
private:
bool has_pointers_;
DISALLOW_COPY_AND_ASSIGN(CheckForPointers);
};
#endif // DEBUG
void Instance::CanonicalizeFieldsLocked(Thread* thread) const {
const intptr_t class_id = GetClassId();
if (class_id >= kNumPredefinedCids) {
// Iterate over all fields, canonicalize numbers and strings, expect all
// other instances to be canonical otherwise report error (return false).
Zone* zone = thread->zone();
Instance& obj = Instance::Handle(zone);
const intptr_t instance_size = SizeFromClass();
ASSERT(instance_size != 0);
const auto unboxed_fields_bitmap =
thread->isolate_group()->shared_class_table()->GetUnboxedFieldsMapAt(
class_id);
for (intptr_t offset = Instance::NextFieldOffset(); offset < instance_size;
offset += kWordSize) {
if (unboxed_fields_bitmap.Get(offset / kWordSize)) {
continue;
}
obj ^= *this->FieldAddrAtOffset(offset);
obj = obj.CanonicalizeLocked(thread);
this->SetFieldAtOffset(offset, obj);
}
} else {
#if defined(DEBUG)
// Make sure that we are not missing any fields.
CheckForPointers has_pointers(IsolateGroup::Current());
this->ptr()->untag()->VisitPointers(&has_pointers);
ASSERT(!has_pointers.has_pointers());
#endif // DEBUG
}
}
InstancePtr Instance::CopyShallowToOldSpace(Thread* thread) const {
return Instance::RawCast(Object::Clone(*this, Heap::kOld));
}
InstancePtr Instance::Canonicalize(Thread* thread) const {
SafepointMutexLocker ml(
thread->isolate_group()->constant_canonicalization_mutex());
return CanonicalizeLocked(thread);
}
InstancePtr Instance::CanonicalizeLocked(Thread* thread) const {
if (this->IsCanonical()) {
return this->ptr();
}
ASSERT(!IsNull());
CanonicalizeFieldsLocked(thread);
Zone* zone = thread->zone();
const Class& cls = Class::Handle(zone, this->clazz());
Instance& result =
Instance::Handle(zone, cls.LookupCanonicalInstance(zone, *this));
if (!result.IsNull()) {
return result.ptr();
}
if (IsNew()) {
ASSERT((thread->isolate() == Dart::vm_isolate()) || !InVMIsolateHeap());
// Create a canonical object in old space.
result ^= Object::Clone(*this, Heap::kOld);
} else {
result = this->ptr();
}
ASSERT(result.IsOld());
result.SetCanonical();
return cls.InsertCanonicalConstant(zone, result);
}
#if defined(DEBUG)
bool Instance::CheckIsCanonical(Thread* thread) const {
Zone* zone = thread->zone();
Instance& result = Instance::Handle(zone);
const Class& cls = Class::Handle(zone, this->clazz());
SafepointMutexLocker ml(
thread->isolate_group()->constant_canonicalization_mutex());
result ^= cls.LookupCanonicalInstance(zone, *this);
return (result.ptr() == this->ptr());
}
#endif // DEBUG
ObjectPtr Instance::GetField(const Field& field) const {
if (FLAG_precompiled_mode && field.is_unboxing_candidate()) {
switch (field.guarded_cid()) {
case kDoubleCid:
return Double::New(*reinterpret_cast<double_t*>(FieldAddr(field)));
case kFloat32x4Cid:
return Float32x4::New(
*reinterpret_cast<simd128_value_t*>(FieldAddr(field)));
case kFloat64x2Cid:
return Float64x2::New(
*reinterpret_cast<simd128_value_t*>(FieldAddr(field)));
default:
if (field.is_non_nullable_integer()) {
return Integer::New(*reinterpret_cast<int64_t*>(FieldAddr(field)));
} else {
UNREACHABLE();
return nullptr;
}
}
} else {
return *FieldAddr(field);
}
}
void Instance::SetField(const Field& field, const Object& value) const {
if (FLAG_precompiled_mode && field.is_unboxing_candidate()) {
switch (field.guarded_cid()) {
case kDoubleCid:
StoreNonPointer(reinterpret_cast<double_t*>(FieldAddr(field)),
Double::Cast(value).value());
break;
case kFloat32x4Cid:
StoreNonPointer(reinterpret_cast<simd128_value_t*>(FieldAddr(field)),
Float32x4::Cast(value).value());
break;
case kFloat64x2Cid:
StoreNonPointer(reinterpret_cast<simd128_value_t*>(FieldAddr(field)),
Float64x2::Cast(value).value());
break;
default:
if (field.is_non_nullable_integer()) {
StoreNonPointer(reinterpret_cast<int64_t*>(FieldAddr(field)),
Integer::Cast(value).AsInt64Value());
} else {
UNREACHABLE();
}
break;
}
} else {
field.RecordStore(value);
const Object* stored_value = field.CloneForUnboxed(value);
StorePointer(FieldAddr(field), stored_value->ptr());
}
}
AbstractTypePtr Instance::GetType(Heap::Space space) const {
if (IsNull()) {
return Type::NullType();
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const Class& cls = Class::Handle(zone, clazz());
if (!cls.is_finalized()) {
// Various predefined classes can be instantiated by the VM or
// Dart_NewString/Integer/TypedData/... before the class is finalized.
ASSERT(cls.is_prefinalized());
cls.EnsureDeclarationLoaded();
}
if (cls.IsClosureClass()) {
FunctionType& signature = FunctionType::Handle(
Closure::Cast(*this).GetInstantiatedSignature(zone));
if (!signature.IsFinalized()) {
signature.SetIsFinalized();
}
signature ^= signature.Canonicalize(thread, nullptr);
return signature.ptr();
}
Type& type = Type::Handle(zone);
if (!cls.IsGeneric()) {
type = cls.DeclarationType();
}
if (type.IsNull()) {
TypeArguments& type_arguments = TypeArguments::Handle(zone);
if (cls.NumTypeArguments() > 0) {
type_arguments = GetTypeArguments();
}
type = Type::New(cls, type_arguments, Nullability::kNonNullable, space);
type.SetIsFinalized();
type ^= type.Canonicalize(thread, nullptr);
}
return type.ptr();
}
TypeArgumentsPtr Instance::GetTypeArguments() const {
ASSERT(!IsType());
const Class& cls = Class::Handle(clazz());
intptr_t field_offset = cls.host_type_arguments_field_offset();
ASSERT(field_offset != Class::kNoTypeArguments);
TypeArguments& type_arguments = TypeArguments::Handle();
type_arguments ^= *FieldAddrAtOffset(field_offset);
return type_arguments.ptr();
}
void Instance::SetTypeArguments(const TypeArguments& value) const {
ASSERT(!IsType());
ASSERT(value.IsNull() || value.IsCanonical());
const Class& cls = Class::Handle(clazz());
intptr_t field_offset = cls.host_type_arguments_field_offset();
ASSERT(field_offset != Class::kNoTypeArguments);
SetFieldAtOffset(field_offset, value);
}
/*
Specification of instance checks (e is T) and casts (e as T), where e evaluates
to a value v and v has runtime type S:
Instance checks (e is T) in weak checking mode in a legacy or opted-in library:
If v == null and T is a legacy type
return LEGACY_SUBTYPE(T, Null) || LEGACY_SUBTYPE(Object, T)
If v == null and T is not a legacy type, return NNBD_SUBTYPE(Null, T)
Otherwise return LEGACY_SUBTYPE(S, T)
Instance checks (e is T) in strong checking mode in a legacy or opted-in lib:
If v == null and T is a legacy type
return LEGACY_SUBTYPE(T, Null) || LEGACY_SUBTYPE(Object, T)
Otherwise return NNBD_SUBTYPE(S, T)
Casts (e as T) in weak checking mode in a legacy or opted-in library:
If LEGACY_SUBTYPE(S, T) then e as T evaluates to v.
Otherwise a CastError is thrown.
Casts (e as T) in strong checking mode in a legacy or opted-in library:
If NNBD_SUBTYPE(S, T) then e as T evaluates to v.
Otherwise a CastError is thrown.
*/
bool Instance::IsInstanceOf(
const AbstractType& other,
const TypeArguments& other_instantiator_type_arguments,
const TypeArguments& other_function_type_arguments) const {
ASSERT(!other.IsDynamicType());
if (IsNull()) {
return Instance::NullIsInstanceOf(other, other_instantiator_type_arguments,
other_function_type_arguments);
}
// In strong mode, compute NNBD_SUBTYPE(runtimeType, other).
// In weak mode, compute LEGACY_SUBTYPE(runtimeType, other).
return RuntimeTypeIsSubtypeOf(other, other_instantiator_type_arguments,
other_function_type_arguments);
}
bool Instance::IsAssignableTo(
const AbstractType& other,
const TypeArguments& other_instantiator_type_arguments,
const TypeArguments& other_function_type_arguments) const {
ASSERT(!other.IsDynamicType());
// In weak mode type casts, whether in legacy or opted-in libraries, the null
// instance is detected and handled in inlined code and therefore cannot be
// encountered here as a Dart null receiver.
ASSERT(IsolateGroup::Current()->use_strict_null_safety_checks() || !IsNull());
// In strong mode, compute NNBD_SUBTYPE(runtimeType, other).
// In weak mode, compute LEGACY_SUBTYPE(runtimeType, other).
return RuntimeTypeIsSubtypeOf(other, other_instantiator_type_arguments,
other_function_type_arguments);
}
// If 'other' type (once instantiated) is a legacy type:
// return LEGACY_SUBTYPE(other, Null) || LEGACY_SUBTYPE(Object, other).
// Otherwise return NNBD_SUBTYPE(Null, T).
// Ignore value of strong flag value.
bool Instance::NullIsInstanceOf(
const AbstractType& other,
const TypeArguments& other_instantiator_type_arguments,
const TypeArguments& other_function_type_arguments) {
ASSERT(other.IsFinalized());
ASSERT(!other.IsTypeRef()); // Must be dereferenced at compile time.
if (other.IsNullable()) {
// This case includes top types (void, dynamic, Object?).
// The uninstantiated nullable type will remain nullable after
// instantiation.
return true;
}
if (other.IsFutureOrType()) {
const auto& type = AbstractType::Handle(other.UnwrapFutureOr());
return NullIsInstanceOf(type, other_instantiator_type_arguments,
other_function_type_arguments);
}
// No need to instantiate type, unless it is a type parameter.
// Note that a typeref cannot refer to a type parameter.
if (other.IsTypeParameter()) {
auto& type = AbstractType::Handle(other.InstantiateFrom(
other_instantiator_type_arguments, other_function_type_arguments,
kAllFree, Heap::kOld));
if (type.IsTypeRef()) {
type = TypeRef::Cast(type).type();
}
return Instance::NullIsInstanceOf(type, Object::null_type_arguments(),
Object::null_type_arguments());
}
return other.IsLegacy() && (other.IsObjectType() || other.IsNeverType());
}
// Must be kept in sync with GenerateNullIsAssignableToType in
// stub_code_compiler.cc if any changes are made.
bool Instance::NullIsAssignableTo(const AbstractType& other) {
Thread* thread = Thread::Current();
auto isolate_group = thread->isolate_group();
// In weak mode, Null is a bottom type (according to LEGACY_SUBTYPE).
if (!isolate_group->use_strict_null_safety_checks()) {
return true;
}
// "Left Null" rule: null is assignable when destination type is either
// legacy or nullable. Otherwise it is not assignable or we cannot tell
// without instantiating type parameter.
if (other.IsLegacy() || other.IsNullable()) {
return true;
}
if (other.IsFutureOrType()) {
return NullIsAssignableTo(
AbstractType::Handle(thread->zone(), other.UnwrapFutureOr()));
}
// Since the TAVs are not available, for non-nullable type parameters
// this returns a conservative approximation of "not assignable" .
return false;
}
// Must be kept in sync with GenerateNullIsAssignableToType in
// stub_code_compiler.cc if any changes are made.
bool Instance::NullIsAssignableTo(
const AbstractType& other,
const TypeArguments& other_instantiator_type_arguments,
const TypeArguments& other_function_type_arguments) {
// Do checks that don't require instantiation first.
if (NullIsAssignableTo(other)) return true;
if (!other.IsTypeParameter()) return false;
const auto& type = AbstractType::Handle(other.InstantiateFrom(
other_instantiator_type_arguments, other_function_type_arguments,
kAllFree, Heap::kNew));
// At runtime, uses of TypeRef should not occur.
ASSERT(!type.IsTypeRef());
return NullIsAssignableTo(type);
}
bool Instance::RuntimeTypeIsSubtypeOf(
const AbstractType& other,
const TypeArguments& other_instantiator_type_arguments,
const TypeArguments& other_function_type_arguments) const {
ASSERT(other.IsFinalized());
ASSERT(!other.IsTypeRef()); // Must be dereferenced at compile time.
ASSERT(ptr() != Object::sentinel().ptr());
// Instance may not have runtimeType dynamic, void, or Never.
if (other.IsTopTypeForSubtyping()) {
return true;
}
Thread* thread = Thread::Current();
auto isolate_group = thread->isolate_group();
// In weak testing mode, Null type is a subtype of any type.
if (IsNull() && !isolate_group->use_strict_null_safety_checks()) {
return true;
}
Zone* zone = thread->zone();
const Class& cls = Class::Handle(zone, clazz());
if (cls.IsClosureClass()) {
if (other.IsDartFunctionType() || other.IsDartClosureType() ||
other.IsObjectType()) {
return true;
}
AbstractType& instantiated_other = AbstractType::Handle(zone, other.ptr());
if (!other.IsInstantiated()) {
instantiated_other = other.InstantiateFrom(
other_instantiator_type_arguments, other_function_type_arguments,
kAllFree, Heap::kOld);
if (instantiated_other.IsTypeRef()) {
instantiated_other = TypeRef::Cast(instantiated_other).type();
}
if (instantiated_other.IsTopTypeForSubtyping() ||
instantiated_other.IsDartFunctionType()) {
return true;
}
}
if (RuntimeTypeIsSubtypeOfFutureOr(zone, instantiated_other)) {
return true;
}
if (!instantiated_other.IsFunctionType()) {
return false;
}
const FunctionType& sig = FunctionType::Handle(
Closure::Cast(*this).GetInstantiatedSignature(zone));
return sig.IsSubtypeOf(FunctionType::Cast(instantiated_other), Heap::kOld);
}
TypeArguments& type_arguments = TypeArguments::Handle(zone);
if (cls.NumTypeArguments() > 0) {
type_arguments = GetTypeArguments();
ASSERT(type_arguments.IsNull() || type_arguments.IsCanonical());
// The number of type arguments in the instance must be greater or equal to
// the number of type arguments expected by the instance class.
// A discrepancy is allowed for closures, which borrow the type argument
// vector of their instantiator, which may be of a subclass of the class
// defining the closure. Truncating the vector to the correct length on
// instantiation is unnecessary. The vector may therefore be longer.
// Also, an optimization reuses the type argument vector of the instantiator
// of generic instances when its layout is compatible.
ASSERT(type_arguments.IsNull() ||
(type_arguments.Length() >= cls.NumTypeArguments()));
}
AbstractType& instantiated_other = AbstractType::Handle(zone, other.ptr());
if (!other.IsInstantiated()) {
instantiated_other = other.InstantiateFrom(
other_instantiator_type_arguments, other_function_type_arguments,
kAllFree, Heap::kOld);
if (instantiated_other.IsTypeRef()) {
instantiated_other = TypeRef::Cast(instantiated_other).type();
}
if (instantiated_other.IsTopTypeForSubtyping()) {
return true;
}
}
if (IsNull()) {
ASSERT(isolate_group->use_strict_null_safety_checks());
if (instantiated_other.IsNullType()) {
return true;
}
if (RuntimeTypeIsSubtypeOfFutureOr(zone, instantiated_other)) {
return true;
}
// At this point, instantiated_other can be a function type.
return !instantiated_other.IsNonNullable();
}
if (!instantiated_other.IsType()) {
return false;
}
// RuntimeType of non-null instance is non-nullable, so there is no need to
// check nullability of other type.
return Class::IsSubtypeOf(cls, type_arguments, Nullability::kNonNullable,
instantiated_other, Heap::kOld);
}
bool Instance::RuntimeTypeIsSubtypeOfFutureOr(Zone* zone,
const AbstractType& other) const {
if (other.IsFutureOrType()) {
const TypeArguments& other_type_arguments =
TypeArguments::Handle(zone, other.arguments());
const AbstractType& other_type_arg =
AbstractType::Handle(zone, other_type_arguments.TypeAtNullSafe(0));
if (other_type_arg.IsTopTypeForSubtyping()) {
return true;
}
if (Class::Handle(zone, clazz()).IsFutureClass()) {
const TypeArguments& type_arguments =
TypeArguments::Handle(zone, GetTypeArguments());
const AbstractType& type_arg =
AbstractType::Handle(zone, type_arguments.TypeAtNullSafe(0));
if (type_arg.IsSubtypeOf(other_type_arg, Heap::kOld)) {
return true;
}
}
// Retry RuntimeTypeIsSubtypeOf after unwrapping type arg of FutureOr.
if (RuntimeTypeIsSubtypeOf(other_type_arg, Object::null_type_arguments(),
Object::null_type_arguments())) {
return true;
}
}
return false;
}
bool Instance::OperatorEquals(const Instance& other) const {
// TODO(koda): Optimize for all builtin classes and all classes
// that do not override operator==.
return DartLibraryCalls::Equals(*this, other) == Object::bool_true().ptr();
}
bool Instance::IsIdenticalTo(const Instance& other) const {
if (ptr() == other.ptr()) return true;
if (IsInteger() && other.IsInteger()) {
return Integer::Cast(*this).Equals(other);
}
if (IsDouble() && other.IsDouble()) {
double other_value = Double::Cast(other).value();
return Double::Cast(*this).BitwiseEqualsToDouble(other_value);
}
return false;
}
intptr_t* Instance::NativeFieldsDataAddr() const {
ASSERT(Thread::Current()->no_safepoint_scope_depth() > 0);
TypedDataPtr native_fields = static_cast<TypedDataPtr>(*NativeFieldsAddr());
if (native_fields == TypedData::null()) {
return NULL;
}
return reinterpret_cast<intptr_t*>(native_fields->untag()->data());
}
void Instance::SetNativeField(int index, intptr_t value) const {
ASSERT(IsValidNativeIndex(index));
Object& native_fields = Object::Handle(*NativeFieldsAddr());
if (native_fields.IsNull()) {
// Allocate backing storage for the native fields.
native_fields = TypedData::New(kIntPtrCid, NumNativeFields());
StorePointer(NativeFieldsAddr(), native_fields.ptr());
}
intptr_t byte_offset = index * sizeof(intptr_t);
TypedData::Cast(native_fields).SetIntPtr(byte_offset, value);
}
void Instance::SetNativeFields(uint16_t num_native_fields,
const intptr_t* field_values) const {
ASSERT(num_native_fields == NumNativeFields());
ASSERT(field_values != NULL);
Object& native_fields = Object::Handle(*NativeFieldsAddr());
if (native_fields.IsNull()) {
// Allocate backing storage for the native fields.
native_fields = TypedData::New(kIntPtrCid, NumNativeFields());
StorePointer(NativeFieldsAddr(), native_fields.ptr());
}
for (uint16_t i = 0; i < num_native_fields; i++) {
intptr_t byte_offset = i * sizeof(intptr_t);
TypedData::Cast(native_fields).SetIntPtr(byte_offset, field_values[i]);
}
}
bool Instance::IsCallable(Function* function) const {
Class& cls = Class::Handle(clazz());
if (cls.IsClosureClass()) {
if (function != nullptr) {
*function = Closure::Cast(*this).function();
}
return true;
}
// Try to resolve a "call" method.
Zone* zone = Thread::Current()->zone();
Function& call_function = Function::Handle(
zone, Resolver::ResolveDynamicAnyArgs(zone, cls, Symbols::Call(),
/*allow_add=*/false));
if (call_function.IsNull()) {
return false;
}
if (function != nullptr) {
*function = call_function.ptr();
}
return true;
}
InstancePtr Instance::New(const Class& cls, Heap::Space space) {
Thread* thread = Thread::Current();
if (cls.EnsureIsAllocateFinalized(thread) != Error::null()) {
return Instance::null();
}
intptr_t instance_size = cls.host_instance_size();
ASSERT(instance_size > 0);
ObjectPtr raw =
Object::Allocate(cls.id(), instance_size, space, /*compressed*/ false);
return static_cast<InstancePtr>(raw);
}
InstancePtr Instance::NewFromCidAndSize(SharedClassTable* shared_class_table,
classid_t cid,
Heap::Space heap) {
const intptr_t instance_size = shared_class_table->SizeAt(cid);
ASSERT(instance_size > 0);
ObjectPtr raw =
Object::Allocate(cid, instance_size, heap, /*compressed*/ false);
return static_cast<InstancePtr>(raw);
}
bool Instance::IsValidFieldOffset(intptr_t offset) const {
Thread* thread = Thread::Current();
REUSABLE_CLASS_HANDLESCOPE(thread);
Class& cls = thread->ClassHandle();
cls = clazz();
return (offset >= 0 && offset <= (cls.host_instance_size() - kWordSize));
}
intptr_t Instance::ElementSizeFor(intptr_t cid) {
if (IsExternalTypedDataClassId(cid) || IsTypedDataClassId(cid) ||
IsTypedDataViewClassId(cid)) {
return TypedDataBase::ElementSizeInBytes(cid);
}
switch (cid) {
case kArrayCid:
case kImmutableArrayCid:
return Array::kBytesPerElement;
case kTypeArgumentsCid:
return TypeArguments::ArrayTraits::kElementSize;
case kOneByteStringCid:
return OneByteString::kBytesPerElement;
case kTwoByteStringCid:
return TwoByteString::kBytesPerElement;
case kExternalOneByteStringCid:
return ExternalOneByteString::kBytesPerElement;
case kExternalTwoByteStringCid:
return ExternalTwoByteString::kBytesPerElement;
default:
UNIMPLEMENTED();
return 0;
}
}
intptr_t Instance::DataOffsetFor(intptr_t cid) {
if (IsExternalTypedDataClassId(cid) || IsExternalStringClassId(cid)) {
// Elements start at offset 0 of the external data.
return 0;
}
if (IsTypedDataClassId(cid)) {
return TypedData::data_offset();
}
switch (cid) {
case kArrayCid:
case kImmutableArrayCid:
return Array::data_offset();
case kTypeArgumentsCid:
return TypeArguments::types_offset();
case kOneByteStringCid:
return OneByteString::data_offset();
case kTwoByteStringCid:
return TwoByteString::data_offset();
default:
UNIMPLEMENTED();
return Array::data_offset();
}
}
const char* Instance::ToCString() const {
if (IsNull()) {
return "null";
} else if (ptr() == Object::sentinel().ptr()) {
return "sentinel";
} else if (ptr() == Object::transition_sentinel().ptr()) {
return "transition_sentinel";
} else if (ptr() == Object::unknown_constant().ptr()) {
return "unknown_constant";
} else if (ptr() == Object::non_constant().ptr()) {
return "non_constant";
} else if (Thread::Current()->no_safepoint_scope_depth() > 0) {
// Can occur when running disassembler.
return "Instance";
} else {
if (IsClosure()) {
return Closure::Cast(*this).ToCString();
}
// Background compiler disassembly of instructions referring to pool objects
// calls this function and requires allocation of Type in old space.
const AbstractType& type = AbstractType::Handle(GetType(Heap::kOld));
const String& type_name = String::Handle(type.UserVisibleName());
return OS::SCreate(Thread::Current()->zone(), "Instance of '%s'",
type_name.ToCString());
}
}
classid_t AbstractType::type_class_id() const {
// AbstractType is an abstract class.
UNREACHABLE();
return kIllegalCid;
}
ClassPtr AbstractType::type_class() const {
// AbstractType is an abstract class.
UNREACHABLE();
return Class::null();
}
TypeArgumentsPtr AbstractType::arguments() const {
// AbstractType is an abstract class.
UNREACHABLE();
return NULL;
}
void AbstractType::set_arguments(const TypeArguments& value) const {
// AbstractType is an abstract class.
UNREACHABLE();
}
Nullability AbstractType::nullability() const {
// AbstractType is an abstract class.
UNREACHABLE();
return Nullability::kNullable;
}
bool AbstractType::IsStrictlyNonNullable() const {
// Null can be assigned to legacy and nullable types.
if (!IsNonNullable()) {
return false;
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
// In weak mode null can be assigned to any type.
if (!thread->isolate_group()->null_safety()) {
return false;
}
if (IsTypeParameter()) {
const auto& bound =
AbstractType::Handle(zone, TypeParameter::Cast(*this).bound());
ASSERT(!bound.IsNull());
return bound.IsStrictlyNonNullable();
}
if (IsFutureOrType()) {
return AbstractType::Handle(zone, UnwrapFutureOr()).IsStrictlyNonNullable();
}
return true;
}
AbstractTypePtr AbstractType::SetInstantiatedNullability(
const TypeParameter& type_param,
Heap::Space space) const {
Nullability result_nullability;
const Nullability arg_nullability = nullability();
const Nullability var_nullability = type_param.nullability();
// Adjust nullability of result 'arg' instantiated from 'var'.
// arg/var ! ? *
// ! ! ? *
// ? ? ? ?
// * * ? *
if (var_nullability == Nullability::kNullable ||
arg_nullability == Nullability::kNullable) {
result_nullability = Nullability::kNullable;
} else if (var_nullability == Nullability::kLegacy ||
arg_nullability == Nullability::kLegacy) {
result_nullability = Nullability::kLegacy;
} else {
// Keep arg nullability.
return ptr();
}
if (arg_nullability == result_nullability) {
return ptr();
}
if (IsType()) {
return Type::Cast(*this).ToNullability(result_nullability, space);
}
if (IsFunctionType()) {
return FunctionType::Cast(*this).ToNullability(result_nullability, space);
}
if (IsTypeParameter()) {
return TypeParameter::Cast(*this).ToNullability(result_nullability, space);
}
// TODO(regis): TypeRefs are problematic, since changing the nullability of
// a type by cloning it may break the graph of a recursive type.
ASSERT(IsTypeRef());
return AbstractType::Handle(TypeRef::Cast(*this).type())
.SetInstantiatedNullability(type_param, space);
}
AbstractTypePtr AbstractType::NormalizeFutureOrType(Heap::Space space) const {
if (IsFutureOrType()) {
Zone* zone = Thread::Current()->zone();
const AbstractType& unwrapped_type =
AbstractType::Handle(zone, UnwrapFutureOr());
const classid_t cid = unwrapped_type.type_class_id();
if (cid == kDynamicCid || cid == kVoidCid) {
return unwrapped_type.ptr();
}
if (cid == kInstanceCid) {
if (IsNonNullable()) {
return unwrapped_type.ptr();
}
if (IsNullable() || unwrapped_type.IsNullable()) {
return Type::Cast(unwrapped_type)
.ToNullability(Nullability::kNullable, space);
}
return Type::Cast(unwrapped_type)
.ToNullability(Nullability::kLegacy, space);
}
if (cid == kNeverCid && unwrapped_type.IsNonNullable()) {
ObjectStore* object_store = IsolateGroup::Current()->object_store();
const Type& future_never_type =
Type::Handle(zone, object_store->non_nullable_future_never_type());
ASSERT(!future_never_type.IsNull());
return future_never_type.ToNullability(nullability(), space);
}
if (cid == kNullCid) {
ObjectStore* object_store = IsolateGroup::Current()->object_store();
ASSERT(object_store->nullable_future_null_type() != Type::null());
return object_store->nullable_future_null_type();
}
if (IsNullable() && unwrapped_type.IsNullable()) {
return Type::Cast(*this).ToNullability(Nullability::kNonNullable, space);
}
}
return ptr();
}
bool AbstractType::IsInstantiated(Genericity genericity,
intptr_t num_free_fun_type_params,
TrailPtr trail) const {
// AbstractType is an abstract class.
UNREACHABLE();
return false;
}
bool AbstractType::IsFinalized() const {
// AbstractType is an abstract class.
UNREACHABLE();
return false;
}
void AbstractType::SetIsFinalized() const {
// AbstractType is an abstract class.
UNREACHABLE();
}
bool AbstractType::IsBeingFinalized() const {
// AbstractType is an abstract class.
UNREACHABLE();
return false;
}
void AbstractType::SetIsBeingFinalized() const {
// AbstractType is an abstract class.
UNREACHABLE();
}
bool AbstractType::IsEquivalent(const Instance& other,
TypeEquality kind,
TrailPtr trail) const {
// AbstractType is an abstract class.
UNREACHABLE();
return false;
}
bool AbstractType::IsRecursive(TrailPtr trail) const {
// AbstractType is an abstract class.
UNREACHABLE();
return false;
}
bool AbstractType::RequireConstCanonicalTypeErasure(Zone* zone,
TrailPtr trail) const {
// AbstractType is an abstract class.
UNREACHABLE();
return false;
}
AbstractTypePtr AbstractType::InstantiateFrom(
const TypeArguments& instantiator_type_arguments,
const TypeArguments& function_type_arguments,
intptr_t num_free_fun_type_params,
Heap::Space space,
TrailPtr trail) const {
// AbstractType is an abstract class.
UNREACHABLE();
return NULL;
}
AbstractTypePtr AbstractType::Canonicalize(Thread* thread,
TrailPtr trail) const {
// AbstractType is an abstract class.
UNREACHABLE();
return NULL;
}
void AbstractType::EnumerateURIs(URIs* uris) const {
// AbstractType is an abstract class.
UNREACHABLE();
}
AbstractTypePtr AbstractType::OnlyBuddyInTrail(TrailPtr trail) const {
if (trail == NULL) {
return AbstractType::null();
}
const intptr_t len = trail->length();
ASSERT((len % 2) == 0);
for (intptr_t i = 0; i < len; i += 2) {
ASSERT(trail->At(i).IsZoneHandle());
ASSERT(trail->At(i + 1).IsZoneHandle());
if (trail->At(i).ptr() == this->ptr()) {
ASSERT(!trail->At(i + 1).IsNull());
return trail->At(i + 1).ptr();
}
}
return AbstractType::null();
}
void AbstractType::AddOnlyBuddyToTrail(TrailPtr* trail,
const AbstractType& buddy) const {
if (*trail == NULL) {
*trail = new Trail(Thread::Current()->zone(), 4);
} else {
ASSERT(OnlyBuddyInTrail(*trail) == AbstractType::null());
}
(*trail)->Add(*this);
(*trail)->Add(buddy);
}
bool AbstractType::TestAndAddToTrail(TrailPtr* trail) const {
if (*trail == NULL) {
*trail = new Trail(Thread::Current()->zone(), 4);
} else {
const intptr_t len = (*trail)->length();
for (intptr_t i = 0; i < len; i++) {
if ((*trail)->At(i).ptr() == this->ptr()) {
return true;
}
}
}
(*trail)->Add(*this);
return false;
}
bool AbstractType::TestAndAddBuddyToTrail(TrailPtr* trail,
const AbstractType& buddy) const {
if (*trail == NULL) {
*trail = new Trail(Thread::Current()->zone(), 4);
} else {
const intptr_t len = (*trail)->length();
ASSERT((len % 2) == 0);
const bool this_is_typeref = IsTypeRef();
const bool buddy_is_typeref = buddy.IsTypeRef();
// Note that at least one of 'this' and 'buddy' should be a typeref, with
// one exception, when the class of the 'this' type implements the 'call'
// method, thereby possibly creating a recursive type (see regress_29405).
for (intptr_t i = 0; i < len; i += 2) {
if ((((*trail)->At(i).ptr() == this->ptr()) ||
(buddy_is_typeref && (*trail)->At(i).Equals(*this))) &&
(((*trail)->At(i + 1).ptr() == buddy.ptr()) ||
(this_is_typeref && (*trail)->At(i + 1).Equals(buddy)))) {
return true;
}
}
}
(*trail)->Add(*this);
(*trail)->Add(buddy);
return false;
}
void AbstractType::AddURI(URIs* uris, const String& name, const String& uri) {
ASSERT(uris != NULL);
const intptr_t len = uris->length();
ASSERT((len % 3) == 0);
bool print_uri = false;
for (intptr_t i = 0; i < len; i += 3) {
if (uris->At(i).Equals(name)) {
if (uris->At(i + 1).Equals(uri)) {
// Same name and same URI: no need to add this already listed URI.
return; // No state change is possible.
} else {
// Same name and different URI: the name is ambiguous, print both URIs.
print_uri = true;
uris->SetAt(i + 2, Symbols::print());
}
}
}
uris->Add(name);
uris->Add(uri);
if (print_uri) {
uris->Add(Symbols::print());
} else {
uris->Add(Symbols::Empty());
}
}
StringPtr AbstractType::PrintURIs(URIs* uris) {
ASSERT(uris != NULL);
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const intptr_t len = uris->length();
ASSERT((len % 3) == 0);
GrowableHandlePtrArray<const String> pieces(zone, 5 * (len / 3));
for (intptr_t i = 0; i < len; i += 3) {
// Only print URIs that have been marked.
if (uris->At(i + 2).ptr() == Symbols::print().ptr()) {
pieces.Add(Symbols::TwoSpaces());
pieces.Add(uris->At(i));
pieces.Add(Symbols::SpaceIsFromSpace());
pieces.Add(uris->At(i + 1));
pieces.Add(Symbols::NewLine());
}
}
return Symbols::FromConcatAll(thread, pieces);
}
const char* AbstractType::NullabilitySuffix(
NameVisibility name_visibility) const {
if (IsDynamicType() || IsVoidType() || IsNullType()) {
// Hide nullable suffix.
return "";
}
// Keep in sync with Nullability enum in runtime/vm/object.h.
switch (nullability()) {
case Nullability::kNullable:
return "?";
case Nullability::kNonNullable:
return "";
case Nullability::kLegacy:
return (FLAG_show_internal_names || name_visibility != kUserVisibleName)
? "*"
: "";
default:
UNREACHABLE();
}
}
StringPtr AbstractType::Name() const {
Thread* thread = Thread::Current();
ZoneTextBuffer printer(thread->zone());
PrintName(kInternalName, &printer);
return Symbols::New(thread, printer.buffer());
}
StringPtr AbstractType::UserVisibleName() const {
Thread* thread = Thread::Current();
ZoneTextBuffer printer(thread->zone());
PrintName(kUserVisibleName, &printer);
return Symbols::New(thread, printer.buffer());
}
StringPtr AbstractType::ScrubbedName() const {
Thread* thread = Thread::Current();
ZoneTextBuffer printer(thread->zone());
PrintName(kScrubbedName, &printer);
return Symbols::New(thread, printer.buffer());
}
void AbstractType::PrintName(NameVisibility name_visibility,
BaseTextBuffer* printer) const {
if (IsTypeRef()) {
// Cycles via base class type arguments are not a problem (not printed).
const AbstractType& ref_type =
AbstractType::Handle(TypeRef::Cast(*this).type());
ref_type.PrintName(name_visibility, printer);
return;
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Class& cls = Class::Handle(zone);
if (IsTypeParameter()) {
const TypeParameter& type_param = TypeParameter::Cast(*this);
printer->AddString(String::Handle(type_param.name()).ToCString());
printer->AddString(NullabilitySuffix(name_visibility));
return;
}
if (IsFunctionType()) {
const char* suffix = NullabilitySuffix(name_visibility);
if (suffix[0] != '\0') {
printer->AddString("(");
}
FunctionType::Cast(*this).Print(name_visibility, printer);
if (suffix[0] != '\0') {
printer->AddString(")");
printer->AddString(suffix);
}
return;
}
const TypeArguments& args = TypeArguments::Handle(zone, arguments());
const intptr_t num_args = args.IsNull() ? 0 : args.Length();
intptr_t first_type_param_index;
intptr_t num_type_params; // Number of type parameters to print.
cls = type_class();
// Do not print the full vector, but only the declared type parameters.
num_type_params = cls.NumTypeParameters();
printer->AddString(cls.NameCString(name_visibility));
if (num_type_params > num_args) {
first_type_param_index = 0;
if (!IsFinalized() || IsBeingFinalized()) {
// TODO(regis): Check if this is dead code.
num_type_params = num_args;
} else {
ASSERT(num_args == 0); // Type is raw.
}
} else {
// The actual type argument vector can be longer than necessary, because
// of type optimizations.
if (IsFinalized() && cls.is_type_finalized()) {
first_type_param_index = cls.NumTypeArguments() - num_type_params;
} else {
first_type_param_index = num_args - num_type_params;
}
}
if (num_type_params == 0) {
// Do nothing.
} else {
args.PrintSubvectorName(first_type_param_index, num_type_params,
name_visibility, printer);
}
printer->AddString(NullabilitySuffix(name_visibility));
// The name is only used for type checking and debugging purposes.
// Unless profiling data shows otherwise, it is not worth caching the name in
// the type.
}
StringPtr AbstractType::ClassName() const {
ASSERT(!IsFunctionType());
return Class::Handle(type_class()).Name();
}
bool AbstractType::IsNullTypeRef() const {
return IsTypeRef() && (TypeRef::Cast(*this).type() == AbstractType::null());
}
bool AbstractType::IsNullType() const {
return type_class_id() == kNullCid;
}
bool AbstractType::IsNeverType() const {
return type_class_id() == kNeverCid;
}
bool AbstractType::IsTopTypeForInstanceOf() const {
const classid_t cid = type_class_id();
if (cid == kDynamicCid || cid == kVoidCid) {
return true;
}
if (cid == kInstanceCid) { // Object type.
return !IsNonNullable(); // kLegacy or kNullable.
}
if (cid == kFutureOrCid) {
// FutureOr<T> where T is a top type behaves as a top type.
return AbstractType::Handle(UnwrapFutureOr()).IsTopTypeForInstanceOf();
}
return false;
}
// Must be kept in sync with GenerateTypeIsTopTypeForSubtyping in
// stub_code_compiler.cc if any changes are made.
bool AbstractType::IsTopTypeForSubtyping() const {
const classid_t cid = type_class_id();
if (cid == kDynamicCid || cid == kVoidCid) {
return true;
}
if (cid == kInstanceCid) { // Object type.
// NNBD weak mode uses LEGACY_SUBTYPE for assignability / 'as' tests,
// and non-nullable Object is a top type according to LEGACY_SUBTYPE.
return !IsNonNullable() ||
!IsolateGroup::Current()->use_strict_null_safety_checks();
}
if (cid == kFutureOrCid) {
// FutureOr<T> where T is a top type behaves as a top type.
return AbstractType::Handle(UnwrapFutureOr()).IsTopTypeForSubtyping();
}
return false;
}
bool AbstractType::IsIntType() const {
return HasTypeClass() &&
(type_class() == Type::Handle(Type::IntType()).type_class());
}
bool AbstractType::IsIntegerImplementationType() const {
return HasTypeClass() &&
(type_class() == IsolateGroup::Current()
->object_store()
->integer_implementation_class());
}
bool AbstractType::IsDoubleType() const {
return HasTypeClass() &&
(type_class() == Type::Handle(Type::Double()).type_class());
}
bool AbstractType::IsFloat32x4Type() const {
// kFloat32x4Cid refers to the private class and cannot be used here.
return HasTypeClass() &&
(type_class() == Type::Handle(Type::Float32x4()).type_class());
}
bool AbstractType::IsFloat64x2Type() const {
// kFloat64x2Cid refers to the private class and cannot be used here.
return HasTypeClass() &&
(type_class() == Type::Handle(Type::Float64x2()).type_class());
}
bool AbstractType::IsInt32x4Type() const {
// kInt32x4Cid refers to the private class and cannot be used here.
return HasTypeClass() &&
(type_class() == Type::Handle(Type::Int32x4()).type_class());
}
bool AbstractType::IsStringType() const {
return HasTypeClass() &&
(type_class() == Type::Handle(Type::StringType()).type_class());
}
bool AbstractType::IsDartFunctionType() const {
return HasTypeClass() &&
(type_class() == Type::Handle(Type::DartFunctionType()).type_class());
}
bool AbstractType::IsDartClosureType() const {
return (type_class_id() == kClosureCid);
}
bool AbstractType::IsFfiPointerType() const {
return HasTypeClass() && type_class_id() == kFfiPointerCid;
}
AbstractTypePtr AbstractType::UnwrapFutureOr() const {
if (!IsFutureOrType()) {
return ptr();
}
if (arguments() == TypeArguments::null()) {
return Type::dynamic_type().ptr();
}
Thread* thread = Thread::Current();
REUSABLE_TYPE_ARGUMENTS_HANDLESCOPE(thread);
TypeArguments& type_args = thread->TypeArgumentsHandle();
type_args = arguments();
REUSABLE_ABSTRACT_TYPE_HANDLESCOPE(thread);
AbstractType& type_arg = thread->AbstractTypeHandle();
type_arg = type_args.TypeAt(0);
while (type_arg.IsFutureOrType()) {
if (type_arg.arguments() == TypeArguments::null()) {
return Type::dynamic_type().ptr();
}
type_args = type_arg.arguments();
type_arg = type_args.TypeAt(0);
}
return type_arg.ptr();
}
bool AbstractType::IsSubtypeOf(const AbstractType& other,
Heap::Space space,
TrailPtr trail) const {
ASSERT(IsFinalized());
ASSERT(other.IsFinalized());
// Reflexivity.
if (ptr() == other.ptr()) {
return true;
}
// Right top type.
if (other.IsTopTypeForSubtyping()) {
return true;
}
// Left bottom type.
// Any form of Never in weak mode maps to Null and Null is a bottom type in
// weak mode. In strong mode, Never and Never* are bottom types. Therefore,
// Never and Never* are bottom types regardless of weak/strong mode.
// Note that we cannot encounter Never?, as it is normalized to Null.
if (IsNeverType()) {
ASSERT(!IsNullable());
return true;
}
// Left top type.
if (IsDynamicType() || IsVoidType()) {
return false;
}
// Left Null type.
if (IsNullType()) {
return Instance::NullIsAssignableTo(other);
}
Thread* thread = Thread::Current();
auto isolate_group = thread->isolate_group();
Zone* zone = thread->zone();
// Type parameters cannot be handled by Class::IsSubtypeOf().
// When comparing two uninstantiated function types, one returning type
// parameter K, the other returning type parameter V, we cannot assume that
// K is a subtype of V, or vice versa. We only return true if K equals V, as
// defined by TypeParameter::Equals.
// The same rule applies when checking the upper bound of a still
// uninstantiated type at compile time. Returning false will defer the test
// to run time.
// There are however some cases that can be decided at compile time.
// For example, with class A<K, V extends K>, new A<T, T> called from within
// a class B<T> will never require a run time bound check, even if T is
// uninstantiated at compile time.
if (IsTypeParameter()) {
const TypeParameter& type_param = TypeParameter::Cast(*this);
if (other.IsTypeParameter()) {
const TypeParameter& other_type_param = TypeParameter::Cast(other);
if (type_param.IsEquivalent(other_type_param,
TypeEquality::kInSubtypeTest)) {
return true;
}
}
const AbstractType& bound = AbstractType::Handle(zone, type_param.bound());
ASSERT(bound.IsFinalized());
// Avoid cycles with F-bounded types.
if (TestAndAddBuddyToTrail(&trail, other)) {
return true;
}
if (bound.IsSubtypeOf(other, space, trail)) {
return true;
}
// Apply additional subtyping rules if 'other' is 'FutureOr'.
if (IsSubtypeOfFutureOr(zone, other, space, trail)) {
return true;
}
return false;
}
if (other.IsTypeParameter()) {
return false;
}
// Function types cannot be handled by Class::IsSubtypeOf().
const bool other_is_dart_function_type = other.IsDartFunctionType();
if (other_is_dart_function_type || other.IsFunctionType()) {
if (IsFunctionType()) {
if (isolate_group->use_strict_null_safety_checks() && IsNullable() &&
other.IsNonNullable()) {
return false;
}
if (other_is_dart_function_type) {
return true;
}
// Check for two function types.
return FunctionType::Cast(*this).IsSubtypeOf(FunctionType::Cast(other),
space);
}
if (other.IsFunctionType()) {
// [this] is not a function type. Therefore, non-function type [this]
// cannot be a subtype of function type [other].
// This check is needed to avoid falling through to class-based type
// tests, which yield incorrect result if [this] = _Closure class,
// and [other] is a function type, because class of a function type is
// also _Closure.
return false;
}
}
if (IsFunctionType()) {
// Apply additional subtyping rules if 'other' is 'FutureOr'.
if (IsSubtypeOfFutureOr(zone, other, space, trail)) {
return true;
}
return false;
}
const Class& type_cls = Class::Handle(zone, type_class());
return Class::IsSubtypeOf(type_cls, TypeArguments::Handle(zone, arguments()),
nullability(), other, space, trail);
}
bool AbstractType::IsSubtypeOfFutureOr(Zone* zone,
const AbstractType& other,
Heap::Space space,
TrailPtr trail) const {
if (other.IsFutureOrType()) {
// This function is only called with a receiver that is either a function
// type or an uninstantiated type parameter, therefore, it cannot be of
// class Future and we can spare the check.
ASSERT(IsFunctionType() || IsTypeParameter());
const TypeArguments& other_type_arguments =
TypeArguments::Handle(zone, other.arguments());
const AbstractType& other_type_arg =
AbstractType::Handle(zone, other_type_arguments.TypeAtNullSafe(0));
if (other_type_arg.IsTopTypeForSubtyping()) {
return true;
}
// Retry the IsSubtypeOf check after unwrapping type arg of FutureOr.
if (IsSubtypeOf(other_type_arg, space, trail)) {
return true;
}
}
return false;
}
intptr_t AbstractType::Hash() const {
// AbstractType is an abstract class.
UNREACHABLE();
return 0;
}
const char* AbstractType::ToCString() const {
if (IsNull()) {
return "AbstractType: null";
}
// AbstractType is an abstract class.
UNREACHABLE();
return "AbstractType";
}
void AbstractType::SetTypeTestingStub(const Code& stub) const {
if (stub.IsNull()) {
// This only happens during bootstrapping when creating Type objects before
// we have the instructions.
ASSERT(type_class_id() == kDynamicCid || type_class_id() == kVoidCid);
StoreNonPointer(&untag()->type_test_stub_entry_point_, 0);
untag()->set_type_test_stub(stub.ptr());
return;
}
Thread* thread = Thread::Current();
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
StoreNonPointer(&untag()->type_test_stub_entry_point_, stub.EntryPoint());
untag()->set_type_test_stub(stub.ptr());
}
TypePtr Type::NullType() {
return IsolateGroup::Current()->object_store()->null_type();
}
TypePtr Type::DynamicType() {
return Object::dynamic_type().ptr();
}
TypePtr Type::VoidType() {
return Object::void_type().ptr();
}
TypePtr Type::NeverType() {
return IsolateGroup::Current()->object_store()->never_type();
}
TypePtr Type::ObjectType() {
return IsolateGroup::Current()->object_store()->object_type();
}
TypePtr Type::BoolType() {
return IsolateGroup::Current()->object_store()->bool_type();
}
TypePtr Type::IntType() {
return IsolateGroup::Current()->object_store()->int_type();
}
TypePtr Type::NullableIntType() {
return IsolateGroup::Current()->object_store()->nullable_int_type();
}
TypePtr Type::SmiType() {
return IsolateGroup::Current()->object_store()->smi_type();
}
TypePtr Type::MintType() {
return IsolateGroup::Current()->object_store()->mint_type();
}
TypePtr Type::Double() {
return IsolateGroup::Current()->object_store()->double_type();
}
TypePtr Type::NullableDouble() {
return IsolateGroup::Current()->object_store()->nullable_double_type();
}
TypePtr Type::Float32x4() {
return IsolateGroup::Current()->object_store()->float32x4_type();
}
TypePtr Type::Float64x2() {
return IsolateGroup::Current()->object_store()->float64x2_type();
}
TypePtr Type::Int32x4() {
return IsolateGroup::Current()->object_store()->int32x4_type();
}
TypePtr Type::Number() {
return IsolateGroup::Current()->object_store()->number_type();
}
TypePtr Type::StringType() {
return IsolateGroup::Current()->object_store()->string_type();
}
TypePtr Type::ArrayType() {
return IsolateGroup::Current()->object_store()->array_type();
}
TypePtr Type::DartFunctionType() {
return IsolateGroup::Current()->object_store()->function_type();
}
TypePtr Type::DartTypeType() {
return IsolateGroup::Current()->object_store()->type_type();
}
TypePtr Type::NewNonParameterizedType(const Class& type_class) {
ASSERT(type_class.NumTypeArguments() == 0);
if (type_class.IsNullClass()) {
return Type::NullType();
}
if (type_class.IsDynamicClass()) {
return Type::DynamicType();
}
if (type_class.IsVoidClass()) {
return Type::VoidType();
}
// It is too early to use the class finalizer, as type_class may not be named
// yet, so do not call DeclarationType().
Type& type = Type::Handle(type_class.declaration_type());
if (type.IsNull()) {
type = Type::New(Class::Handle(type_class.ptr()),
Object::null_type_arguments(), Nullability::kNonNullable);
type.SetIsFinalized();
type ^= type.Canonicalize(Thread::Current(), nullptr);
type_class.set_declaration_type(type);
}
ASSERT(type.IsFinalized());
return type.ptr();
}
void Type::SetIsFinalized() const {
ASSERT(!IsFinalized());
if (IsInstantiated()) {
set_type_state(UntaggedType::kFinalizedInstantiated);
} else {
set_type_state(UntaggedType::kFinalizedUninstantiated);
}
}
void FunctionType::SetIsFinalized() const {
ASSERT(!IsFinalized());
if (IsInstantiated()) {
set_type_state(UntaggedFunctionType::kFinalizedInstantiated);
} else {
set_type_state(UntaggedFunctionType::kFinalizedUninstantiated);
}
}
void Type::SetIsBeingFinalized() const {
ASSERT(!IsFinalized() && !IsBeingFinalized());
set_type_state(UntaggedType::kBeingFinalized);
}
void FunctionType::SetIsBeingFinalized() const {
ASSERT(!IsFinalized() && !IsBeingFinalized());
set_type_state(UntaggedFunctionType::kBeingFinalized);
}
TypePtr Type::ToNullability(Nullability value, Heap::Space space) const {
if (nullability() == value) {
return ptr();
}
// Type parameter instantiation may request a nullability change, which should
// be ignored for types dynamic and void. Type Null cannot be the result of
// instantiating a non-nullable type parameter (TypeError thrown).
const classid_t cid = type_class_id();
if (cid == kDynamicCid || cid == kVoidCid || cid == kNullCid) {
return ptr();
}
if (cid == kNeverCid && value == Nullability::kNullable) {
// Normalize Never? to Null.
return Type::NullType();
}
// Clone type and set new nullability.
Type& type = Type::Handle();
// Always cloning in old space and removing space parameter would not satisfy
// currently existing requests for type instantiation in new space.
type ^= Object::Clone(*this, space);
type.set_nullability(value);
type.SetHash(0);
type.SetTypeTestingStub(
Code::Handle(TypeTestingStubGenerator::DefaultCodeForType(type)));
if (IsCanonical()) {
// Object::Clone does not clone canonical bit.
ASSERT(!type.IsCanonical());
type ^= type.Canonicalize(Thread::Current(), nullptr);
}
return type.ptr();
}
FunctionTypePtr FunctionType::ToNullability(Nullability value,
Heap::Space space) const {
if (nullability() == value) {
return ptr();
}
// Clone function type and set new nullability.
FunctionType& type = FunctionType::Handle();
// Always cloning in old space and removing space parameter would not satisfy
// currently existing requests for type instantiation in new space.
type ^= Object::Clone(*this, space);
type.set_nullability(value);
type.SetHash(0);
type.SetTypeTestingStub(
Code::Handle(TypeTestingStubGenerator::DefaultCodeForType(type)));
if (IsCanonical()) {
// Object::Clone does not clone canonical bit.
ASSERT(!type.IsCanonical());
type ^= type.Canonicalize(Thread::Current(), nullptr);
}
return type.ptr();
}
classid_t Type::type_class_id() const {
return Smi::Value(untag()->type_class_id());
}
ClassPtr Type::type_class() const {
return IsolateGroup::Current()->class_table()->At(type_class_id());
}
bool Type::IsInstantiated(Genericity genericity,
intptr_t num_free_fun_type_params,
TrailPtr trail) const {
if (untag()->type_state_ == UntaggedType::kFinalizedInstantiated) {
return true;
}
if ((genericity == kAny) && (num_free_fun_type_params == kAllFree) &&
(untag()->type_state_ == UntaggedType::kFinalizedUninstantiated)) {
return false;
}
if (arguments() == TypeArguments::null()) {
return true;
}
const TypeArguments& args = TypeArguments::Handle(arguments());
intptr_t num_type_args = args.Length();
intptr_t len = num_type_args; // Check the full vector of type args.
ASSERT(num_type_args > 0);
// This type is not instantiated if it refers to type parameters.
const Class& cls = Class::Handle(type_class());
len = cls.NumTypeParameters(); // Check the type parameters only.
if (len > num_type_args) {
// This type has the wrong number of arguments and is not finalized yet.
// Type arguments are reset to null when finalizing such a type.
ASSERT(!IsFinalized());
len = num_type_args;
}
return (len == 0) ||
args.IsSubvectorInstantiated(num_type_args - len, len, genericity,
num_free_fun_type_params, trail);
}
AbstractTypePtr Type::InstantiateFrom(
const TypeArguments& instantiator_type_arguments,
const TypeArguments& function_type_arguments,
intptr_t num_free_fun_type_params,
Heap::Space space,
TrailPtr trail) const {
Zone* zone = Thread::Current()->zone();
ASSERT(IsFinalized() || IsBeingFinalized());
ASSERT(!IsInstantiated());
// Note that the type class has to be resolved at this time, but not
// necessarily finalized yet. We may be checking bounds at compile time or
// finalizing the type argument vector of a recursive type.
const Class& cls = Class::Handle(zone, type_class());
TypeArguments& type_arguments = TypeArguments::Handle(zone, arguments());
ASSERT(type_arguments.Length() == cls.NumTypeArguments());
type_arguments = type_arguments.InstantiateFrom(
instantiator_type_arguments, function_type_arguments,
num_free_fun_type_params, space, trail);
// A returned empty_type_arguments indicates a failed instantiation in dead
// code that must be propagated up to the caller, the optimizing compiler.
if (type_arguments.ptr() == Object::empty_type_arguments().ptr()) {
return Type::null();
}
// This uninstantiated type is not modified, as it can be instantiated
// with different instantiators. Allocate a new instantiated version of it.
const Type& instantiated_type =
Type::Handle(zone, Type::New(cls, type_arguments, nullability(), space));
if (IsFinalized()) {
instantiated_type.SetIsFinalized();
} else {
if (IsBeingFinalized()) {
instantiated_type.SetIsBeingFinalized();
}
}
// Canonicalization is not part of instantiation.
return instantiated_type.NormalizeFutureOrType(space);
}
bool Type::IsEquivalent(const Instance& other,
TypeEquality kind,
TrailPtr trail) const {
ASSERT(!IsNull());
if (ptr() == other.ptr()) {
return true;
}
if (other.IsTypeRef()) {
// Unfold right hand type. Divergence is controlled by left hand type.
const AbstractType& other_ref_type =
AbstractType::Handle(TypeRef::Cast(other).type());
ASSERT(!other_ref_type.IsTypeRef());
return IsEquivalent(other_ref_type, kind, trail);
}
if (!other.IsType()) {
return false;
}
const Type& other_type = Type::Cast(other);
if (type_class_id() != other_type.type_class_id()) {
return false;
}
Nullability this_type_nullability = nullability();
Nullability other_type_nullability = other_type.nullability();
Thread* thread = Thread::Current();
auto isolate_group = thread->isolate_group();
Zone* zone = thread->zone();
if (kind == TypeEquality::kInSubtypeTest) {
if (isolate_group->use_strict_null_safety_checks() &&
this_type_nullability == Nullability::kNullable &&
other_type_nullability == Nullability::kNonNullable) {
return false;
}
} else {
if (kind == TypeEquality::kSyntactical) {
if (this_type_nullability == Nullability::kLegacy) {
this_type_nullability = Nullability::kNonNullable;
}
if (other_type_nullability == Nullability::kLegacy) {
other_type_nullability = Nullability::kNonNullable;
}
} else {
ASSERT(kind == TypeEquality::kCanonical);
ASSERT(IsFinalized() && other_type.IsFinalized());
}
if (this_type_nullability != other_type_nullability) {
return false;
}
}
if (!IsFinalized() || !other_type.IsFinalized()) {
return false; // Too early to decide if equal.
}
if (arguments() == other_type.arguments()) {
return true;
}
if (arguments() != other_type.arguments()) {
const Class& cls = Class::Handle(zone, type_class());
const intptr_t num_type_params = cls.NumTypeParameters(thread);
// Shortcut unnecessary handle allocation below if non-generic.
if (num_type_params > 0) {
const intptr_t num_type_args = cls.NumTypeArguments();
const intptr_t from_index = num_type_args - num_type_params;
const TypeArguments& type_args = TypeArguments::Handle(zone, arguments());
const TypeArguments& other_type_args =
TypeArguments::Handle(zone, other_type.arguments());
if (type_args.IsNull()) {
// Ignore from_index.
if (!other_type_args.IsRaw(0, num_type_args)) {
return false;
}
} else if (other_type_args.IsNull()) {
// Ignore from_index.
if (!type_args.IsRaw(0, num_type_args)) {
return false;
}
} else if (!type_args.IsSubvectorEquivalent(other_type_args, from_index,
num_type_params, kind,
trail)) {
return false;
}
#ifdef DEBUG
if ((from_index > 0) && !type_args.IsNull() &&
!other_type_args.IsNull()) {
// Verify that the type arguments of the super class match, since they
// depend solely on the type parameters that were just verified to
// match.
ASSERT(type_args.Length() >= (from_index + num_type_params));
ASSERT(other_type_args.Length() >= (from_index + num_type_params));
AbstractType& type_arg = AbstractType::Handle(zone);
AbstractType& other_type_arg = AbstractType::Handle(zone);
for (intptr_t i = 0; i < from_index; i++) {
type_arg = type_args.TypeAt(i);
other_type_arg = other_type_args.TypeAt(i);
ASSERT(type_arg.IsEquivalent(other_type_arg, kind, trail));
}
}
#endif
}
}
return true;
}
bool FunctionType::IsEquivalent(const Instance& other,
TypeEquality kind,
TrailPtr trail) const {
ASSERT(!IsNull());
if (ptr() == other.ptr()) {
return true;
}
if (other.IsTypeRef()) {
// Unfold right hand type. Divergence is controlled by left hand type.
const AbstractType& other_ref_type =
AbstractType::Handle(TypeRef::Cast(other).type());
ASSERT(!other_ref_type.IsTypeRef());
return IsEquivalent(other_ref_type, kind, trail);
}
if (!other.IsFunctionType()) {
return false;
}
const FunctionType& other_type = FunctionType::Cast(other);
if (packed_fields() != other_type.packed_fields()) {
// Different number of parent type arguments or of parameters.
return false;
}
Nullability this_type_nullability = nullability();
Nullability other_type_nullability = other_type.nullability();
Thread* thread = Thread::Current();
auto isolate_group = thread->isolate_group();
Zone* zone = thread->zone();
if (kind == TypeEquality::kInSubtypeTest) {
if (isolate_group->null_safety() &&
this_type_nullability == Nullability::kNullable &&
other_type_nullability == Nullability::kNonNullable) {
return false;
}
} else {
if (kind == TypeEquality::kSyntactical) {
if (this_type_nullability == Nullability::kLegacy) {
this_type_nullability = Nullability::kNonNullable;
}
if (other_type_nullability == Nullability::kLegacy) {
other_type_nullability = Nullability::kNonNullable;
}
} else {
ASSERT(kind == TypeEquality::kCanonical);
ASSERT(IsFinalized() && other_type.IsFinalized());
}
if (this_type_nullability != other_type_nullability) {
return false;
}
}
if (!IsFinalized() || !other_type.IsFinalized()) {
return false; // Too early to decide if equal.
}
// Equal function types must have equal signature types and equal optional
// named arguments.
// Compare function type parameters and their bounds.
// Check the type parameters and bounds of generic functions.
if (!HasSameTypeParametersAndBounds(other_type, kind, trail)) {
return false;
}
AbstractType& param_type = Type::Handle(zone);
AbstractType& other_param_type = Type::Handle(zone);
// Check the result type.
param_type = result_type();
other_param_type = other_type.result_type();
if (!param_type.IsEquivalent(other_param_type, kind, trail)) {
return false;
}
// Check the types of all parameters.
const intptr_t num_params = NumParameters();
ASSERT(other_type.NumParameters() == num_params);
for (intptr_t i = 0; i < num_params; i++) {
param_type = ParameterTypeAt(i);
other_param_type = other_type.ParameterTypeAt(i);
// Use contravariant order in case we test for subtyping.
if (!other_param_type.IsEquivalent(param_type, kind, trail)) {
return false;
}
}
// Check the names and types of optional named parameters.
if (!HasOptionalNamedParameters()) {
ASSERT(!other_type.HasOptionalNamedParameters()); // Same packed_fields.
return true;
}
for (intptr_t i = num_fixed_parameters(); i < num_params; i++) {
if (ParameterNameAt(i) != other_type.ParameterNameAt(i)) {
return false;
}
if (IsRequiredAt(i) != other_type.IsRequiredAt(i)) {
return false;
}
}
return true;
}
bool Type::IsRecursive(TrailPtr trail) const {
return TypeArguments::Handle(arguments()).IsRecursive(trail);
}
bool Type::RequireConstCanonicalTypeErasure(Zone* zone, TrailPtr trail) const {
if (IsNonNullable()) {
return true;
}
if (IsLegacy()) {
// It is not possible for a legacy type parameter to have a non-nullable
// bound or non-nullable default argument.
return false;
}
const Class& cls = Class::Handle(zone, type_class());
const intptr_t num_type_params = cls.NumTypeParameters();
const intptr_t num_type_args = cls.NumTypeArguments();
const intptr_t from_index = num_type_args - num_type_params;
return TypeArguments::Handle(zone, arguments())
.RequireConstCanonicalTypeErasure(zone, from_index, num_type_params,
trail);
}
bool Type::IsDeclarationTypeOf(const Class& cls) const {
ASSERT(type_class() == cls.ptr());
if (cls.IsNullClass()) {
return true;
}
if (cls.IsGeneric() || cls.IsClosureClass()) {
return false;
}
return nullability() == Nullability::kNonNullable;
}
// Keep in sync with TypeSerializationCluster::IsInCanonicalSet.
AbstractTypePtr Type::Canonicalize(Thread* thread, TrailPtr trail) const {
Zone* zone = thread->zone();
ASSERT(IsFinalized());
if (IsCanonical()) {
#ifdef DEBUG
TypeArguments& type_args = TypeArguments::Handle(zone, arguments());
ASSERT(type_args.IsCanonical());
ASSERT(type_args.IsOld());
#endif
return this->ptr();
}
auto isolate_group = thread->isolate_group();
const classid_t cid = type_class_id();
if (cid == kDynamicCid) {
ASSERT(Object::dynamic_type().IsCanonical());
return Object::dynamic_type().ptr();
}
if (cid == kVoidCid) {
ASSERT(Object::void_type().IsCanonical());
return Object::void_type().ptr();
}
const Class& cls = Class::Handle(zone, type_class());
// Fast canonical lookup/registry for simple types.
if (IsDeclarationTypeOf(cls)) {
ASSERT(!cls.IsNullClass() || IsNullable());
Type& type = Type::Handle(zone, cls.declaration_type());
if (type.IsNull()) {
ASSERT(!cls.ptr()->untag()->InVMIsolateHeap() ||
(isolate_group == Dart::vm_isolate_group()));
// Canonicalize the type arguments of the supertype, if any.
TypeArguments& type_args = TypeArguments::Handle(zone, arguments());
type_args = type_args.Canonicalize(thread, trail);
if (IsCanonical()) {
// Canonicalizing type_args canonicalized this type.
ASSERT(IsRecursive());
return this->ptr();
}
set_arguments(type_args);
type = cls.declaration_type();
// May be set while canonicalizing type args.
if (type.IsNull()) {
SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
// Recheck if type exists.
type = cls.declaration_type();
if (type.IsNull()) {
if (this->IsNew()) {
type ^= Object::Clone(*this, Heap::kOld);
} else {
type = this->ptr();
}
ASSERT(type.IsOld());
type.ComputeHash();
type.SetCanonical();
cls.set_declaration_type(type);
return type.ptr();
}
}
}
ASSERT(this->Equals(type));
ASSERT(type.IsOld());
if (type.IsCanonical()) {
return type.ptr();
}
}
Type& type = Type::Handle(zone);
ObjectStore* object_store = isolate_group->object_store();
{
SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
CanonicalTypeSet table(zone, object_store->canonical_types());
type ^= table.GetOrNull(CanonicalTypeKey(*this));
ASSERT(object_store->canonical_types() == table.Release().ptr());
}
if (type.IsNull()) {
// The type was not found in the table. It is not canonical yet.
// Canonicalize the type arguments.
TypeArguments& type_args = TypeArguments::Handle(zone, arguments());
// In case the type is first canonicalized at runtime, its type argument
// vector may be longer than necessary. If so, reallocate a vector of the
// exact size to prevent multiple "canonical" types.
if (!type_args.IsNull()) {
const intptr_t num_type_args = cls.NumTypeArguments();
ASSERT(type_args.Length() >= num_type_args);
if (type_args.Length() > num_type_args) {
TypeArguments& new_type_args =
TypeArguments::Handle(zone, TypeArguments::New(num_type_args));
AbstractType& type_arg = AbstractType::Handle(zone);
for (intptr_t i = 0; i < num_type_args; i++) {
type_arg = type_args.TypeAt(i);
new_type_args.SetTypeAt(i, type_arg);
}
type_args = new_type_args.ptr();
set_arguments(type_args);
SetHash(0); // Flush cached hash value.
}
}
type_args = type_args.Canonicalize(thread, trail);
if (IsCanonical()) {
// Canonicalizing type_args canonicalized this type as a side effect.
ASSERT(IsRecursive());
// A type can be recursive due to a cycle in its type arguments.
return this->ptr();
}
set_arguments(type_args);
ASSERT(type_args.IsNull() || type_args.IsOld());
// Check to see if the type got added to canonical table as part of the
// type arguments canonicalization.
SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
CanonicalTypeSet table(zone, object_store->canonical_types());
type ^= table.GetOrNull(CanonicalTypeKey(*this));
if (type.IsNull()) {
// Add this type into the canonical table of types.
if (this->IsNew()) {
type ^= Object::Clone(*this, Heap::kOld);
} else {
type = this->ptr();
}
ASSERT(type.IsOld());
type.SetCanonical(); // Mark object as being canonical.
bool present = table.Insert(type);
ASSERT(!present);
}
object_store->set_canonical_types(table.Release());
}
return type.ptr();
}
#if defined(DEBUG)
bool Type::CheckIsCanonical(Thread* thread) const {
if (IsRecursive()) {
return true;
}
const classid_t cid = type_class_id();
if (cid == kDynamicCid) {
return (ptr() == Object::dynamic_type().ptr());
}
if (cid == kVoidCid) {
return (ptr() == Object::void_type().ptr());
}
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
Type& type = Type::Handle(zone);
const Class& cls = Class::Handle(zone, type_class());
// Fast canonical lookup/registry for simple types.
if (IsDeclarationTypeOf(cls)) {
type = cls.declaration_type();
ASSERT(type.IsCanonical());
return (ptr() == type.ptr());
}
ObjectStore* object_store = isolate_group->object_store();
{
SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
CanonicalTypeSet table(zone, object_store->canonical_types());
type ^= table.GetOrNull(CanonicalTypeKey(*this));
object_store->set_canonical_types(table.Release());
}
return (ptr() == type.ptr());
}
#endif // DEBUG
void Type::EnumerateURIs(URIs* uris) const {
if (IsDynamicType() || IsVoidType() || IsNeverType()) {
return;
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const Class& cls = Class::Handle(zone, type_class());
const String& name = String::Handle(zone, cls.UserVisibleName());
const Library& library = Library::Handle(zone, cls.library());
const String& uri = String::Handle(zone, library.url());
AddURI(uris, name, uri);
const TypeArguments& type_args = TypeArguments::Handle(zone, arguments());
type_args.EnumerateURIs(uris);
}
intptr_t Type::ComputeHash() const {
ASSERT(IsFinalized());
uint32_t result = type_class_id();
// A legacy type should have the same hash as its non-nullable version to be
// consistent with the definition of type equality in Dart code.
Nullability type_nullability = nullability();
if (type_nullability == Nullability::kLegacy) {
type_nullability = Nullability::kNonNullable;
}
result = CombineHashes(result, static_cast<uint32_t>(type_nullability));
uint32_t type_args_hash = TypeArguments::kAllDynamicHash;
if (arguments() != TypeArguments::null()) {
// Only include hashes of type arguments corresponding to type parameters.
// This prevents obtaining different hashes depending on the location of
// TypeRefs in the super class type argument vector.
// Note that TypeRefs can also appear as type arguments corresponding to
// type parameters, typically after an instantiation at runtime.
// These are dealt with in TypeArguments::HashForRange, which is also called
// to compute the hash of a full standalone TypeArguments.
const TypeArguments& type_args = TypeArguments::Handle(arguments());
const Class& cls = Class::Handle(type_class());
const intptr_t num_type_params = cls.NumTypeParameters();
if (num_type_params > 0) {
const intptr_t from_index = cls.NumTypeArguments() - num_type_params;
type_args_hash = type_args.HashForRange(from_index, num_type_params);
}
}
result = CombineHashes(result, type_args_hash);
result = FinalizeHash(result, kHashBits);
SetHash(result);
return result;
}
intptr_t FunctionType::ComputeHash() const {
ASSERT(IsFinalized());
uint32_t result = packed_fields();
// A legacy type should have the same hash as its non-nullable version to be
// consistent with the definition of type equality in Dart code.
Nullability type_nullability = nullability();
if (type_nullability == Nullability::kLegacy) {
type_nullability = Nullability::kNonNullable;
}
result = CombineHashes(result, static_cast<uint32_t>(type_nullability));
AbstractType& type = AbstractType::Handle();
const intptr_t num_type_params = NumTypeParameters();
if (num_type_params > 0) {
const TypeArguments& type_params = TypeArguments::Handle(type_parameters());
for (intptr_t i = 0; i < num_type_params; i++) {
type = type_params.TypeAt(i);
type = TypeParameter::Cast(type).bound();
result = CombineHashes(result, type.Hash());
}
}
type = result_type();
result = CombineHashes(result, type.Hash());
const intptr_t num_params = NumParameters();
for (intptr_t i = 0; i < num_params; i++) {
type = ParameterTypeAt(i);
result = CombineHashes(result, type.Hash());
}
if (HasOptionalNamedParameters()) {
String& param_name = String::Handle();
for (intptr_t i = num_fixed_parameters(); i < num_params; i++) {
param_name = ParameterNameAt(i);
result = CombineHashes(result, param_name.Hash());
}
// Required flag is not hashed, see comment above about legacy type.
}
result = FinalizeHash(result, kHashBits);
SetHash(result);
return result;
}
void Type::set_type_class(const Class& value) const {
ASSERT(!value.IsNull());
untag()->set_type_class_id(Smi::New(value.id()));
}
void Type::set_arguments(const TypeArguments& value) const {
ASSERT(!IsCanonical());
untag()->set_arguments(value.ptr());
}
TypePtr Type::New(Heap::Space space) {
ObjectPtr raw = Object::Allocate(Type::kClassId, Type::InstanceSize(), space,
/*compressed*/ true);
return static_cast<TypePtr>(raw);
}
TypePtr Type::New(const Class& clazz,
const TypeArguments& arguments,
Nullability nullability,
Heap::Space space) {
Zone* Z = Thread::Current()->zone();
const Type& result = Type::Handle(Z, Type::New(space));
result.set_type_class(clazz);
result.set_arguments(arguments);
result.SetHash(0);
result.StoreNonPointer(&result.untag()->type_state_,
UntaggedType::kAllocated);
result.set_nullability(nullability);
result.SetTypeTestingStub(
Code::Handle(Z, TypeTestingStubGenerator::DefaultCodeForType(result)));
return result.ptr();
}
void Type::set_type_state(uint8_t state) const {
ASSERT((state >= UntaggedType::kAllocated) &&
(state <= UntaggedType::kFinalizedUninstantiated));
StoreNonPointer(&untag()->type_state_, state);
}
const char* Type::ToCString() const {
if (IsNull()) {
return "Type: null";
}
Zone* zone = Thread::Current()->zone();
ZoneTextBuffer args(zone);
const TypeArguments& type_args = TypeArguments::Handle(zone, arguments());
const char* args_cstr = "";
if (!type_args.IsNull()) {
type_args.PrintSubvectorName(0, type_args.Length(), kInternalName, &args);
args_cstr = args.buffer();
}
const Class& cls = Class::Handle(zone, type_class());
const char* class_name;
const String& name = String::Handle(zone, cls.Name());
class_name = name.IsNull() ? "<null>" : name.ToCString();
const char* suffix = NullabilitySuffix(kInternalName);
if (IsFinalized() && IsRecursive()) {
const intptr_t hash = Hash();
return OS::SCreate(zone, "Type: (H%" Px ") %s%s%s", hash, class_name,
args_cstr, suffix);
} else {
return OS::SCreate(zone, "Type: %s%s%s", class_name, args_cstr, suffix);
}
}
bool FunctionType::IsRecursive(TrailPtr trail) const {
AbstractType& type = AbstractType::Handle();
const intptr_t num_type_params = NumTypeParameters();
if (num_type_params > 0) {
const TypeArguments& type_params = TypeArguments::Handle(type_parameters());
for (intptr_t i = 0; i < num_type_params; i++) {
type = type_params.TypeAt(i);
if (type.IsRecursive(trail)) {
return true;
}
}
}
type = result_type();
if (type.IsRecursive(trail)) {
return true;
}
const intptr_t num_params = NumParameters();
for (intptr_t i = 0; i < num_params; i++) {
type = ParameterTypeAt(i);
if (type.IsRecursive(trail)) {
return true;
}
}
return false;
}
bool FunctionType::RequireConstCanonicalTypeErasure(Zone* zone,
TrailPtr trail) const {
if (IsNonNullable()) {
return true;
}
if (IsLegacy()) {
// It is not possible for a function type to have a non-nullable type in
// its signature.
return false;
}
AbstractType& type = AbstractType::Handle(zone);
const intptr_t num_type_params = NumTypeParameters();
if (num_type_params > 0) {
const TypeArguments& type_params = TypeArguments::Handle(type_parameters());
TypeParameter& type_param = TypeParameter::Handle(zone);
for (intptr_t i = 0; i < num_type_params; i++) {
type_param ^= type_params.TypeAt(i);
type = type_param.bound();
if (type.RequireConstCanonicalTypeErasure(zone, trail)) {
return true;
}
type = type_param.default_argument();
if (type.RequireConstCanonicalTypeErasure(zone, trail)) {
return true;
}
}
}
type = result_type();
if (type.RequireConstCanonicalTypeErasure(zone, trail)) {
return true;
}
const intptr_t num_params = NumParameters();
for (intptr_t i = 0; i < num_params; i++) {
type = ParameterTypeAt(i);
if (type.RequireConstCanonicalTypeErasure(zone, trail)) {
return true;
}
}
return false;
}
AbstractTypePtr FunctionType::Canonicalize(Thread* thread,
TrailPtr trail) const {
ASSERT(IsFinalized());
Zone* zone = thread->zone();
if (IsCanonical()) {
#ifdef DEBUG
// Verify that all fields are allocated in old space and are canonical.
AbstractType& type = AbstractType::Handle(zone);
const intptr_t num_type_params = NumTypeParameters();
if (num_type_params > 0) {
const TypeArguments& type_params =
TypeArguments::Handle(zone, type_parameters());
ASSERT(type_params.IsOld());
for (intptr_t i = 0; i < num_type_params; i++) {
type = type_params.TypeAt(i);
ASSERT(type.IsOld());
ASSERT(type.IsCanonical());
}
}
type = result_type();
ASSERT(type.IsOld());
ASSERT(type.IsCanonical());
ASSERT(Array::Handle(zone, parameter_types()).IsOld());
ASSERT(Array::Handle(zone, parameter_names()).IsOld());
const intptr_t num_params = NumParameters();
for (intptr_t i = 0; i < num_params; i++) {
type = ParameterTypeAt(i);
ASSERT(type.IsOld());
ASSERT(type.IsCanonical());
}
#endif
return ptr();
}
auto isolate_group = thread->isolate_group();
ObjectStore* object_store = isolate_group->object_store();
FunctionType& sig = FunctionType::Handle(zone);
{
SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
CanonicalFunctionTypeSet table(zone,
object_store->canonical_function_types());
sig ^= table.GetOrNull(CanonicalFunctionTypeKey(*this));
ASSERT(object_store->canonical_function_types() == table.Release().ptr());
}
if (sig.IsNull()) {
// The function type was not found in the table. It is not canonical yet.
// Canonicalize its type parameters and types.
const intptr_t num_type_params = NumTypeParameters();
if (num_type_params > 0) {
const TypeArguments& type_params =
TypeArguments::Handle(zone, type_parameters());
ASSERT(type_params.IsOld());
TypeParameter& type_param = TypeParameter::Handle(zone);
for (intptr_t i = 0; i < num_type_params; i++) {
type_param ^= type_params.TypeAt(i);
if (!type_param.IsCanonical()) {
type_param ^= type_param.Canonicalize(thread, trail);
type_params.SetTypeAt(i, type_param);
SetHash(0);
}
}
}
AbstractType& type = AbstractType::Handle(zone);
type = result_type();
if (!type.IsCanonical()) {
type = type.Canonicalize(thread, trail);
set_result_type(type);
SetHash(0);
}
ASSERT(Array::Handle(zone, parameter_types()).IsOld());
ASSERT(Array::Handle(zone, parameter_names()).IsOld());
const intptr_t num_params = NumParameters();
for (intptr_t i = 0; i < num_params; i++) {
type = ParameterTypeAt(i);
if (!type.IsCanonical()) {
type = type.Canonicalize(thread, trail);
SetParameterTypeAt(i, type);
SetHash(0);
}
}
if (IsCanonical()) {
// Canonicalizing signature types canonicalized this signature as a
// side effect.
ASSERT(IsRecursive());
return this->ptr();
}
// Check to see if the function type got added to canonical table as part
// of the canonicalization of its signature types.
SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
CanonicalFunctionTypeSet table(zone,
object_store->canonical_function_types());
sig ^= table.GetOrNull(CanonicalFunctionTypeKey(*this));
if (sig.IsNull()) {
// Add this function type into the canonical table of function types.
if (this->IsNew()) {
sig ^= Object::Clone(*this, Heap::kOld);
} else {
sig = this->ptr();
}
ASSERT(sig.IsOld());
sig.SetCanonical(); // Mark object as being canonical.
bool present = table.Insert(sig);
ASSERT(!present);
}
object_store->set_canonical_function_types(table.Release());
}
return sig.ptr();
}
#if defined(DEBUG)
bool FunctionType::CheckIsCanonical(Thread* thread) const {
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
FunctionType& type = FunctionType::Handle(zone);
ObjectStore* object_store = isolate_group->object_store();
{
SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
CanonicalFunctionTypeSet table(zone,
object_store->canonical_function_types());
type ^= table.GetOrNull(CanonicalFunctionTypeKey(*this));
object_store->set_canonical_function_types(table.Release());
}
return ptr() == type.ptr();
}
#endif // DEBUG
void FunctionType::EnumerateURIs(URIs* uris) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
AbstractType& type = AbstractType::Handle(zone);
const intptr_t num_params = NumParameters();
for (intptr_t i = 0; i < num_params; i++) {
type = ParameterTypeAt(i);
type.EnumerateURIs(uris);
}
// Handle result type last, since it appears last in the user visible name.
type = result_type();
type.EnumerateURIs(uris);
}
bool TypeRef::RequireConstCanonicalTypeErasure(Zone* zone,
TrailPtr trail) const {
if (TestAndAddToTrail(&trail)) {
return false;
}
const AbstractType& ref_type = AbstractType::Handle(zone, type());
return !ref_type.IsNull() &&
ref_type.RequireConstCanonicalTypeErasure(zone, trail);
}
bool TypeRef::IsInstantiated(Genericity genericity,
intptr_t num_free_fun_type_params,
TrailPtr trail) const {
if (TestAndAddToTrail(&trail)) {
return true;
}
const AbstractType& ref_type = AbstractType::Handle(type());
return !ref_type.IsNull() &&
ref_type.IsInstantiated(genericity, num_free_fun_type_params, trail);
}
bool TypeRef::IsEquivalent(const Instance& other,
TypeEquality kind,
TrailPtr trail) const {
if (ptr() == other.ptr()) {
return true;
}
if (!other.IsAbstractType()) {
return false;
}
if (TestAndAddBuddyToTrail(&trail, AbstractType::Cast(other))) {
return true;
}
const AbstractType& ref_type = AbstractType::Handle(type());
return !ref_type.IsNull() && ref_type.IsEquivalent(other, kind, trail);
}
AbstractTypePtr TypeRef::InstantiateFrom(
const TypeArguments& instantiator_type_arguments,
const TypeArguments& function_type_arguments,
intptr_t num_free_fun_type_params,
Heap::Space space,
TrailPtr trail) const {
TypeRef& instantiated_type_ref = TypeRef::Handle();
instantiated_type_ref ^= OnlyBuddyInTrail(trail);
if (!instantiated_type_ref.IsNull()) {
return instantiated_type_ref.ptr();
}
instantiated_type_ref = TypeRef::New();
AddOnlyBuddyToTrail(&trail, instantiated_type_ref);
AbstractType& ref_type = AbstractType::Handle(type());
ASSERT(!ref_type.IsNull() && !ref_type.IsTypeRef());
AbstractType& instantiated_ref_type = AbstractType::Handle();
instantiated_ref_type = ref_type.InstantiateFrom(
instantiator_type_arguments, function_type_arguments,
num_free_fun_type_params, space, trail);
// A returned null type indicates a failed instantiation in dead code that
// must be propagated up to the caller, the optimizing compiler.
if (instantiated_ref_type.IsNull()) {
return TypeRef::null();
}
ASSERT(!instantiated_ref_type.IsTypeRef());
instantiated_type_ref.set_type(instantiated_ref_type);
instantiated_type_ref.SetTypeTestingStub(Code::Handle(
TypeTestingStubGenerator::DefaultCodeForType(instantiated_type_ref)));
return instantiated_type_ref.ptr();
}
void TypeRef::set_type(const AbstractType& value) const {
ASSERT(value.IsNull() || value.IsType() || value.IsFunctionType());
untag()->set_type(value.ptr());
}
// A TypeRef cannot be canonical by definition. Only its referenced type can be.
// Consider the type Derived, where class Derived extends Base<Derived>.
// The first type argument of its flattened type argument vector is Derived,
// represented by a TypeRef pointing to itself.
AbstractTypePtr TypeRef::Canonicalize(Thread* thread, TrailPtr trail) const {
if (TestAndAddToTrail(&trail)) {
return ptr();
}
// TODO(regis): Try to reduce the number of nodes required to represent the
// referenced recursive type.
AbstractType& ref_type = AbstractType::Handle(type());
ASSERT(!ref_type.IsNull());
ref_type = ref_type.Canonicalize(thread, trail);
set_type(ref_type);
return ptr();
}
#if defined(DEBUG)
bool TypeRef::CheckIsCanonical(Thread* thread) const {
AbstractType& ref_type = AbstractType::Handle(type());
ASSERT(!ref_type.IsNull());
return ref_type.CheckIsCanonical(thread);
}
#endif // DEBUG
void TypeRef::EnumerateURIs(URIs* uris) const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const AbstractType& ref_type = AbstractType::Handle(zone, type());
ASSERT(!ref_type.IsDynamicType() && !ref_type.IsVoidType() &&
!ref_type.IsNeverType());
const Class& cls = Class::Handle(zone, ref_type.type_class());
const String& name = String::Handle(zone, cls.UserVisibleName());
const Library& library = Library::Handle(zone, cls.library());
const String& uri = String::Handle(zone, library.url());
AddURI(uris, name, uri);
// Break cycle by not printing type arguments.
}
intptr_t TypeRef::Hash() const {
// Do not use hash of the referenced type because
// - we could be in process of calculating it (as TypeRef is used to
// represent recursive references to types).
// - referenced type might be incomplete (e.g. not all its
// type arguments are set).
const AbstractType& ref_type = AbstractType::Handle(type());
ASSERT(!ref_type.IsNull());
uint32_t result = ref_type.type_class_id();
// A legacy type should have the same hash as its non-nullable version to be
// consistent with the definition of type equality in Dart code.
Nullability ref_type_nullability = ref_type.nullability();
if (ref_type_nullability == Nullability::kLegacy) {
ref_type_nullability = Nullability::kNonNullable;
}
result = CombineHashes(result, static_cast<uint32_t>(ref_type_nullability));
return FinalizeHash(result, kHashBits);
}
TypeRefPtr TypeRef::New() {
ObjectPtr raw = Object::Allocate(TypeRef::kClassId, TypeRef::InstanceSize(),
Heap::kOld, /*compressed*/ true);
return static_cast<TypeRefPtr>(raw);
}
TypeRefPtr TypeRef::New(const AbstractType& type) {
Zone* Z = Thread::Current()->zone();
const TypeRef& result = TypeRef::Handle(Z, TypeRef::New());
result.set_type(type);
result.SetTypeTestingStub(
Code::Handle(Z, TypeTestingStubGenerator::DefaultCodeForType(result)));
return result.ptr();
}
const char* TypeRef::ToCString() const {
Zone* zone = Thread::Current()->zone();
AbstractType& ref_type = AbstractType::Handle(zone, type());
if (ref_type.IsNull()) {
return "TypeRef: null";
}
ZoneTextBuffer printer(zone);
printer.AddString("TypeRef: ");
ref_type.PrintName(kInternalName, &printer);
if (ref_type.IsFinalized()) {
const intptr_t hash = ref_type.Hash();
printer.Printf(" (H%" Px ")", hash);
}
return printer.buffer();
}
void TypeParameter::SetIsFinalized() const {
ASSERT(!IsFinalized());
set_flags(UntaggedTypeParameter::FinalizedBit::update(
true, UntaggedTypeParameter::BeingFinalizedBit::update(false,
untag()->flags_)));
}
void TypeParameter::SetIsBeingFinalized() const {
ASSERT(!IsFinalized());
set_flags(
UntaggedTypeParameter::BeingFinalizedBit::update(true, untag()->flags_));
}
void TypeParameter::SetGenericCovariantImpl(bool value) const {
set_flags(UntaggedTypeParameter::GenericCovariantImplBit::update(
value, untag()->flags_));
}
void TypeParameter::set_nullability(Nullability value) const {
StoreNonPointer(&untag()->nullability_, static_cast<uint8_t>(value));
}
TypeParameterPtr TypeParameter::ToNullability(Nullability value,
Heap::Space space) const {
if (nullability() == value) {
return ptr();
}
// Clone type parameter and set new nullability.
TypeParameter& type_parameter = TypeParameter::Handle();
type_parameter ^= Object::Clone(*this, space);
type_parameter.set_nullability(value);
type_parameter.SetHash(0);
type_parameter.SetTypeTestingStub(Code::Handle(
TypeTestingStubGenerator::DefaultCodeForType(type_parameter)));
if (IsCanonical()) {
// Object::Clone does not clone canonical bit.
ASSERT(!type_parameter.IsCanonical());
ASSERT(IsFinalized());
ASSERT(type_parameter.IsFinalized());
type_parameter ^= type_parameter.Canonicalize(Thread::Current(), nullptr);
}
return type_parameter.ptr();
}
bool TypeParameter::IsInstantiated(Genericity genericity,
intptr_t num_free_fun_type_params,
TrailPtr trail) const {
// Bounds of class type parameters are ignored in the VM.
if (IsClassTypeParameter()) {
return genericity == kFunctions;
}
ASSERT(IsFunctionTypeParameter());
if ((genericity != kCurrentClass) && (index() < num_free_fun_type_params)) {
return false;
}
// Although the type parameter is instantiated, its bound may not be.
const AbstractType& upper_bound = AbstractType::Handle(bound());
ASSERT(!upper_bound.IsTypeRef());
if (upper_bound.IsTypeParameter() || upper_bound.IsFunctionType() ||
upper_bound.arguments() != TypeArguments::null()) {
// Use trail to break cycles created by bound referring to type parameter.
if (!TestAndAddToTrail(&trail) &&
!upper_bound.IsInstantiated(genericity, num_free_fun_type_params,
trail)) {
return false;
}
}
return true;
}
bool TypeParameter::IsEquivalent(const Instance& other,
TypeEquality kind,
TrailPtr trail) const {
if (ptr() == other.ptr()) {
return true;
}
if (other.IsTypeRef()) {
// Unfold right hand type. Divergence is controlled by left hand type.
const AbstractType& other_ref_type =
AbstractType::Handle(TypeRef::Cast(other).type());
ASSERT(!other_ref_type.IsTypeRef());
return IsEquivalent(other_ref_type, kind, trail);
}
if (!other.IsTypeParameter()) {
return false;
}
const TypeParameter& other_type_param = TypeParameter::Cast(other);
ASSERT(IsFinalized() && other_type_param.IsFinalized());
// Compare index, name, bound, default argument, and flags.
if (IsFunctionTypeParameter()) {
if (!other_type_param.IsFunctionTypeParameter()) {
return false;
}
if (kind == TypeEquality::kInSubtypeTest) {
// To be equivalent, the function type parameters should be declared
// at the same position in the generic function. Their index therefore
// needs adjustment before comparison.
// Example: 'foo<F>(bar<B>(B b)) { }' and 'baz<Z>(Z z) { }', baz can
// be assigned to bar, although B has index 1 and Z index 0.
if (index() - base() !=
other_type_param.index() - other_type_param.base()) {
return false;
}
AbstractType& upper_bound = AbstractType::Handle(bound());
AbstractType& other_type_param_upper_bound =
AbstractType::Handle(other_type_param.bound());
// Bounds that are mutual subtypes are considered equal.
if (!upper_bound.IsSubtypeOf(other_type_param_upper_bound, Heap::kOld) ||
!other_type_param_upper_bound.IsSubtypeOf(upper_bound, Heap::kOld)) {
return false;
}
} else {
if (base() != other_type_param.base() ||
index() != other_type_param.index()) {
return false;
}
// Compare bounds.
if (TestAndAddBuddyToTrail(&trail, other_type_param)) {
return true;
}
AbstractType& type = AbstractType::Handle(bound());
AbstractType& other_type = AbstractType::Handle(other_type_param.bound());
if (!type.IsEquivalent(other_type, kind, trail)) {
return false;
}
if (kind == TypeEquality::kCanonical) {
// Compare names.
if (name() != other_type_param.name()) {
return false;
}
// Compare default arguments.
type = default_argument();
other_type = other_type_param.default_argument();
if (type.IsNull()) {
if (!other_type.IsNull()) {
return false;
}
} else if (!type.IsEquivalent(other_type, kind, trail)) {
return false;
}
}
}
if (IsGenericCovariantImpl() != other_type_param.IsGenericCovariantImpl()) {
return false;
}
} else {
if (!other_type_param.IsClassTypeParameter()) {
return false;
}
if (kind == TypeEquality::kCanonical) {
if (parameterized_class_id() !=
other_type_param.parameterized_class_id()) {
// This also rejects finalized vs unfinalized comparison.
return false;
}
if (base() != other_type_param.base() ||
index() != other_type_param.index() ||
name() != other_type_param.name()) {
return false;
}
} else {
if (index() != other_type_param.index()) {
return false;
}
}
// Compare bounds.
if (TestAndAddBuddyToTrail(&trail, other_type_param)) {
return true;
}
AbstractType& upper_bound = AbstractType::Handle(bound());
AbstractType& other_type_param_upper_bound =
AbstractType::Handle(other_type_param.bound());
if (!upper_bound.IsEquivalent(other_type_param_upper_bound, kind, trail)) {
return false;
}
}
// Compare nullability.
Nullability this_type_param_nullability = nullability();
Nullability other_type_param_nullability = other_type_param.nullability();
if (kind == TypeEquality::kInSubtypeTest) {
if (IsolateGroup::Current()->use_strict_null_safety_checks() &&
(this_type_param_nullability == Nullability::kNullable) &&
(other_type_param_nullability == Nullability::kNonNullable)) {
return false;
}
} else {
if (kind == TypeEquality::kSyntactical) {
if (this_type_param_nullability == Nullability::kLegacy) {
this_type_param_nullability = Nullability::kNonNullable;
}
if (other_type_param_nullability == Nullability::kLegacy) {
other_type_param_nullability = Nullability::kNonNullable;
}
} else {
ASSERT(kind == TypeEquality::kCanonical);
}
if (this_type_param_nullability != other_type_param_nullability) {
return false;
}
}
return true;
}
bool TypeParameter::IsRecursive(TrailPtr trail) const {
if (TestAndAddToTrail(&trail)) {
return true;
}
AbstractType& type = AbstractType::Handle();
type = bound();
if (type.IsRecursive(trail)) {
return true;
}
type = default_argument();
if (type.IsRecursive(trail)) {
return true;
}
return false;
}
void TypeParameter::set_parameterized_class(const Class& value) const {
// Set value may be null.
classid_t cid = kFunctionCid; // Denotes a function type parameter.
if (!value.IsNull()) {
cid = value.id();
}
set_parameterized_class_id(cid);
}
void TypeParameter::set_parameterized_class_id(classid_t value) const {
StoreNonPointer(&untag()->parameterized_class_id_, value);
}
classid_t TypeParameter::parameterized_class_id() const {
return untag()->parameterized_class_id_;
}
ClassPtr TypeParameter::parameterized_class() const {
classid_t cid = parameterized_class_id();
// A canonicalized class type parameter does not refer to its class anymore.
if (cid == kClassCid || cid == kFunctionCid) {
return Class::null();
}
return IsolateGroup::Current()->class_table()->At(cid);
}
void TypeParameter::set_base(intptr_t value) const {
ASSERT(value >= 0);
ASSERT(Utils::IsUint(16, value));
StoreNonPointer(&untag()->base_, value);
}
void TypeParameter::set_index(intptr_t value) const {
ASSERT(value >= 0);
ASSERT(Utils::IsUint(16, value));
StoreNonPointer(&untag()->index_, value);
}
void TypeParameter::set_name(const String& value) const {
ASSERT(value.IsSymbol());
untag()->set_name(value.ptr());
}
void TypeParameter::set_bound(const AbstractType& value) const {
ASSERT(!IsCanonical());
untag()->set_bound(value.ptr());
}
void TypeParameter::set_default_argument(const AbstractType& value) const {
ASSERT(!IsCanonical());
untag()->set_default_argument(value.ptr());
}
AbstractTypePtr TypeParameter::GetFromTypeArguments(
const TypeArguments& instantiator_type_arguments,
const TypeArguments& function_type_arguments) const {
ASSERT(IsFinalized());
const TypeArguments& type_args = IsFunctionTypeParameter()
? function_type_arguments
: instantiator_type_arguments;
return type_args.TypeAtNullSafe(index());
}
AbstractTypePtr TypeParameter::InstantiateFrom(
const TypeArguments& instantiator_type_arguments,
const TypeArguments& function_type_arguments,
intptr_t num_free_fun_type_params,
Heap::Space space,
TrailPtr trail) const {
AbstractType& result = AbstractType::Handle();
if (IsFunctionTypeParameter()) {
ASSERT(IsFinalized());
if (index() >= num_free_fun_type_params) {
// Do not instantiate the function type parameter, but possibly its bound.
result = ptr();
AbstractType& upper_bound = AbstractType::Handle(bound());
if (!upper_bound.IsInstantiated(kAny, num_free_fun_type_params,
nullptr)) {
// Use trail to break cycles created by bound referring to type param.
// The instantiation trail must contain pairs, so add itself as buddy.
if (TestAndAddBuddyToTrail(&trail, *this)) {
// If the type parameter is already in the trail, it is returned
// unchanged here and will be processed when returning from recursion.
return ptr();
}
upper_bound = upper_bound.InstantiateFrom(
instantiator_type_arguments, function_type_arguments,
num_free_fun_type_params, space, trail);
if (upper_bound.ptr() == Type::NeverType()) {
// Normalize 'X extends Never' to 'Never'.
result = Type::NeverType();
} else if (upper_bound.ptr() != bound()) {
result ^= Object::Clone(result, space);
TypeParameter::Cast(result).set_bound(upper_bound);
}
}
} else if (function_type_arguments.IsNull()) {
return Type::DynamicType();
} else {
result = function_type_arguments.TypeAt(index());
ASSERT(!result.IsTypeParameter());
}
} else {
ASSERT(IsClassTypeParameter());
ASSERT(IsFinalized() || IsBeingFinalized());
if (instantiator_type_arguments.IsNull()) {
return Type::DynamicType();
}
if (instantiator_type_arguments.Length() <= index()) {
// InstantiateFrom can be invoked from a compilation pipeline with
// mismatching type arguments vector. This can only happen for
// a dynamically unreachable code - which compiler can't remove
// statically for some reason.
// To prevent crashes we return AbstractType::null(), understood by caller
// (see AssertAssignableInstr::Canonicalize).
return AbstractType::null();
}
result = instantiator_type_arguments.TypeAt(index());
// Instantiating a class type parameter cannot result in a
// function type parameter.
// Bounds of class type parameters are ignored in the VM.
}
result = result.SetInstantiatedNullability(*this, space);
// Canonicalization is not part of instantiation.
return result.NormalizeFutureOrType(space);
}
AbstractTypePtr TypeParameter::Canonicalize(Thread* thread,
TrailPtr trail) const {
ASSERT(IsFinalized());
Zone* zone = thread->zone();
if (IsCanonical()) {
#ifdef DEBUG
// Verify that all fields are allocated in old space and are canonical.
AbstractType& type = AbstractType::Handle(zone);
type = bound();
ASSERT(type.IsOld());
ASSERT(type.IsCanonical());
type = default_argument();
ASSERT(type.IsOld());
ASSERT(type.IsCanonical());
#endif
return this->ptr();
}
auto isolate_group = thread->isolate_group();
ObjectStore* object_store = isolate_group->object_store();
TypeParameter& type_parameter = TypeParameter::Handle(zone);
{
SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
CanonicalTypeParameterSet table(zone,
object_store->canonical_type_parameters());
type_parameter ^= table.GetOrNull(CanonicalTypeParameterKey(*this));
ASSERT(object_store->canonical_type_parameters() == table.Release().ptr());
}
if (type_parameter.IsNull()) {
// The type parameter was not found in the table. It is not canonical yet.
// Canonicalize its bound and default argument.
// However, if the type parameter is already being canonicalized, it is part
// of a cycle via its bound. Return it now and let the caller finish
// canonicalizing it.
if (TestAndAddToTrail(&trail)) {
return ptr();
}
AbstractType& type = AbstractType::Handle(zone);
type = bound();
type = type.Canonicalize(thread, trail);
if (IsCanonical()) {
// Canonicalizing bound or default argument canonicalized this type
// parameter as a side effect.
ASSERT(IsRecursive()); // Self-referring bound or default argument.
return ptr();
}
set_bound(type);
type = default_argument();
type = type.Canonicalize(thread, trail);
if (IsCanonical()) {
// Canonicalizing bound or default argument canonicalized this type
// parameter as a side effect.
ASSERT(IsRecursive()); // Self-referring bound or default argument.
return this->ptr();
}
set_default_argument(type);
// Check to see if the type parameter got added to canonical table as part
// of the canonicalization of its bound and default argument.
SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
CanonicalTypeParameterSet table(zone,
object_store->canonical_type_parameters());
type_parameter ^= table.GetOrNull(CanonicalTypeParameterKey(*this));
if (type_parameter.IsNull()) {
// Add this type parameter into the canonical table of type parameters.
if (this->IsNew()) {
type_parameter ^= Object::Clone(*this, Heap::kOld);
} else {
type_parameter = this->ptr();
}
ASSERT(type_parameter.IsOld());
type_parameter.SetCanonical(); // Mark object as being canonical.
bool present = table.Insert(type_parameter);
ASSERT(!present);
}
object_store->set_canonical_type_parameters(table.Release());
}
return type_parameter.ptr();
}
#if defined(DEBUG)
bool TypeParameter::CheckIsCanonical(Thread* thread) const {
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
TypeParameter& type_parameter = TypeParameter::Handle(zone);
ObjectStore* object_store = isolate_group->object_store();
{
SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex());
CanonicalTypeParameterSet table(zone,
object_store->canonical_type_parameters());
type_parameter ^= table.GetOrNull(CanonicalTypeParameterKey(*this));
object_store->set_canonical_type_parameters(table.Release());
}
return (ptr() == type_parameter.ptr());
}
#endif // DEBUG
intptr_t TypeParameter::ComputeHash() const {
ASSERT(IsFinalized() || IsBeingFinalized()); // Bound may not be finalized.
uint32_t result = parameterized_class_id();
// Hashing the bound reduces collisions, but may also create cycles.
// Therefore, we only hash the type_class_id of the bound,
// and do not use its full hash, as we do for TypeRef.
const AbstractType& upper_bound = AbstractType::Handle(bound());
if (upper_bound.IsTypeParameter()) {
ASSERT(upper_bound.IsFinalized() || upper_bound.IsBeingFinalized());
result = CombineHashes(result, TypeParameter::Cast(upper_bound).index());
} else {
// Note that the bound may not be finalized yet.
result = CombineHashes(result, upper_bound.type_class_id());
}
// Since the default argument is ignored when comparing two generic function
// types for type equality, the hash does not depend on it.
result = CombineHashes(result, IsGenericCovariantImpl() ? 1 : 0);
result = CombineHashes(result, base());
result = CombineHashes(result, index());
result = CombineHashes(result, String::Handle(name()).Hash());
// A legacy type should have the same hash as its non-nullable version to be
// consistent with the definition of type equality in Dart code.
Nullability type_param_nullability = nullability();
if (type_param_nullability == Nullability::kLegacy) {
type_param_nullability = Nullability::kNonNullable;
}
result = CombineHashes(result, static_cast<uint32_t>(type_param_nullability));
result = FinalizeHash(result, kHashBits);
SetHash(result);
return result;
}
TypeParameterPtr TypeParameter::New() {
ObjectPtr raw =
Object::Allocate(TypeParameter::kClassId, TypeParameter::InstanceSize(),
Heap::kOld, /*compressed*/ true);
return static_cast<TypeParameterPtr>(raw);
}
TypeParameterPtr TypeParameter::New(const Class& parameterized_class,
intptr_t base,
intptr_t index,
const String& name,
const AbstractType& bound,
bool is_generic_covariant_impl,
Nullability nullability) {
Zone* Z = Thread::Current()->zone();
const TypeParameter& result = TypeParameter::Handle(Z, TypeParameter::New());
result.set_parameterized_class(parameterized_class);
result.set_base(base);
result.set_index(index);
result.set_name(name);
result.set_bound(bound);
result.set_default_argument(Object::dynamic_type());
result.set_flags(0);
result.set_nullability(nullability);
result.SetGenericCovariantImpl(is_generic_covariant_impl);
result.SetHash(0);
result.SetTypeTestingStub(
Code::Handle(Z, TypeTestingStubGenerator::DefaultCodeForType(result)));
return result.ptr();
}
void TypeParameter::set_flags(uint8_t flags) const {
StoreNonPointer(&untag()->flags_, flags);
}
const char* TypeParameter::ToCString() const {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
ZoneTextBuffer printer(zone);
printer.Printf("TypeParameter: ");
printer.AddString(String::Handle(zone, name()).ToCString());
printer.AddString(NullabilitySuffix(kInternalName));
printer.Printf("; bound: ");
const AbstractType& upper_bound = AbstractType::Handle(bound());
if (upper_bound.IsNull()) {
printer.AddString("<null>");
} else {
upper_bound.PrintName(kInternalName, &printer);
}
if (FLAG_show_internal_names) {
printer.Printf("; default: ");
const AbstractType& default_arg = AbstractType::Handle(default_argument());
if (default_arg.IsNull()) {
printer.AddString("<null>");
} else {
default_arg.PrintName(kInternalName, &printer);
}
}
return printer.buffer();
}
InstancePtr Number::CanonicalizeLocked(Thread* thread) const {
intptr_t cid = GetClassId();
switch (cid) {
case kSmiCid:
return static_cast<SmiPtr>(raw_value());
case kMintCid:
return Mint::NewCanonicalLocked(thread, Mint::Cast(*this).value());
case kDoubleCid:
return Double::NewCanonicalLocked(thread, Double::Cast(*this).value());
default:
UNREACHABLE();
}
return Instance::null();
}
#if defined(DEBUG)
bool Number::CheckIsCanonical(Thread* thread) const {
intptr_t cid = GetClassId();
Zone* zone = thread->zone();
const Class& cls = Class::Handle(zone, this->clazz());
switch (cid) {
case kSmiCid:
return true;
case kMintCid: {
Mint& result = Mint::Handle(zone);
result ^= cls.LookupCanonicalMint(zone, Mint::Cast(*this).value());
return (result.ptr() == this->ptr());
}
case kDoubleCid: {
Double& dbl = Double::Handle(zone);
dbl ^= cls.LookupCanonicalDouble(zone, Double::Cast(*this).value());
return (dbl.ptr() == this->ptr());
}
default:
UNREACHABLE();
}
return false;
}
#endif // DEBUG
const char* Number::ToCString() const {
// Number is an interface. No instances of Number should exist.
UNREACHABLE();
return "Number";
}
const char* Integer::ToCString() const {
// Integer is an interface. No instances of Integer should exist except null.
ASSERT(IsNull());
return "NULL Integer";
}
IntegerPtr Integer::New(const String& str, Heap::Space space) {
// We are not supposed to have integers represented as two byte strings.
ASSERT(str.IsOneByteString());
if (str.IsNull() || (str.Length() == 0)) {
return Integer::null();
}
int64_t value = 0;
const char* cstr = str.ToCString();
if (!OS::StringToInt64(cstr, &value)) {
// Out of range.
return Integer::null();
}
return Integer::New(value, space);
}
IntegerPtr Integer::NewCanonical(const String& str) {
// We are not supposed to have integers represented as two byte strings.
ASSERT(str.IsOneByteString());
int64_t value = 0;
const char* cstr = str.ToCString();
if (!OS::StringToInt64(cstr, &value)) {
// Out of range.
return Integer::null();
}
return NewCanonical(value);
}
IntegerPtr Integer::NewCanonical(int64_t value) {
if (Smi::IsValid(value)) {
return Smi::New(static_cast<intptr_t>(value));
}
return Mint::NewCanonical(value);
}
IntegerPtr Integer::New(int64_t value, Heap::Space space) {
const bool is_smi = Smi::IsValid(value);
if (is_smi) {
return Smi::New(static_cast<intptr_t>(value));
}
return Mint::New(value, space);
}
IntegerPtr Integer::NewFromUint64(uint64_t value, Heap::Space space) {
return Integer::New(static_cast<int64_t>(value), space);
}
bool Integer::IsValueInRange(uint64_t value) {
return (value <= static_cast<uint64_t>(Mint::kMaxValue));
}
bool Integer::Equals(const Instance& other) const {
// Integer is an abstract class.
UNREACHABLE();
return false;
}
bool Integer::IsZero() const {
// Integer is an abstract class.
UNREACHABLE();
return false;
}
bool Integer::IsNegative() const {
// Integer is an abstract class.
UNREACHABLE();
return false;
}
double Integer::AsDoubleValue() const {
// Integer is an abstract class.
UNREACHABLE();
return 0.0;
}
int64_t Integer::AsInt64Value() const {
// Integer is an abstract class.
UNREACHABLE();
return 0;
}
uint32_t Integer::AsTruncatedUint32Value() const {
// Integer is an abstract class.
UNREACHABLE();
return 0;
}
bool Integer::FitsIntoSmi() const {
// Integer is an abstract class.
UNREACHABLE();
return false;
}
int Integer::CompareWith(const Integer& other) const {
// Integer is an abstract class.
UNREACHABLE();
return 0;
}
IntegerPtr Integer::AsValidInteger() const {
if (IsSmi()) return ptr();
if (IsMint()) {
Mint& mint = Mint::Handle();
mint ^= ptr();
if (Smi::IsValid(mint.value())) {
return Smi::New(static_cast<intptr_t>(mint.value()));
} else {
return ptr();
}
}
return ptr();
}
const char* Integer::ToHexCString(Zone* zone) const {
ASSERT(IsSmi() || IsMint());
int64_t value = AsInt64Value();
if (value < 0) {
return OS::SCreate(zone, "-0x%" PX64, -static_cast<uint64_t>(value));
} else {
return OS::SCreate(zone, "0x%" PX64, static_cast<uint64_t>(value));
}
}
IntegerPtr Integer::ArithmeticOp(Token::Kind operation,
const Integer& other,
Heap::Space space) const {
// In 32-bit mode, the result of any operation between two Smis will fit in a
// 32-bit signed result, except the product of two Smis, which will be 64-bit.
// In 64-bit mode, the result of any operation between two Smis will fit in a
// 64-bit signed result, except the product of two Smis (see below).
if (IsSmi() && other.IsSmi()) {
const intptr_t left_value = Smi::Value(Smi::RawCast(ptr()));
const intptr_t right_value = Smi::Value(Smi::RawCast(other.ptr()));
switch (operation) {
case Token::kADD:
return Integer::New(left_value + right_value, space);
case Token::kSUB:
return Integer::New(left_value - right_value, space);
case Token::kMUL:
return Integer::New(
Utils::MulWithWrapAround(static_cast<int64_t>(left_value),
static_cast<int64_t>(right_value)),
space);
case Token::kTRUNCDIV:
return Integer::New(left_value / right_value, space);
case Token::kMOD: {
const intptr_t remainder = left_value % right_value;
if (remainder < 0) {
if (right_value < 0) {
return Integer::New(remainder - right_value, space);
} else {
return Integer::New(remainder + right_value, space);
}
}
return Integer::New(remainder, space);
}
default:
UNIMPLEMENTED();
}
}
const int64_t left_value = AsInt64Value();
const int64_t right_value = other.AsInt64Value();
switch (operation) {
case Token::kADD:
return Integer::New(Utils::AddWithWrapAround(left_value, right_value),
space);
case Token::kSUB:
return Integer::New(Utils::SubWithWrapAround(left_value, right_value),
space);
case Token::kMUL:
return Integer::New(Utils::MulWithWrapAround(left_value, right_value),
space);
case Token::kTRUNCDIV:
if ((left_value == Mint::kMinValue) && (right_value == -1)) {
// Division special case: overflow in int64_t.
// MIN_VALUE / -1 = (MAX_VALUE + 1), which wraps around to MIN_VALUE
return Integer::New(Mint::kMinValue, space);
}
return Integer::New(left_value / right_value, space);
case Token::kMOD: {
if ((left_value == Mint::kMinValue) && (right_value == -1)) {
// Modulo special case: overflow in int64_t.
// MIN_VALUE % -1 = 0 for reason given above.
return Integer::New(0, space);
}
const int64_t remainder = left_value % right_value;
if (remainder < 0) {
if (right_value < 0) {
return Integer::New(remainder - right_value, space);
} else {
return Integer::New(remainder + right_value, space);
}
}
return Integer::New(remainder, space);
}
default:
UNIMPLEMENTED();
return Integer::null();
}
}
IntegerPtr Integer::BitOp(Token::Kind kind,
const Integer& other,
Heap::Space space) const {
if (IsSmi() && other.IsSmi()) {
intptr_t op1_value = Smi::Value(Smi::RawCast(ptr()));
intptr_t op2_value = Smi::Value(Smi::RawCast(other.ptr()));
intptr_t result = 0;
switch (kind) {
case Token::kBIT_AND:
result = op1_value & op2_value;
break;
case Token::kBIT_OR:
result = op1_value | op2_value;
break;
case Token::kBIT_XOR:
result = op1_value ^ op2_value;
break;
default:
UNIMPLEMENTED();
}
ASSERT(Smi::IsValid(result));
return Smi::New(result);
} else {
int64_t a = AsInt64Value();
int64_t b = other.AsInt64Value();
switch (kind) {
case Token::kBIT_AND:
return Integer::New(a & b, space);
case Token::kBIT_OR:
return Integer::New(a | b, space);
case Token::kBIT_XOR:
return Integer::New(a ^ b, space);
default:
UNIMPLEMENTED();
return Integer::null();
}
}
}
IntegerPtr Integer::ShiftOp(Token::Kind kind,
const Integer& other,
Heap::Space space) const {
int64_t a = AsInt64Value();
int64_t b = other.AsInt64Value();
ASSERT(b >= 0);
switch (kind) {
case Token::kSHL:
return Integer::New(Utils::ShiftLeftWithTruncation(a, b), space);
case Token::kSHR:
return Integer::New(a >> Utils::Minimum<int64_t>(b, Mint::kBits), space);
case Token::kUSHR:
return Integer::New(
(b >= kBitsPerInt64) ? 0 : static_cast<uint64_t>(a) >> b, space);
default:
UNIMPLEMENTED();
return Integer::null();
}
}
bool Smi::Equals(const Instance& other) const {
if (other.IsNull() || !other.IsSmi()) {
return false;
}
return (this->Value() == Smi::Cast(other).Value());
}
double Smi::AsDoubleValue() const {
return static_cast<double>(this->Value());
}
int64_t Smi::AsInt64Value() const {
return this->Value();
}
uint32_t Smi::AsTruncatedUint32Value() const {
return this->Value() & 0xFFFFFFFF;
}
int Smi::CompareWith(const Integer& other) const {
if (other.IsSmi()) {
const Smi& other_smi = Smi::Cast(other);
if (this->Value() < other_smi.Value()) {
return -1;
} else if (this->Value() > other_smi.Value()) {
return 1;
} else {
return 0;
}
}
ASSERT(!other.FitsIntoSmi());
if (other.IsMint()) {
if (this->IsNegative() == other.IsNegative()) {
return this->IsNegative() ? 1 : -1;
}
return this->IsNegative() ? -1 : 1;
}
UNREACHABLE();
return 0;
}
const char* Smi::ToCString() const {
return OS::SCreate(Thread::Current()->zone(), "%" Pd "", Value());
}
ClassPtr Smi::Class() {
return IsolateGroup::Current()->object_store()->smi_class();
}
void Mint::set_value(int64_t value) const {
StoreNonPointer(&untag()->value_, value);
}
MintPtr Mint::New(int64_t val, Heap::Space space) {
// Do not allocate a Mint if Smi would do.
ASSERT(!Smi::IsValid(val));
ASSERT(IsolateGroup::Current()->object_store()->mint_class() !=
Class::null());
Mint& result = Mint::Handle();
{
ObjectPtr raw = Object::Allocate(Mint::kClassId, Mint::InstanceSize(),
space, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_value(val);
return result.ptr();
}
MintPtr Mint::NewCanonical(int64_t value) {
Thread* thread = Thread::Current();
SafepointMutexLocker ml(
thread->isolate_group()->constant_canonicalization_mutex());
return NewCanonicalLocked(thread, value);
}
MintPtr Mint::NewCanonicalLocked(Thread* thread, int64_t value) {
// Do not allocate a Mint if Smi would do.
ASSERT(!Smi::IsValid(value));
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
const Class& cls =
Class::Handle(zone, isolate_group->object_store()->mint_class());
Mint& canonical_value =
Mint::Handle(zone, cls.LookupCanonicalMint(zone, value));
if (!canonical_value.IsNull()) {
return canonical_value.ptr();
}
canonical_value = Mint::New(value, Heap::kOld);
canonical_value.SetCanonical();
// The value needs to be added to the constants list. Grow the list if
// it is full.
cls.InsertCanonicalMint(zone, canonical_value);
return canonical_value.ptr();
}
bool Mint::Equals(const Instance& other) const {
if (this->ptr() == other.ptr()) {
// Both handles point to the same raw instance.
return true;
}
if (!other.IsMint() || other.IsNull()) {
return false;
}
return value() == Mint::Cast(other).value();
}
double Mint::AsDoubleValue() const {
return static_cast<double>(this->value());
}
int64_t Mint::AsInt64Value() const {
return this->value();
}
uint32_t Mint::AsTruncatedUint32Value() const {
return this->value() & 0xFFFFFFFF;
}
bool Mint::FitsIntoSmi() const {
return Smi::IsValid(AsInt64Value());
}
int Mint::CompareWith(const Integer& other) const {
ASSERT(!FitsIntoSmi());
ASSERT(other.IsMint() || other.IsSmi());
int64_t a = AsInt64Value();
int64_t b = other.AsInt64Value();
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
}
const char* Mint::ToCString() const {
return OS::SCreate(Thread::Current()->zone(), "%" Pd64 "", value());
}
void Double::set_value(double value) const {
StoreNonPointer(&untag()->value_, value);
}
bool Double::BitwiseEqualsToDouble(double value) const {
intptr_t value_offset = Double::value_offset();
void* this_addr = reinterpret_cast<void*>(
reinterpret_cast<uword>(this->untag()) + value_offset);
void* other_addr = reinterpret_cast<void*>(&value);
return (memcmp(this_addr, other_addr, sizeof(value)) == 0);
}
bool Double::OperatorEquals(const Instance& other) const {
if (this->IsNull() || other.IsNull()) {
return (this->IsNull() && other.IsNull());
}
if (!other.IsDouble()) {
return false;
}
return this->value() == Double::Cast(other).value();
}
bool Double::CanonicalizeEquals(const Instance& other) const {
if (this->ptr() == other.ptr()) {
return true; // "===".
}
if (other.IsNull() || !other.IsDouble()) {
return false;
}
return BitwiseEqualsToDouble(Double::Cast(other).value());
}
uint32_t Double::CanonicalizeHash() const {
return Hash64To32(bit_cast<uint64_t>(value()));
}
DoublePtr Double::New(double d, Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->double_class() !=
Class::null());
Double& result = Double::Handle();
{
ObjectPtr raw = Object::Allocate(Double::kClassId, Double::InstanceSize(),
space, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_value(d);
return result.ptr();
}
DoublePtr Double::New(const String& str, Heap::Space space) {
double double_value;
if (!CStringToDouble(str.ToCString(), str.Length(), &double_value)) {
return Double::Handle().ptr();
}
return New(double_value, space);
}
DoublePtr Double::NewCanonical(double value) {
Thread* thread = Thread::Current();
SafepointMutexLocker ml(
thread->isolate_group()->constant_canonicalization_mutex());
return NewCanonicalLocked(thread, value);
}
DoublePtr Double::NewCanonicalLocked(Thread* thread, double value) {
Zone* zone = thread->zone();
auto isolate_group = thread->isolate_group();
const Class& cls =
Class::Handle(zone, isolate_group->object_store()->double_class());
// Linear search to see whether this value is already present in the
// list of canonicalized constants.
Double& canonical_value =
Double::Handle(zone, cls.LookupCanonicalDouble(zone, value));
if (!canonical_value.IsNull()) {
return canonical_value.ptr();
}
canonical_value = Double::New(value, Heap::kOld);
canonical_value.SetCanonical();
// The value needs to be added to the constants list.
cls.InsertCanonicalDouble(zone, canonical_value);
return canonical_value.ptr();
}
DoublePtr Double::NewCanonical(const String& str) {
double double_value;
if (!CStringToDouble(str.ToCString(), str.Length(), &double_value)) {
return Double::Handle().ptr();
}
return NewCanonical(double_value);
}
StringPtr Number::ToString(Heap::Space space) const {
// Refactoring can avoid Zone::Alloc and strlen, but gains are insignificant.
const char* cstr = ToCString();
intptr_t len = strlen(cstr);
// Resulting string is ASCII ...
#ifdef DEBUG
for (intptr_t i = 0; i < len; ++i) {
ASSERT(static_cast<uint8_t>(cstr[i]) < 128);
}
#endif // DEBUG
// ... which is a subset of Latin-1.
return String::FromLatin1(reinterpret_cast<const uint8_t*>(cstr), len, space);
}
const char* Double::ToCString() const {
if (isnan(value())) {
return "NaN";
}
if (isinf(value())) {
return value() < 0 ? "-Infinity" : "Infinity";
}
const int kBufferSize = 128;
char* buffer = Thread::Current()->zone()->Alloc<char>(kBufferSize);
buffer[kBufferSize - 1] = '\0';
DoubleToCString(value(), buffer, kBufferSize);
return buffer;
}
void StringHasher::Add(const String& str, intptr_t begin_index, intptr_t len) {
ASSERT(begin_index >= 0);
ASSERT(len >= 0);
ASSERT((begin_index + len) <= str.Length());
if (len == 0) {
return;
}
if (str.IsOneByteString()) {
NoSafepointScope no_safepoint;
Add(OneByteString::CharAddr(str, begin_index), len);
} else if (str.IsExternalOneByteString()) {
NoSafepointScope no_safepoint;
Add(ExternalOneByteString::CharAddr(str, begin_index), len);
} else if (str.IsTwoByteString()) {
NoSafepointScope no_safepoint;
Add(TwoByteString::CharAddr(str, begin_index), len);
} else if (str.IsExternalOneByteString()) {
NoSafepointScope no_safepoint;
Add(ExternalTwoByteString::CharAddr(str, begin_index), len);
} else {
UNREACHABLE();
}
}
intptr_t String::Hash(const String& str, intptr_t begin_index, intptr_t len) {
StringHasher hasher;
hasher.Add(str, begin_index, len);
return hasher.Finalize();
}
intptr_t String::HashConcat(const String& str1, const String& str2) {
StringHasher hasher;
hasher.Add(str1, 0, str1.Length());
hasher.Add(str2, 0, str2.Length());
return hasher.Finalize();
}
intptr_t String::Hash(StringPtr raw) {
StringHasher hasher;
uword length = Smi::Value(raw->untag()->length());
if (raw->IsOneByteString() || raw->IsExternalOneByteString()) {
const uint8_t* data;
if (raw->IsOneByteString()) {
data = static_cast<OneByteStringPtr>(raw)->untag()->data();
} else {
ASSERT(raw->IsExternalOneByteString());
ExternalOneByteStringPtr str = static_cast<ExternalOneByteStringPtr>(raw);
data = str->untag()->external_data_;
}
return String::Hash(data, length);
} else {
const uint16_t* data;
if (raw->IsTwoByteString()) {
data = static_cast<TwoByteStringPtr>(raw)->untag()->data();
} else {
ASSERT(raw->IsExternalTwoByteString());
ExternalTwoByteStringPtr str = static_cast<ExternalTwoByteStringPtr>(raw);
data = str->untag()->external_data_;
}
return String::Hash(data, length);
}
}
intptr_t String::Hash(const char* characters, intptr_t len) {
StringHasher hasher;
hasher.Add(reinterpret_cast<const uint8_t*>(characters), len);
return hasher.Finalize();
}
intptr_t String::Hash(const uint8_t* characters, intptr_t len) {
StringHasher hasher;
hasher.Add(characters, len);
return hasher.Finalize();
}
intptr_t String::Hash(const uint16_t* characters, intptr_t len) {
StringHasher hasher;
hasher.Add(characters, len);
return hasher.Finalize();
}
intptr_t String::CharSize() const {
intptr_t class_id = ptr()->GetClassId();
if (class_id == kOneByteStringCid || class_id == kExternalOneByteStringCid) {
return kOneByteChar;
}
ASSERT(class_id == kTwoByteStringCid ||
class_id == kExternalTwoByteStringCid);
return kTwoByteChar;
}
void* String::GetPeer() const {
intptr_t class_id = ptr()->GetClassId();
if (class_id == kExternalOneByteStringCid) {
return ExternalOneByteString::GetPeer(*this);
}
ASSERT(class_id == kExternalTwoByteStringCid);
return ExternalTwoByteString::GetPeer(*this);
}
bool String::Equals(const Instance& other) const {
if (this->ptr() == other.ptr()) {
// Both handles point to the same raw instance.
return true;
}
if (!other.IsString()) {
return false;
}
const String& other_string = String::Cast(other);
return Equals(other_string);
}
bool String::Equals(const String& str,
intptr_t begin_index,
intptr_t len) const {
ASSERT(begin_index >= 0);
ASSERT((begin_index == 0) || (begin_index < str.Length()));
ASSERT(len >= 0);
ASSERT(len <= str.Length());
if (len != this->Length()) {
return false; // Lengths don't match.
}
for (intptr_t i = 0; i < len; i++) {
if (CharAt(i) != str.CharAt(begin_index + i)) {
return false;
}
}
return true;
}
bool String::Equals(const char* cstr) const {
ASSERT(cstr != NULL);
CodePointIterator it(*this);
intptr_t len = strlen(cstr);
while (it.Next()) {
if (*cstr == '\0') {
// Lengths don't match.
return false;
}
int32_t ch;
intptr_t consumed =
Utf8::Decode(reinterpret_cast<const uint8_t*>(cstr), len, &ch);
if (consumed == 0 || it.Current() != ch) {
return false;
}
cstr += consumed;
len -= consumed;
}
return *cstr == '\0';
}
bool String::Equals(const uint8_t* latin1_array, intptr_t len) const {
if (len != this->Length()) {
// Lengths don't match.
return false;
}
for (intptr_t i = 0; i < len; i++) {
if (this->CharAt(i) != latin1_array[i]) {
return false;
}
}
return true;
}
bool String::Equals(const uint16_t* utf16_array, intptr_t len) const {
if (len != this->Length()) {
// Lengths don't match.
return false;
}
for (intptr_t i = 0; i < len; i++) {
if (this->CharAt(i) != LoadUnaligned(&utf16_array[i])) {
return false;
}
}
return true;
}
bool String::Equals(const int32_t* utf32_array, intptr_t len) const {
if (len < 0) return false;
intptr_t j = 0;
for (intptr_t i = 0; i < len; ++i) {
if (Utf::IsSupplementary(utf32_array[i])) {
uint16_t encoded[2];
Utf16::Encode(utf32_array[i], &encoded[0]);
if (j + 1 >= Length()) return false;
if (CharAt(j++) != encoded[0]) return false;
if (CharAt(j++) != encoded[1]) return false;
} else {
if (j >= Length()) return false;
if (CharAt(j++) != utf32_array[i]) return false;
}
}
return j == Length();
}
bool String::EqualsConcat(const String& str1, const String& str2) const {
return (Length() == str1.Length() + str2.Length()) &&
str1.Equals(*this, 0, str1.Length()) &&
str2.Equals(*this, str1.Length(), str2.Length());
}
intptr_t String::CompareTo(const String& other) const {
const intptr_t this_len = this->Length();
const intptr_t other_len = other.IsNull() ? 0 : other.Length();
const intptr_t len = (this_len < other_len) ? this_len : other_len;
for (intptr_t i = 0; i < len; i++) {
uint16_t this_code_unit = this->CharAt(i);
uint16_t other_code_unit = other.CharAt(i);
if (this_code_unit < other_code_unit) {
return -1;
}
if (this_code_unit > other_code_unit) {
return 1;
}
}
if (this_len < other_len) return -1;
if (this_len > other_len) return 1;
return 0;
}
bool String::StartsWith(StringPtr str, StringPtr prefix) {
if (prefix == String::null()) return false;
const intptr_t length = String::LengthOf(str);
const intptr_t prefix_length = String::LengthOf(prefix);
if (prefix_length > length) return false;
for (intptr_t i = 0; i < prefix_length; i++) {
if (String::CharAt(str, i) != String::CharAt(prefix, i)) {
return false;
}
}
return true;
}
bool String::EndsWith(const String& other) const {
if (other.IsNull()) {
return false;
}
const intptr_t len = this->Length();
const intptr_t other_len = other.Length();
const intptr_t offset = len - other_len;
if ((other_len == 0) || (other_len > len)) {
return false;
}
for (int i = offset; i < len; i++) {
if (this->CharAt(i) != other.CharAt(i - offset)) {
return false;
}
}
return true;
}
InstancePtr String::CanonicalizeLocked(Thread* thread) const {
if (IsCanonical()) {
return this->ptr();
}
return Symbols::New(Thread::Current(), *this);
}
#if defined(DEBUG)
bool String::CheckIsCanonical(Thread* thread) const {
Zone* zone = thread->zone();
const String& str = String::Handle(zone, Symbols::Lookup(thread, *this));
return (str.ptr() == this->ptr());
}
#endif // DEBUG
StringPtr String::New(const char* cstr, Heap::Space space) {
ASSERT(cstr != NULL);
intptr_t array_len = strlen(cstr);
const uint8_t* utf8_array = reinterpret_cast<const uint8_t*>(cstr);
return String::FromUTF8(utf8_array, array_len, space);
}
StringPtr String::FromUTF8(const uint8_t* utf8_array,
intptr_t array_len,
Heap::Space space) {
Utf8::Type type;
intptr_t len = Utf8::CodeUnitCount(utf8_array, array_len, &type);
if (type == Utf8::kLatin1) {
const String& strobj = String::Handle(OneByteString::New(len, space));
if (len > 0) {
NoSafepointScope no_safepoint;
if (!Utf8::DecodeToLatin1(utf8_array, array_len,
OneByteString::DataStart(strobj), len)) {
Utf8::ReportInvalidByte(utf8_array, array_len, len);
return String::null();
}
}
return strobj.ptr();
}
ASSERT((type == Utf8::kBMP) || (type == Utf8::kSupplementary));
const String& strobj = String::Handle(TwoByteString::New(len, space));
NoSafepointScope no_safepoint;
if (!Utf8::DecodeToUTF16(utf8_array, array_len,
TwoByteString::DataStart(strobj), len)) {
Utf8::ReportInvalidByte(utf8_array, array_len, len);
return String::null();
}
return strobj.ptr();
}
StringPtr String::FromLatin1(const uint8_t* latin1_array,
intptr_t array_len,
Heap::Space space) {
return OneByteString::New(latin1_array, array_len, space);
}
StringPtr String::FromUTF16(const uint16_t* utf16_array,
intptr_t array_len,
Heap::Space space) {
bool is_one_byte_string = true;
for (intptr_t i = 0; i < array_len; ++i) {
if (!Utf::IsLatin1(LoadUnaligned(&utf16_array[i]))) {
is_one_byte_string = false;
break;
}
}
if (is_one_byte_string) {
return OneByteString::New(utf16_array, array_len, space);
}
return TwoByteString::New(utf16_array, array_len, space);
}
StringPtr String::FromUTF32(const int32_t* utf32_array,
intptr_t array_len,
Heap::Space space) {
bool is_one_byte_string = true;
intptr_t utf16_len = array_len;
for (intptr_t i = 0; i < array_len; ++i) {
if (!Utf::IsLatin1(utf32_array[i])) {
is_one_byte_string = false;
if (Utf::IsSupplementary(utf32_array[i])) {
utf16_len += 1;
}
}
}
if (is_one_byte_string) {
return OneByteString::New(utf32_array, array_len, space);
}
return TwoByteString::New(utf16_len, utf32_array, array_len, space);
}
StringPtr String::New(const String& str, Heap::Space space) {
// Currently this just creates a copy of the string in the correct space.
// Once we have external string support, this will also create a heap copy of
// the string if necessary. Some optimizations are possible, such as not
// copying internal strings into the same space.
intptr_t len = str.Length();
String& result = String::Handle();
intptr_t char_size = str.CharSize();
if (char_size == kOneByteChar) {
result = OneByteString::New(len, space);
} else {
ASSERT(char_size == kTwoByteChar);
result = TwoByteString::New(len, space);
}
String::Copy(result, 0, str, 0, len);
return result.ptr();
}
StringPtr String::NewExternal(const uint8_t* characters,
intptr_t len,
void* peer,
intptr_t external_allocation_size,
Dart_HandleFinalizer callback,
Heap::Space space) {
return ExternalOneByteString::New(characters, len, peer,
external_allocation_size, callback, space);
}
StringPtr String::NewExternal(const uint16_t* characters,
intptr_t len,
void* peer,
intptr_t external_allocation_size,
Dart_HandleFinalizer callback,
Heap::Space space) {
return ExternalTwoByteString::New(characters, len, peer,
external_allocation_size, callback, space);
}
void String::Copy(const String& dst,
intptr_t dst_offset,
const uint8_t* characters,
intptr_t len) {
ASSERT(dst_offset >= 0);
ASSERT(len >= 0);
ASSERT(len <= (dst.Length() - dst_offset));
if (dst.IsOneByteString()) {
NoSafepointScope no_safepoint;
if (len > 0) {
memmove(OneByteString::CharAddr(dst, dst_offset), characters, len);
}
} else if (dst.IsTwoByteString()) {
for (intptr_t i = 0; i < len; ++i) {
*TwoByteString::CharAddr(dst, i + dst_offset) = characters[i];
}
}
}
void String::Copy(const String& dst,
intptr_t dst_offset,
const uint16_t* utf16_array,
intptr_t array_len) {
ASSERT(dst_offset >= 0);
ASSERT(array_len >= 0);
ASSERT(array_len <= (dst.Length() - dst_offset));
if (dst.IsOneByteString()) {
NoSafepointScope no_safepoint;
for (intptr_t i = 0; i < array_len; ++i) {
ASSERT(Utf::IsLatin1(LoadUnaligned(&utf16_array[i])));
*OneByteString::CharAddr(dst, i + dst_offset) = utf16_array[i];
}
} else {
ASSERT(dst.IsTwoByteString());
NoSafepointScope no_safepoint;
if (array_len > 0) {
memmove(TwoByteString::CharAddr(dst, dst_offset), utf16_array,
array_len * 2);
}
}
}
void String::Copy(const String& dst,
intptr_t dst_offset,
const String& src,
intptr_t src_offset,
intptr_t len) {
ASSERT(dst_offset >= 0);
ASSERT(src_offset >= 0);
ASSERT(len >= 0);
ASSERT(len <= (dst.Length() - dst_offset));
ASSERT(len <= (src.Length() - src_offset));
if (len > 0) {
intptr_t char_size = src.CharSize();
if (char_size == kOneByteChar) {
if (src.IsOneByteString()) {
NoSafepointScope no_safepoint;
String::Copy(dst, dst_offset, OneByteString::CharAddr(src, src_offset),
len);
} else {
ASSERT(src.IsExternalOneByteString());
NoSafepointScope no_safepoint;
String::Copy(dst, dst_offset,
ExternalOneByteString::CharAddr(src, src_offset), len);
}
} else {
ASSERT(char_size == kTwoByteChar);
if (src.IsTwoByteString()) {
NoSafepointScope no_safepoint;
String::Copy(dst, dst_offset, TwoByteString::CharAddr(src, src_offset),
len);
} else {
ASSERT(src.IsExternalTwoByteString());
NoSafepointScope no_safepoint;
String::Copy(dst, dst_offset,
ExternalTwoByteString::CharAddr(src, src_offset), len);
}
}
}
}
StringPtr String::EscapeSpecialCharacters(const String& str) {
if (str.IsOneByteString()) {
return OneByteString::EscapeSpecialCharacters(str);
}
if (str.IsTwoByteString()) {
return TwoByteString::EscapeSpecialCharacters(str);
}
if (str.IsExternalOneByteString()) {
return ExternalOneByteString::EscapeSpecialCharacters(str);
}
ASSERT(str.IsExternalTwoByteString());
// If EscapeSpecialCharacters is frequently called on external two byte
// strings, we should implement it directly on ExternalTwoByteString rather
// than first converting to a TwoByteString.
return TwoByteString::EscapeSpecialCharacters(
String::Handle(TwoByteString::New(str, Heap::kNew)));
}
static bool IsPercent(int32_t c) {
return c == '%';
}
static bool IsHexCharacter(int32_t c) {
if (c >= '0' && c <= '9') {
return true;
}
if (c >= 'A' && c <= 'F') {
return true;
}
return false;
}
static bool IsURISafeCharacter(int32_t c) {
if ((c >= '0') && (c <= '9')) {
return true;
}
if ((c >= 'a') && (c <= 'z')) {
return true;
}
if ((c >= 'A') && (c <= 'Z')) {
return true;
}
return (c == '-') || (c == '_') || (c == '.') || (c == '~');
}
static int32_t GetHexCharacter(int32_t c) {
ASSERT(c >= 0);
ASSERT(c < 16);
const char* hex = "0123456789ABCDEF";
return hex[c];
}
static int32_t GetHexValue(int32_t c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'A' && c <= 'F') {
return c - 'A' + 10;
}
UNREACHABLE();
return 0;
}
static int32_t MergeHexCharacters(int32_t c1, int32_t c2) {
return GetHexValue(c1) << 4 | GetHexValue(c2);
}
const char* String::EncodeIRI(const String& str) {
const intptr_t len = Utf8::Length(str);
Zone* zone = Thread::Current()->zone();
uint8_t* utf8 = zone->Alloc<uint8_t>(len);
str.ToUTF8(utf8, len);
intptr_t num_escapes = 0;
for (int i = 0; i < len; ++i) {
uint8_t byte = utf8[i];
if (!IsURISafeCharacter(byte)) {
num_escapes += 2;
}
}
intptr_t cstr_len = len + num_escapes + 1;
char* cstr = zone->Alloc<char>(cstr_len);
intptr_t index = 0;
for (int i = 0; i < len; ++i) {
uint8_t byte = utf8[i];
if (!IsURISafeCharacter(byte)) {
cstr[index++] = '%';
cstr[index++] = GetHexCharacter(byte >> 4);
cstr[index++] = GetHexCharacter(byte & 0xF);
} else {
ASSERT(byte <= 127);
cstr[index++] = byte;
}
}
cstr[index] = '\0';
return cstr;
}
StringPtr String::DecodeIRI(const String& str) {
CodePointIterator cpi(str);
intptr_t num_escapes = 0;
intptr_t len = str.Length();
{
CodePointIterator cpi(str);
while (cpi.Next()) {
int32_t code_point = cpi.Current();
if (IsPercent(code_point)) {
// Verify that the two characters following the % are hex digits.
if (!cpi.Next()) {
return String::null();
}
int32_t code_point = cpi.Current();
if (!IsHexCharacter(code_point)) {
return String::null();
}
if (!cpi.Next()) {
return String::null();
}
code_point = cpi.Current();
if (!IsHexCharacter(code_point)) {
return String::null();
}
num_escapes += 2;
}
}
}
intptr_t utf8_len = len - num_escapes;
ASSERT(utf8_len >= 0);
Zone* zone = Thread::Current()->zone();
uint8_t* utf8 = zone->Alloc<uint8_t>(utf8_len);
{
intptr_t index = 0;
CodePointIterator cpi(str);
while (cpi.Next()) {
ASSERT(index < utf8_len);
int32_t code_point = cpi.Current();
if (IsPercent(code_point)) {
cpi.Next();
int32_t ch1 = cpi.Current();
cpi.Next();
int32_t ch2 = cpi.Current();
int32_t merged = MergeHexCharacters(ch1, ch2);
ASSERT(merged >= 0 && merged < 256);
utf8[index] = static_cast<uint8_t>(merged);
} else {
ASSERT(code_point >= 0 && code_point < 256);
utf8[index] = static_cast<uint8_t>(code_point);
}
index++;
}
}
return FromUTF8(utf8, utf8_len);
}
StringPtr String::NewFormatted(const char* format, ...) {
va_list args;
va_start(args, format);
StringPtr result = NewFormattedV(format, args);
NoSafepointScope no_safepoint;
va_end(args);
return result;
}
StringPtr String::NewFormatted(Heap::Space space, const char* format, ...) {
va_list args;
va_start(args, format);
StringPtr result = NewFormattedV(format, args, space);
NoSafepointScope no_safepoint;
va_end(args);
return result;
}
StringPtr String::NewFormattedV(const char* format,
va_list args,
Heap::Space space) {
va_list args_copy;
va_copy(args_copy, args);
intptr_t len = Utils::VSNPrint(NULL, 0, format, args_copy);
va_end(args_copy);
Zone* zone = Thread::Current()->zone();
char* buffer = zone->Alloc<char>(len + 1);
Utils::VSNPrint(buffer, (len + 1), format, args);
return String::New(buffer, space);
}
StringPtr String::Concat(const String& str1,
const String& str2,
Heap::Space space) {
ASSERT(!str1.IsNull() && !str2.IsNull());
intptr_t char_size = Utils::Maximum(str1.CharSize(), str2.CharSize());
if (char_size == kTwoByteChar) {
return TwoByteString::Concat(str1, str2, space);
}
return OneByteString::Concat(str1, str2, space);
}
StringPtr String::ConcatAll(const Array& strings, Heap::Space space) {
return ConcatAllRange(strings, 0, strings.Length(), space);
}
StringPtr String::ConcatAllRange(const Array& strings,
intptr_t start,
intptr_t end,
Heap::Space space) {
ASSERT(!strings.IsNull());
ASSERT(start >= 0);
ASSERT(end <= strings.Length());
intptr_t result_len = 0;
String& str = String::Handle();
intptr_t char_size = kOneByteChar;
// Compute 'char_size' and 'result_len'.
for (intptr_t i = start; i < end; i++) {
str ^= strings.At(i);
const intptr_t str_len = str.Length();
if ((kMaxElements - result_len) < str_len) {
Exceptions::ThrowOOM();
UNREACHABLE();
}
result_len += str_len;
char_size = Utils::Maximum(char_size, str.CharSize());
}
if (char_size == kOneByteChar) {
return OneByteString::ConcatAll(strings, start, end, result_len, space);
}
ASSERT(char_size == kTwoByteChar);
return TwoByteString::ConcatAll(strings, start, end, result_len, space);
}
StringPtr String::SubString(const String& str,
intptr_t begin_index,
Heap::Space space) {
ASSERT(!str.IsNull());
if (begin_index >= str.Length()) {
return String::null();
}
return String::SubString(str, begin_index, (str.Length() - begin_index),
space);
}
StringPtr String::SubString(Thread* thread,
const String& str,
intptr_t begin_index,
intptr_t length,
Heap::Space space) {
ASSERT(!str.IsNull());
ASSERT(begin_index >= 0);
ASSERT(length >= 0);
if (begin_index <= str.Length() && length == 0) {
return Symbols::Empty().ptr();
}
if (begin_index > str.Length()) {
return String::null();
}
bool is_one_byte_string = true;
intptr_t char_size = str.CharSize();
if (char_size == kTwoByteChar) {
for (intptr_t i = begin_index; i < begin_index + length; ++i) {
if (!Utf::IsLatin1(str.CharAt(i))) {
is_one_byte_string = false;
break;
}
}
}
REUSABLE_STRING_HANDLESCOPE(thread);
String& result = thread->StringHandle();
if (is_one_byte_string) {
result = OneByteString::New(length, space);
} else {
result = TwoByteString::New(length, space);
}
String::Copy(result, 0, str, begin_index, length);
return result.ptr();
}
const char* String::ToCString() const {
if (IsNull()) {
return "String: null";
}
const intptr_t len = Utf8::Length(*this);
Zone* zone = Thread::Current()->zone();
uint8_t* result = zone->Alloc<uint8_t>(len + 1);
ToUTF8(result, len);
result[len] = 0;
return reinterpret_cast<const char*>(result);
}
char* String::ToMallocCString() const {
const intptr_t len = Utf8::Length(*this);
uint8_t* result = reinterpret_cast<uint8_t*>(malloc(len + 1));
ToUTF8(result, len);
result[len] = 0;
return reinterpret_cast<char*>(result);
}
void String::ToUTF8(uint8_t* utf8_array, intptr_t array_len) const {
ASSERT(array_len >= Utf8::Length(*this));
Utf8::Encode(*this, reinterpret_cast<char*>(utf8_array), array_len);
}
static FinalizablePersistentHandle* AddFinalizer(const Object& referent,
void* peer,
Dart_HandleFinalizer callback,
intptr_t external_size) {
ASSERT(callback != NULL);
return FinalizablePersistentHandle::New(IsolateGroup::Current(), referent,
peer, callback, external_size,
/*auto_delete=*/true);
}
StringPtr String::Transform(int32_t (*mapping)(int32_t ch),
const String& str,
Heap::Space space) {
ASSERT(!str.IsNull());
bool has_mapping = false;
int32_t dst_max = 0;
CodePointIterator it(str);
while (it.Next()) {
int32_t src = it.Current();
int32_t dst = mapping(src);
if (src != dst) {
has_mapping = true;
}
dst_max = Utils::Maximum(dst_max, dst);
}
if (!has_mapping) {
return str.ptr();
}
if (Utf::IsLatin1(dst_max)) {
return OneByteString::Transform(mapping, str, space);
}
ASSERT(Utf::IsBmp(dst_max) || Utf::IsSupplementary(dst_max));
return TwoByteString::Transform(mapping, str, space);
}
StringPtr String::ToUpperCase(const String& str, Heap::Space space) {
// TODO(cshapiro): create a fast-path for OneByteString instances.
return Transform(CaseMapping::ToUpper, str, space);
}
StringPtr String::ToLowerCase(const String& str, Heap::Space space) {
// TODO(cshapiro): create a fast-path for OneByteString instances.
return Transform(CaseMapping::ToLower, str, space);
}
bool String::ParseDouble(const String& str,
intptr_t start,
intptr_t end,
double* result) {
ASSERT(0 <= start);
ASSERT(start <= end);
ASSERT(end <= str.Length());
intptr_t length = end - start;
NoSafepointScope no_safepoint;
const uint8_t* startChar;
if (str.IsOneByteString()) {
startChar = OneByteString::CharAddr(str, start);
} else if (str.IsExternalOneByteString()) {
startChar = ExternalOneByteString::CharAddr(str, start);
} else {
uint8_t* chars = Thread::Current()->zone()->Alloc<uint8_t>(length);
for (intptr_t i = 0; i < length; i++) {
int32_t ch = str.CharAt(start + i);
if (ch < 128) {
chars[i] = ch;
} else {
return false; // Not ASCII, so definitely not valid double numeral.
}
}
startChar = chars;
}
return CStringToDouble(reinterpret_cast<const char*>(startChar), length,
result);
}
// Check to see if 'str1' matches 'str2' as is or
// once the private key separator is stripped from str2.
//
// Things are made more complicated by the fact that constructors are
// added *after* the private suffix, so "foo@123.named" should match
// "foo.named".
//
// Also, the private suffix can occur more than once in the name, as in:
//
// _ReceivePortImpl@6be832b._internal@6be832b
//
template <typename T1, typename T2>
static bool EqualsIgnoringPrivateKey(const String& str1, const String& str2) {
intptr_t len = str1.Length();
intptr_t str2_len = str2.Length();
if (len == str2_len) {
for (intptr_t i = 0; i < len; i++) {
if (T1::CharAt(str1, i) != T2::CharAt(str2, i)) {
return false;
}
}
return true;
}
if (len < str2_len) {
return false; // No way they can match.
}
intptr_t pos = 0;
intptr_t str2_pos = 0;
while (pos < len) {
int32_t ch = T1::CharAt(str1, pos);
pos++;
if ((str2_pos < str2_len) && (ch == T2::CharAt(str2, str2_pos))) {
str2_pos++;
continue;
}
if (ch == Library::kPrivateKeySeparator) {
// Consume a private key separator if str1 has it but str2 does not.
while ((pos < len) && (T1::CharAt(str1, pos) != '.') &&
(T1::CharAt(str1, pos) != '&')) {
pos++;
}
// Resume matching characters.
continue;
}
return false;
}
// We have reached the end of mangled_name string.
ASSERT(pos == len);
return (str2_pos == str2_len);
}
#define EQUALS_IGNORING_PRIVATE_KEY(class_id, type, str1, str2) \
switch (class_id) { \
case kOneByteStringCid: \
return dart::EqualsIgnoringPrivateKey<type, OneByteString>(str1, str2); \
case kTwoByteStringCid: \
return dart::EqualsIgnoringPrivateKey<type, TwoByteString>(str1, str2); \
case kExternalOneByteStringCid: \
return dart::EqualsIgnoringPrivateKey<type, ExternalOneByteString>( \
str1, str2); \
case kExternalTwoByteStringCid: \
return dart::EqualsIgnoringPrivateKey<type, ExternalTwoByteString>( \
str1, str2); \
} \
UNREACHABLE();
bool String::EqualsIgnoringPrivateKey(const String& str1, const String& str2) {
if (str1.ptr() == str2.ptr()) {
return true; // Both handles point to the same raw instance.
}
NoSafepointScope no_safepoint;
intptr_t str1_class_id = str1.ptr()->GetClassId();
intptr_t str2_class_id = str2.ptr()->GetClassId();
switch (str1_class_id) {
case kOneByteStringCid:
EQUALS_IGNORING_PRIVATE_KEY(str2_class_id, OneByteString, str1, str2);
break;
case kTwoByteStringCid:
EQUALS_IGNORING_PRIVATE_KEY(str2_class_id, TwoByteString, str1, str2);
break;
case kExternalOneByteStringCid:
EQUALS_IGNORING_PRIVATE_KEY(str2_class_id, ExternalOneByteString, str1,
str2);
break;
case kExternalTwoByteStringCid:
EQUALS_IGNORING_PRIVATE_KEY(str2_class_id, ExternalTwoByteString, str1,
str2);
break;
}
UNREACHABLE();
return false;
}
bool String::CodePointIterator::Next() {
ASSERT(index_ >= -1);
intptr_t length = Utf16::Length(ch_);
if (index_ < (end_ - length)) {
index_ += length;
ch_ = str_.CharAt(index_);
if (Utf16::IsLeadSurrogate(ch_) && (index_ < (end_ - 1))) {
int32_t ch2 = str_.CharAt(index_ + 1);
if (Utf16::IsTrailSurrogate(ch2)) {
ch_ = Utf16::Decode(ch_, ch2);
}
}
return true;
}
index_ = end_;
return false;
}
OneByteStringPtr OneByteString::EscapeSpecialCharacters(const String& str) {
intptr_t len = str.Length();
if (len > 0) {
intptr_t num_escapes = 0;
for (intptr_t i = 0; i < len; i++) {
num_escapes += EscapeOverhead(CharAt(str, i));
}
const String& dststr =
String::Handle(OneByteString::New(len + num_escapes, Heap::kNew));
intptr_t index = 0;
for (intptr_t i = 0; i < len; i++) {
uint8_t ch = CharAt(str, i);
if (IsSpecialCharacter(ch)) {
SetCharAt(dststr, index, '\\');
SetCharAt(dststr, index + 1, SpecialCharacter(ch));
index += 2;
} else if (IsAsciiNonprintable(ch)) {
SetCharAt(dststr, index, '\\');
SetCharAt(dststr, index + 1, 'x');
SetCharAt(dststr, index + 2, GetHexCharacter(ch >> 4));
SetCharAt(dststr, index + 3, GetHexCharacter(ch & 0xF));
index += 4;
} else {
SetCharAt(dststr, index, ch);
index += 1;
}
}
return OneByteString::raw(dststr);
}
return OneByteString::raw(Symbols::Empty());
}
OneByteStringPtr ExternalOneByteString::EscapeSpecialCharacters(
const String& str) {
intptr_t len = str.Length();
if (len > 0) {
intptr_t num_escapes = 0;
for (intptr_t i = 0; i < len; i++) {
num_escapes += EscapeOverhead(CharAt(str, i));
}
const String& dststr =
String::Handle(OneByteString::New(len + num_escapes, Heap::kNew));
intptr_t index = 0;
for (intptr_t i = 0; i < len; i++) {
uint8_t ch = CharAt(str, i);
if (IsSpecialCharacter(ch)) {
OneByteString::SetCharAt(dststr, index, '\\');
OneByteString::SetCharAt(dststr, index + 1, SpecialCharacter(ch));
index += 2;
} else if (IsAsciiNonprintable(ch)) {
OneByteString::SetCharAt(dststr, index, '\\');
OneByteString::SetCharAt(dststr, index + 1, 'x');
OneByteString::SetCharAt(dststr, index + 2, GetHexCharacter(ch >> 4));
OneByteString::SetCharAt(dststr, index + 3, GetHexCharacter(ch & 0xF));
index += 4;
} else {
OneByteString::SetCharAt(dststr, index, ch);
index += 1;
}
}
return OneByteString::raw(dststr);
}
return OneByteString::raw(Symbols::Empty());
}
OneByteStringPtr OneByteString::New(intptr_t len, Heap::Space space) {
ASSERT((IsolateGroup::Current() == Dart::vm_isolate_group()) ||
((IsolateGroup::Current()->object_store() != NULL) &&
(IsolateGroup::Current()->object_store()->one_byte_string_class() !=
Class::null())));
if (len < 0 || len > kMaxElements) {
// This should be caught before we reach here.
FATAL1("Fatal error in OneByteString::New: invalid len %" Pd "\n", len);
}
{
ObjectPtr raw = Object::Allocate(OneByteString::kClassId,
OneByteString::InstanceSize(len), space,
/*compressed*/ true);
NoSafepointScope no_safepoint;
OneByteStringPtr result = static_cast<OneByteStringPtr>(raw);
result->untag()->set_length(Smi::New(len));
#if !defined(HASH_IN_OBJECT_HEADER)
result->untag()->set_hash(Smi::New(0));
#endif
return result;
}
}
OneByteStringPtr OneByteString::New(const uint8_t* characters,
intptr_t len,
Heap::Space space) {
const String& result = String::Handle(OneByteString::New(len, space));
if (len > 0) {
NoSafepointScope no_safepoint;
memmove(DataStart(result), characters, len);
}
return OneByteString::raw(result);
}
OneByteStringPtr OneByteString::New(const uint16_t* characters,
intptr_t len,
Heap::Space space) {
const String& result = String::Handle(OneByteString::New(len, space));
NoSafepointScope no_safepoint;
for (intptr_t i = 0; i < len; ++i) {
ASSERT(Utf::IsLatin1(characters[i]));
*CharAddr(result, i) = characters[i];
}
return OneByteString::raw(result);
}
OneByteStringPtr OneByteString::New(const int32_t* characters,
intptr_t len,
Heap::Space space) {
const String& result = String::Handle(OneByteString::New(len, space));
NoSafepointScope no_safepoint;
for (intptr_t i = 0; i < len; ++i) {
ASSERT(Utf::IsLatin1(characters[i]));
*CharAddr(result, i) = characters[i];
}
return OneByteString::raw(result);
}
OneByteStringPtr OneByteString::New(const String& str, Heap::Space space) {
intptr_t len = str.Length();
const String& result = String::Handle(OneByteString::New(len, space));
String::Copy(result, 0, str, 0, len);
return OneByteString::raw(result);
}
OneByteStringPtr OneByteString::New(const String& other_one_byte_string,
intptr_t other_start_index,
intptr_t other_len,
Heap::Space space) {
const String& result = String::Handle(OneByteString::New(other_len, space));
ASSERT(other_one_byte_string.IsOneByteString());
if (other_len > 0) {
NoSafepointScope no_safepoint;
memmove(OneByteString::DataStart(result),
OneByteString::CharAddr(other_one_byte_string, other_start_index),
other_len);
}
return OneByteString::raw(result);
}
OneByteStringPtr OneByteString::New(const TypedData& other_typed_data,
intptr_t other_start_index,
intptr_t other_len,
Heap::Space space) {
const String& result = String::Handle(OneByteString::New(other_len, space));
ASSERT(other_typed_data.ElementSizeInBytes() == 1);
if (other_len > 0) {
NoSafepointScope no_safepoint;
memmove(OneByteString::DataStart(result),
other_typed_data.DataAddr(other_start_index), other_len);
}
return OneByteString::raw(result);
}
OneByteStringPtr OneByteString::New(const ExternalTypedData& other_typed_data,
intptr_t other_start_index,
intptr_t other_len,
Heap::Space space) {
const String& result = String::Handle(OneByteString::New(other_len, space));
ASSERT(other_typed_data.ElementSizeInBytes() == 1);
if (other_len > 0) {
NoSafepointScope no_safepoint;
memmove(OneByteString::DataStart(result),
other_typed_data.DataAddr(other_start_index), other_len);
}
return OneByteString::raw(result);
}
OneByteStringPtr OneByteString::Concat(const String& str1,
const String& str2,
Heap::Space space) {
intptr_t len1 = str1.Length();
intptr_t len2 = str2.Length();
intptr_t len = len1 + len2;
const String& result = String::Handle(OneByteString::New(len, space));
String::Copy(result, 0, str1, 0, len1);
String::Copy(result, len1, str2, 0, len2);
return OneByteString::raw(result);
}
OneByteStringPtr OneByteString::ConcatAll(const Array& strings,
intptr_t start,
intptr_t end,
intptr_t len,
Heap::Space space) {
ASSERT(!strings.IsNull());
ASSERT(start >= 0);
ASSERT(end <= strings.Length());
const String& result = String::Handle(OneByteString::New(len, space));
String& str = String::Handle();
intptr_t pos = 0;
for (intptr_t i = start; i < end; i++) {
str ^= strings.At(i);
const intptr_t str_len = str.Length();
String::Copy(result, pos, str, 0, str_len);
ASSERT((kMaxElements - pos) >= str_len);
pos += str_len;
}
return OneByteString::raw(result);
}
OneByteStringPtr OneByteString::Transform(int32_t (*mapping)(int32_t ch),
const String& str,
Heap::Space space) {
ASSERT(!str.IsNull());
intptr_t len = str.Length();
const String& result = String::Handle(OneByteString::New(len, space));
NoSafepointScope no_safepoint;
for (intptr_t i = 0; i < len; ++i) {
int32_t ch = mapping(str.CharAt(i));
ASSERT(Utf::IsLatin1(ch));
*CharAddr(result, i) = ch;
}
return OneByteString::raw(result);
}
OneByteStringPtr OneByteString::SubStringUnchecked(const String& str,
intptr_t begin_index,
intptr_t length,
Heap::Space space) {
ASSERT(!str.IsNull() && str.IsOneByteString());
ASSERT(begin_index >= 0);
ASSERT(length >= 0);
if (begin_index <= str.Length() && length == 0) {
return OneByteString::raw(Symbols::Empty());
}
ASSERT(begin_index < str.Length());
OneByteStringPtr result = OneByteString::New(length, space);
NoSafepointScope no_safepoint;
if (length > 0) {
uint8_t* dest = &result->untag()->data()[0];
const uint8_t* src = &untag(str)->data()[begin_index];
memmove(dest, src, length);
}
return result;
}
TwoByteStringPtr TwoByteString::EscapeSpecialCharacters(const String& str) {
intptr_t len = str.Length();
if (len > 0) {
intptr_t num_escapes = 0;
for (intptr_t i = 0; i < len; i++) {
num_escapes += EscapeOverhead(CharAt(str, i));
}
const String& dststr =
String::Handle(TwoByteString::New(len + num_escapes, Heap::kNew));
intptr_t index = 0;
for (intptr_t i = 0; i < len; i++) {
uint16_t ch = CharAt(str, i);
if (IsSpecialCharacter(ch)) {
SetCharAt(dststr, index, '\\');
SetCharAt(dststr, index + 1, SpecialCharacter(ch));
index += 2;
} else if (IsAsciiNonprintable(ch)) {
SetCharAt(dststr, index, '\\');
SetCharAt(dststr, index + 1, 'x');
SetCharAt(dststr, index + 2, GetHexCharacter(ch >> 4));
SetCharAt(dststr, index + 3, GetHexCharacter(ch & 0xF));
index += 4;
} else {
SetCharAt(dststr, index, ch);
index += 1;
}
}
return TwoByteString::raw(dststr);
}
return TwoByteString::New(0, Heap::kNew);
}
TwoByteStringPtr TwoByteString::New(intptr_t len, Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->two_byte_string_class() !=
nullptr);
if (len < 0 || len > kMaxElements) {
// This should be caught before we reach here.
FATAL1("Fatal error in TwoByteString::New: invalid len %" Pd "\n", len);
}
String& result = String::Handle();
{
ObjectPtr raw = Object::Allocate(TwoByteString::kClassId,
TwoByteString::InstanceSize(len), space,
/*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.SetLength(len);
result.SetHash(0);
}
return TwoByteString::raw(result);
}
TwoByteStringPtr TwoByteString::New(const uint16_t* utf16_array,
intptr_t array_len,
Heap::Space space) {
ASSERT(array_len > 0);
const String& result = String::Handle(TwoByteString::New(array_len, space));
{
NoSafepointScope no_safepoint;
memmove(DataStart(result), utf16_array, (array_len * 2));
}
return TwoByteString::raw(result);
}
TwoByteStringPtr TwoByteString::New(intptr_t utf16_len,
const int32_t* utf32_array,
intptr_t array_len,
Heap::Space space) {
ASSERT((array_len > 0) && (utf16_len >= array_len));
const String& result = String::Handle(TwoByteString::New(utf16_len, space));
{
NoSafepointScope no_safepoint;
intptr_t j = 0;
for (intptr_t i = 0; i < array_len; ++i) {
if (Utf::IsSupplementary(utf32_array[i])) {
ASSERT(j < (utf16_len - 1));
Utf16::Encode(utf32_array[i], CharAddr(result, j));
j += 2;
} else {
ASSERT(j < utf16_len);
*CharAddr(result, j) = utf32_array[i];
j += 1;
}
}
}
return TwoByteString::raw(result);
}
TwoByteStringPtr TwoByteString::New(const String& str, Heap::Space space) {
intptr_t len = str.Length();
const String& result = String::Handle(TwoByteString::New(len, space));
String::Copy(result, 0, str, 0, len);
return TwoByteString::raw(result);
}
TwoByteStringPtr TwoByteString::New(const TypedData& other_typed_data,
intptr_t other_start_index,
intptr_t other_len,
Heap::Space space) {
const String& result = String::Handle(TwoByteString::New(other_len, space));
if (other_len > 0) {
NoSafepointScope no_safepoint;
memmove(TwoByteString::DataStart(result),
other_typed_data.DataAddr(other_start_index),
other_len * sizeof(uint16_t));
}
return TwoByteString::raw(result);
}
TwoByteStringPtr TwoByteString::New(const ExternalTypedData& other_typed_data,
intptr_t other_start_index,
intptr_t other_len,
Heap::Space space) {
const String& result = String::Handle(TwoByteString::New(other_len, space));
if (other_len > 0) {
NoSafepointScope no_safepoint;
memmove(TwoByteString::DataStart(result),
other_typed_data.DataAddr(other_start_index),
other_len * sizeof(uint16_t));
}
return TwoByteString::raw(result);
}
TwoByteStringPtr TwoByteString::Concat(const String& str1,
const String& str2,
Heap::Space space) {
intptr_t len1 = str1.Length();
intptr_t len2 = str2.Length();
intptr_t len = len1 + len2;
const String& result = String::Handle(TwoByteString::New(len, space));
String::Copy(result, 0, str1, 0, len1);
String::Copy(result, len1, str2, 0, len2);
return TwoByteString::raw(result);
}
TwoByteStringPtr TwoByteString::ConcatAll(const Array& strings,
intptr_t start,
intptr_t end,
intptr_t len,
Heap::Space space) {
ASSERT(!strings.IsNull());
ASSERT(start >= 0);
ASSERT(end <= strings.Length());
const String& result = String::Handle(TwoByteString::New(len, space));
String& str = String::Handle();
intptr_t pos = 0;
for (intptr_t i = start; i < end; i++) {
str ^= strings.At(i);
const intptr_t str_len = str.Length();
String::Copy(result, pos, str, 0, str_len);
ASSERT((kMaxElements - pos) >= str_len);
pos += str_len;
}
return TwoByteString::raw(result);
}
TwoByteStringPtr TwoByteString::Transform(int32_t (*mapping)(int32_t ch),
const String& str,
Heap::Space space) {
ASSERT(!str.IsNull());
intptr_t len = str.Length();
const String& result = String::Handle(TwoByteString::New(len, space));
String::CodePointIterator it(str);
intptr_t i = 0;
NoSafepointScope no_safepoint;
while (it.Next()) {
int32_t src = it.Current();
int32_t dst = mapping(src);
ASSERT(dst >= 0 && dst <= 0x10FFFF);
intptr_t len = Utf16::Length(dst);
if (len == 1) {
*CharAddr(result, i) = dst;
} else {
ASSERT(len == 2);
Utf16::Encode(dst, CharAddr(result, i));
}
i += len;
}
return TwoByteString::raw(result);
}
ExternalOneByteStringPtr ExternalOneByteString::New(
const uint8_t* data,
intptr_t len,
void* peer,
intptr_t external_allocation_size,
Dart_HandleFinalizer callback,
Heap::Space space) {
ASSERT(IsolateGroup::Current()
->object_store()
->external_one_byte_string_class() != Class::null());
if (len < 0 || len > kMaxElements) {
// This should be caught before we reach here.
FATAL1("Fatal error in ExternalOneByteString::New: invalid len %" Pd "\n",
len);
}
String& result = String::Handle();
{
ObjectPtr raw = Object::Allocate(ExternalOneByteString::kClassId,
ExternalOneByteString::InstanceSize(),
space, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.SetLength(len);
result.SetHash(0);
SetExternalData(result, data, peer);
}
AddFinalizer(result, peer, callback, external_allocation_size);
return ExternalOneByteString::raw(result);
}
ExternalTwoByteStringPtr ExternalTwoByteString::New(
const uint16_t* data,
intptr_t len,
void* peer,
intptr_t external_allocation_size,
Dart_HandleFinalizer callback,
Heap::Space space) {
ASSERT(IsolateGroup::Current()
->object_store()
->external_two_byte_string_class() != Class::null());
if (len < 0 || len > kMaxElements) {
// This should be caught before we reach here.
FATAL1("Fatal error in ExternalTwoByteString::New: invalid len %" Pd "\n",
len);
}
String& result = String::Handle();
{
ObjectPtr raw = Object::Allocate(ExternalTwoByteString::kClassId,
ExternalTwoByteString::InstanceSize(),
space, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.SetLength(len);
result.SetHash(0);
SetExternalData(result, data, peer);
}
AddFinalizer(result, peer, callback, external_allocation_size);
return ExternalTwoByteString::raw(result);
}
const char* Bool::ToCString() const {
return value() ? "true" : "false";
}
bool Array::CanonicalizeEquals(const Instance& other) const {
if (this->ptr() == other.ptr()) {
// Both handles point to the same raw instance.
return true;
}
// An Array may be compared to an ImmutableArray.
if (!other.IsArray() || other.IsNull()) {
return false;
}
// First check if both arrays have the same length and elements.
const Array& other_arr = Array::Cast(other);
intptr_t len = this->Length();
if (len != other_arr.Length()) {
return false;
}
for (intptr_t i = 0; i < len; i++) {
if (this->At(i) != other_arr.At(i)) {
return false;
}
}
// Now check if both arrays have the same type arguments.
if (GetTypeArguments() == other.GetTypeArguments()) {
return true;
}
const TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments());
const TypeArguments& other_type_args =
TypeArguments::Handle(other.GetTypeArguments());
if (!type_args.Equals(other_type_args)) {
return false;
}
return true;
}
uint32_t Array::CanonicalizeHash() const {
intptr_t len = Length();
if (len == 0) {
return 1;
}
Thread* thread = Thread::Current();
uint32_t hash = thread->heap()->GetCanonicalHash(ptr());
if (hash != 0) {
return hash;
}
hash = len;
Instance& member = Instance::Handle(GetTypeArguments());
hash = CombineHashes(hash, member.CanonicalizeHash());
for (intptr_t i = 0; i < len; i++) {
member ^= At(i);
hash = CombineHashes(hash, member.CanonicalizeHash());
}
hash = FinalizeHash(hash, kHashBits);
thread->heap()->SetCanonicalHash(ptr(), hash);
return hash;
}
ArrayPtr Array::New(intptr_t len, Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->array_class() !=
Class::null());
ArrayPtr result = New(kClassId, len, space);
if (UseCardMarkingForAllocation(len)) {
ASSERT(result->IsOldObject());
result->untag()->SetCardRememberedBitUnsynchronized();
}
return result;
}
ArrayPtr Array::New(intptr_t len,
const AbstractType& element_type,
Heap::Space space) {
const Array& result = Array::Handle(Array::New(len, space));
if (!element_type.IsDynamicType()) {
TypeArguments& type_args = TypeArguments::Handle(TypeArguments::New(1));
type_args.SetTypeAt(0, element_type);
type_args = type_args.Canonicalize(Thread::Current(), nullptr);
result.SetTypeArguments(type_args);
}
return result.ptr();
}
ArrayPtr Array::New(intptr_t class_id, intptr_t len, Heap::Space space) {
if (!IsValidLength(len)) {
// This should be caught before we reach here.
FATAL1("Fatal error in Array::New: invalid len %" Pd "\n", len);
}
{
ArrayPtr raw = static_cast<ArrayPtr>(Object::Allocate(
class_id, Array::InstanceSize(len), space, /*compressed*/ false));
NoSafepointScope no_safepoint;
raw->untag()->set_length(Smi::New(len));
return raw;
}
}
ArrayPtr Array::Slice(intptr_t start,
intptr_t count,
bool with_type_argument) const {
// TODO(vegorov) introduce an array allocation method that fills newly
// allocated array with values from the given source array instead of
// null-initializing all elements.
Array& dest = Array::Handle(Array::New(count));
dest.StoreArrayPointers(dest.ObjectAddr(0), ObjectAddr(start), count);
if (with_type_argument) {
dest.SetTypeArguments(TypeArguments::Handle(GetTypeArguments()));
}
return dest.ptr();
}
void Array::MakeImmutable() const {
if (IsImmutable()) return;
ASSERT(!IsCanonical());
untag()->SetClassId(kImmutableArrayCid);
}
const char* Array::ToCString() const {
if (IsNull()) {
return IsImmutable() ? "_ImmutableList NULL" : "_List NULL";
}
Zone* zone = Thread::Current()->zone();
const char* format =
IsImmutable() ? "_ImmutableList len:%" Pd : "_List len:%" Pd;
return zone->PrintToString(format, Length());
}
ArrayPtr Array::Grow(const Array& source,
intptr_t new_length,
Heap::Space space) {
Zone* zone = Thread::Current()->zone();
const Array& result = Array::Handle(zone, Array::New(new_length, space));
intptr_t len = 0;
if (!source.IsNull()) {
len = source.Length();
result.SetTypeArguments(
TypeArguments::Handle(zone, source.GetTypeArguments()));
}
ASSERT(new_length >= len); // Cannot copy 'source' into new array.
ASSERT(new_length != len); // Unnecessary copying of array.
PassiveObject& obj = PassiveObject::Handle(zone);
for (int i = 0; i < len; i++) {
obj = source.At(i);
result.SetAt(i, obj);
}
return result.ptr();
}
void Array::Truncate(intptr_t new_len) const {
if (IsNull()) {
return;
}
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const Array& array = Array::Handle(zone, this->ptr());
intptr_t old_len = array.Length();
ASSERT(new_len <= old_len);
if (old_len == new_len) {
return;
}
intptr_t old_size = Array::InstanceSize(old_len);
intptr_t new_size = Array::InstanceSize(new_len);
NoSafepointScope no_safepoint;
// If there is any left over space fill it with either an Array object or
// just a plain object (depending on the amount of left over space) so
// that it can be traversed over successfully during garbage collection.
Object::MakeUnusedSpaceTraversable(array, old_size, new_size);
// Update the size in the header field and length of the array object.
// These release operations are balanced by acquire operations in the
// concurrent sweeper.
uword old_tags = array.untag()->tags_;
uword new_tags;
ASSERT(kArrayCid == UntaggedObject::ClassIdTag::decode(old_tags));
do {
new_tags = UntaggedObject::SizeTag::update(new_size, old_tags);
} while (!array.untag()->tags_.compare_exchange_weak(
old_tags, new_tags, std::memory_order_release));
// Between the CAS of the header above and the SetLength below, the array is
// temporarily in an inconsistent state. The header is considered the
// overriding source of object size by UntaggedObject::HeapSize, but the
// ASSERTs in UntaggedObject::HeapSizeFromClass must handle this special case.
array.SetLengthRelease(new_len);
}
ArrayPtr Array::MakeFixedLength(const GrowableObjectArray& growable_array,
bool unique) {
ASSERT(!growable_array.IsNull());
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
intptr_t used_len = growable_array.Length();
// Get the type arguments and prepare to copy them.
const TypeArguments& type_arguments =
TypeArguments::Handle(growable_array.GetTypeArguments());
if (used_len == 0) {
if (type_arguments.IsNull() && !unique) {
// This is a raw List (as in no type arguments), so we can return the
// simple empty array.
return Object::empty_array().ptr();
}
// The backing array may be a shared instance, or may not have correct
// type parameters. Create a new empty array.
Heap::Space space = thread->IsMutatorThread() ? Heap::kNew : Heap::kOld;
Array& array = Array::Handle(zone, Array::New(0, space));
array.SetTypeArguments(type_arguments);
return array.ptr();
}
const Array& array = Array::Handle(zone, growable_array.data());
ASSERT(array.IsArray());
array.SetTypeArguments(type_arguments);
// Null the GrowableObjectArray, we are removing its backing array.
growable_array.SetLength(0);
growable_array.SetData(Object::empty_array());
// Truncate the old backing array and return it.
array.Truncate(used_len);
return array.ptr();
}
void Array::CanonicalizeFieldsLocked(Thread* thread) const {
intptr_t len = Length();
if (len > 0) {
Zone* zone = thread->zone();
Instance& obj = Instance::Handle(zone);
for (intptr_t i = 0; i < len; i++) {
obj ^= At(i);
obj = obj.CanonicalizeLocked(thread);
this->SetAt(i, obj);
}
}
}
ImmutableArrayPtr ImmutableArray::New(intptr_t len, Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->immutable_array_class() !=
Class::null());
return static_cast<ImmutableArrayPtr>(Array::New(kClassId, len, space));
}
void GrowableObjectArray::Add(const Object& value, Heap::Space space) const {
ASSERT(!IsNull());
if (Length() == Capacity()) {
// Grow from 0 to 3, and then double + 1.
intptr_t new_capacity = (Capacity() * 2) | 3;
if (new_capacity <= Capacity()) {
Exceptions::ThrowOOM();
UNREACHABLE();
}
Grow(new_capacity, space);
}
ASSERT(Length() < Capacity());
intptr_t index = Length();
SetLength(index + 1);
SetAt(index, value);
}
void GrowableObjectArray::Grow(intptr_t new_capacity, Heap::Space space) const {
ASSERT(new_capacity > Capacity());
const Array& contents = Array::Handle(data());
const Array& new_contents =
Array::Handle(Array::Grow(contents, new_capacity, space));
untag()->set_data(new_contents.ptr());
}
ObjectPtr GrowableObjectArray::RemoveLast() const {
ASSERT(!IsNull());
ASSERT(Length() > 0);
intptr_t index = Length() - 1;
const Array& contents = Array::Handle(data());
const PassiveObject& obj = PassiveObject::Handle(contents.At(index));
contents.SetAt(index, Object::null_object());
SetLength(index);
return obj.ptr();
}
GrowableObjectArrayPtr GrowableObjectArray::New(intptr_t capacity,
Heap::Space space) {
ArrayPtr raw_data = (capacity == 0) ? Object::empty_array().ptr()
: Array::New(capacity, space);
const Array& data = Array::Handle(raw_data);
return New(data, space);
}
GrowableObjectArrayPtr GrowableObjectArray::New(const Array& array,
Heap::Space space) {
ASSERT(
IsolateGroup::Current()->object_store()->growable_object_array_class() !=
Class::null());
GrowableObjectArray& result = GrowableObjectArray::Handle();
{
ObjectPtr raw = Object::Allocate(GrowableObjectArray::kClassId,
GrowableObjectArray::InstanceSize(), space,
/*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
result.SetLength(0);
result.SetData(array);
}
return result.ptr();
}
const char* GrowableObjectArray::ToCString() const {
if (IsNull()) {
return "_GrowableList: null";
}
return OS::SCreate(Thread::Current()->zone(),
"Instance(length:%" Pd ") of '_GrowableList'", Length());
}
// Equivalent to Dart's operator "==" and hashCode.
class DefaultHashTraits {
public:
static const char* Name() { return "DefaultHashTraits"; }
static bool ReportStats() { return false; }
static bool IsMatch(const Object& a, const Object& b) {
if (a.IsNull() || b.IsNull()) {
return (a.IsNull() && b.IsNull());
} else {
return Instance::Cast(a).OperatorEquals(Instance::Cast(b));
}
}
static uword Hash(const Object& obj) {
if (obj.IsNull()) {
return 0;
}
// TODO(koda): Ensure VM classes only produce Smi hash codes, and remove
// non-Smi cases once Dart-side implementation is complete.
Thread* thread = Thread::Current();
REUSABLE_INSTANCE_HANDLESCOPE(thread);
Instance& hash_code = thread->InstanceHandle();
hash_code ^= Instance::Cast(obj).HashCode();
if (hash_code.IsSmi()) {
// May waste some bits on 64-bit, to ensure consistency with non-Smi case.
return static_cast<uword>(Smi::Cast(hash_code).AsTruncatedUint32Value());
} else if (hash_code.IsInteger()) {
return static_cast<uword>(
Integer::Cast(hash_code).AsTruncatedUint32Value());
} else {
return 0;
}
}
};
LinkedHashMapPtr LinkedHashMap::NewDefault(Heap::Space space) {
const Array& data = Array::Handle(Array::New(kInitialIndexSize, space));
const TypedData& index = TypedData::Handle(
TypedData::New(kTypedDataUint32ArrayCid, kInitialIndexSize, space));
// On 32-bit, the top bits are wasted to avoid Mint allocation.
static const intptr_t kAvailableBits = (kSmiBits >= 32) ? 32 : kSmiBits;
static const intptr_t kInitialHashMask =
(1 << (kAvailableBits - kInitialIndexBits)) - 1;
return LinkedHashMap::New(data, index, kInitialHashMask, 0, 0, space);
}
LinkedHashMapPtr LinkedHashMap::New(const Array& data,
const TypedData& index,
intptr_t hash_mask,
intptr_t used_data,
intptr_t deleted_keys,
Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->linked_hash_map_class() !=
Class::null());
LinkedHashMap& result =
LinkedHashMap::Handle(LinkedHashMap::NewUninitialized(space));
result.SetData(data);
result.SetIndex(index);
result.SetHashMask(hash_mask);
result.SetUsedData(used_data);
result.SetDeletedKeys(deleted_keys);
return result.ptr();
}
LinkedHashMapPtr LinkedHashMap::NewUninitialized(Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->linked_hash_map_class() !=
Class::null());
LinkedHashMap& result = LinkedHashMap::Handle();
{
ObjectPtr raw =
Object::Allocate(LinkedHashMap::kClassId, LinkedHashMap::InstanceSize(),
space, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
return result.ptr();
}
const char* LinkedHashMap::ToCString() const {
Zone* zone = Thread::Current()->zone();
return zone->PrintToString("_LinkedHashMap len:%" Pd, Length());
}
const char* FutureOr::ToCString() const {
// FutureOr is an abstract class.
UNREACHABLE();
}
Float32x4Ptr Float32x4::New(float v0,
float v1,
float v2,
float v3,
Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->float32x4_class() !=
Class::null());
Float32x4& result = Float32x4::Handle();
{
ObjectPtr raw =
Object::Allocate(Float32x4::kClassId, Float32x4::InstanceSize(), space,
/*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_x(v0);
result.set_y(v1);
result.set_z(v2);
result.set_w(v3);
return result.ptr();
}
Float32x4Ptr Float32x4::New(simd128_value_t value, Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->float32x4_class() !=
Class::null());
Float32x4& result = Float32x4::Handle();
{
ObjectPtr raw =
Object::Allocate(Float32x4::kClassId, Float32x4::InstanceSize(), space,
/*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_value(value);
return result.ptr();
}
simd128_value_t Float32x4::value() const {
return LoadUnaligned(
reinterpret_cast<const simd128_value_t*>(&untag()->value_));
}
void Float32x4::set_value(simd128_value_t value) const {
StoreUnaligned(reinterpret_cast<simd128_value_t*>(&ptr()->untag()->value_),
value);
}
void Float32x4::set_x(float value) const {
StoreNonPointer(&untag()->value_[0], value);
}
void Float32x4::set_y(float value) const {
StoreNonPointer(&untag()->value_[1], value);
}
void Float32x4::set_z(float value) const {
StoreNonPointer(&untag()->value_[2], value);
}
void Float32x4::set_w(float value) const {
StoreNonPointer(&untag()->value_[3], value);
}
float Float32x4::x() const {
return untag()->value_[0];
}
float Float32x4::y() const {
return untag()->value_[1];
}
float Float32x4::z() const {
return untag()->value_[2];
}
float Float32x4::w() const {
return untag()->value_[3];
}
const char* Float32x4::ToCString() const {
float _x = x();
float _y = y();
float _z = z();
float _w = w();
return OS::SCreate(Thread::Current()->zone(), "[%f, %f, %f, %f]", _x, _y, _z,
_w);
}
Int32x4Ptr Int32x4::New(int32_t v0,
int32_t v1,
int32_t v2,
int32_t v3,
Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->int32x4_class() !=
Class::null());
Int32x4& result = Int32x4::Handle();
{
ObjectPtr raw = Object::Allocate(Int32x4::kClassId, Int32x4::InstanceSize(),
space, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_x(v0);
result.set_y(v1);
result.set_z(v2);
result.set_w(v3);
return result.ptr();
}
Int32x4Ptr Int32x4::New(simd128_value_t value, Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->int32x4_class() !=
Class::null());
Int32x4& result = Int32x4::Handle();
{
ObjectPtr raw = Object::Allocate(Int32x4::kClassId, Int32x4::InstanceSize(),
space, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_value(value);
return result.ptr();
}
void Int32x4::set_x(int32_t value) const {
StoreNonPointer(&untag()->value_[0], value);
}
void Int32x4::set_y(int32_t value) const {
StoreNonPointer(&untag()->value_[1], value);
}
void Int32x4::set_z(int32_t value) const {
StoreNonPointer(&untag()->value_[2], value);
}
void Int32x4::set_w(int32_t value) const {
StoreNonPointer(&untag()->value_[3], value);
}
int32_t Int32x4::x() const {
return untag()->value_[0];
}
int32_t Int32x4::y() const {
return untag()->value_[1];
}
int32_t Int32x4::z() const {
return untag()->value_[2];
}
int32_t Int32x4::w() const {
return untag()->value_[3];
}
simd128_value_t Int32x4::value() const {
return LoadUnaligned(
reinterpret_cast<const simd128_value_t*>(&untag()->value_));
}
void Int32x4::set_value(simd128_value_t value) const {
StoreUnaligned(reinterpret_cast<simd128_value_t*>(&ptr()->untag()->value_),
value);
}
const char* Int32x4::ToCString() const {
int32_t _x = x();
int32_t _y = y();
int32_t _z = z();
int32_t _w = w();
return OS::SCreate(Thread::Current()->zone(), "[%08x, %08x, %08x, %08x]", _x,
_y, _z, _w);
}
Float64x2Ptr Float64x2::New(double value0, double value1, Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->float64x2_class() !=
Class::null());
Float64x2& result = Float64x2::Handle();
{
ObjectPtr raw =
Object::Allocate(Float64x2::kClassId, Float64x2::InstanceSize(), space,
/*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_x(value0);
result.set_y(value1);
return result.ptr();
}
Float64x2Ptr Float64x2::New(simd128_value_t value, Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->float64x2_class() !=
Class::null());
Float64x2& result = Float64x2::Handle();
{
ObjectPtr raw =
Object::Allocate(Float64x2::kClassId, Float64x2::InstanceSize(), space,
/*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_value(value);
return result.ptr();
}
double Float64x2::x() const {
return untag()->value_[0];
}
double Float64x2::y() const {
return untag()->value_[1];
}
void Float64x2::set_x(double x) const {
StoreNonPointer(&untag()->value_[0], x);
}
void Float64x2::set_y(double y) const {
StoreNonPointer(&untag()->value_[1], y);
}
simd128_value_t Float64x2::value() const {
return simd128_value_t().readFrom(&untag()->value_[0]);
}
void Float64x2::set_value(simd128_value_t value) const {
StoreSimd128(&untag()->value_[0], value);
}
const char* Float64x2::ToCString() const {
double _x = x();
double _y = y();
return OS::SCreate(Thread::Current()->zone(), "[%f, %f]", _x, _y);
}
const intptr_t
TypedDataBase::element_size_table[TypedDataBase::kNumElementSizes] = {
1, // kTypedDataInt8ArrayCid.
1, // kTypedDataUint8ArrayCid.
1, // kTypedDataUint8ClampedArrayCid.
2, // kTypedDataInt16ArrayCid.
2, // kTypedDataUint16ArrayCid.
4, // kTypedDataInt32ArrayCid.
4, // kTypedDataUint32ArrayCid.
8, // kTypedDataInt64ArrayCid.
8, // kTypedDataUint64ArrayCid.
4, // kTypedDataFloat32ArrayCid.
8, // kTypedDataFloat64ArrayCid.
16, // kTypedDataFloat32x4ArrayCid.
16, // kTypedDataInt32x4ArrayCid.
16, // kTypedDataFloat64x2ArrayCid,
};
bool TypedData::CanonicalizeEquals(const Instance& other) const {
if (this->ptr() == other.ptr()) {
// Both handles point to the same raw instance.
return true;
}
if (!other.IsTypedData() || other.IsNull()) {
return false;
}
const TypedData& other_typed_data = TypedData::Cast(other);
if (this->ElementType() != other_typed_data.ElementType()) {
return false;
}
const intptr_t len = this->LengthInBytes();
if (len != other_typed_data.LengthInBytes()) {
return false;
}
NoSafepointScope no_safepoint;
return (len == 0) ||
(memcmp(DataAddr(0), other_typed_data.DataAddr(0), len) == 0);
}
uint32_t TypedData::CanonicalizeHash() const {
const intptr_t len = this->LengthInBytes();
if (len == 0) {
return 1;
}
uint32_t hash = len;
for (intptr_t i = 0; i < len; i++) {
hash = CombineHashes(len, GetUint8(i));
}
return FinalizeHash(hash, kHashBits);
}
TypedDataPtr TypedData::New(intptr_t class_id,
intptr_t len,
Heap::Space space) {
if (len < 0 || len > TypedData::MaxElements(class_id)) {
FATAL1("Fatal error in TypedData::New: invalid len %" Pd "\n", len);
}
TypedData& result = TypedData::Handle();
{
const intptr_t length_in_bytes = len * ElementSizeInBytes(class_id);
ObjectPtr raw =
Object::Allocate(class_id, TypedData::InstanceSize(length_in_bytes),
space, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.SetLength(len);
result.RecomputeDataField();
}
return result.ptr();
}
const char* TypedData::ToCString() const {
switch (GetClassId()) {
#define CASE_TYPED_DATA_CLASS(clazz) \
case kTypedData##clazz##Cid: \
return #clazz;
CLASS_LIST_TYPED_DATA(CASE_TYPED_DATA_CLASS);
#undef CASE_TYPED_DATA_CLASS
}
return "TypedData";
}
FinalizablePersistentHandle* ExternalTypedData::AddFinalizer(
void* peer,
Dart_HandleFinalizer callback,
intptr_t external_size) const {
return dart::AddFinalizer(*this, peer, callback, external_size);
}
ExternalTypedDataPtr ExternalTypedData::New(
intptr_t class_id,
uint8_t* data,
intptr_t len,
Heap::Space space,
bool perform_eager_msan_initialization_check) {
if (len < 0 || len > ExternalTypedData::MaxElements(class_id)) {
FATAL1("Fatal error in ExternalTypedData::New: invalid len %" Pd "\n", len);
}
if (perform_eager_msan_initialization_check) {
// Once the TypedData is created, Dart might read this memory. Check for
// intialization at construction to make it easier to track the source.
MSAN_CHECK_INITIALIZED(data, len);
}
ExternalTypedData& result = ExternalTypedData::Handle();
{
ObjectPtr raw =
Object::Allocate(class_id, ExternalTypedData::InstanceSize(), space,
/*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.SetLength(len);
result.SetData(data);
}
return result.ptr();
}
ExternalTypedDataPtr ExternalTypedData::NewFinalizeWithFree(uint8_t* data,
intptr_t len) {
ExternalTypedData& result = ExternalTypedData::Handle(ExternalTypedData::New(
kExternalTypedDataUint8ArrayCid, data, len, Heap::kOld));
result.AddFinalizer(
data, [](void* isolate_callback_data, void* data) { free(data); }, len);
return result.ptr();
}
TypedDataViewPtr TypedDataView::New(intptr_t class_id, Heap::Space space) {
auto& result = TypedDataView::Handle();
{
ObjectPtr raw = Object::Allocate(class_id, TypedDataView::InstanceSize(),
space, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.Clear();
}
return result.ptr();
}
TypedDataViewPtr TypedDataView::New(intptr_t class_id,
const TypedDataBase& typed_data,
intptr_t offset_in_bytes,
intptr_t length,
Heap::Space space) {
auto& result = TypedDataView::Handle(TypedDataView::New(class_id, space));
result.InitializeWith(typed_data, offset_in_bytes, length);
return result.ptr();
}
const char* TypedDataBase::ToCString() const {
// There are no instances of RawTypedDataBase.
UNREACHABLE();
return nullptr;
}
const char* TypedDataView::ToCString() const {
auto zone = Thread::Current()->zone();
return OS::SCreate(zone, "TypedDataView(cid: %" Pd ")", GetClassId());
}
const char* ExternalTypedData::ToCString() const {
return "ExternalTypedData";
}
PointerPtr Pointer::New(const AbstractType& type_arg,
uword native_address,
Heap::Space space) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
TypeArguments& type_args = TypeArguments::Handle(zone);
type_args = TypeArguments::New(1);
type_args.SetTypeAt(Pointer::kNativeTypeArgPos, type_arg);
type_args = type_args.Canonicalize(thread, nullptr);
const Class& cls =
Class::Handle(IsolateGroup::Current()->class_table()->At(kFfiPointerCid));
cls.EnsureIsAllocateFinalized(Thread::Current());
Pointer& result = Pointer::Handle(zone);
result ^= Object::Allocate(kFfiPointerCid, Pointer::InstanceSize(), space,
/*compressed*/ false);
result.SetTypeArguments(type_args);
result.SetNativeAddress(native_address);
return result.ptr();
}
const char* Pointer::ToCString() const {
TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments());
String& type_args_name = String::Handle(type_args.UserVisibleName());
return OS::SCreate(Thread::Current()->zone(), "Pointer%s: address=0x%" Px,
type_args_name.ToCString(), NativeAddress());
}
DynamicLibraryPtr DynamicLibrary::New(void* handle, Heap::Space space) {
DynamicLibrary& result = DynamicLibrary::Handle();
result ^=
Object::Allocate(kFfiDynamicLibraryCid, DynamicLibrary::InstanceSize(),
space, /*compressed*/ false);
NoSafepointScope no_safepoint;
result.SetHandle(handle);
return result.ptr();
}
bool Pointer::IsPointer(const Instance& obj) {
return IsFfiPointerClassId(obj.ptr()->GetClassId());
}
bool Instance::IsPointer() const {
return Pointer::IsPointer(*this);
}
const char* DynamicLibrary::ToCString() const {
return OS::SCreate(Thread::Current()->zone(), "DynamicLibrary: handle=0x%" Px,
reinterpret_cast<uintptr_t>(GetHandle()));
}
CapabilityPtr Capability::New(uint64_t id, Heap::Space space) {
Capability& result = Capability::Handle();
{
ObjectPtr raw =
Object::Allocate(Capability::kClassId, Capability::InstanceSize(),
space, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
result.StoreNonPointer(&result.untag()->id_, id);
}
return result.ptr();
}
const char* Capability::ToCString() const {
return "Capability";
}
ReceivePortPtr ReceivePort::New(Dart_Port id,
const String& debug_name,
bool is_control_port,
Heap::Space space) {
ASSERT(id != ILLEGAL_PORT);
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const SendPort& send_port =
SendPort::Handle(zone, SendPort::New(id, thread->isolate()->origin_id()));
#if !defined(PRODUCT)
const StackTrace& allocation_location_ =
HasStack() ? GetCurrentStackTrace(0) : StackTrace::Handle();
#endif // !defined(PRODUCT)
ReceivePort& result = ReceivePort::Handle(zone);
{
ObjectPtr raw =
Object::Allocate(ReceivePort::kClassId, ReceivePort::InstanceSize(),
space, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.untag()->set_send_port(send_port.ptr());
#if !defined(PRODUCT)
result.untag()->set_debug_name(debug_name.ptr());
result.untag()->set_allocation_location(allocation_location_.ptr());
#endif // !defined(PRODUCT)
}
if (is_control_port) {
PortMap::SetPortState(id, PortMap::kControlPort);
} else {
PortMap::SetPortState(id, PortMap::kLivePort);
}
return result.ptr();
}
const char* ReceivePort::ToCString() const {
return "ReceivePort";
}
SendPortPtr SendPort::New(Dart_Port id, Heap::Space space) {
return New(id, Isolate::Current()->origin_id(), space);
}
SendPortPtr SendPort::New(Dart_Port id,
Dart_Port origin_id,
Heap::Space space) {
ASSERT(id != ILLEGAL_PORT);
SendPort& result = SendPort::Handle();
{
ObjectPtr raw =
Object::Allocate(SendPort::kClassId, SendPort::InstanceSize(), space,
/*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
result.StoreNonPointer(&result.untag()->id_, id);
result.StoreNonPointer(&result.untag()->origin_id_, origin_id);
}
return result.ptr();
}
const char* SendPort::ToCString() const {
return "SendPort";
}
static void TransferableTypedDataFinalizer(void* isolate_callback_data,
void* peer) {
delete (reinterpret_cast<TransferableTypedDataPeer*>(peer));
}
TransferableTypedDataPtr TransferableTypedData::New(uint8_t* data,
intptr_t length,
Heap::Space space) {
TransferableTypedDataPeer* peer = new TransferableTypedDataPeer(data, length);
Thread* thread = Thread::Current();
TransferableTypedData& result = TransferableTypedData::Handle();
{
ObjectPtr raw = Object::Allocate(TransferableTypedData::kClassId,
TransferableTypedData::InstanceSize(),
space, /*compressed*/ false);
NoSafepointScope no_safepoint;
thread->heap()->SetPeer(raw, peer);
result ^= raw;
}
// Set up finalizer so it frees allocated memory if handle is
// garbage-collected.
peer->set_handle(
FinalizablePersistentHandle::New(thread->isolate_group(), result, peer,
&TransferableTypedDataFinalizer, length,
/*auto_delete=*/true));
return result.ptr();
}
const char* TransferableTypedData::ToCString() const {
return "TransferableTypedData";
}
bool Closure::CanonicalizeEquals(const Instance& other) const {
if (!other.IsClosure()) return false;
const Closure& other_closure = Closure::Cast(other);
return (instantiator_type_arguments() ==
other_closure.instantiator_type_arguments()) &&
(function_type_arguments() ==
other_closure.function_type_arguments()) &&
(delayed_type_arguments() == other_closure.delayed_type_arguments()) &&
(function() == other_closure.function()) &&
(context() == other_closure.context());
}
void Closure::CanonicalizeFieldsLocked(Thread* thread) const {
TypeArguments& type_args = TypeArguments::Handle();
type_args = instantiator_type_arguments();
if (!type_args.IsNull()) {
type_args = type_args.Canonicalize(thread, nullptr);
set_instantiator_type_arguments(type_args);
}
type_args = function_type_arguments();
if (!type_args.IsNull()) {
type_args = type_args.Canonicalize(thread, nullptr);
set_function_type_arguments(type_args);
}
type_args = delayed_type_arguments();
if (!type_args.IsNull()) {
type_args = type_args.Canonicalize(thread, nullptr);
set_delayed_type_arguments(type_args);
}
// Ignore function, context, hash.
}
intptr_t Closure::NumTypeParameters(Thread* thread) const {
// Only check for empty here, as the null TAV is used to mean that the
// closed-over delayed type parameters were all of dynamic type.
if (delayed_type_arguments() != Object::empty_type_arguments().ptr()) {
return 0;
} else {
const auto& closure_function = Function::Handle(thread->zone(), function());
return closure_function.NumTypeParameters();
}
}
const char* Closure::ToCString() const {
auto const thread = Thread::Current();
auto const zone = thread->zone();
ZoneTextBuffer buffer(zone);
buffer.AddString("Closure: ");
const Function& fun = Function::Handle(zone, function());
const FunctionType& sig =
FunctionType::Handle(zone, GetInstantiatedSignature(zone));
sig.Print(kUserVisibleName, &buffer);
if (fun.IsImplicitClosureFunction()) {
buffer.Printf(" from %s", fun.ToCString());
}
return buffer.buffer();
}
int64_t Closure::ComputeHash() const {
Thread* thread = Thread::Current();
DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame());
Zone* zone = thread->zone();
const Function& func = Function::Handle(zone, function());
uint32_t result = 0;
if (func.IsImplicitInstanceClosureFunction()) {
// Implicit instance closures are not unique, so combine function's hash
// code with identityHashCode of cached receiver.
result = static_cast<uint32_t>(func.ComputeClosureHash());
const Context& context = Context::Handle(zone, this->context());
const Instance& receiver =
Instance::Handle(zone, Instance::RawCast(context.At(0)));
const Object& receiverHash =
Object::Handle(zone, receiver.IdentityHashCode());
if (receiverHash.IsError()) {
Exceptions::PropagateError(Error::Cast(receiverHash));
UNREACHABLE();
}
result = CombineHashes(
result, Integer::Cast(receiverHash).AsTruncatedUint32Value());
} else {
// Explicit closures and implicit static closures are unique,
// so identityHashCode of closure object is good enough.
const Object& identityHash = Object::Handle(zone, this->IdentityHashCode());
if (identityHash.IsError()) {
Exceptions::PropagateError(Error::Cast(identityHash));
UNREACHABLE();
}
result = Integer::Cast(identityHash).AsTruncatedUint32Value();
}
return FinalizeHash(result, String::kHashBits);
}
ClosurePtr Closure::New(const TypeArguments& instantiator_type_arguments,
const TypeArguments& function_type_arguments,
const Function& function,
const Context& context,
Heap::Space space) {
return Closure::New(instantiator_type_arguments, function_type_arguments,
function.IsGeneric() ? Object::empty_type_arguments()
: Object::null_type_arguments(),
function, context, space);
}
ClosurePtr Closure::New(const TypeArguments& instantiator_type_arguments,
const TypeArguments& function_type_arguments,
const TypeArguments& delayed_type_arguments,
const Function& function,
const Context& context,
Heap::Space space) {
Closure& result = Closure::Handle();
{
ObjectPtr raw = Object::Allocate(Closure::kClassId, Closure::InstanceSize(),
space, /*compressed*/ false);
NoSafepointScope no_safepoint;
result ^= raw;
result.untag()->set_instantiator_type_arguments(
instantiator_type_arguments.ptr());
result.untag()->set_function_type_arguments(function_type_arguments.ptr());
result.untag()->set_delayed_type_arguments(delayed_type_arguments.ptr());
result.untag()->set_function(function.ptr());
result.untag()->set_context(context.ptr());
}
return result.ptr();
}
ClosurePtr Closure::New() {
ObjectPtr raw = Object::Allocate(Closure::kClassId, Closure::InstanceSize(),
Heap::kOld, /*compressed*/ false);
return static_cast<ClosurePtr>(raw);
}
FunctionTypePtr Closure::GetInstantiatedSignature(Zone* zone) const {
Function& fun = Function::Handle(zone, function());
FunctionType& sig = FunctionType::Handle(zone, fun.signature());
TypeArguments& fn_type_args =
TypeArguments::Handle(zone, function_type_arguments());
const TypeArguments& delayed_type_args =
TypeArguments::Handle(zone, delayed_type_arguments());
const TypeArguments& inst_type_args =
TypeArguments::Handle(zone, instantiator_type_arguments());
// We detect the case of a partial tearoff type application and substitute the
// type arguments for the type parameters of the function.
intptr_t num_free_params;
if (delayed_type_args.ptr() != Object::empty_type_arguments().ptr()) {
num_free_params = kCurrentAndEnclosingFree;
fn_type_args = delayed_type_args.Prepend(
zone, fn_type_args, sig.NumParentTypeArguments(),
sig.NumTypeParameters() + sig.NumParentTypeArguments());
} else {
num_free_params = kAllFree;
}
if (num_free_params == kCurrentAndEnclosingFree ||
!sig.IsInstantiated(kAny)) {
sig ^= sig.InstantiateFrom(inst_type_args, fn_type_args, num_free_params,
Heap::kOld);
}
return sig.ptr();
}
bool StackTrace::skip_sync_start_in_parent_stack() const {
return untag()->skip_sync_start_in_parent_stack;
}
void StackTrace::set_skip_sync_start_in_parent_stack(bool value) const {
StoreNonPointer(&untag()->skip_sync_start_in_parent_stack, value);
}
intptr_t StackTrace::Length() const {
const Array& code_array = Array::Handle(untag()->code_array());
return code_array.Length();
}
ObjectPtr StackTrace::CodeAtFrame(intptr_t frame_index) const {
const Array& code_array = Array::Handle(untag()->code_array());
return code_array.At(frame_index);
}
void StackTrace::SetCodeAtFrame(intptr_t frame_index,
const Object& code) const {
const Array& code_array = Array::Handle(untag()->code_array());
code_array.SetAt(frame_index, code);
}
uword StackTrace::PcOffsetAtFrame(intptr_t frame_index) const {
const TypedData& pc_offset_array =
TypedData::Handle(untag()->pc_offset_array());
return pc_offset_array.GetUintPtr(frame_index * kWordSize);
}
void StackTrace::SetPcOffsetAtFrame(intptr_t frame_index,
uword pc_offset) const {
const TypedData& pc_offset_array =
TypedData::Handle(untag()->pc_offset_array());
pc_offset_array.SetUintPtr(frame_index * kWordSize, pc_offset);
}
void StackTrace::set_async_link(const StackTrace& async_link) const {
untag()->set_async_link(async_link.ptr());
}
void StackTrace::set_code_array(const Array& code_array) const {
untag()->set_code_array(code_array.ptr());
}
void StackTrace::set_pc_offset_array(const TypedData& pc_offset_array) const {
untag()->set_pc_offset_array(pc_offset_array.ptr());
}
void StackTrace::set_expand_inlined(bool value) const {
StoreNonPointer(&untag()->expand_inlined_, value);
}
bool StackTrace::expand_inlined() const {
return untag()->expand_inlined_;
}
StackTracePtr StackTrace::New(const Array& code_array,
const TypedData& pc_offset_array,
Heap::Space space) {
StackTrace& result = StackTrace::Handle();
{
ObjectPtr raw =
Object::Allocate(StackTrace::kClassId, StackTrace::InstanceSize(),
space, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_code_array(code_array);
result.set_pc_offset_array(pc_offset_array);
result.set_expand_inlined(true); // default.
result.set_skip_sync_start_in_parent_stack(false);
return result.ptr();
}
StackTracePtr StackTrace::New(const Array& code_array,
const TypedData& pc_offset_array,
const StackTrace& async_link,
bool skip_sync_start_in_parent_stack,
Heap::Space space) {
StackTrace& result = StackTrace::Handle();
{
ObjectPtr raw =
Object::Allocate(StackTrace::kClassId, StackTrace::InstanceSize(),
space, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_async_link(async_link);
result.set_code_array(code_array);
result.set_pc_offset_array(pc_offset_array);
result.set_expand_inlined(true); // default.
result.set_skip_sync_start_in_parent_stack(skip_sync_start_in_parent_stack);
return result.ptr();
}
#if defined(DART_PRECOMPILED_RUNTIME)
// Prints the best representation(s) for the call address.
static void PrintNonSymbolicStackFrameBody(BaseTextBuffer* buffer,
uword call_addr,
uword isolate_instructions,
uword vm_instructions) {
const Image vm_image(reinterpret_cast<const void*>(vm_instructions));
const Image isolate_image(
reinterpret_cast<const void*>(isolate_instructions));
if (isolate_image.contains(call_addr)) {
auto const symbol_name = kIsolateSnapshotInstructionsAsmSymbol;
auto const offset = call_addr - isolate_instructions;
// Only print the relocated address of the call when we know the saved
// debugging information (if any) will have the same relocated address.
if (isolate_image.compiled_to_elf()) {
const uword relocated_section_start =
isolate_image.instructions_relocated_address();
buffer->Printf(" virt %" Pp "", relocated_section_start + offset);
}
buffer->Printf(" %s+0x%" Px "", symbol_name, offset);
} else if (vm_image.contains(call_addr)) {
auto const offset = call_addr - vm_instructions;
// We currently don't print 'virt' entries for vm addresses, even if
// they were compiled to ELF, as we should never encounter these in
// non-symbolic stack traces (since stub addresses are stripped).
//
// In case they leak due to code issues elsewhere, we still print them as
// <vm symbol>+<offset>, just to distinguish from other cases.
buffer->Printf(" %s+0x%" Px "", kVmSnapshotInstructionsAsmSymbol, offset);
} else {
// This case should never happen, since these are not addresses within the
// VM or app isolate instructions sections, so make it easy to notice.
buffer->Printf(" <invalid Dart instruction address>");
}
buffer->Printf("\n");
}
#endif
static void PrintSymbolicStackFrameIndex(BaseTextBuffer* buffer,
intptr_t frame_index) {
buffer->Printf("#%-6" Pd "", frame_index);
}
static void PrintSymbolicStackFrameBody(BaseTextBuffer* buffer,
const char* function_name,
const char* url,
intptr_t line = -1,
intptr_t column = -1) {
buffer->Printf(" %s (%s", function_name, url);
if (line >= 0) {
buffer->Printf(":%" Pd "", line);
if (column >= 0) {
buffer->Printf(":%" Pd "", column);
}
}
buffer->Printf(")\n");
}
static void PrintSymbolicStackFrame(Zone* zone,
BaseTextBuffer* buffer,
const Function& function,
TokenPosition token_pos_or_line,
intptr_t frame_index,
bool is_line = false) {
ASSERT(!function.IsNull());
const auto& script = Script::Handle(zone, function.script());
const char* function_name = function.QualifiedUserVisibleNameCString();
const char* url = script.IsNull()
? "Kernel"
: String::Handle(zone, script.url()).ToCString();
// If the URI starts with "data:application/dart;" this is a URI encoded
// script so we shouldn't print the entire URI because it could be very long.
if (strstr(url, "data:application/dart;") == url) {
url = "<data:application/dart>";
}
intptr_t line = -1;
intptr_t column = -1;
if (is_line) {
ASSERT(token_pos_or_line.IsNoSource() || token_pos_or_line.IsReal());
if (token_pos_or_line.IsReal()) {
line = token_pos_or_line.Pos();
}
} else {
ASSERT(!script.IsNull());
script.GetTokenLocation(token_pos_or_line, &line, &column);
}
PrintSymbolicStackFrameIndex(buffer, frame_index);
PrintSymbolicStackFrameBody(buffer, function_name, url, line, column);
}
const char* StackTrace::ToCString() const {
auto const T = Thread::Current();
auto const zone = T->zone();
auto& stack_trace = StackTrace::Handle(zone, this->ptr());
auto& owner = Object::Handle(zone);
auto& function = Function::Handle(zone);
auto& code_object = Object::Handle(zone);
auto& code = Code::Handle(zone);
NoSafepointScope no_allocation;
GrowableArray<const Function*> inlined_functions;
GrowableArray<TokenPosition> inlined_token_positions;
ZoneTextBuffer buffer(zone, 1024);
#if defined(DART_PRECOMPILED_RUNTIME)
auto const isolate_instructions = reinterpret_cast<uword>(
T->isolate_group()->source()->snapshot_instructions);
auto const vm_instructions = reinterpret_cast<uword>(
Dart::vm_isolate_group()->source()->snapshot_instructions);
if (FLAG_dwarf_stack_traces_mode) {
const Image isolate_instructions_image(
reinterpret_cast<const void*>(isolate_instructions));
const Image vm_instructions_image(
reinterpret_cast<const void*>(vm_instructions));
auto const isolate_relocated_address =
isolate_instructions_image.instructions_relocated_address();
auto const vm_relocated_address =
vm_instructions_image.instructions_relocated_address();
// The Dart standard requires the output of StackTrace.toString to include
// all pending activations with precise source locations (i.e., to expand
// inlined frames and provide line and column numbers).
buffer.Printf(
"Warning: This VM has been configured to produce stack traces "
"that violate the Dart standard.\n");
// This prologue imitates Android's debuggerd to make it possible to paste
// the stack trace into ndk-stack.
buffer.Printf(
"*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
OSThread* thread = OSThread::Current();
buffer.Printf("pid: %" Pd ", tid: %" Pd ", name %s\n", OS::ProcessId(),
OSThread::ThreadIdToIntPtr(thread->id()), thread->name());
if (auto const build_id = isolate_instructions_image.build_id()) {
const intptr_t length = isolate_instructions_image.build_id_length();
buffer.Printf("build_id: '");
for (intptr_t i = 0; i < length; i++) {
buffer.Printf("%2.2x", build_id[i]);
}
buffer.Printf("'\n");
}
// Print the dso_base of the VM and isolate_instructions. We print both here
// as the VM and isolate may be loaded from different snapshot images.
buffer.Printf("isolate_dso_base: %" Px "",
isolate_instructions - isolate_relocated_address);
buffer.Printf(", vm_dso_base: %" Px "\n",
vm_instructions - vm_relocated_address);
buffer.Printf("isolate_instructions: %" Px "", isolate_instructions);
buffer.Printf(", vm_instructions: %" Px "\n", vm_instructions);
}
#endif
// Iterate through the stack frames and create C string description
// for each frame.
intptr_t frame_index = 0;
uint32_t frame_skip = 0;
// If we're already in a gap, don't print multiple gap markers.
bool in_gap = false;
do {
for (intptr_t i = frame_skip; i < stack_trace.Length(); i++) {
code_object = stack_trace.CodeAtFrame(i);
if (code_object.IsNull()) {
// Check for a null function, which indicates a gap in a StackOverflow
// or OutOfMemory trace.
if ((i < (stack_trace.Length() - 1)) &&
(stack_trace.CodeAtFrame(i + 1) != Code::null())) {
buffer.AddString("...\n...\n");
// To account for gap frames.
frame_index += stack_trace.PcOffsetAtFrame(i);
}
continue;
}
if (code_object.ptr() == StubCode::AsynchronousGapMarker().ptr()) {
if (!in_gap) {
buffer.AddString("<asynchronous suspension>\n");
}
in_gap = true;
continue;
}
const uword pc_offset = stack_trace.PcOffsetAtFrame(i);
ASSERT(code_object.IsCode());
code ^= code_object.ptr();
ASSERT(code.IsFunctionCode());
owner = code.owner();
if (owner.IsFunction()) {
function ^= owner.ptr();
} else {
function = Function::null();
}
const uword pc = code.PayloadStart() + pc_offset;
// If the function is not to be shown, skip.
if (!FLAG_show_invisible_frames && !function.IsNull() &&
!function.is_visible()) {
continue;
}
// A visible frame ends any gap we might be in.
in_gap = false;
#if defined(DART_PRECOMPILED_RUNTIME)
// When printing non-symbolic frames, we normally print call
// addresses, not return addresses, by subtracting one from the PC to
// get an address within the preceding instruction.
//
// The one exception is a normal closure registered as a listener on a
// future. In this case, the returned pc_offset is 0, as the closure
// is invoked with the value of the resolved future. Thus, we must
// report the return address, as returning a value before the closure
// payload will cause failures to decode the frame using DWARF info.
const bool is_future_listener = pc_offset == 0;
const uword call_addr = is_future_listener ? pc : pc - 1;
if (FLAG_dwarf_stack_traces_mode) {
// This output is formatted like Android's debuggerd. Note debuggerd
// prints call addresses instead of return addresses.
buffer.Printf(" #%02" Pd " abs %" Pp "", frame_index, call_addr);
PrintNonSymbolicStackFrameBody(&buffer, call_addr, isolate_instructions,
vm_instructions);
frame_index++;
continue;
}
if (function.IsNull()) {
in_gap = false;
// We can't print the symbolic information since the owner was not
// retained, so instead print the static symbol + offset like the
// non-symbolic stack traces.
PrintSymbolicStackFrameIndex(&buffer, frame_index);
PrintNonSymbolicStackFrameBody(&buffer, call_addr, isolate_instructions,
vm_instructions);
frame_index++;
continue;
}
#endif
if (code.is_optimized() && stack_trace.expand_inlined()) {
code.GetInlinedFunctionsAtReturnAddress(pc_offset, &inlined_functions,
&inlined_token_positions);
ASSERT(inlined_functions.length() >= 1);
for (intptr_t j = inlined_functions.length() - 1; j >= 0; j--) {
const auto& inlined = *inlined_functions[j];
auto const pos = inlined_token_positions[j];
PrintSymbolicStackFrame(zone, &buffer, inlined, pos, frame_index,
/*is_line=*/FLAG_precompiled_mode);
frame_index++;
}
continue;
}
auto const pos = code.GetTokenIndexOfPC(pc);
PrintSymbolicStackFrame(zone, &buffer, function, pos, frame_index);
frame_index++;
}
// Follow the link.
frame_skip = stack_trace.skip_sync_start_in_parent_stack()
? StackTrace::kSyncAsyncCroppedFrames
: 0;
stack_trace = stack_trace.async_link();
} while (!stack_trace.IsNull());
return buffer.buffer();
}
static void DwarfStackTracesHandler(bool value) {
FLAG_dwarf_stack_traces_mode = value;
#if defined(PRODUCT)
// We can safely remove function objects in precompiled snapshots if the
// runtime will generate DWARF stack traces and we don't have runtime
// debugging options like the observatory available.
if (value) {
FLAG_retain_function_objects = false;
FLAG_retain_code_objects = false;
}
#endif
}
DEFINE_FLAG_HANDLER(DwarfStackTracesHandler,
dwarf_stack_traces,
"Omit CodeSourceMaps in precompiled snapshots and don't "
"symbolize stack traces in the precompiled runtime.");
void RegExp::set_pattern(const String& pattern) const {
untag()->set_pattern(pattern.ptr());
}
void RegExp::set_function(intptr_t cid,
bool sticky,
const Function& value) const {
if (sticky) {
switch (cid) {
case kOneByteStringCid:
return untag()->set_one_byte_sticky(value.ptr());
case kTwoByteStringCid:
return untag()->set_two_byte_sticky(value.ptr());
case kExternalOneByteStringCid:
return untag()->set_external_one_byte_sticky(value.ptr());
case kExternalTwoByteStringCid:
return untag()->set_external_two_byte_sticky(value.ptr());
}
} else {
switch (cid) {
case kOneByteStringCid:
return untag()->set_one_byte(value.ptr());
case kTwoByteStringCid:
return untag()->set_two_byte(value.ptr());
case kExternalOneByteStringCid:
return untag()->set_external_one_byte(value.ptr());
case kExternalTwoByteStringCid:
return untag()->set_external_two_byte(value.ptr());
}
}
}
void RegExp::set_bytecode(bool is_one_byte,
bool sticky,
const TypedData& bytecode) const {
if (sticky) {
if (is_one_byte) {
untag()->set_one_byte_sticky(bytecode.ptr());
} else {
untag()->set_two_byte_sticky(bytecode.ptr());
}
} else {
if (is_one_byte) {
untag()->set_one_byte(bytecode.ptr());
} else {
untag()->set_two_byte(bytecode.ptr());
}
}
}
void RegExp::set_num_bracket_expressions(intptr_t value) const {
untag()->num_bracket_expressions_ = value;
}
void RegExp::set_capture_name_map(const Array& array) const {
untag()->set_capture_name_map(array.ptr());
}
RegExpPtr RegExp::New(Heap::Space space) {
RegExp& result = RegExp::Handle();
{
ObjectPtr raw = Object::Allocate(RegExp::kClassId, RegExp::InstanceSize(),
space, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
result.set_type(kUninitialized);
result.set_flags(RegExpFlags());
result.set_num_bracket_expressions(-1);
result.set_num_registers(/*is_one_byte=*/false, -1);
result.set_num_registers(/*is_one_byte=*/true, -1);
}
return result.ptr();
}
const char* RegExpFlags::ToCString() const {
switch (value_ & ~kGlobal) {
case kIgnoreCase | kMultiLine | kDotAll | kUnicode:
return "imsu";
case kIgnoreCase | kMultiLine | kDotAll:
return "ims";
case kIgnoreCase | kMultiLine | kUnicode:
return "imu";
case kIgnoreCase | kUnicode | kDotAll:
return "ius";
case kMultiLine | kDotAll | kUnicode:
return "msu";
case kIgnoreCase | kMultiLine:
return "im";
case kIgnoreCase | kDotAll:
return "is";
case kIgnoreCase | kUnicode:
return "iu";
case kMultiLine | kDotAll:
return "ms";
case kMultiLine | kUnicode:
return "mu";
case kDotAll | kUnicode:
return "su";
case kIgnoreCase:
return "i";
case kMultiLine:
return "m";
case kDotAll:
return "s";
case kUnicode:
return "u";
default:
break;
}
return "";
}
bool RegExp::CanonicalizeEquals(const Instance& other) const {
if (this->ptr() == other.ptr()) {
return true; // "===".
}
if (other.IsNull() || !other.IsRegExp()) {
return false;
}
const RegExp& other_js = RegExp::Cast(other);
// Match the pattern.
const String& str1 = String::Handle(pattern());
const String& str2 = String::Handle(other_js.pattern());
if (!str1.Equals(str2)) {
return false;
}
// Match the flags.
if (flags() != other_js.flags()) {
return false;
}
return true;
}
const char* RegExp::ToCString() const {
const String& str = String::Handle(pattern());
return OS::SCreate(Thread::Current()->zone(), "RegExp: pattern=%s flags=%s",
str.ToCString(), flags().ToCString());
}
WeakPropertyPtr WeakProperty::New(Heap::Space space) {
ASSERT(IsolateGroup::Current()->object_store()->weak_property_class() !=
Class::null());
ObjectPtr raw =
Object::Allocate(WeakProperty::kClassId, WeakProperty::InstanceSize(),
space, /*compressed*/ false);
return static_cast<WeakPropertyPtr>(raw);
}
const char* WeakProperty::ToCString() const {
return "_WeakProperty";
}
AbstractTypePtr MirrorReference::GetAbstractTypeReferent() const {
ASSERT(Object::Handle(referent()).IsAbstractType());
return AbstractType::Cast(Object::Handle(referent())).ptr();
}
ClassPtr MirrorReference::GetClassReferent() const {
ASSERT(Object::Handle(referent()).IsClass());
return Class::Cast(Object::Handle(referent())).ptr();
}
FieldPtr MirrorReference::GetFieldReferent() const {
ASSERT(Object::Handle(referent()).IsField());
return Field::Cast(Object::Handle(referent())).ptr();
}
FunctionPtr MirrorReference::GetFunctionReferent() const {
ASSERT(Object::Handle(referent()).IsFunction());
return Function::Cast(Object::Handle(referent())).ptr();
}
FunctionTypePtr MirrorReference::GetFunctionTypeReferent() const {
ASSERT(Object::Handle(referent()).IsFunctionType());
return FunctionType::Cast(Object::Handle(referent())).ptr();
}
LibraryPtr MirrorReference::GetLibraryReferent() const {
ASSERT(Object::Handle(referent()).IsLibrary());
return Library::Cast(Object::Handle(referent())).ptr();
}
TypeParameterPtr MirrorReference::GetTypeParameterReferent() const {
ASSERT(Object::Handle(referent()).IsTypeParameter());
return TypeParameter::Cast(Object::Handle(referent())).ptr();
}
MirrorReferencePtr MirrorReference::New(const Object& referent,
Heap::Space space) {
MirrorReference& result = MirrorReference::Handle();
{
ObjectPtr raw = Object::Allocate(MirrorReference::kClassId,
MirrorReference::InstanceSize(), space,
/*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_referent(referent);
return result.ptr();
}
const char* MirrorReference::ToCString() const {
return "_MirrorReference";
}
void UserTag::MakeActive() const {
Isolate* isolate = Isolate::Current();
ASSERT(isolate != NULL);
isolate->set_current_tag(*this);
}
UserTagPtr UserTag::New(const String& label, Heap::Space space) {
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
ASSERT(isolate->tag_table() != GrowableObjectArray::null());
// Canonicalize by name.
UserTag& result = UserTag::Handle(FindTagInIsolate(thread, label));
if (!result.IsNull()) {
// Tag already exists, return existing instance.
return result.ptr();
}
if (TagTableIsFull(thread)) {
const String& error = String::Handle(String::NewFormatted(
"UserTag instance limit (%" Pd ") reached.", UserTags::kMaxUserTags));
const Array& args = Array::Handle(Array::New(1));
args.SetAt(0, error);
Exceptions::ThrowByType(Exceptions::kUnsupported, args);
}
// No tag with label exists, create and register with isolate tag table.
{
ObjectPtr raw = Object::Allocate(UserTag::kClassId, UserTag::InstanceSize(),
space, /*compressed*/ true);
NoSafepointScope no_safepoint;
result ^= raw;
}
result.set_label(label);
AddTagToIsolate(thread, result);
return result.ptr();
}
UserTagPtr UserTag::DefaultTag() {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Isolate* isolate = thread->isolate();
ASSERT(isolate != NULL);
if (isolate->default_tag() != UserTag::null()) {
// Already created.
return isolate->default_tag();
}
// Create default tag.
const UserTag& result =
UserTag::Handle(zone, UserTag::New(Symbols::Default()));
ASSERT(result.tag() == UserTags::kDefaultUserTag);
isolate->set_default_tag(result);
return result.ptr();
}
UserTagPtr UserTag::FindTagInIsolate(Thread* thread, const String& label) {
Isolate* isolate = thread->isolate();
Zone* zone = thread->zone();
ASSERT(isolate->tag_table() != GrowableObjectArray::null());
const GrowableObjectArray& tag_table =
GrowableObjectArray::Handle(zone, isolate->tag_table());
UserTag& other = UserTag::Handle(zone);
String& tag_label = String::Handle(zone);
for (intptr_t i = 0; i < tag_table.Length(); i++) {
other ^= tag_table.At(i);
ASSERT(!other.IsNull());
tag_label = other.label();
ASSERT(!tag_label.IsNull());
if (tag_label.Equals(label)) {
return other.ptr();
}
}
return UserTag::null();
}
void UserTag::AddTagToIsolate(Thread* thread, const UserTag& tag) {
Isolate* isolate = thread->isolate();
Zone* zone = thread->zone();
ASSERT(isolate->tag_table() != GrowableObjectArray::null());
const GrowableObjectArray& tag_table =
GrowableObjectArray::Handle(zone, isolate->tag_table());
ASSERT(!TagTableIsFull(thread));
#if defined(DEBUG)
// Verify that no existing tag has the same tag id.
UserTag& other = UserTag::Handle(thread->zone());
for (intptr_t i = 0; i < tag_table.Length(); i++) {
other ^= tag_table.At(i);
ASSERT(!other.IsNull());
ASSERT(tag.tag() != other.tag());
}
#endif
// Generate the UserTag tag id by taking the length of the isolate's
// tag table + kUserTagIdOffset.
uword tag_id = tag_table.Length() + UserTags::kUserTagIdOffset;
ASSERT(tag_id >= UserTags::kUserTagIdOffset);
ASSERT(tag_id < (UserTags::kUserTagIdOffset + UserTags::kMaxUserTags));
tag.set_tag(tag_id);
tag_table.Add(tag);
}
bool UserTag::TagTableIsFull(Thread* thread) {
Isolate* isolate = thread->isolate();
ASSERT(isolate->tag_table() != GrowableObjectArray::null());
const GrowableObjectArray& tag_table =
GrowableObjectArray::Handle(thread->zone(), isolate->tag_table());
ASSERT(tag_table.Length() <= UserTags::kMaxUserTags);
return tag_table.Length() == UserTags::kMaxUserTags;
}
UserTagPtr UserTag::FindTagById(uword tag_id) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Isolate* isolate = thread->isolate();
ASSERT(isolate->tag_table() != GrowableObjectArray::null());
const GrowableObjectArray& tag_table =
GrowableObjectArray::Handle(zone, isolate->tag_table());
UserTag& tag = UserTag::Handle(zone);
for (intptr_t i = 0; i < tag_table.Length(); i++) {
tag ^= tag_table.At(i);
if (tag.tag() == tag_id) {
return tag.ptr();
}
}
return UserTag::null();
}
const char* UserTag::ToCString() const {
const String& tag_label = String::Handle(label());
return tag_label.ToCString();
}
void DumpTypeTable(Isolate* isolate) {
OS::PrintErr("canonical types:\n");
CanonicalTypeSet table(isolate->group()->object_store()->canonical_types());
table.Dump();
table.Release();
}
void DumpFunctionTypeTable(Isolate* isolate) {
OS::PrintErr("canonical function types:\n");
CanonicalFunctionTypeSet table(
isolate->group()->object_store()->canonical_function_types());
table.Dump();
table.Release();
}
void DumpTypeParameterTable(Isolate* isolate) {
OS::PrintErr("canonical type parameters (cloned from declarations):\n");
CanonicalTypeParameterSet table(
isolate->group()->object_store()->canonical_type_parameters());
table.Dump();
table.Release();
}
void DumpTypeArgumentsTable(Isolate* isolate) {
OS::PrintErr("canonical type arguments:\n");
CanonicalTypeArgumentsSet table(
isolate->group()->object_store()->canonical_type_arguments());
table.Dump();
table.Release();
}
EntryPointPragma FindEntryPointPragma(IsolateGroup* IG,
const Array& metadata,
Field* reusable_field_handle,
Object* pragma) {
for (intptr_t i = 0; i < metadata.Length(); i++) {
*pragma = metadata.At(i);
if (pragma->clazz() != IG->object_store()->pragma_class()) {
continue;
}
*reusable_field_handle = IG->object_store()->pragma_name();
if (Instance::Cast(*pragma).GetField(*reusable_field_handle) !=
Symbols::vm_entry_point().ptr()) {
continue;
}
*reusable_field_handle = IG->object_store()->pragma_options();
*pragma = Instance::Cast(*pragma).GetField(*reusable_field_handle);
if (pragma->ptr() == Bool::null() || pragma->ptr() == Bool::True().ptr()) {
return EntryPointPragma::kAlways;
break;
}
if (pragma->ptr() == Symbols::Get().ptr()) {
return EntryPointPragma::kGetterOnly;
}
if (pragma->ptr() == Symbols::Set().ptr()) {
return EntryPointPragma::kSetterOnly;
}
if (pragma->ptr() == Symbols::Call().ptr()) {
return EntryPointPragma::kCallOnly;
}
}
return EntryPointPragma::kNever;
}
DART_WARN_UNUSED_RESULT
ErrorPtr VerifyEntryPoint(
const Library& lib,
const Object& member,
const Object& annotated,
std::initializer_list<EntryPointPragma> allowed_kinds) {
#if defined(DART_PRECOMPILED_RUNTIME)
// Annotations are discarded in the AOT snapshot, so we can't determine
// precisely if this member was marked as an entry-point. Instead, we use
// "has_pragma()" as a proxy, since that bit is usually retained.
bool is_marked_entrypoint = true;
if (annotated.IsClass() && !Class::Cast(annotated).has_pragma()) {
is_marked_entrypoint = false;
} else if (annotated.IsField() && !Field::Cast(annotated).has_pragma()) {
is_marked_entrypoint = false;
} else if (annotated.IsFunction() &&
!Function::Cast(annotated).has_pragma()) {
is_marked_entrypoint = false;
}
#else
Object& metadata = Object::Handle(Object::empty_array().ptr());
if (!annotated.IsNull()) {
metadata = lib.GetMetadata(annotated);
}
if (metadata.IsError()) return Error::RawCast(metadata.ptr());
ASSERT(!metadata.IsNull() && metadata.IsArray());
EntryPointPragma pragma =
FindEntryPointPragma(IsolateGroup::Current(), Array::Cast(metadata),
&Field::Handle(), &Object::Handle());
bool is_marked_entrypoint = pragma == EntryPointPragma::kAlways;
if (!is_marked_entrypoint) {
for (const auto allowed_kind : allowed_kinds) {
if (pragma == allowed_kind) {
is_marked_entrypoint = true;
break;
}
}
}
#endif
if (!is_marked_entrypoint) {
return EntryPointMemberInvocationError(member);
}
return Error::null();
}
DART_WARN_UNUSED_RESULT
ErrorPtr EntryPointFieldInvocationError(const String& getter_name) {
if (!FLAG_verify_entry_points) return Error::null();
char const* error = OS::SCreate(
Thread::Current()->zone(),
"ERROR: Entry-points do not allow invoking fields "
"(failure to resolve '%s')\n"
"ERROR: See "
"https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/"
"aot/entry_point_pragma.md\n",
getter_name.ToCString());
OS::PrintErr("%s", error);
return ApiError::New(String::Handle(String::New(error)));
}
DART_WARN_UNUSED_RESULT
ErrorPtr EntryPointMemberInvocationError(const Object& member) {
const char* member_cstring =
member.IsFunction()
? OS::SCreate(
Thread::Current()->zone(), "%s (kind %s)",
Function::Cast(member).ToLibNamePrefixedQualifiedCString(),
Function::KindToCString(Function::Cast(member).kind()))
: member.ToCString();
if (!FLAG_verify_entry_points) {
// Print a warning, but do not return an error.
char const* warning = OS::SCreate(
Thread::Current()->zone(),
"WARNING: '%s' is accessed through Dart C API without being marked as "
"an entry point; its tree-shaken signature cannot be verified.\n"
"WARNING: See "
"https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/"
"aot/entry_point_pragma.md\n",
member_cstring);
OS::PrintErr("%s", warning);
return Error::null();
}
char const* error = OS::SCreate(
Thread::Current()->zone(),
"ERROR: It is illegal to access '%s' through Dart C API.\n"
"ERROR: See "
"https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/"
"aot/entry_point_pragma.md\n",
member_cstring);
OS::PrintErr("%s", error);
return ApiError::New(String::Handle(String::New(error)));
}
ErrorPtr Function::VerifyCallEntryPoint() const {
if (!FLAG_verify_entry_points) return Error::null();
const Class& cls = Class::Handle(Owner());
const Library& lib = Library::Handle(cls.library());
switch (kind()) {
case UntaggedFunction::kRegularFunction:
case UntaggedFunction::kSetterFunction:
case UntaggedFunction::kConstructor:
return dart::VerifyEntryPoint(lib, *this, *this,
{EntryPointPragma::kCallOnly});
break;
case UntaggedFunction::kGetterFunction:
return dart::VerifyEntryPoint(
lib, *this, *this,
{EntryPointPragma::kCallOnly, EntryPointPragma::kGetterOnly});
break;
case UntaggedFunction::kImplicitGetter:
return dart::VerifyEntryPoint(lib, *this, Field::Handle(accessor_field()),
{EntryPointPragma::kGetterOnly});
break;
case UntaggedFunction::kImplicitSetter:
return dart::VerifyEntryPoint(lib, *this, Field::Handle(accessor_field()),
{EntryPointPragma::kSetterOnly});
case UntaggedFunction::kMethodExtractor:
return Function::Handle(extracted_method_closure())
.VerifyClosurizedEntryPoint();
break;
default:
return dart::VerifyEntryPoint(lib, *this, Object::Handle(), {});
break;
}
}
ErrorPtr Function::VerifyClosurizedEntryPoint() const {
if (!FLAG_verify_entry_points) return Error::null();
const Class& cls = Class::Handle(Owner());
const Library& lib = Library::Handle(cls.library());
switch (kind()) {
case UntaggedFunction::kRegularFunction:
return dart::VerifyEntryPoint(lib, *this, *this,
{EntryPointPragma::kGetterOnly});
case UntaggedFunction::kImplicitClosureFunction: {
const Function& parent = Function::Handle(parent_function());
return dart::VerifyEntryPoint(lib, parent, parent,
{EntryPointPragma::kGetterOnly});
}
default:
UNREACHABLE();
}
}
ErrorPtr Field::VerifyEntryPoint(EntryPointPragma pragma) const {
if (!FLAG_verify_entry_points) return Error::null();
const Class& cls = Class::Handle(Owner());
const Library& lib = Library::Handle(cls.library());
return dart::VerifyEntryPoint(lib, *this, *this, {pragma});
}
ErrorPtr Class::VerifyEntryPoint() const {
if (!FLAG_verify_entry_points) return Error::null();
const Library& lib = Library::Handle(library());
if (!lib.IsNull()) {
return dart::VerifyEntryPoint(lib, *this, *this, {});
} else {
return Error::null();
}
}
} // namespace dart