// 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 #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(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(RAW_NULL); BoolPtr Object::true_ = static_cast(RAW_NULL); BoolPtr Object::false_ = static_cast(RAW_NULL); ClassPtr Object::class_class_ = static_cast(RAW_NULL); ClassPtr Object::dynamic_class_ = static_cast(RAW_NULL); ClassPtr Object::void_class_ = static_cast(RAW_NULL); ClassPtr Object::type_arguments_class_ = static_cast(RAW_NULL); ClassPtr Object::patch_class_class_ = static_cast(RAW_NULL); ClassPtr Object::function_class_ = static_cast(RAW_NULL); ClassPtr Object::closure_data_class_ = static_cast(RAW_NULL); ClassPtr Object::ffi_trampoline_data_class_ = static_cast(RAW_NULL); ClassPtr Object::field_class_ = static_cast(RAW_NULL); ClassPtr Object::script_class_ = static_cast(RAW_NULL); ClassPtr Object::library_class_ = static_cast(RAW_NULL); ClassPtr Object::namespace_class_ = static_cast(RAW_NULL); ClassPtr Object::kernel_program_info_class_ = static_cast(RAW_NULL); ClassPtr Object::code_class_ = static_cast(RAW_NULL); ClassPtr Object::instructions_class_ = static_cast(RAW_NULL); ClassPtr Object::instructions_section_class_ = static_cast(RAW_NULL); ClassPtr Object::instructions_table_class_ = static_cast(RAW_NULL); ClassPtr Object::object_pool_class_ = static_cast(RAW_NULL); ClassPtr Object::pc_descriptors_class_ = static_cast(RAW_NULL); ClassPtr Object::code_source_map_class_ = static_cast(RAW_NULL); ClassPtr Object::compressed_stackmaps_class_ = static_cast(RAW_NULL); ClassPtr Object::var_descriptors_class_ = static_cast(RAW_NULL); ClassPtr Object::exception_handlers_class_ = static_cast(RAW_NULL); ClassPtr Object::context_class_ = static_cast(RAW_NULL); ClassPtr Object::context_scope_class_ = static_cast(RAW_NULL); ClassPtr Object::singletargetcache_class_ = static_cast(RAW_NULL); ClassPtr Object::unlinkedcall_class_ = static_cast(RAW_NULL); ClassPtr Object::monomorphicsmiablecall_class_ = static_cast(RAW_NULL); ClassPtr Object::icdata_class_ = static_cast(RAW_NULL); ClassPtr Object::megamorphic_cache_class_ = static_cast(RAW_NULL); ClassPtr Object::subtypetestcache_class_ = static_cast(RAW_NULL); ClassPtr Object::loadingunit_class_ = static_cast(RAW_NULL); ClassPtr Object::api_error_class_ = static_cast(RAW_NULL); ClassPtr Object::language_error_class_ = static_cast(RAW_NULL); ClassPtr Object::unhandled_exception_class_ = static_cast(RAW_NULL); ClassPtr Object::unwind_error_class_ = static_cast(RAW_NULL); ClassPtr Object::weak_serialization_reference_class_ = static_cast(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(len), &name[start_pos]); } // Remove private keys, but retain getter/setter/constructor/mixin manglings. StringPtr String::RemovePrivateKey(const String& name) { ASSERT(name.IsOneByteString()); GrowableArray 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(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 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 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(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(address + kHeapObjectTag)->untag()->value_ = false; } { // Allocate true. uword address = heap->Allocate(Bool::InstanceSize(), Heap::kOld); true_ = static_cast(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(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(true_) == static_cast(null_) + kTrueOffsetFromNull); ASSERT(static_cast(false_) == static_cast(null_) + kFalseOffsetFromNull); ASSERT((static_cast(true_) & kBoolValueMask) == 0); ASSERT((static_cast(false_) & kBoolValueMask) != 0); ASSERT(static_cast(false_) == (static_cast(true_) | kBoolValueMask)); ASSERT((static_cast(null_) & kBoolVsNullMask) == 0); ASSERT((static_cast(true_) & kBoolVsNullMask) != 0); ASSERT((static_cast(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(address + kHeapObjectTag); InitializeObject(address, Class::kClassId, size, /*compressed*/ true); Class fake; // Initialization from Class::New. // 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(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(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(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(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(isolate_group); type_arguments_class_ = cls.ptr(); cls = Class::New(isolate_group); patch_class_class_ = cls.ptr(); cls = Class::New(isolate_group); function_class_ = cls.ptr(); cls = Class::New(isolate_group); closure_data_class_ = cls.ptr(); cls = Class::New(isolate_group); ffi_trampoline_data_class_ = cls.ptr(); cls = Class::New(isolate_group); field_class_ = cls.ptr(); cls = Class::New(isolate_group); script_class_ = cls.ptr(); cls = Class::New(isolate_group); library_class_ = cls.ptr(); cls = Class::New(isolate_group); namespace_class_ = cls.ptr(); cls = Class::New(isolate_group); kernel_program_info_class_ = cls.ptr(); cls = Class::New(isolate_group); code_class_ = cls.ptr(); cls = Class::New(isolate_group); instructions_class_ = cls.ptr(); cls = Class::New(isolate_group); instructions_section_class_ = cls.ptr(); cls = Class::New(isolate_group); instructions_table_class_ = cls.ptr(); cls = Class::New(isolate_group); object_pool_class_ = cls.ptr(); cls = Class::New(isolate_group); pc_descriptors_class_ = cls.ptr(); cls = Class::New(isolate_group); code_source_map_class_ = cls.ptr(); cls = Class::New(isolate_group); compressed_stackmaps_class_ = cls.ptr(); cls = Class::New(isolate_group); var_descriptors_class_ = cls.ptr(); cls = Class::New(isolate_group); exception_handlers_class_ = cls.ptr(); cls = Class::New(isolate_group); context_class_ = cls.ptr(); cls = Class::New(isolate_group); context_scope_class_ = cls.ptr(); cls = Class::New(isolate_group); singletargetcache_class_ = cls.ptr(); cls = Class::New(isolate_group); unlinkedcall_class_ = cls.ptr(); cls = Class::New( isolate_group); monomorphicsmiablecall_class_ = cls.ptr(); cls = Class::New(isolate_group); icdata_class_ = cls.ptr(); cls = Class::New(isolate_group); megamorphic_cache_class_ = cls.ptr(); cls = Class::New(isolate_group); subtypetestcache_class_ = cls.ptr(); cls = Class::New(isolate_group); loadingunit_class_ = cls.ptr(); cls = Class::New(isolate_group); api_error_class_ = cls.ptr(); cls = Class::New(isolate_group); language_error_class_ = cls.ptr(); cls = Class::New(isolate_group); unhandled_exception_class_ = cls.ptr(); cls = Class::New(isolate_group); unwind_error_class_ = cls.ptr(); cls = Class::New( 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(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(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(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(isolate_group); isolate_group->object_store()->set_mint_class(cls); cls = Class::New(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(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(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(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(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(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(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(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(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*/ true); TypeArguments::initializeHandle( empty_type_arguments_, static_cast(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(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(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(isolate_group); cls.set_is_allocate_finalized(); cls.set_is_declaration_loaded(); cls.set_is_type_finalized(); cls = Class::New(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(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(RAW_NULL); true_ = static_cast(RAW_NULL); false_ = static_cast(RAW_NULL); class_class_ = static_cast(RAW_NULL); dynamic_class_ = static_cast(RAW_NULL); void_class_ = static_cast(RAW_NULL); type_arguments_class_ = static_cast(RAW_NULL); patch_class_class_ = static_cast(RAW_NULL); function_class_ = static_cast(RAW_NULL); closure_data_class_ = static_cast(RAW_NULL); ffi_trampoline_data_class_ = static_cast(RAW_NULL); field_class_ = static_cast(RAW_NULL); script_class_ = static_cast(RAW_NULL); library_class_ = static_cast(RAW_NULL); namespace_class_ = static_cast(RAW_NULL); kernel_program_info_class_ = static_cast(RAW_NULL); code_class_ = static_cast(RAW_NULL); instructions_class_ = static_cast(RAW_NULL); instructions_section_class_ = static_cast(RAW_NULL); instructions_table_class_ = static_cast(RAW_NULL); object_pool_class_ = static_cast(RAW_NULL); pc_descriptors_class_ = static_cast(RAW_NULL); code_source_map_class_ = static_cast(RAW_NULL); compressed_stackmaps_class_ = static_cast(RAW_NULL); var_descriptors_class_ = static_cast(RAW_NULL); exception_handlers_class_ = static_cast(RAW_NULL); context_class_ = static_cast(RAW_NULL); context_scope_class_ = static_cast(RAW_NULL); singletargetcache_class_ = static_cast(RAW_NULL); unlinkedcall_class_ = static_cast(RAW_NULL); monomorphicsmiablecall_class_ = static_cast(RAW_NULL); icdata_class_ = static_cast(RAW_NULL); megamorphic_cache_class_ = static_cast(RAW_NULL); subtypetestcache_class_ = static_cast(RAW_NULL); loadingunit_class_ = static_cast(RAW_NULL); api_error_class_ = static_cast(RAW_NULL); language_error_class_ = static_cast(RAW_NULL); unhandled_exception_class_ = static_cast(RAW_NULL); unwind_error_class_ = static_cast(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(instructions_table, InstructionsTable); 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(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(UntaggedObject::ToAddr(str) + size), 0, str->untag()->HeapSize() - size); } else if (cid == kTwoByteStringCid) { TwoByteStringPtr str = static_cast(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(UntaggedObject::ToAddr(str) + size), 0, str->untag()->HeapSize() - size); } else if (cid == kExternalOneByteStringCid) { ExternalOneByteStringPtr str = static_cast(object); if (String::GetCachedHash(str) == 0) { intptr_t hash = String::Hash(str); String::SetCachedHash(str, hash); } } else if (cid == kExternalTwoByteStringCid) { ExternalTwoByteStringPtr str = static_cast(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(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(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(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(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(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(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( 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(kInitialCanonicalTypeSize, Heap::kOld); object_store->set_canonical_types(array); // Initialize hash set for canonical function types. const intptr_t kInitialCanonicalFunctionTypeSize = 16; array = HashTables::New( 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( 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( 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(isolate_group)); const Class& function_type_cls = Class::Handle( zone, Class::New(isolate_group)); const Class& type_ref_cls = Class::Handle(zone, Class::New(isolate_group)); const Class& type_parameter_cls = Class::Handle( zone, Class::New(isolate_group)); const Class& library_prefix_cls = Class::Handle( zone, Class::New(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 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(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(isolate_group); RegisterPrivateClass(cls, Symbols::_CapabilityImpl(), isolate_lib); pending_classes.Add(cls); cls = Class::New(isolate_group); RegisterPrivateClass(cls, Symbols::_RawReceivePortImpl(), isolate_lib); pending_classes.Add(cls); cls = Class::New(isolate_group); RegisterPrivateClass(cls, Symbols::_SendPortImpl(), isolate_lib); pending_classes.Add(cls); cls = Class::New( isolate_group); RegisterPrivateClass(cls, Symbols::_TransferableTypedDataImpl(), isolate_lib); pending_classes.Add(cls); const Class& stacktrace_cls = Class::Handle( zone, Class::New(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(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(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(isolate_group); object_store->set_bool_class(cls); RegisterClass(cls, Symbols::Bool(), core_lib); pending_classes.Add(cls); cls = Class::New(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(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(isolate_group); object_store->set_integer_implementation_class(cls); RegisterPrivateClass(cls, Symbols::_IntegerImplementation(), core_lib); pending_classes.Add(cls); cls = Class::New(isolate_group); object_store->set_smi_class(cls); RegisterPrivateClass(cls, Symbols::_Smi(), core_lib); pending_classes.Add(cls); cls = Class::New(isolate_group); object_store->set_mint_class(cls); RegisterPrivateClass(cls, Symbols::_Mint(), core_lib); pending_classes.Add(cls); cls = Class::New(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(isolate_group); object_store->set_closure_class(cls); RegisterPrivateClass(cls, Symbols::_Closure(), core_lib); pending_classes.Add(cls); cls = Class::New(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(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(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(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(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(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(isolate_group); RegisterPrivateClass(cls, Symbols::_Float32x4(), lib); pending_classes.Add(cls); object_store->set_float32x4_class(cls); cls = Class::New(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(isolate_group); RegisterPrivateClass(cls, Symbols::_Int32x4(), lib); pending_classes.Add(cls); object_store->set_int32x4_class(cls); cls = Class::New(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(isolate_group); RegisterPrivateClass(cls, Symbols::_Float64x2(), lib); pending_classes.Add(cls); object_store->set_float64x2_class(cls); cls = Class::New(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(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(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(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(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(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(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 , , // , and . 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(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(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(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(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(kInstanceCid, isolate_group); object_store->set_object_class(cls); cls = Class::New(isolate_group); cls = Class::New(isolate_group); cls = Class::New(isolate_group); cls = Class::New(isolate_group); cls = Class::New(isolate_group); cls = Class::New(isolate_group); object_store->set_array_class(cls); cls = Class::New(kImmutableArrayCid, isolate_group); object_store->set_immutable_array_class(cls); cls = Class::New( isolate_group); object_store->set_growable_object_array_class(cls); cls = Class::New(isolate_group); object_store->set_linked_hash_map_class(cls); cls = Class::New(isolate_group); object_store->set_float32x4_class(cls); cls = Class::New(isolate_group); object_store->set_int32x4_class(cls); cls = Class::New(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(kFfiNativeTypeCid, isolate_group); object_store->set_ffi_native_type_class(cls); #define REGISTER_FFI_CLASS(clazz) \ cls = Class::New(kFfi##clazz##Cid, isolate_group); CLASS_LIST_FFI_TYPE_MARKER(REGISTER_FFI_CLASS); #undef REGISTER_FFI_CLASS cls = Class::New(kFfiNativeFunctionCid, isolate_group); cls = Class::NewPointerClass(kFfiPointerCid, isolate_group); object_store->set_ffi_pointer_class(cls); cls = Class::New(kFfiDynamicLibraryCid, isolate_group); cls = Class::New(kByteBufferCid, isolate_group, /*register_isolate_group=*/false); cls.set_instance_size_in_words(0, 0); isolate_group->class_table()->Register(cls); cls = Class::New(isolate_group); object_store->set_integer_implementation_class(cls); cls = Class::New(isolate_group); object_store->set_smi_class(cls); cls = Class::New(isolate_group); object_store->set_mint_class(cls); cls = Class::New(isolate_group); object_store->set_double_class(cls); cls = Class::New(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(isolate_group); object_store->set_bool_class(cls); cls = Class::New(kNullCid, isolate_group); object_store->set_null_class(cls); cls = Class::New(kNeverCid, isolate_group); object_store->set_never_class(cls); cls = Class::New(isolate_group); cls = Class::New(isolate_group); cls = Class::New(isolate_group); cls = Class::New(isolate_group); cls = Class::New(isolate_group); cls = Class::New(isolate_group); cls = Class::New(isolate_group); object_store->set_weak_property_class(cls); cls = Class::New(isolate_group); cls = Class::New(isolate_group); cls = Class::New(isolate_group); cls = Class::New( 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(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(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(cur) = initial_value; cur += kWordSize; } } else { // Check that MemorySantizer understands this is initialized. MSAN_CHECK_INITIALIZED(reinterpret_cast(address), size); #if defined(DEBUG) while (cur < end) { ASSERT(*reinterpret_cast(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(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(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(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(clone_addr + kHeaderSizeInBytes), reinterpret_cast(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 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::kClassId); NOT_IN_PRECOMPILED(result.set_token_pos(TokenPosition::kNoSource)); NOT_IN_PRECOMPILED(result.set_end_token_pos(TokenPosition::kNoSource)); result.set_instance_size(FakeObject::InstanceSize(), compiler::target::RoundedAllocationSize( TargetFakeObject::InstanceSize())); result.set_type_arguments_field_offset_in_words(kNoTypeArguments, RTN::Class::kNoTypeArguments); const intptr_t host_next_field_offset = FakeObject::NextFieldOffset(); const intptr_t target_next_field_offset = TargetFakeObject::NextFieldOffset(); result.set_next_field_offset(host_next_field_offset, target_next_field_offset); COMPILE_ASSERT((FakeObject::kClassId != kInstanceCid)); result.set_id(FakeObject::kClassId); 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(); } #if !defined(DART_PRECOMPILED_RUNTIME) static void ReportTooManyTypeArguments(const Class& cls) { Report::MessageF(Report::kError, Script::Handle(cls.script()), cls.token_pos(), Report::AtLocation, "too many type parameters declared in class '%s' or in its " "super classes", String::Handle(cls.Name()).ToCString()); UNREACHABLE(); } #endif // !defined(DART_PRECOMPILED_RUNTIME) void Class::set_num_type_arguments(intptr_t value) const { #if defined(DART_PRECOMPILED_RUNTIME) UNREACHABLE(); #else if (!Utils::IsInt(16, value)) { ReportTooManyTypeArguments(*this); } // We allow concurrent calculation of the number of type arguments. If two // threads perform this operation it doesn't matter which one wins. DEBUG_ONLY(intptr_t old_value = num_type_arguments()); DEBUG_ASSERT(old_value == kUnknownNumTypeArguments || old_value == value); StoreNonPointer( &untag()->num_type_arguments_, value); #endif // defined(DART_PRECOMPILED_RUNTIME) } 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 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(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( &untag()->state_bits_, static_cast(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(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(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( 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; } #if defined(DART_PRECOMPILED_RUNTIME) UNREACHABLE(); return 0; #else num_type_args = ComputeNumTypeArguments(); ASSERT(num_type_args != kUnknownNumTypeArguments); set_num_type_arguments(num_type_args); return num_type_args; #endif // defined(DART_PRECOMPILED_RUNTIME) } 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() == String::null()) { break; } i++; } if (i == dispatchers.Length()) { const intptr_t new_len = cache.Length() == 0 ? static_cast(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(args_desc); entry.Set(dispatcher); entry.Set( 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(); if (name.IsNull()) break; // Reached last entry. if (!name.Equals(target_name)) continue; desc = dispatcher.Get(); if (desc.ptr() != args_desc.ptr()) continue; function = dispatcher.Get(); 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(); } 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(); } #if defined(DEBUG) static bool IsMutatorOrAtSafepoint() { Thread* thread = Thread::Current(); return thread->IsMutatorThread() || thread->IsAtSafepoint(); } #endif #if !defined(DART_PRECOMPILED_RUNTIME) 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); }; 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()); } 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()); } #endif // !defined(DART_PRECOMPILED_RUNTIME) 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 } // 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(); } #if defined(DART_PRECOMPILED_RUNTIME) UNREACHABLE(); return Error::null(); #else 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(); #endif // defined(DART_PRECOMPILED_RUNTIME) } // 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(); } #if defined(DART_PRECOMPILED_RUNTIME) UNREACHABLE(); #else error ^= ClassFinalizer::AllocateFinalizeClass(*this); #endif // defined(DART_PRECOMPILED_RUNTIME) return error.ptr(); } void Class::SetFields(const Array& value) const { ASSERT(!value.IsNull()); #if defined(DEBUG) Thread* thread = Thread::Current(); ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); // Verify that all the fields in the array have this class as owner. Field& field = Field::Handle(); intptr_t len = value.Length(); for (intptr_t i = 0; i < len; i++) { field ^= value.At(i); ASSERT(field.IsOriginal()); ASSERT(field.Owner() == ptr()); } #endif // The value of static fields is already initialized to null. set_fields(value); } void Class::AddField(const Field& field) const { #if defined(DEBUG) Thread* thread = Thread::Current(); ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); #endif const Array& arr = Array::Handle(fields()); const Array& new_arr = Array::Handle(Array::Grow(arr, arr.Length() + 1)); new_arr.SetAt(arr.Length(), field); SetFields(new_arr); } void Class::AddFields(const GrowableArray& 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 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(index == kIllegalCid ? kInstanceCid : index); NOT_IN_PRECOMPILED(result.set_token_pos(TokenPosition::kNoSource)); NOT_IN_PRECOMPILED(result.set_end_token_pos(TokenPosition::kNoSource)); const intptr_t host_instance_size = FakeInstance::InstanceSize(); const intptr_t target_instance_size = compiler::target::RoundedAllocationSize( TargetFakeInstance::InstanceSize()); result.set_instance_size(host_instance_size, target_instance_size); result.set_type_arguments_field_offset_in_words(kNoTypeArguments, RTN::Class::kNoTypeArguments); const intptr_t host_next_field_offset = FakeInstance::NextFieldOffset(); const intptr_t target_next_field_offset = TargetFakeInstance::NextFieldOffset(); result.set_next_field_offset(host_next_field_offset, target_next_field_offset); result.set_id(index); 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 ClassPtr Class::New(intptr_t index, IsolateGroup* isolate_group, bool register_class, bool is_abstract) { Class& result = Class::Handle(NewCommon(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(kIllegalCid)); result.set_library(lib); result.set_name(name); result.set_script(script); NOT_IN_PRECOMPILED(result.set_token_pos(token_pos)); // The size gets initialized to 0. Once the class gets finalized the class // finalizer will set the correct size. ASSERT(!result.is_finalized() && !result.is_prefinalized()); result.set_instance_size_in_words(0, 0); if (register_class) { IsolateGroup::Current()->RegisterClass(result); } return result.ptr(); } ClassPtr Class::NewInstanceClass() { return Class::New(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( 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( 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( 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( 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( 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 kInstructionsTableCid: return Symbols::InstructionsTable().ToCString(); case kObjectPoolCid: return Symbols::ObjectPool().ToCString(); case kCodeSourceMapCid: return Symbols::CodeSourceMap().ToCString(); case kPcDescriptorsCid: return Symbols::PcDescriptors().ToCString(); case kCompressedStackMapsCid: return Symbols::CompressedStackMaps().ToCString(); case kLocalVarDescriptorsCid: return Symbols::LocalVarDescriptors().ToCString(); case kExceptionHandlersCid: return Symbols::ExceptionHandlers().ToCString(); case kContextCid: return Symbols::Context().ToCString(); case kContextScopeCid: return Symbols::ContextScope().ToCString(); case 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()); } #if !defined(DART_PRECOMPILED_RUNTIME) 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); } #endif // !defined(DART_PRECOMPILED_RUNTIME) 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()); } #if !defined(DART_PRECOMPILED_RUNTIME) void Class::AddDirectImplementor(const Class& implementor, bool is_mixin) const { ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); ASSERT(is_implemented()); ASSERT(!implementor.IsNull()); GrowableObjectArray& direct_implementors = GrowableObjectArray::Handle(untag()->direct_implementors()); if (direct_implementors.IsNull()) { direct_implementors = GrowableObjectArray::New(4, Heap::kOld); untag()->set_direct_implementors(direct_implementors.ptr()); } #if defined(DEBUG) // Verify that the same class is not added twice. // The only exception is mixins: when mixin application is transformed, // mixin is added to the end of interfaces list and may be duplicated: // class X = A with B implements B; // This is rare and harmless. if (!is_mixin) { for (intptr_t i = 0; i < direct_implementors.Length(); i++) { ASSERT(direct_implementors.At(i) != implementor.ptr()); } } #endif direct_implementors.Add(implementor, Heap::kOld); } void Class::set_direct_implementors( const GrowableObjectArray& implementors) const { ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); untag()->set_direct_implementors(implementors.ptr()); } void Class::AddDirectSubclass(const Class& subclass) const { ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); ASSERT(!subclass.IsNull()); ASSERT(subclass.SuperClass() == ptr()); // Do not keep track of the direct subclasses of class Object. ASSERT(!IsObjectClass()); GrowableObjectArray& direct_subclasses = GrowableObjectArray::Handle(untag()->direct_subclasses()); if (direct_subclasses.IsNull()) { direct_subclasses = GrowableObjectArray::New(4, Heap::kOld); untag()->set_direct_subclasses(direct_subclasses.ptr()); } #if defined(DEBUG) // Verify that the same class is not added twice. for (intptr_t i = 0; i < direct_subclasses.Length(); i++) { ASSERT(direct_subclasses.At(i) != subclass.ptr()); } #endif direct_subclasses.Add(subclass, Heap::kOld); } void Class::set_direct_subclasses(const GrowableObjectArray& subclasses) const { ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); untag()->set_direct_subclasses(subclasses.ptr()); } #endif // !defined(DART_PRECOMPILED_RUNTIME) ArrayPtr Class::constants() const { return untag()->constants(); } void Class::set_constants(const Array& value) const { untag()->set_constants(value.ptr()); } void Class::set_declaration_type(const Type& value) const { ASSERT(id() != kDynamicCid && id() != kVoidCid); ASSERT(!value.IsNull() && value.IsCanonical() && value.IsOld()); ASSERT((declaration_type() == Object::null()) || (declaration_type() == value.ptr())); // Set during own finalization. // Since DeclarationType is used as the runtime type of instances of a // non-generic class, its nullability must be kNonNullable. // The exception is DeclarationType of Null which is kNullable. ASSERT(value.type_class_id() != kNullCid || value.IsNullable()); ASSERT(value.type_class_id() == kNullCid || value.IsNonNullable()); untag()->set_declaration_type(value.ptr()); } TypePtr Class::DeclarationType() const { ASSERT(is_declaration_loaded()); if (IsNullClass()) { return Type::NullType(); } if (IsDynamicClass()) { return Type::DynamicType(); } if (IsVoidClass()) { return Type::VoidType(); } if (declaration_type() != Type::null()) { return declaration_type(); } { auto thread = Thread::Current(); SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); if (declaration_type() != Type::null()) { return declaration_type(); } // For efficiency, the runtimeType intrinsic returns the type cached by // DeclarationType without checking its nullability. Therefore, we // consistently cache the kNonNullable version of the type. // The exception is type Null which is stored as kNullable. Type& type = Type::Handle(Type::New(*this, TypeArguments::Handle(type_parameters()), Nullability::kNonNullable)); type ^= ClassFinalizer::FinalizeType(type); set_declaration_type(type); return type.ptr(); } } #if !defined(DART_PRECOMPILED_RUNTIME) void Class::set_allocation_stub(const Code& value) const { // Never clear the stub as it may still be a target, but will be GC-d if // not referenced. ASSERT(!value.IsNull()); ASSERT(untag()->allocation_stub() == Code::null()); untag()->set_allocation_stub(value.ptr()); } #endif // !defined(DART_PRECOMPILED_RUNTIME) void Class::DisableAllocationStub() const { #if defined(DART_PRECOMPILED_RUNTIME) UNREACHABLE(); #else { const Code& existing_stub = Code::Handle(allocation_stub()); if (existing_stub.IsNull()) { return; } } auto thread = Thread::Current(); SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); const Code& existing_stub = Code::Handle(allocation_stub()); if (existing_stub.IsNull()) { return; } ASSERT(!existing_stub.IsDisabled()); // Change the stub so that the next caller will regenerate the stub. existing_stub.DisableStubCode(); // Disassociate the existing stub from class. untag()->set_allocation_stub(Code::null()); #endif // defined(DART_PRECOMPILED_RUNTIME) } bool Class::IsDartFunctionClass() const { return ptr() == Type::Handle(Type::DartFunctionType()).type_class(); } bool Class::IsFutureClass() const { // Looking up future_class in the object store would not work, because // this function is called during class finalization, before the object store // field would be initialized by InitKnownObjects(). return (Name() == Symbols::Future().ptr()) && (library() == Library::AsyncLibrary()); } // Checks if type T0 is a subtype of type T1. // Type T0 is specified by class 'cls' parameterized with 'type_arguments' and // by 'nullability', and type T1 is specified by 'other' and must have a type // class. 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 then: // T0 <: T1 iff Future <: T1 and S0 <: T1 if (this_cid == kFutureOrCid) { // Check Future <: 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 then: // T0 <: T1 iff any of the following hold: // either T0 <: Future // 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 when T0 is Future. if (this_class.IsFutureClass()) { const AbstractType& type_arg = AbstractType::Handle(zone, type_arguments.TypeAtNullSafe(0)); // If T0 is Future, then T0 <: Future, iff S0 <: S1. if (type_arg.IsSubtypeOf(other_type_arg, space, trail)) { if (verified_nullability) { return true; } } } // Check T0 <: Future when T0 is FutureOr 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. // 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(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(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(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 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 > CanonicalDoubleSet; typedef UnorderedHashSet > 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 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(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(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(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)); } uword TypeArguments::HashForRange(intptr_t from_index, intptr_t len) const { if (IsNull()) return kAllDynamicHash; if (IsRaw(from_index, len)) return kAllDynamicHash; uint32_t result = 0; AbstractType& type = AbstractType::Handle(); for (intptr_t i = 0; i < len; i++) { type = TypeAt(from_index + i); // 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; } uword TypeArguments::ComputeHash() const { if (IsNull()) return kAllDynamicHash; const uword result = HashForRange(0, Length()); 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(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*/ true); 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(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()); } uword 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 {}; class StringSpecializationCid : public BitField { }; 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(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(value); } void Function::set_modifier(UntaggedFunction::AsyncModifier value) const { untag()->kind_tag_.Update(value); } void Function::set_recognized_kind(MethodRecognizer::Kind value) const { // Prevent multiple settings of kind. ASSERT((value == MethodRecognizer::kUnknown) || !IsRecognized()); untag()->kind_tag_.Update(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(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(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("", 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(""))) { // 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& 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* 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(value.ptr()); } ArrayPtr Function::ic_data_array() const { return untag()->ic_data_array(); } 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()) { // Dynamic method call through field/getter involves dynamic call of // the field/getter. return metadata.getter_called_dynamically || metadata.method_or_setter_called_dynamically; } else { return metadata.method_or_setter_called_dynamically; } #else UNREACHABLE(); return true; #endif } bool Function::PrologueNeedsArgumentsDescriptor() const { // 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(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(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(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(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(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(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 { ASSERT(!IsOriginal()); const Field& original = Field::Handle(Original()); if (!original.is_unboxing_candidate()) { return; } auto thread = Thread::Current(); SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); if (!original.is_unboxing_candidate()) { return; } // Ensures that to-be-disabled existing code won't continue running as we // update field properties as it might write into now boxed field thinking // it still holds unboxed(reusable box) value. thread->isolate_group()->RunWithStoppedMutators([&]() { 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( &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(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(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(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( 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 and class C 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* 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()) ? "" : 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 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 and value has type // G. 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 and field has type G and G != C. // Compute C 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 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 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 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 . // 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 at G is exactly G which means we can simply // compare values type arguements () to fields type arguments // () 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(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 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* trail) const { if (!HasExports()) { return Object::null(); } if (trail == NULL) { trail = new ZoneGrowableArray(); } 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(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(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(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(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 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_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: "@<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(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(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(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* 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(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(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 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& 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 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( Thread::Current()->heap()->GetPeer(ptr())); #else return nullptr; #endif } void Instructions::set_stats(CodeStatistics* stats) const { #if defined(DART_PRECOMPILER) Thread::Current()->heap()->SetPeer(ptr(), stats); #endif } const char* InstructionsSection::ToCString() const { return "InstructionsSection"; } void InstructionsTable::set_length(intptr_t value) const { StoreNonPointer(&untag()->length_, value); } void InstructionsTable::set_start_pc(uword value) const { StoreNonPointer(&untag()->start_pc_, value); } void InstructionsTable::set_end_pc(uword value) const { StoreNonPointer(&untag()->end_pc_, value); } void InstructionsTable::set_descriptors(const Array& value) const { untag()->set_descriptors(value.ptr()); } InstructionsTablePtr InstructionsTable::New(intptr_t length, uword start_pc, uword end_pc) { ASSERT(Object::instructions_table_class() != Class::null()); ASSERT(length >= 0); ASSERT(start_pc <= end_pc); ASSERT(Utils::IsAligned(start_pc, kPayloadAlignment)); Thread* thread = Thread::Current(); InstructionsTable& result = InstructionsTable::Handle(thread->zone()); { uword size = InstructionsTable::InstanceSize(length); ObjectPtr raw = Object::Allocate(InstructionsTable::kClassId, size, Heap::kOld, /*compressed*/ false); NoSafepointScope no_safepoint; result ^= raw; result.set_length(length); } const Array& descriptors = (length == 0) ? Object::empty_array() : Array::Handle(Array::New(length, Heap::kOld)); result.set_descriptors(descriptors); result.set_start_pc(start_pc); result.set_end_pc(end_pc); return result.ptr(); } void InstructionsTable::SetEntryAt(intptr_t index, uword payload_start, bool has_monomorphic_entrypoint, ObjectPtr descriptor) const { ASSERT((0 <= index) && (index < length())); ASSERT(ContainsPc(payload_start)); ASSERT(Utils::IsAligned(payload_start, kPayloadAlignment)); const uint32_t pc_offset = ConvertPcToOffset(payload_start); ASSERT((index == 0) || (PcOffsetAt(index - 1) <= pc_offset)); ASSERT((pc_offset & kHasMonomorphicEntrypointFlag) == 0); untag()->data()[index] = pc_offset | (has_monomorphic_entrypoint ? kHasMonomorphicEntrypointFlag : 0); descriptors()->untag()->set_element(index, descriptor); } bool InstructionsTable::ContainsPc(InstructionsTablePtr table, uword pc) { return (InstructionsTable::start_pc(table) <= pc) && (pc < InstructionsTable::end_pc(table)); } uint32_t InstructionsTable::ConvertPcToOffset(InstructionsTablePtr table, uword pc) { ASSERT(InstructionsTable::ContainsPc(table, pc)); const uint32_t pc_offset = static_cast(pc - InstructionsTable::start_pc(table)); ASSERT(InstructionsTable::start_pc(table) + pc_offset == pc); // No overflow. return pc_offset; } intptr_t InstructionsTable::FindEntry(InstructionsTablePtr table, uword pc) { // This can run in the middle of GC and must not allocate handles. NoSafepointScope no_safepoint; if (!InstructionsTable::ContainsPc(table, pc)) return -1; const uint32_t pc_offset = InstructionsTable::ConvertPcToOffset(table, pc); intptr_t lo = 0; intptr_t hi = InstructionsTable::length(table) - 1; while (lo <= hi) { intptr_t mid = (hi - lo + 1) / 2 + lo; ASSERT(mid >= lo); ASSERT(mid <= hi); if (pc_offset < InstructionsTable::PcOffsetAt(table, mid)) { hi = mid - 1; } else if ((mid != hi) && (pc_offset >= InstructionsTable::PcOffsetAt(table, mid + 1))) { lo = mid + 1; } else { return mid; } } return -1; } ObjectPtr InstructionsTable::DescriptorAt(InstructionsTablePtr table, intptr_t index) { ASSERT((0 <= index) && (index < InstructionsTable::length(table))); return table->untag()->descriptors()->untag()->element(index); } uword InstructionsTable::PayloadStartAt(InstructionsTablePtr table, intptr_t index) { return InstructionsTable::start_pc(table) + InstructionsTable::PcOffsetAt(table, index); } uword InstructionsTable::EntryPointAt(intptr_t index) const { return PayloadStartAt(index) + (HasMonomorphicEntryPointAt(index) ? Instructions::kPolymorphicEntryOffsetAOT : 0); } const char* InstructionsTable::ToCString() const { return "InstructionsTable"; } 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(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"; } uword CompressedStackMaps::Hash() 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(info.begin_pos.Pos()), static_cast(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(info.begin_pos.Pos()), static_cast(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(info.begin_pos.Pos()), static_cast(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(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(kMaxUint32)) || (handler_pc_offset < static_cast(kMaxUint32))); info->handler_pc_offset = handler_pc_offset; info->needs_stacktrace = static_cast(needs_stacktrace); info->has_catch_all = static_cast(has_catch_all); info->is_generated = static_cast(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(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); } uword UnlinkedCall::Hash() const { return String::Handle(target_name()).Hash(); } bool UnlinkedCall::Equals(const UnlinkedCall& other) const { return (target_name() == other.target_name()) && (arguments_descriptor() == other.arguments_descriptor()) && (can_patch_to_monomorphic() == other.can_patch_to_monomorphic()); } const char* UnlinkedCall::ToCString() const { return "UnlinkedCall"; } UnlinkedCallPtr UnlinkedCall::New() { 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(value.ptr())); } void ICData::set_owner(const Function& value) const { untag()->set_owner(static_cast(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(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& cids) const { return FindCheck(cids) != -1; } #endif // DEBUG intptr_t ICData::FindCheck(const GrowableArray& cids) const { const intptr_t len = NumberOfChecks(); GrowableArray 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& 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& 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& 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 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 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 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* 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* 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 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(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 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* 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 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()); auto right = Smi::Value(entries[i + 1].Get()); 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())); 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(); #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())); return entries[i].Set(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() == Function::null()) { ASSERT(!code.IsNull() && Object::Handle(code.owner()).IsClass()); } else { ASSERT(code.IsNull() || (code.function() == entries[i].Get())); } #endif return entries[i].Set(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 comments_; }; #endif const CodeComments& Code::comments() const { #if defined(PRODUCT) auto comments = static_cast(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(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(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(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(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(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(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(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 StubCode::HasBeenInitialized() && (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* functions, GrowableArray* 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(old_capacity); if (static_cast(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(filled_entry_count() + 1) <= (kLoadFactor * static_cast(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(); } 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(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() == Object::null()); entry.Set(instance_class_id_or_function); entry.Set(destination_type); entry.Set(instance_type_arguments); entry.Set(instantiator_type_arguments); entry.Set(function_type_arguments); entry.Set( instance_parent_function_type_arguments); entry.Set( instance_delayed_type_arguments); entry.Set(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(); *destination_type = entry.Get(); *instance_type_arguments = entry.Get(); *instantiator_type_arguments = entry.Get(); *function_type_arguments = entry.Get(); *instance_parent_function_type_arguments = entry.Get(); *instance_delayed_type_arguments = entry.Get(); *test_result ^= entry.Get(); } 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() == instance_class_id_or_function.ptr() && entry.Get() == destination_type.ptr() && entry.Get() == instance_type_arguments.ptr() && entry.Get() == instantiator_type_arguments.ptr() && entry.Get() == function_type_arguments.ptr() && entry.Get() == instance_parent_function_type_arguments.ptr() && entry.Get() == instance_delayed_type_arguments.ptr()) { if (index != nullptr) { *index = i; } if (result != nullptr) { *result ^= entry.Get(); } 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(instance_class_id_or_function.ptr()), static_cast(destination_type.ptr()), static_cast(instance_type_arguments.ptr()), static_cast(instantiator_type_arguments.ptr()), static_cast(function_type_arguments.ptr()), static_cast(instance_parent_function_type_arguments.ptr()), static_cast(instance_delayed_type_arguments.ptr()), static_cast(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(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(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 = ""; } } const Instance& stack = Instance::Handle(stacktrace()); strtmp = DartLibraryCalls::ToString(stack); const char* stack_str = ""; 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(this->untag()); uword other_addr = reinterpret_cast(other.untag()); for (intptr_t offset = Instance::NextFieldOffset(); offset < instance_size; offset += kWordSize) { if ((*reinterpret_cast(this_addr + offset)) != (*reinterpret_cast(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(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(this_addr + offset)); hash = CombineHashes( hash, *reinterpret_cast(this_addr + offset + 4)); } else { hash = CombineHashes(hash, *reinterpret_cast(this_addr + offset)); } } else { member ^= *reinterpret_cast(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(FieldAddr(field))); case kFloat32x4Cid: return Float32x4::New( *reinterpret_cast(FieldAddr(field))); case kFloat64x2Cid: return Float64x2::New( *reinterpret_cast(FieldAddr(field))); default: if (field.is_non_nullable_integer()) { return Integer::New(*reinterpret_cast(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(FieldAddr(field)), Double::Cast(value).value()); break; case kFloat32x4Cid: StoreNonPointer(reinterpret_cast(FieldAddr(field)), Float32x4::Cast(value).value()); break; case kFloat64x2Cid: StoreNonPointer(reinterpret_cast(FieldAddr(field)), Float64x2::Cast(value).value()); break; default: if (field.is_non_nullable_integer()) { StoreNonPointer(reinterpret_cast(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(*NativeFieldsAddr()); if (native_fields == TypedData::null()) { return NULL; } return reinterpret_cast(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(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(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 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 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 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, new A called from within // a class B 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; } uword 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); } uword 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(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; } uword 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(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(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() ? "" : 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. // 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. } uword 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(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(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(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(bar(B b)) { }' and 'baz(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 uword 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(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(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(""); } 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(""); } 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(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(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(value)); } return Mint::New(value, space); } IntegerPtr Integer::NewFromUint64(uint64_t value, Heap::Space space) { return Integer::New(static_cast(value), space); } bool Integer::IsValueInRange(uint64_t value) { return (value <= static_cast(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(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(value)); } else { return OS::SCreate(zone, "0x%" PX64, static_cast(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(left_value), static_cast(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(b, Mint::kBits), space); case Token::kUSHR: return Integer::New( (b >= kBitsPerInt64) ? 0 : static_cast(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(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(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( reinterpret_cast(this->untag()) + value_offset); void* other_addr = reinterpret_cast(&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(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(cstr[i]) < 128); } #endif // DEBUG // ... which is a subset of Latin-1. return String::FromLatin1(reinterpret_cast(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(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(); } } uword String::Hash(const String& str, intptr_t begin_index, intptr_t len) { StringHasher hasher; hasher.Add(str, begin_index, len); return hasher.Finalize(); } uword String::HashConcat(const String& str1, const String& str2) { StringHasher hasher; hasher.Add(str1, 0, str1.Length()); hasher.Add(str2, 0, str2.Length()); return hasher.Finalize(); } uword String::Hash(StringPtr raw) { StringHasher hasher; uword length = Smi::Value(raw->untag()->length()); if (raw->IsOneByteString() || raw->IsExternalOneByteString()) { const uint8_t* data; if (raw->IsOneByteString()) { data = static_cast(raw)->untag()->data(); } else { ASSERT(raw->IsExternalOneByteString()); ExternalOneByteStringPtr str = static_cast(raw); data = str->untag()->external_data_; } return String::Hash(data, length); } else { const uint16_t* data; if (raw->IsTwoByteString()) { data = static_cast(raw)->untag()->data(); } else { ASSERT(raw->IsExternalTwoByteString()); ExternalTwoByteStringPtr str = static_cast(raw); data = str->untag()->external_data_; } return String::Hash(data, length); } } uword String::Hash(const char* characters, intptr_t len) { StringHasher hasher; hasher.Add(reinterpret_cast(characters), len); return hasher.Finalize(); } uword String::Hash(const uint8_t* characters, intptr_t len) { StringHasher hasher; hasher.Add(characters, len); return hasher.Finalize(); } uword String::Hash(const uint16_t* characters, intptr_t len) { StringHasher hasher; hasher.Add(characters, len); return hasher.Finalize(); } intptr_t String::CharSize() const { intptr_t class_id = ptr()->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(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(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(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(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(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(merged); } else { ASSERT(code_point >= 0 && code_point < 256); utf8[index] = static_cast(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(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(len + 1); ToUTF8(result, len); result[len] = 0; return reinterpret_cast(result); } char* String::ToMallocCString() const { const intptr_t len = Utf8::Length(*this); uint8_t* result = reinterpret_cast(malloc(len + 1)); ToUTF8(result, len); result[len] = 0; return reinterpret_cast(result); } void String::ToUTF8(uint8_t* utf8_array, intptr_t array_len) const { ASSERT(array_len >= Utf8::Length(*this)); Utf8::Encode(*this, reinterpret_cast(utf8_array), array_len); } const char* String::ToCString(Thread* thread, StringPtr ptr) { if (ptr == nullptr) return nullptr; REUSABLE_STRING_HANDLESCOPE(thread); String& str = reused_string_handle.Handle(); str = ptr; return str.ToCString(); } static FinalizablePersistentHandle* AddFinalizer(const Object& referent, void* peer, Dart_HandleFinalizer callback, intptr_t external_size) { ASSERT(callback != 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(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(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 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(str1, str2); \ case kTwoByteStringCid: \ return dart::EqualsIgnoringPrivateKey(str1, str2); \ case kExternalOneByteStringCid: \ return dart::EqualsIgnoringPrivateKey( \ str1, str2); \ case kExternalTwoByteStringCid: \ return dart::EqualsIgnoringPrivateKey( \ 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(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(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(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(Smi::Cast(hash_code).AsTruncatedUint32Value()); } else if (hash_code.IsInteger()) { return static_cast( 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(&untag()->value_)); } void Float32x4::set_value(simd128_value_t value) const { StoreUnaligned(reinterpret_cast(&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(&untag()->value_)); } void Int32x4::set_value(simd128_value_t value) const { StoreUnaligned(reinterpret_cast(&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(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(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(); } uword Closure::ComputeHash() const { Thread* thread = Thread::Current(); DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame()); Zone* zone = thread->zone(); const Function& func = Function::Handle(zone, function()); uint32_t result = 0; if (func.IsImplicitInstanceClosureFunction()) { // Implicit instance closures are not unique, so combine function's hash // code with identityHashCode of cached receiver. result = static_cast(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(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(vm_instructions)); const Image isolate_image( reinterpret_cast(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 // +, 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(" "); } 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 = ""; } 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 inlined_functions; GrowableArray inlined_token_positions; ZoneTextBuffer buffer(zone, 1024); #if defined(DART_PRECOMPILED_RUNTIME) auto const isolate_instructions = reinterpret_cast( T->isolate_group()->source()->snapshot_instructions); auto const vm_instructions = reinterpret_cast( Dart::vm_isolate_group()->source()->snapshot_instructions); if (FLAG_dwarf_stack_traces_mode) { const Image isolate_instructions_image( reinterpret_cast(isolate_instructions)); const Image vm_instructions_image( reinterpret_cast(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("\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(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 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