| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| #include "vm/object.h" | |
| #include <memory> | |
| #include "include/dart_api.h" | |
| #include "lib/integers.h" | |
| #include "lib/stacktrace.h" | |
| #include "platform/assert.h" | |
| #include "platform/text_buffer.h" | |
| #include "platform/unaligned.h" | |
| #include "platform/unicode.h" | |
| #include "vm/bit_vector.h" | |
| #include "vm/bootstrap.h" | |
| #include "vm/bytecode_reader.h" | |
| #include "vm/canonical_tables.h" | |
| #include "vm/class_finalizer.h" | |
| #include "vm/class_id.h" | |
| #include "vm/closure_functions_cache.h" | |
| #include "vm/code_comments.h" | |
| #include "vm/code_descriptors.h" | |
| #include "vm/code_observers.h" | |
| #include "vm/compiler/assembler/disassembler.h" | |
| #include "vm/compiler/assembler/disassembler_kbc.h" | |
| #include "vm/compiler/jit/compiler.h" | |
| #include "vm/compiler/method_recognizer.h" | |
| #include "vm/compiler/runtime_api.h" | |
| #include "vm/cpu.h" | |
| #include "vm/dart.h" | |
| #include "vm/dart_api_state.h" | |
| #include "vm/dart_entry.h" | |
| #include "vm/datastream.h" | |
| #include "vm/debugger.h" | |
| #include "vm/deopt_instructions.h" | |
| #include "vm/double_conversion.h" | |
| #include "vm/elf.h" | |
| #include "vm/exceptions.h" | |
| #include "vm/growable_array.h" | |
| #include "vm/hash.h" | |
| #include "vm/hash_table.h" | |
| #include "vm/heap/become.h" | |
| #include "vm/heap/heap.h" | |
| #include "vm/heap/sampler.h" | |
| #include "vm/heap/weak_code.h" | |
| #include "vm/image_snapshot.h" | |
| #include "vm/isolate_reload.h" | |
| #include "vm/kernel.h" | |
| #include "vm/kernel_binary.h" | |
| #include "vm/kernel_isolate.h" | |
| #include "vm/kernel_loader.h" | |
| #include "vm/line_starts_reader.h" | |
| #include "vm/log.h" | |
| #include "vm/native_symbol.h" | |
| #include "vm/object_graph.h" | |
| #include "vm/object_store.h" | |
| #include "vm/os.h" | |
| #include "vm/parser.h" | |
| #include "vm/profiler.h" | |
| #include "vm/resolver.h" | |
| #include "vm/reusable_handles.h" | |
| #include "vm/reverse_pc_lookup_cache.h" | |
| #include "vm/runtime_entry.h" | |
| #include "vm/scopes.h" | |
| #include "vm/stack_frame.h" | |
| #include "vm/stub_code.h" | |
| #include "vm/symbols.h" | |
| #include "vm/tags.h" | |
| #include "vm/thread_registry.h" | |
| #include "vm/timeline.h" | |
| #include "vm/tsan_symbolize.h" | |
| #include "vm/type_testing_stubs.h" | |
| #include "vm/zone_text_buffer.h" | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| #include "vm/compiler/aot/precompiler.h" | |
| #include "vm/compiler/assembler/assembler.h" | |
| #include "vm/compiler/backend/code_statistics.h" | |
| #include "vm/compiler/backend/il.h" | |
| #include "vm/compiler/compiler_state.h" | |
| #include "vm/compiler/frontend/kernel_fingerprints.h" | |
| #include "vm/compiler/frontend/kernel_translation_helper.h" | |
| #include "vm/compiler/intrinsifier.h" | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| namespace dart { | |
| DEFINE_FLAG(uint64_t, | |
| huge_method_cutoff_in_code_size, | |
| 200000, | |
| "Huge method cutoff in unoptimized code size (in bytes)."); | |
| DEFINE_FLAG( | |
| bool, | |
| show_internal_names, | |
| false, | |
| "Show names of internal classes (e.g. \"OneByteString\") in error messages " | |
| "instead of showing the corresponding interface names (e.g. \"String\"). " | |
| "Also show legacy nullability in type names."); | |
| DEFINE_FLAG(bool, | |
| remove_script_timestamps_for_test, | |
| false, | |
| "Remove script timestamps to allow for deterministic testing."); | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| DEFINE_FLAG(bool, use_register_cc, true, "Use register calling conventions"); | |
| #endif | |
| DECLARE_FLAG(bool, intrinsify); | |
| DECLARE_FLAG(bool, trace_deoptimization); | |
| DECLARE_FLAG(bool, trace_deoptimization_verbose); | |
| DECLARE_FLAG(bool, trace_reload); | |
| DECLARE_FLAG(bool, write_protect_code); | |
| DECLARE_FLAG(bool, precompiled_mode); | |
| DECLARE_FLAG(int, max_polymorphic_checks); | |
| static const char* const kGetterPrefix = "get:"; | |
| static const intptr_t kGetterPrefixLength = strlen(kGetterPrefix); | |
| static const char* const kSetterPrefix = "set:"; | |
| static const intptr_t kSetterPrefixLength = strlen(kSetterPrefix); | |
| static const char* const kInitPrefix = "init:"; | |
| static const intptr_t kInitPrefixLength = strlen(kInitPrefix); | |
| cpp_vtable Object::builtin_vtables_[kNumPredefinedCids] = {}; | |
| #define CHECK_ERROR(error) \ | |
| { \ | |
| ErrorPtr err = (error); \ | |
| if (err != Error::null()) { \ | |
| return err; \ | |
| } \ | |
| } | |
| static void AppendSubString(BaseTextBuffer* buffer, | |
| const char* name, | |
| intptr_t start_pos, | |
| intptr_t len) { | |
| buffer->Printf("%.*s", static_cast<int>(len), &name[start_pos]); | |
| } | |
| // Used to define setters and getters for untagged object fields that are | |
| // defined with the WSR_COMPRESSED_POINTER_FIELD macro. See | |
| // PRECOMPILER_WSR_FIELD_DECLARATION in object.h for more information. | |
| #if defined(DART_PRECOMPILER) | |
| #define PRECOMPILER_WSR_FIELD_DEFINITION(Class, Type, Name) \ | |
| Type##Ptr Class::Name() const { \ | |
| return Type::RawCast(WeakSerializationReference::Unwrap(untag()->Name())); \ | |
| } | |
| #else | |
| #define PRECOMPILER_WSR_FIELD_DEFINITION(Class, Type, Name) \ | |
| void Class::set_##Name(const Type& value) const { \ | |
| untag()->set_##Name(value.ptr()); \ | |
| } | |
| #endif | |
| PRECOMPILER_WSR_FIELD_DEFINITION(ClosureData, Function, parent_function) | |
| PRECOMPILER_WSR_FIELD_DEFINITION(Function, FunctionType, signature) | |
| #undef PRECOMPILER_WSR_FIELD_DEFINITION | |
| // Suppress lint for the following definition because the whitespace/parens | |
| // check doesn't like __VA_OPT__(, ), which is what the formatter produces here. | |
| // NOLINTBEGIN | |
| #define TRACE_TYPE_CHECKS_VERBOSE(format, ...) \ | |
| if (FLAG_trace_type_checks_verbose) { \ | |
| OS::PrintErr(format __VA_OPT__(, ) __VA_ARGS__); \ | |
| } | |
| // NOLINTEND | |
| // Takes a vm internal name and makes it suitable for external user. | |
| // | |
| // Examples: | |
| // | |
| // Internal getter and setter prefixes are changed: | |
| // | |
| // get:foo -> foo | |
| // set:foo -> foo= | |
| // | |
| // Private name mangling is removed, possibly multiple times: | |
| // | |
| // _ReceivePortImpl@709387912 -> _ReceivePortImpl | |
| // _ReceivePortImpl@709387912._internal@709387912 -> | |
| // _ReceivePortImpl._internal | |
| // _C@6328321&_E@6328321&_F@6328321 -> _C&_E&_F | |
| // | |
| // The trailing . on the default constructor name is dropped: | |
| // | |
| // List. -> List | |
| // | |
| // And so forth: | |
| // | |
| // get:foo@6328321 -> foo | |
| // _MyClass@6328321. -> _MyClass | |
| // _MyClass@6328321.named -> _MyClass.named | |
| // | |
| // For extension methods the following demangling is done | |
| // ext|func -> ext.func (instance extension method) | |
| // ext|get#prop -> ext.prop (instance extension getter) | |
| // ext|set#prop -> ext.prop= (instance extension setter) | |
| // ext|sfunc -> ext.sfunc (static extension method) | |
| // get:ext|sprop -> ext.sprop (static extension getter) | |
| // set:ext|sprop -> ext.sprop= (static extension setter) | |
| // | |
| const char* String::ScrubName(const String& name, bool is_extension) { | |
| Thread* thread = Thread::Current(); | |
| NoSafepointScope no_safepoint(thread); | |
| Zone* zone = thread->zone(); | |
| ZoneTextBuffer printer(zone); | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| if (name.Equals(Symbols::TopLevel())) { | |
| // Name of invisible top-level class. | |
| return ""; | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| const char* cname = name.ToCString(); | |
| ASSERT(strlen(cname) == static_cast<size_t>(name.Length())); | |
| const intptr_t name_len = name.Length(); | |
| // First remove all private name mangling and if 'is_extension' is true | |
| // substitute the first '|' character with '.'. | |
| intptr_t start_pos = 0; | |
| intptr_t sum_segment_len = 0; | |
| for (intptr_t i = 0; i < name_len; i++) { | |
| if ((cname[i] == '@') && ((i + 1) < name_len) && (cname[i + 1] >= '0') && | |
| (cname[i + 1] <= '9')) { | |
| // Append the current segment to the unmangled name. | |
| const intptr_t segment_len = i - start_pos; | |
| sum_segment_len += segment_len; | |
| AppendSubString(&printer, cname, start_pos, segment_len); | |
| // Advance until past the name mangling. The private keys are only | |
| // numbers so we skip until the first non-number. | |
| i++; // Skip the '@'. | |
| while ((i < name.Length()) && (name.CharAt(i) >= '0') && | |
| (name.CharAt(i) <= '9')) { | |
| i++; | |
| } | |
| start_pos = i; | |
| i--; // Account for for-loop increment. | |
| } else if (is_extension && cname[i] == '|') { | |
| // Append the current segment to the unmangled name. | |
| const intptr_t segment_len = i - start_pos; | |
| AppendSubString(&printer, cname, start_pos, segment_len); | |
| // Append the '.' character (replaces '|' with '.'). | |
| AppendSubString(&printer, ".", 0, 1); | |
| start_pos = i + 1; | |
| // Account for length of segments added so far. | |
| sum_segment_len += (segment_len + 1); | |
| } | |
| } | |
| const char* unmangled_name = nullptr; | |
| if (start_pos == 0) { | |
| // No name unmangling needed, reuse the name that was passed in. | |
| unmangled_name = cname; | |
| sum_segment_len = name_len; | |
| } else if (name.Length() != start_pos) { | |
| // Append the last segment. | |
| const intptr_t segment_len = name.Length() - start_pos; | |
| sum_segment_len += segment_len; | |
| AppendSubString(&printer, cname, start_pos, segment_len); | |
| } | |
| if (unmangled_name == nullptr) { | |
| // Merge unmangled_segments. | |
| unmangled_name = printer.buffer(); | |
| } | |
| printer.Clear(); | |
| intptr_t start = 0; | |
| intptr_t len = sum_segment_len; | |
| bool is_setter = false; | |
| if (is_extension) { | |
| // First scan till we see the '.' character. | |
| for (intptr_t i = 0; i < len; i++) { | |
| if (unmangled_name[i] == '.') { | |
| intptr_t slen = i + 1; | |
| intptr_t plen = slen - start; | |
| AppendSubString(&printer, unmangled_name, start, plen); | |
| unmangled_name += slen; | |
| len -= slen; | |
| break; | |
| } else if (unmangled_name[i] == ':') { | |
| if (start != 0) { | |
| // Reset and break. | |
| start = 0; | |
| is_setter = false; | |
| break; | |
| } | |
| if (unmangled_name[0] == 's') { | |
| is_setter = true; | |
| } | |
| start = i + 1; | |
| } | |
| } | |
| } | |
| intptr_t dot_pos = -1; // Position of '.' in the name, if any. | |
| start = 0; | |
| for (intptr_t i = start; i < len; i++) { | |
| if (unmangled_name[i] == ':' || | |
| (is_extension && unmangled_name[i] == '#')) { | |
| if (start != 0) { | |
| // Reset and break. | |
| start = 0; | |
| dot_pos = -1; | |
| break; | |
| } | |
| ASSERT(start == 0); // Only one : is possible in getters or setters. | |
| if (unmangled_name[0] == 's') { | |
| ASSERT(!is_setter); | |
| is_setter = true; | |
| } | |
| start = i + 1; | |
| } else if (unmangled_name[i] == '.') { | |
| if (dot_pos != -1) { | |
| // Reset and break. | |
| start = 0; | |
| dot_pos = -1; | |
| break; | |
| } | |
| ASSERT(dot_pos == -1); // Only one dot is supported. | |
| dot_pos = i; | |
| } | |
| } | |
| if (!is_extension && (start == 0) && (dot_pos == -1)) { | |
| // This unmangled_name is fine as it is. | |
| return unmangled_name; | |
| } | |
| // Drop the trailing dot if needed. | |
| intptr_t end = ((dot_pos + 1) == len) ? dot_pos : len; | |
| intptr_t substr_len = end - start; | |
| AppendSubString(&printer, unmangled_name, start, substr_len); | |
| if (is_setter) { | |
| const char* equals = Symbols::Equals().ToCString(); | |
| const intptr_t equals_len = strlen(equals); | |
| AppendSubString(&printer, equals, 0, equals_len); | |
| } | |
| return printer.buffer(); | |
| } | |
| StringPtr String::ScrubNameRetainPrivate(const String& name, | |
| bool is_extension) { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| intptr_t len = name.Length(); | |
| intptr_t start = 0; | |
| intptr_t at_pos = -1; // Position of '@' in the name, if any. | |
| bool is_setter = false; | |
| String& result = String::Handle(); | |
| // If extension strip out the leading prefix e.g" ext|func would strip out | |
| // 'ext|'. | |
| if (is_extension) { | |
| // First scan till we see the '|' character. | |
| for (intptr_t i = 0; i < len; i++) { | |
| if (name.CharAt(i) == '|') { | |
| result = String::SubString(name, start, (i - start)); | |
| result = String::Concat(result, Symbols::Dot()); | |
| start = i + 1; | |
| break; | |
| } else if (name.CharAt(i) == ':') { | |
| if (start != 0) { | |
| // Reset and break. | |
| start = 0; | |
| is_setter = false; | |
| break; | |
| } | |
| if (name.CharAt(0) == 's') { | |
| is_setter = true; | |
| } | |
| start = i + 1; | |
| } | |
| } | |
| } | |
| for (intptr_t i = start; i < len; i++) { | |
| if (name.CharAt(i) == ':' || (is_extension && name.CharAt(i) == '#')) { | |
| // Only one : is possible in getters or setters. | |
| ASSERT(is_extension || start == 0); | |
| if (name.CharAt(start) == 's') { | |
| is_setter = true; | |
| } | |
| start = i + 1; | |
| } else if (name.CharAt(i) == '@') { | |
| // Setters should have only one @ so we know where to put the =. | |
| ASSERT(!is_setter || (at_pos == -1)); | |
| at_pos = i; | |
| } | |
| } | |
| if (start == 0) { | |
| // This unmangled_name is fine as it is. | |
| return name.ptr(); | |
| } | |
| if (is_extension) { | |
| const String& fname = | |
| String::Handle(String::SubString(name, start, (len - start))); | |
| result = String::Concat(result, fname); | |
| } else { | |
| result = String::SubString(name, start, (len - start)); | |
| } | |
| if (is_setter) { | |
| // Setters need to end with '='. | |
| if (at_pos == -1) { | |
| return String::Concat(result, Symbols::Equals()); | |
| } else { | |
| const String& pre_at = | |
| String::Handle(String::SubString(result, 0, at_pos - 4)); | |
| const String& post_at = | |
| String::Handle(String::SubString(name, at_pos, len - at_pos)); | |
| result = String::Concat(pre_at, Symbols::Equals()); | |
| result = String::Concat(result, post_at); | |
| } | |
| } | |
| return result.ptr(); | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| return name.ptr(); // In AOT, return argument unchanged. | |
| } | |
| template <typename type> | |
| static bool IsSpecialCharacter(type value) { | |
| return ((value == '"') || (value == '\n') || (value == '\f') || | |
| (value == '\b') || (value == '\t') || (value == '\v') || | |
| (value == '\r') || (value == '\\') || (value == '$')); | |
| } | |
| static inline bool IsAsciiNonprintable(int32_t c) { | |
| return ((0 <= c) && (c < 32)) || (c == 127); | |
| } | |
| static int32_t EscapeOverhead(int32_t c) { | |
| if (IsSpecialCharacter(c)) { | |
| return 1; // 1 additional byte for the backslash. | |
| } else if (IsAsciiNonprintable(c)) { | |
| return 3; // 3 additional bytes to encode c as \x00. | |
| } | |
| return 0; | |
| } | |
| template <typename type> | |
| static type SpecialCharacter(type value) { | |
| if (value == '"') { | |
| return '"'; | |
| } else if (value == '\n') { | |
| return 'n'; | |
| } else if (value == '\f') { | |
| return 'f'; | |
| } else if (value == '\b') { | |
| return 'b'; | |
| } else if (value == '\t') { | |
| return 't'; | |
| } else if (value == '\v') { | |
| return 'v'; | |
| } else if (value == '\r') { | |
| return 'r'; | |
| } else if (value == '\\') { | |
| return '\\'; | |
| } else if (value == '$') { | |
| return '$'; | |
| } | |
| UNREACHABLE(); | |
| return '\0'; | |
| } | |
| #if defined(DART_DYNAMIC_MODULES) | |
| static BytecodePtr CreateVMInternalBytecode(KernelBytecode::Opcode opcode) { | |
| const KBCInstr* instructions = nullptr; | |
| intptr_t instructions_size = 0; | |
| KernelBytecode::GetVMInternalBytecodeInstructions(opcode, &instructions, | |
| &instructions_size); | |
| const auto& bytecode = Bytecode::Handle( | |
| Bytecode::New(reinterpret_cast<uword>(instructions), instructions_size, | |
| -1, TypedDataBase::Handle(), Object::empty_object_pool())); | |
| bytecode.set_pc_descriptors(Object::empty_descriptors()); | |
| bytecode.set_exception_handlers(Object::empty_exception_handlers()); | |
| #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| bytecode.set_var_descriptors(Object::empty_var_descriptors()); | |
| #endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| return bytecode.ptr(); | |
| } | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| void Object::InitNullAndBool(IsolateGroup* isolate_group) { | |
| Thread* thread = Thread::Current(); | |
| auto heap = isolate_group->heap(); | |
| // TODO(iposva): NoSafepointScope needs to be added here. | |
| // Allocate and initialize the null instance. | |
| // 'null_' must be the first object allocated as it is used in allocation to | |
| // clear the pointer fields of objects. | |
| { | |
| uword address = | |
| heap->Allocate(thread, Instance::InstanceSize(), Heap::kOld); | |
| Roots::set_null_obj(static_cast<InstancePtr>(address + kHeapObjectTag)); | |
| InitializeObjectVariant<Instance>(address, kNullCid); | |
| Roots::null_obj()->untag()->SetCanonical(); | |
| #if defined(HASH_IN_OBJECT_HEADER) | |
| Object::SetCachedHashIfNotSet(Roots::null_obj(), kNullIdentityHash); | |
| #endif | |
| } | |
| // Allocate and initialize the bool instances. | |
| // These must be allocated such that at kBoolValueBitPosition, the address | |
| // of true is 0 and the address of false is 1, and their addresses are | |
| // otherwise identical. | |
| { | |
| // Allocate a dummy bool object to give true the desired alignment. | |
| uword address = heap->Allocate(thread, Bool::InstanceSize(), Heap::kOld); | |
| InitializeObject<Bool>(address); | |
| static_cast<BoolPtr>(address + kHeapObjectTag)->untag()->value_ = false; | |
| } | |
| { | |
| // Allocate true. | |
| uword address = heap->Allocate(thread, Bool::InstanceSize(), Heap::kOld); | |
| Roots::set_true_obj(static_cast<BoolPtr>(address + kHeapObjectTag)); | |
| InitializeObject<Bool>(address); | |
| Roots::true_obj()->untag()->value_ = true; | |
| Roots::true_obj()->untag()->SetCanonical(); | |
| #if defined(HASH_IN_OBJECT_HEADER) | |
| Object::SetCachedHashIfNotSet(Roots::true_obj(), kTrueIdentityHash); | |
| #endif | |
| } | |
| { | |
| // Allocate false. | |
| uword address = heap->Allocate(thread, Bool::InstanceSize(), Heap::kOld); | |
| Roots::set_false_obj(static_cast<BoolPtr>(address + kHeapObjectTag)); | |
| InitializeObject<Bool>(address); | |
| Roots::false_obj()->untag()->value_ = false; | |
| Roots::false_obj()->untag()->SetCanonical(); | |
| #if defined(HASH_IN_OBJECT_HEADER) | |
| Object::SetCachedHashIfNotSet(Roots::false_obj(), kFalseIdentityHash); | |
| #endif | |
| } | |
| { | |
| uword address = | |
| heap->Allocate(thread, Sentinel::InstanceSize(), Heap::kOld); | |
| Roots::set_sentinel_obj(static_cast<SentinelPtr>(address + kHeapObjectTag)); | |
| InitializeObject<Sentinel>(address); | |
| } | |
| { | |
| uword address = | |
| heap->Allocate(thread, Sentinel::InstanceSize(), Heap::kOld); | |
| Roots::set_unknown_constant_obj( | |
| static_cast<SentinelPtr>(address + kHeapObjectTag)); | |
| InitializeObject<Sentinel>(address); | |
| } | |
| { | |
| uword address = | |
| heap->Allocate(thread, Sentinel::InstanceSize(), Heap::kOld); | |
| Roots::set_non_constant_obj( | |
| static_cast<SentinelPtr>(address + kHeapObjectTag)); | |
| InitializeObject<Sentinel>(address); | |
| } | |
| { | |
| uword address = | |
| heap->Allocate(thread, Sentinel::InstanceSize(), Heap::kOld); | |
| Roots::set_optimized_out_obj( | |
| static_cast<SentinelPtr>(address + kHeapObjectTag)); | |
| InitializeObject<Sentinel>(address); | |
| } | |
| // Check that the objects have been allocated at appropriate addresses. | |
| uword null_ = static_cast<uword>(Roots::null_obj()); | |
| uword true_ = static_cast<uword>(Roots::true_obj()); | |
| uword false_ = static_cast<uword>(Roots::false_obj()); | |
| ASSERT(true_ == null_ + kTrueOffsetFromNull); | |
| ASSERT(false_ == null_ + kFalseOffsetFromNull); | |
| ASSERT((true_ & kBoolValueMask) == 0); | |
| ASSERT((false_ & kBoolValueMask) != 0); | |
| ASSERT(false_ == (true_ | kBoolValueMask)); | |
| ASSERT((null_ & kBoolVsNullMask) == 0); | |
| ASSERT((true_ & kBoolVsNullMask) != 0); | |
| ASSERT((false_ & kBoolVsNullMask) != 0); | |
| // And prevent the compactor from disrupting these patterns. | |
| Page::Of(Roots::null_obj())->set_never_evacuate(true); | |
| Roots::null_object().initRO(Object::null()); | |
| Roots::null_class().initRO(Class::null()); | |
| Roots::null_array().initRO(Array::null()); | |
| Roots::null_string().initRO(String::null()); | |
| Roots::null_instance().initRO(Instance::null()); | |
| Roots::null_function().initRO(Function::null()); | |
| Roots::null_function_type().initRO(FunctionType::null()); | |
| Roots::null_record_type().initRO(RecordType::null()); | |
| Roots::null_type_arguments().initRO(TypeArguments::null()); | |
| Roots::null_closure().initRO(Closure::null()); | |
| Roots::null_bytecode().initRO(Bytecode::null()); | |
| Roots::empty_type_arguments().initRO(TypeArguments::null()); | |
| Roots::null_abstract_type().initRO(AbstractType::null()); | |
| Roots::null_compressed_stackmaps().initRO(CompressedStackMaps::null()); | |
| Roots::bool_true().initRO(Roots::true_obj()); | |
| Roots::bool_false().initRO(Roots::false_obj()); | |
| Roots::sentinel().initRO(Roots::sentinel_obj()); | |
| Roots::unknown_constant().initRO(Roots::unknown_constant_obj()); | |
| Roots::non_constant().initRO(Roots::non_constant_obj()); | |
| Roots::optimized_out().initRO(Roots::optimized_out_obj()); | |
| } | |
| void Object::InitVtables() { | |
| { | |
| Object fake_handle; | |
| builtin_vtables_[kObjectCid] = fake_handle.vtable(); | |
| } | |
| #define INIT_VTABLE(clazz) \ | |
| { \ | |
| clazz fake_handle; \ | |
| builtin_vtables_[k##clazz##Cid] = fake_handle.vtable(); \ | |
| } | |
| CLASS_LIST_NO_OBJECT_NOR_STRING_NOR_ARRAY_NOR_MAP(INIT_VTABLE) | |
| INIT_VTABLE(GrowableObjectArray) | |
| #undef INIT_VTABLE | |
| #define INIT_VTABLE(clazz) \ | |
| { \ | |
| Map fake_handle; \ | |
| builtin_vtables_[k##clazz##Cid] = fake_handle.vtable(); \ | |
| } | |
| CLASS_LIST_MAPS(INIT_VTABLE) | |
| #undef INIT_VTABLE | |
| #define INIT_VTABLE(clazz) \ | |
| { \ | |
| Set fake_handle; \ | |
| builtin_vtables_[k##clazz##Cid] = fake_handle.vtable(); \ | |
| } | |
| CLASS_LIST_SETS(INIT_VTABLE) | |
| #undef INIT_VTABLE | |
| #define INIT_VTABLE(clazz) \ | |
| { \ | |
| Array fake_handle; \ | |
| builtin_vtables_[k##clazz##Cid] = fake_handle.vtable(); \ | |
| } | |
| CLASS_LIST_FIXED_LENGTH_ARRAYS(INIT_VTABLE) | |
| #undef INIT_VTABLE | |
| #define INIT_VTABLE(clazz) \ | |
| { \ | |
| String fake_handle; \ | |
| builtin_vtables_[k##clazz##Cid] = fake_handle.vtable(); \ | |
| } | |
| CLASS_LIST_STRINGS(INIT_VTABLE) | |
| #undef INIT_VTABLE | |
| { | |
| Instance fake_handle; | |
| builtin_vtables_[kFfiNativeTypeCid] = fake_handle.vtable(); | |
| } | |
| #define INIT_VTABLE(clazz) \ | |
| { \ | |
| Instance fake_handle; \ | |
| builtin_vtables_[kFfi##clazz##Cid] = fake_handle.vtable(); \ | |
| } | |
| CLASS_LIST_FFI_TYPE_MARKER(INIT_VTABLE) | |
| #undef INIT_VTABLE | |
| { | |
| Instance fake_handle; | |
| builtin_vtables_[kFfiNativeFunctionCid] = fake_handle.vtable(); | |
| } | |
| { | |
| Pointer fake_handle; | |
| builtin_vtables_[kPointerCid] = fake_handle.vtable(); | |
| } | |
| { | |
| DynamicLibrary fake_handle; | |
| builtin_vtables_[kDynamicLibraryCid] = fake_handle.vtable(); | |
| } | |
| #define INIT_VTABLE(clazz) \ | |
| { \ | |
| TypedData fake_internal_handle; \ | |
| builtin_vtables_[kTypedData##clazz##Cid] = fake_internal_handle.vtable(); \ | |
| TypedDataView fake_view_handle; \ | |
| builtin_vtables_[kTypedData##clazz##ViewCid] = fake_view_handle.vtable(); \ | |
| builtin_vtables_[kUnmodifiableTypedData##clazz##ViewCid] = \ | |
| fake_view_handle.vtable(); \ | |
| ExternalTypedData fake_external_handle; \ | |
| builtin_vtables_[kExternalTypedData##clazz##Cid] = \ | |
| fake_external_handle.vtable(); \ | |
| } | |
| CLASS_LIST_TYPED_DATA(INIT_VTABLE) | |
| #undef INIT_VTABLE | |
| { | |
| TypedDataView fake_handle; | |
| builtin_vtables_[kByteDataViewCid] = fake_handle.vtable(); | |
| builtin_vtables_[kUnmodifiableByteDataViewCid] = fake_handle.vtable(); | |
| } | |
| { | |
| LinkedHashBase fake_handle; | |
| builtin_vtables_[kLinkedHashBaseCid] = fake_handle.vtable(); | |
| } | |
| { | |
| Instance fake_handle; | |
| builtin_vtables_[kByteBufferCid] = fake_handle.vtable(); | |
| builtin_vtables_[kNullCid] = fake_handle.vtable(); | |
| builtin_vtables_[kDynamicCid] = fake_handle.vtable(); | |
| builtin_vtables_[kVoidCid] = fake_handle.vtable(); | |
| builtin_vtables_[kNeverCid] = fake_handle.vtable(); | |
| } | |
| } | |
| // V(Name, Instruction) | |
| #define FOR_EACH_SHARED_BYTECODE_STUB(V) \ | |
| V(implicit_getter, VMInternal_ImplicitGetter) \ | |
| V(implicit_setter, VMInternal_ImplicitSetter) \ | |
| V(implicit_static_getter, VMInternal_ImplicitStaticGetter) \ | |
| V(implicit_shared_static_getter, VMInternal_ImplicitSharedStaticGetter) \ | |
| V(implicit_static_setter, VMInternal_ImplicitStaticSetter) \ | |
| V(implicit_shared_static_setter, VMInternal_ImplicitSharedStaticSetter) \ | |
| V(method_extractor_with_ita, VMInternal_MethodExtractorWithITA) \ | |
| V(method_extractor_without_ita, VMInternal_MethodExtractorWithoutITA) \ | |
| V(invoke_closure, VMInternal_InvokeClosure) \ | |
| V(invoke_field, VMInternal_InvokeField) \ | |
| V(nsm_dispatcher, VMInternal_NoSuchMethodDispatcher) \ | |
| V(dynamic_invocation_forwarder, VMInternal_ForwardDynamicInvocation) \ | |
| V(implicit_static_closure, VMInternal_ImplicitStaticClosure) \ | |
| V(implicit_instance_closure, VMInternal_ImplicitInstanceClosure) \ | |
| V(implicit_constructor_closure, VMInternal_ImplicitConstructorClosure) | |
| #define ROOTS_SHARED_BYTECODE_GETTER(Name) Roots::Name##_bytecode() | |
| #define OBJECT_SHARED_BYTECODE_GETTER(Name) Object::Name##_bytecode() | |
| void Object::Init(IsolateGroup* isolate_group) { | |
| Heap* heap = isolate_group->heap(); | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread != nullptr); | |
| // Ensure lock checks in setters are happy. | |
| SafepointWriteRwLocker ml(thread, isolate_group->program_lock()); | |
| // Initialize the empty array and empty instantiations cache array handles to | |
| // null_ in order to be able to check if the empty and zero arrays were | |
| // allocated (RAW_NULL is not available). | |
| Roots::empty_array().initRO(Array::null()); | |
| Roots::empty_instantiations_cache_array().initRO(Array::null()); | |
| Roots::empty_subtype_test_cache_array().initRO(Array::null()); | |
| Roots::mutable_empty_array().initRO(Array::null()); | |
| Class& cls = Class::Handle(); | |
| // Allocate and initialize the class class. | |
| { | |
| intptr_t size = Class::InstanceSize(); | |
| uword address = heap->Allocate(thread, size, Heap::kOld); | |
| InitializeObject<Class>(address); | |
| Class fake; | |
| // Initialization from Class::New<Class>. | |
| // Directly set ptr_ to break a circular dependency: SetRaw will attempt | |
| // to lookup class class in the class table where it is not registered yet. | |
| cls.ptr_ = static_cast<ClassPtr>(address + kHeapObjectTag); | |
| ASSERT(builtin_vtables_[kClassCid] == fake.vtable()); | |
| cls.set_instance_size( | |
| Class::InstanceSize(), | |
| compiler::target::RoundedAllocationSize(RTN::Class::InstanceSize())); | |
| const intptr_t host_next_field_offset = Class::NextFieldOffset(); | |
| const intptr_t target_next_field_offset = RTN::Class::NextFieldOffset(); | |
| cls.set_next_field_offset(host_next_field_offset, target_next_field_offset); | |
| cls.set_id(Class::kClassId); | |
| cls.set_state_bits(0); | |
| cls.set_is_allocate_finalized(); | |
| cls.set_is_declaration_loaded(); | |
| cls.set_is_type_finalized(); | |
| cls.set_type_arguments_field_offset_in_words(Class::kNoTypeArguments, | |
| RTN::Class::kNoTypeArguments); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_num_native_fields(0); | |
| cls.InitEmptyFields(); | |
| isolate_group->class_table()->Register(cls); | |
| } | |
| // Allocate and initialize the null class. | |
| cls = Class::New<Instance, RTN::Instance>(kNullCid, isolate_group); | |
| cls.set_num_type_arguments_unsafe(0); | |
| isolate_group->object_store()->set_null_class(cls); | |
| // Allocate and initialize Never class. | |
| cls = Class::New<Instance, RTN::Instance>(kNeverCid, isolate_group); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_allocate_finalized(); | |
| cls.set_is_declaration_loaded(); | |
| cls.set_is_type_finalized(); | |
| isolate_group->object_store()->set_never_class(cls); | |
| // Allocate and initialize the free list element class. | |
| cls = Class::New<FreeListElement::FakeInstance, | |
| RTN::FreeListElement::FakeInstance>(kFreeListElement, | |
| isolate_group); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_allocate_finalized(); | |
| cls.set_is_declaration_loaded(); | |
| cls.set_is_type_finalized(); | |
| // Allocate and initialize the forwarding corpse class. | |
| cls = Class::New<ForwardingCorpse::FakeInstance, | |
| RTN::ForwardingCorpse::FakeInstance>(kForwardingCorpse, | |
| isolate_group); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_allocate_finalized(); | |
| cls.set_is_declaration_loaded(); | |
| cls.set_is_type_finalized(); | |
| // Allocate the remaining VM internal classes. | |
| cls = Class::New<Sentinel, RTN::Sentinel>(isolate_group); | |
| cls = Class::New<TypeParameters, RTN::TypeParameters>(isolate_group); | |
| cls = Class::New<TypeArguments, RTN::TypeArguments>(isolate_group); | |
| cls = Class::New<PatchClass, RTN::PatchClass>(isolate_group); | |
| cls = Class::New<Function, RTN::Function>(isolate_group); | |
| cls = Class::New<ClosureData, RTN::ClosureData>(isolate_group); | |
| cls = Class::New<FfiTrampolineData, RTN::FfiTrampolineData>(isolate_group); | |
| cls = Class::New<Field, RTN::Field>(isolate_group); | |
| cls = Class::New<Script, RTN::Script>(isolate_group); | |
| cls = Class::New<Library, RTN::Library>(isolate_group); | |
| cls = Class::New<Namespace, RTN::Namespace>(isolate_group); | |
| cls = Class::New<KernelProgramInfo, RTN::KernelProgramInfo>(isolate_group); | |
| cls = Class::New<Code, RTN::Code>(isolate_group); | |
| cls = Class::New<Instructions, RTN::Instructions>(isolate_group); | |
| cls = | |
| Class::New<InstructionsSection, RTN::InstructionsSection>(isolate_group); | |
| cls = Class::New<InstructionsTable, RTN::InstructionsTable>(isolate_group); | |
| cls = Class::New<ObjectPool, RTN::ObjectPool>(isolate_group); | |
| cls = Class::New<PcDescriptors, RTN::PcDescriptors>(isolate_group); | |
| cls = Class::New<CodeSourceMap, RTN::CodeSourceMap>(isolate_group); | |
| cls = | |
| Class::New<CompressedStackMaps, RTN::CompressedStackMaps>(isolate_group); | |
| cls = | |
| Class::New<LocalVarDescriptors, RTN::LocalVarDescriptors>(isolate_group); | |
| cls = Class::New<LocalVarDescriptor, RTN::LocalVarDescriptor>(isolate_group); | |
| cls = Class::New<ExceptionHandlers, RTN::ExceptionHandlers>(isolate_group); | |
| cls = Class::New<Context, RTN::Context>(isolate_group); | |
| cls = Class::New<ContextScope, RTN::ContextScope>(isolate_group); | |
| cls = Class::New<Bytecode, RTN::Bytecode>(isolate_group); | |
| cls = Class::New<SingleTargetCache, RTN::SingleTargetCache>(isolate_group); | |
| cls = Class::New<UnlinkedCall, RTN::UnlinkedCall>(isolate_group); | |
| cls = Class::New<MonomorphicSmiableCall, RTN::MonomorphicSmiableCall>( | |
| isolate_group); | |
| cls = Class::New<ICData, RTN::ICData>(isolate_group); | |
| cls = Class::New<MegamorphicCache, RTN::MegamorphicCache>(isolate_group); | |
| cls = Class::New<SubtypeTestCache, RTN::SubtypeTestCache>(isolate_group); | |
| cls = Class::New<LoadingUnit, RTN::LoadingUnit>(isolate_group); | |
| cls = Class::New<ApiError, RTN::ApiError>(isolate_group); | |
| cls = Class::New<LanguageError, RTN::LanguageError>(isolate_group); | |
| cls = Class::New<UnhandledException, RTN::UnhandledException>(isolate_group); | |
| cls = Class::New<UnwindError, RTN::UnwindError>(isolate_group); | |
| cls = Class::New<WeakSerializationReference, RTN::WeakSerializationReference>( | |
| isolate_group); | |
| cls = Class::New<WeakArray, RTN::WeakArray>(isolate_group); | |
| ASSERT(isolate_group->class_table()->At(kClassCid) != Roots::null_obj()); | |
| // Pre-allocate classes in the vm isolate so that we can for example create a | |
| // symbol table and populate it with some frequently used strings as symbols. | |
| cls = Class::New<Array, RTN::Array>(isolate_group); | |
| isolate_group->object_store()->set_array_class(cls); | |
| cls.set_type_arguments_field_offset(Array::type_arguments_offset(), | |
| RTN::Array::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(1); | |
| cls = Class::New<Array, RTN::Array>(kImmutableArrayCid, isolate_group); | |
| isolate_group->object_store()->set_immutable_array_class(cls); | |
| cls.set_type_arguments_field_offset(Array::type_arguments_offset(), | |
| RTN::Array::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(1); | |
| // In order to be able to canonicalize arguments descriptors early. | |
| cls.set_is_prefinalized(); | |
| cls = | |
| Class::New<GrowableObjectArray, RTN::GrowableObjectArray>(isolate_group); | |
| isolate_group->object_store()->set_growable_object_array_class(cls); | |
| cls.set_type_arguments_field_offset( | |
| GrowableObjectArray::type_arguments_offset(), | |
| RTN::GrowableObjectArray::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(1); | |
| cls = Class::NewStringClass(kOneByteStringCid, isolate_group); | |
| isolate_group->object_store()->set_one_byte_string_class(cls); | |
| cls = Class::NewStringClass(kTwoByteStringCid, isolate_group); | |
| isolate_group->object_store()->set_two_byte_string_class(cls); | |
| cls = Class::New<Mint, RTN::Mint>(isolate_group); | |
| isolate_group->object_store()->set_mint_class(cls); | |
| cls = Class::New<Double, RTN::Double>(isolate_group); | |
| isolate_group->object_store()->set_double_class(cls); | |
| cls = Class::New<Float32x4, RTN::Float32x4>(isolate_group); | |
| isolate_group->object_store()->set_float32x4_class(cls); | |
| cls = Class::New<Float64x2, RTN::Float64x2>(isolate_group); | |
| isolate_group->object_store()->set_float64x2_class(cls); | |
| cls = Class::New<Int32x4, RTN::Int32x4>(isolate_group); | |
| isolate_group->object_store()->set_int32x4_class(cls); | |
| // Ensure that class kExternalTypedDataUint8ArrayCid is registered as we | |
| // need it when reading in the token stream of bootstrap classes in the VM | |
| // isolate. | |
| Class::NewExternalTypedDataClass(kExternalTypedDataUint8ArrayCid, | |
| isolate_group); | |
| // Needed for object pools of VM isolate stubs. | |
| Class::NewTypedDataClass(kTypedDataInt8ArrayCid, isolate_group); | |
| // Allocate and initialize the empty_array instance. | |
| { | |
| uword address = heap->Allocate(thread, Array::InstanceSize(0), Heap::kOld); | |
| InitializeObjectVariant<Array>(address, kImmutableArrayCid, 0); | |
| Roots::empty_array().initRO( | |
| static_cast<ArrayPtr>(address + kHeapObjectTag)); | |
| Roots::empty_array().untag()->set_length(Smi::New(0)); | |
| Roots::empty_array().SetCanonical(); | |
| Roots::empty_array().SetDeeplyImmutable(); | |
| } | |
| { | |
| uword address = heap->Allocate(thread, Array::InstanceSize(0), Heap::kOld); | |
| InitializeObjectVariant<Array>(address, kArrayCid, 0); | |
| Roots::mutable_empty_array().initRO( | |
| static_cast<ArrayPtr>(address + kHeapObjectTag)); | |
| Roots::mutable_empty_array().untag()->set_length(Smi::New(0)); | |
| Roots::mutable_empty_array().SetCanonical(); | |
| Roots::mutable_empty_array().SetDeeplyImmutable(); | |
| } | |
| Smi& smi = Smi::Handle(); | |
| // Allocate and initialize the empty instantiations cache array instance, | |
| // which contains metadata as the first element and a sentinel value | |
| // at the start of the first entry. | |
| { | |
| const intptr_t array_size = | |
| static_cast<intptr_t>(TypeArguments::Cache::kHeaderSize) + | |
| static_cast<intptr_t>(TypeArguments::Cache::kEntrySize); | |
| uword address = | |
| heap->Allocate(thread, Array::InstanceSize(array_size), Heap::kOld); | |
| InitializeObjectVariant<Array>(address, kImmutableArrayCid, array_size); | |
| Roots::empty_instantiations_cache_array().initRO( | |
| static_cast<ArrayPtr>(address + kHeapObjectTag)); | |
| Roots::empty_instantiations_cache_array().untag()->set_length( | |
| Smi::New(array_size)); | |
| // The empty cache has no occupied entries and is not a hash-based cache. | |
| smi = Smi::New(0); | |
| Roots::empty_instantiations_cache_array().SetAt( | |
| TypeArguments::Cache::kMetadataIndex, smi); | |
| // Make the first (and only) entry unoccupied by setting its first element | |
| // to the sentinel value. | |
| smi = TypeArguments::Cache::Sentinel(); | |
| InstantiationsCacheTable table(Roots::empty_instantiations_cache_array()); | |
| table.At(0).Set<TypeArguments::Cache::kSentinelIndex>(smi); | |
| // The other contents of the array are immaterial. | |
| } | |
| // Allocate and initialize the empty subtype test cache array instance, | |
| // which contains a single unoccupied entry. | |
| { | |
| const intptr_t array_size = SubtypeTestCache::kTestEntryLength; | |
| uword address = | |
| heap->Allocate(thread, Array::InstanceSize(array_size), Heap::kOld); | |
| InitializeObjectVariant<Array>(address, kImmutableArrayCid, array_size); | |
| Roots::empty_subtype_test_cache_array().initRO( | |
| static_cast<ArrayPtr>(address + kHeapObjectTag)); | |
| Roots::empty_subtype_test_cache_array().untag()->set_length( | |
| Smi::New(array_size)); | |
| // Make the first (and only) entry unoccupied by setting its first element | |
| // to the null value. | |
| Roots::empty_subtype_test_cache_array().SetAt( | |
| SubtypeTestCache::kInstanceCidOrSignature, Object::null_object()); | |
| smi = TypeArguments::Cache::Sentinel(); | |
| SubtypeTestCacheTable table(Roots::empty_subtype_test_cache_array()); | |
| table.At(0).Set<SubtypeTestCache::kInstanceCidOrSignature>( | |
| Object::null_object()); | |
| // The other contents of the array are immaterial. | |
| } | |
| // Allocate and initialize the canonical empty context scope object. | |
| { | |
| uword address = | |
| heap->Allocate(thread, ContextScope::InstanceSize(0), Heap::kOld); | |
| InitializeObject<ContextScope>(address, 0); | |
| Roots::empty_context_scope().initRO( | |
| static_cast<ContextScopePtr>(address + kHeapObjectTag)); | |
| Roots::empty_context_scope().StoreNonPointer( | |
| &Roots::empty_context_scope().untag()->num_variables_, 0); | |
| Roots::empty_context_scope().StoreNonPointer( | |
| &Roots::empty_context_scope().untag()->is_implicit_, true); | |
| } | |
| // Allocate and initialize the canonical empty object pool object. | |
| { | |
| uword address = | |
| heap->Allocate(thread, ObjectPool::InstanceSize(0), Heap::kOld); | |
| InitializeObject<ObjectPool>(address, 0); | |
| Roots::empty_object_pool().initRO( | |
| static_cast<ObjectPoolPtr>(address + kHeapObjectTag)); | |
| Roots::empty_object_pool().StoreNonPointer( | |
| &Roots::empty_object_pool().untag()->length_, 0); | |
| } | |
| // Allocate and initialize the empty_compressed_stackmaps instance. | |
| { | |
| const intptr_t instance_size = CompressedStackMaps::InstanceSize(0); | |
| uword address = heap->Allocate(thread, instance_size, Heap::kOld); | |
| InitializeObject<CompressedStackMaps>(address, 0); | |
| Roots::empty_compressed_stackmaps().initRO( | |
| static_cast<CompressedStackMapsPtr>(address + kHeapObjectTag)); | |
| Roots::empty_compressed_stackmaps().untag()->payload()->set_flags_and_size( | |
| 0); | |
| } | |
| // Allocate and initialize the empty_descriptors instance. | |
| { | |
| uword address = | |
| heap->Allocate(thread, PcDescriptors::InstanceSize(0), Heap::kOld); | |
| InitializeObject<PcDescriptors>(address, 0); | |
| Roots::empty_descriptors().initRO( | |
| static_cast<PcDescriptorsPtr>(address + kHeapObjectTag)); | |
| Roots::empty_descriptors().StoreNonPointer( | |
| &Roots::empty_descriptors().untag()->length_, 0); | |
| } | |
| // Allocate and initialize the canonical empty variable descriptor object. | |
| { | |
| uword address = heap->Allocate(thread, LocalVarDescriptors::InstanceSize(0), | |
| Heap::kOld); | |
| InitializeObject<LocalVarDescriptors>(address, 0); | |
| Roots::empty_var_descriptors().initRO( | |
| static_cast<LocalVarDescriptorsPtr>(address + kHeapObjectTag)); | |
| Roots::empty_var_descriptors().StoreNonPointer( | |
| &Roots::empty_var_descriptors().untag()->num_entries_, 0); | |
| } | |
| // Allocate and initialize the canonical empty exception handler info object. | |
| // The vast majority of all functions do not contain an exception handler | |
| // and can share this canonical descriptor. | |
| { | |
| uword address = | |
| heap->Allocate(thread, ExceptionHandlers::InstanceSize(0), Heap::kOld); | |
| InitializeObject<ExceptionHandlers>(address, 0); | |
| Roots::empty_exception_handlers().initRO( | |
| static_cast<ExceptionHandlersPtr>(address + kHeapObjectTag)); | |
| Roots::empty_exception_handlers().StoreNonPointer( | |
| &Roots::empty_exception_handlers().untag()->packed_fields_, 0); | |
| } | |
| // Empty exception handlers for async/async* functions. | |
| { | |
| uword address = | |
| heap->Allocate(thread, ExceptionHandlers::InstanceSize(0), Heap::kOld); | |
| InitializeObject<ExceptionHandlers>(address, 0); | |
| Roots::empty_async_exception_handlers().initRO( | |
| static_cast<ExceptionHandlersPtr>(address + kHeapObjectTag)); | |
| Roots::empty_async_exception_handlers().StoreNonPointer( | |
| &Roots::empty_async_exception_handlers().untag()->packed_fields_, | |
| UntaggedExceptionHandlers::AsyncHandlerBit::update(true, 0)); | |
| } | |
| // Allocate and initialize the canonical empty type arguments object. | |
| { | |
| uword address = | |
| heap->Allocate(thread, TypeArguments::InstanceSize(0), Heap::kOld); | |
| InitializeObject<TypeArguments>(address, 0); | |
| Roots::empty_type_arguments().initRO( | |
| static_cast<TypeArgumentsPtr>(address + kHeapObjectTag)); | |
| Roots::empty_type_arguments().untag()->set_length(Smi::New(0)); | |
| Roots::empty_type_arguments().untag()->set_hash(Smi::New(0)); | |
| Roots::empty_type_arguments().untag()->set_nullability(Smi::New(0)); | |
| Roots::empty_type_arguments().ComputeHash(); | |
| Roots::empty_type_arguments().SetCanonical(); | |
| } | |
| cls = Class::New<Instance, RTN::Instance>(kDynamicCid, isolate_group); | |
| cls.set_is_abstract(); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_allocate_finalized(); | |
| cls.set_is_declaration_loaded(); | |
| cls.set_is_type_finalized(); | |
| cls = Class::New<Instance, RTN::Instance>(kVoidCid, isolate_group); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_allocate_finalized(); | |
| cls.set_is_declaration_loaded(); | |
| cls.set_is_type_finalized(); | |
| cls = Class::New<Type, RTN::Type>(isolate_group); | |
| cls.set_is_allocate_finalized(); | |
| cls.set_is_declaration_loaded(); | |
| cls.set_is_type_finalized(); | |
| cls = Class::New<FunctionType, RTN::FunctionType>(isolate_group); | |
| cls.set_is_allocate_finalized(); | |
| cls.set_is_declaration_loaded(); | |
| cls.set_is_type_finalized(); | |
| cls = Class::New<RecordType, RTN::RecordType>(isolate_group); | |
| cls.set_is_allocate_finalized(); | |
| cls.set_is_declaration_loaded(); | |
| cls.set_is_type_finalized(); | |
| cls = isolate_group->class_table()->At(kDynamicCid); | |
| Roots::dynamic_type().initRO( | |
| Type::New(cls, Object::null_type_arguments(), Nullability::kNullable)); | |
| Roots::dynamic_type().SetIsFinalized(); | |
| Roots::dynamic_type().ComputeHash(); | |
| Roots::dynamic_type().SetCanonical(); | |
| cls = isolate_group->class_table()->At(kVoidCid); | |
| Roots::void_type().initRO( | |
| Type::New(cls, Object::null_type_arguments(), Nullability::kNullable)); | |
| Roots::void_type().SetIsFinalized(); | |
| Roots::void_type().ComputeHash(); | |
| Roots::void_type().SetCanonical(); | |
| // Since TypeArguments objects are passed as function arguments, make them | |
| // behave as Dart instances, although they are just VM objects. | |
| // Note that we cannot set the super type to ObjectType, which does not live | |
| // in the vm isolate. See special handling in Class::SuperClass(). | |
| cls = isolate_group->class_table()->At(kTypeArgumentsCid); | |
| cls.set_interfaces(Object::empty_array()); | |
| cls.SetFields(Object::empty_array()); | |
| cls.SetFunctions(Object::empty_array()); | |
| cls = Class::New<Bool, RTN::Bool>(isolate_group); | |
| isolate_group->object_store()->set_bool_class(cls); | |
| Roots::smi_illegal_cid().initRO(Smi::New(kIllegalCid)); | |
| Roots::smi_zero().initRO(Smi::New(0)); | |
| String& error_str = String::Handle(); | |
| error_str = String::New( | |
| "Callbacks into the Dart VM are currently prohibited. Either there are " | |
| "outstanding pointers from Dart_TypedDataAcquireData that have not been " | |
| "released with Dart_TypedDataReleaseData, or a finalizer is running.", | |
| Heap::kOld); | |
| Roots::no_callbacks_error().initRO(ApiError::New(error_str, Heap::kOld)); | |
| error_str = String::New("isolate is exiting", Heap::kOld); | |
| Roots::unwind_error().initRO(UnwindError::New(error_str, Heap::kOld)); | |
| error_str = String::New( | |
| "No api calls are allowed while unwind is in progress", Heap::kOld); | |
| Roots::unwind_in_progress_error().initRO( | |
| UnwindError::New(error_str, Heap::kOld)); | |
| error_str = String::New("SnapshotWriter Error", Heap::kOld); | |
| Roots::snapshot_writer_error().initRO( | |
| LanguageError::New(error_str, Report::kError, Heap::kOld)); | |
| error_str = String::New("Branch offset overflow", Heap::kOld); | |
| Roots::branch_offset_error().initRO( | |
| LanguageError::New(error_str, Report::kBailout, Heap::kOld)); | |
| error_str = String::New("Background Compilation Failed", Heap::kOld); | |
| Roots::background_compilation_error().initRO( | |
| LanguageError::New(error_str, Report::kBailout, Heap::kOld)); | |
| error_str = String::New("No debuggable code where breakpoint was requested", | |
| Heap::kOld); | |
| Roots::no_debuggable_code_error().initRO( | |
| LanguageError::New(error_str, Report::kError, Heap::kOld)); | |
| error_str = String::New("Out of memory", Heap::kOld); | |
| Roots::out_of_memory_error().initRO( | |
| LanguageError::New(error_str, Report::kError, Heap::kOld)); | |
| Roots::unhandled_oom_exception().initRO( | |
| UnhandledException::New(error_str, StackTrace::Handle(), Heap::kOld)); | |
| // Allocate the parameter types and names for synthetic getters. | |
| Roots::synthetic_getter_parameter_types().initRO(Array::New(1, Heap::kOld)); | |
| Roots::synthetic_getter_parameter_types().SetAt(0, Object::dynamic_type()); | |
| Roots::synthetic_getter_parameter_names().initRO(Array::New(1, Heap::kOld)); | |
| // Fill in synthetic_getter_parameter_names_ later, after symbols are | |
| // initialized (in Object::FinalizeVMIsolate). | |
| // synthetic_getter_parameter_names_ object needs to be created earlier as | |
| // VM isolate snapshot reader references it before Object::FinalizeVMIsolate. | |
| #if defined(DART_DYNAMIC_MODULES) | |
| #define INITIALIZE_SHARED_BYTECODE(Name, Instruction) \ | |
| ROOTS_SHARED_BYTECODE_GETTER(Name).initRO( \ | |
| CreateVMInternalBytecode(KernelBytecode::k##Instruction)); | |
| #else | |
| #define INITIALIZE_SHARED_BYTECODE(Name, Instruction) \ | |
| ROOTS_SHARED_BYTECODE_GETTER(Name).initRO(Bytecode::null()); | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| FOR_EACH_SHARED_BYTECODE_STUB(INITIALIZE_SHARED_BYTECODE) | |
| #undef INITIALIZE_SHARED_BYTECODE | |
| Roots::uninitialized_index().initRO( | |
| TypedData::New(kTypedDataUint32ArrayCid, | |
| LinkedHashBase::kUninitializedIndexSize, Heap::kOld)); | |
| Roots::uninitialized_data().initRO(Array::New(0, Heap::kOld)); | |
| Roots::empty_coverage_array().initRO( | |
| TypedData::New(kTypedDataUint32ArrayCid, 0, Heap::kOld)); | |
| // Some thread fields need to be reinitialized as null constants have not been | |
| // initialized until now. | |
| thread->ClearStickyError(); | |
| ASSERT(!Roots::null_object().IsSmi()); | |
| ASSERT(!Roots::null_class().IsSmi()); | |
| ASSERT(Roots::null_class().IsClass()); | |
| ASSERT(!Roots::null_array().IsSmi()); | |
| ASSERT(Roots::null_array().IsArray()); | |
| ASSERT(!Roots::null_string().IsSmi()); | |
| ASSERT(Roots::null_string().IsString()); | |
| ASSERT(!Roots::null_instance().IsSmi()); | |
| ASSERT(Roots::null_instance().IsInstance()); | |
| ASSERT(!Roots::null_function().IsSmi()); | |
| ASSERT(Roots::null_function().IsFunction()); | |
| ASSERT(!Roots::null_function_type().IsSmi()); | |
| ASSERT(Roots::null_function_type().IsFunctionType()); | |
| ASSERT(!Roots::null_record_type().IsSmi()); | |
| ASSERT(Roots::null_record_type().IsRecordType()); | |
| ASSERT(!Roots::null_type_arguments().IsSmi()); | |
| ASSERT(Roots::null_type_arguments().IsTypeArguments()); | |
| ASSERT(!Roots::null_compressed_stackmaps().IsSmi()); | |
| ASSERT(Roots::null_compressed_stackmaps().IsCompressedStackMaps()); | |
| ASSERT(!Roots::empty_array().IsSmi()); | |
| ASSERT(Roots::empty_array().IsArray()); | |
| ASSERT(!Roots::empty_instantiations_cache_array().IsSmi()); | |
| ASSERT(Roots::empty_instantiations_cache_array().IsArray()); | |
| ASSERT(!Roots::empty_subtype_test_cache_array().IsSmi()); | |
| ASSERT(Roots::empty_subtype_test_cache_array().IsArray()); | |
| ASSERT(!Roots::empty_type_arguments().IsSmi()); | |
| ASSERT(Roots::empty_type_arguments().IsTypeArguments()); | |
| ASSERT(!Roots::empty_context_scope().IsSmi()); | |
| ASSERT(Roots::empty_context_scope().IsContextScope()); | |
| ASSERT(!Roots::empty_compressed_stackmaps().IsSmi()); | |
| ASSERT(Roots::empty_compressed_stackmaps().IsCompressedStackMaps()); | |
| ASSERT(!Roots::empty_descriptors().IsSmi()); | |
| ASSERT(Roots::empty_descriptors().IsPcDescriptors()); | |
| ASSERT(!Roots::empty_var_descriptors().IsSmi()); | |
| ASSERT(Roots::empty_var_descriptors().IsLocalVarDescriptors()); | |
| ASSERT(!Roots::empty_exception_handlers().IsSmi()); | |
| ASSERT(Roots::empty_exception_handlers().IsExceptionHandlers()); | |
| ASSERT(!Roots::empty_async_exception_handlers().IsSmi()); | |
| ASSERT(Roots::empty_async_exception_handlers().IsExceptionHandlers()); | |
| ASSERT(!Roots::sentinel().IsSmi()); | |
| ASSERT(Roots::sentinel().IsSentinel()); | |
| ASSERT(!Roots::unknown_constant().IsSmi()); | |
| ASSERT(Roots::unknown_constant().IsSentinel()); | |
| ASSERT(!Roots::non_constant().IsSmi()); | |
| ASSERT(Roots::non_constant().IsSentinel()); | |
| ASSERT(!Roots::optimized_out().IsSmi()); | |
| ASSERT(Roots::optimized_out().IsSentinel()); | |
| ASSERT(!Roots::bool_true().IsSmi()); | |
| ASSERT(Roots::bool_true().IsBool()); | |
| ASSERT(!Roots::bool_false().IsSmi()); | |
| ASSERT(Roots::bool_false().IsBool()); | |
| ASSERT(Roots::smi_illegal_cid().IsSmi()); | |
| ASSERT(Roots::smi_zero().IsSmi()); | |
| ASSERT(!Roots::no_callbacks_error().IsSmi()); | |
| ASSERT(Roots::no_callbacks_error().IsApiError()); | |
| ASSERT(!Roots::unwind_error().IsSmi()); | |
| ASSERT(Roots::unwind_error().IsUnwindError()); | |
| ASSERT(!Roots::unwind_in_progress_error().IsSmi()); | |
| ASSERT(Roots::unwind_in_progress_error().IsUnwindError()); | |
| ASSERT(!Roots::snapshot_writer_error().IsSmi()); | |
| ASSERT(Roots::snapshot_writer_error().IsLanguageError()); | |
| ASSERT(!Roots::branch_offset_error().IsSmi()); | |
| ASSERT(Roots::branch_offset_error().IsLanguageError()); | |
| ASSERT(!Roots::background_compilation_error().IsSmi()); | |
| ASSERT(Roots::background_compilation_error().IsLanguageError()); | |
| ASSERT(!Roots::out_of_memory_error().IsSmi()); | |
| ASSERT(Roots::out_of_memory_error().IsLanguageError()); | |
| ASSERT(!Roots::unhandled_oom_exception().IsSmi()); | |
| ASSERT(Roots::unhandled_oom_exception().IsUnhandledException()); | |
| ASSERT(!Roots::synthetic_getter_parameter_types().IsSmi()); | |
| ASSERT(Roots::synthetic_getter_parameter_types().IsArray()); | |
| ASSERT(!Roots::synthetic_getter_parameter_names().IsSmi()); | |
| ASSERT(Roots::synthetic_getter_parameter_names().IsArray()); | |
| #define CHECK_SHARED_BYTECODE(Name, _) \ | |
| ASSERT(!ROOTS_SHARED_BYTECODE_GETTER(Name).IsSmi()); \ | |
| ASSERT(ROOTS_SHARED_BYTECODE_GETTER(Name).IsBytecode()); | |
| FOR_EACH_SHARED_BYTECODE_STUB(CHECK_SHARED_BYTECODE) | |
| #undef CHECK_SHARED_BYTECODE | |
| ASSERT(!Roots::uninitialized_index().IsSmi()); | |
| ASSERT(Roots::uninitialized_index().IsTypedData()); | |
| ASSERT(!Roots::uninitialized_data().IsSmi()); | |
| ASSERT(Roots::uninitialized_data().IsArray()); | |
| ASSERT(!Roots::empty_coverage_array().IsSmi()); | |
| ASSERT(Roots::empty_coverage_array().IsTypedData()); | |
| } | |
| void Object::FinishInit(IsolateGroup* isolate_group) { | |
| // Finish initialization of synthetic_getter_parameter_names_ which was | |
| // Started in Object::InitOnce() | |
| Roots::synthetic_getter_parameter_names().SetAt(0, Symbols::This()); | |
| // Set up names for all VM singleton classes. | |
| Class& cls = Class::Handle(); | |
| ClassTable* table = isolate_group->class_table(); | |
| #define SET_CLASS_NAME(class_name, name) \ | |
| cls = table->At(class_name); \ | |
| cls.set_name(Symbols::name()); | |
| SET_CLASS_NAME(kClassCid, Class); | |
| SET_CLASS_NAME(kDynamicCid, Dynamic); | |
| SET_CLASS_NAME(kVoidCid, Void); | |
| SET_CLASS_NAME(kTypeParametersCid, TypeParameters); | |
| SET_CLASS_NAME(kTypeArgumentsCid, TypeArguments); | |
| SET_CLASS_NAME(kPatchClassCid, PatchClass); | |
| SET_CLASS_NAME(kFunctionCid, Function); | |
| SET_CLASS_NAME(kClosureDataCid, ClosureData); | |
| SET_CLASS_NAME(kFfiTrampolineDataCid, FfiTrampolineData); | |
| SET_CLASS_NAME(kFieldCid, Field); | |
| SET_CLASS_NAME(kScriptCid, Script); | |
| SET_CLASS_NAME(kLibraryCid, LibraryClass); | |
| SET_CLASS_NAME(kNamespaceCid, Namespace); | |
| SET_CLASS_NAME(kKernelProgramInfoCid, KernelProgramInfo); | |
| SET_CLASS_NAME(kWeakSerializationReferenceCid, WeakSerializationReference); | |
| SET_CLASS_NAME(kWeakArrayCid, WeakArray); | |
| SET_CLASS_NAME(kCodeCid, Code); | |
| SET_CLASS_NAME(kInstructionsCid, Instructions); | |
| SET_CLASS_NAME(kInstructionsSectionCid, InstructionsSection); | |
| SET_CLASS_NAME(kInstructionsTableCid, InstructionsTable); | |
| SET_CLASS_NAME(kObjectPoolCid, ObjectPool); | |
| SET_CLASS_NAME(kCodeSourceMapCid, CodeSourceMap); | |
| SET_CLASS_NAME(kPcDescriptorsCid, PcDescriptors); | |
| SET_CLASS_NAME(kCompressedStackMapsCid, CompressedStackMaps); | |
| SET_CLASS_NAME(kLocalVarDescriptorsCid, LocalVarDescriptors); | |
| SET_CLASS_NAME(kLocalVarDescriptorCid, LocalVarDescriptor); | |
| SET_CLASS_NAME(kExceptionHandlersCid, ExceptionHandlers); | |
| SET_CLASS_NAME(kContextCid, Context); | |
| SET_CLASS_NAME(kContextScopeCid, ContextScope); | |
| SET_CLASS_NAME(kBytecodeCid, Bytecode); | |
| SET_CLASS_NAME(kSentinelCid, Sentinel); | |
| SET_CLASS_NAME(kSingleTargetCacheCid, SingleTargetCache); | |
| SET_CLASS_NAME(kUnlinkedCallCid, UnlinkedCall); | |
| SET_CLASS_NAME(kMonomorphicSmiableCallCid, MonomorphicSmiableCall); | |
| SET_CLASS_NAME(kICDataCid, ICData); | |
| SET_CLASS_NAME(kMegamorphicCacheCid, MegamorphicCache); | |
| SET_CLASS_NAME(kSubtypeTestCacheCid, SubtypeTestCache); | |
| SET_CLASS_NAME(kLoadingUnitCid, LoadingUnit); | |
| SET_CLASS_NAME(kApiErrorCid, ApiError); | |
| SET_CLASS_NAME(kLanguageErrorCid, LanguageError); | |
| SET_CLASS_NAME(kUnhandledExceptionCid, UnhandledException); | |
| SET_CLASS_NAME(kUnwindErrorCid, UnwindError); | |
| // Set up names for classes which are also pre-allocated in the vm isolate. | |
| SET_CLASS_NAME(kArrayCid, _List); | |
| SET_CLASS_NAME(kOneByteStringCid, OneByteString); | |
| SET_CLASS_NAME(kNeverCid, Never); | |
| // Set up names for the pseudo-classes for free list elements and forwarding | |
| // corpses. Mainly this makes VM debugging easier. | |
| SET_CLASS_NAME(kFreeListElement, FreeListElement); | |
| SET_CLASS_NAME(kForwardingCorpse, ForwardingCorpse); | |
| #undef SET_CLASS_NAME | |
| #if defined(DART_PRECOMPILER) | |
| const auto& function = | |
| Function::Handle(StubCode::UnknownDartCode().function()); | |
| function.set_name(Symbols::OptimizedOut()); | |
| #endif // defined(DART_PRECOMPILER) | |
| // The type testing stubs we initialize in AbstractType objects for the | |
| // canonical type of kDynamicCid/kVoidCid need to be set in this | |
| // method, which is called after StubCode::InitOnce(). | |
| Code& code = Code::Handle(); | |
| code = TypeTestingStubGenerator::DefaultCodeForType(Roots::dynamic_type()); | |
| Roots::dynamic_type().InitializeTypeTestingStubNonAtomic(code); | |
| code = TypeTestingStubGenerator::DefaultCodeForType(Roots::void_type()); | |
| Roots::void_type().InitializeTypeTestingStubNonAtomic(code); | |
| } | |
| void Object::FinalizeReadOnlyObject(ObjectPtr object) { | |
| NoSafepointScope no_safepoint; | |
| intptr_t cid = object->GetClassIdOfHeapObject(); | |
| if (cid == kOneByteStringCid) { | |
| OneByteStringPtr str = static_cast<OneByteStringPtr>(object); | |
| if (String::GetCachedHash(str) == 0) { | |
| intptr_t hash = String::Hash(str); | |
| String::SetCachedHashIfNotSet(str, hash); | |
| } | |
| intptr_t size = OneByteString::UnroundedSize(str); | |
| ASSERT(size <= str->untag()->HeapSize()); | |
| memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(str) + size), 0, | |
| str->untag()->HeapSize() - size); | |
| } else if (cid == kTwoByteStringCid) { | |
| TwoByteStringPtr str = static_cast<TwoByteStringPtr>(object); | |
| if (String::GetCachedHash(str) == 0) { | |
| intptr_t hash = String::Hash(str); | |
| String::SetCachedHashIfNotSet(str, hash); | |
| } | |
| ASSERT(String::GetCachedHash(str) != 0); | |
| intptr_t size = TwoByteString::UnroundedSize(str); | |
| ASSERT(size <= str->untag()->HeapSize()); | |
| memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(str) + size), 0, | |
| str->untag()->HeapSize() - size); | |
| } else if (cid == kCodeSourceMapCid) { | |
| CodeSourceMapPtr map = CodeSourceMap::RawCast(object); | |
| intptr_t size = CodeSourceMap::UnroundedSize(map); | |
| ASSERT(size <= map->untag()->HeapSize()); | |
| memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(map) + size), 0, | |
| map->untag()->HeapSize() - size); | |
| } else if (cid == kCompressedStackMapsCid) { | |
| CompressedStackMapsPtr maps = CompressedStackMaps::RawCast(object); | |
| intptr_t size = CompressedStackMaps::UnroundedSize(maps); | |
| ASSERT(size <= maps->untag()->HeapSize()); | |
| memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(maps) + size), 0, | |
| maps->untag()->HeapSize() - size); | |
| } else if (cid == kPcDescriptorsCid) { | |
| PcDescriptorsPtr desc = PcDescriptors::RawCast(object); | |
| intptr_t size = PcDescriptors::UnroundedSize(desc); | |
| ASSERT(size <= desc->untag()->HeapSize()); | |
| memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(desc) + size), 0, | |
| desc->untag()->HeapSize() - size); | |
| } | |
| } | |
| // Make unused space in an object whose type has been transformed safe | |
| // for traversing during GC. | |
| // The unused part of the transformed object is marked as a FreeListElement | |
| // object that is not inserted into to the freelist. | |
| void Object::MakeUnusedSpaceTraversable(const Object& obj, | |
| intptr_t original_size, | |
| intptr_t used_size) { | |
| ASSERT(Thread::Current()->no_safepoint_scope_depth() > 0); | |
| ASSERT(!obj.IsNull()); | |
| ASSERT(original_size >= used_size); | |
| if (original_size > used_size) { | |
| intptr_t leftover_size = original_size - used_size; | |
| uword addr = UntaggedObject::ToAddr(obj.ptr()) + used_size; | |
| if (obj.ptr()->IsNewObject()) { | |
| FreeListElement::AsElementNew(addr, leftover_size); | |
| } else { | |
| FreeListElement::AsElement(addr, leftover_size); | |
| } | |
| // On architectures with a relaxed memory model, the concurrent marker may | |
| // observe the write of the filler object's header before observing the | |
| // new array length, and so treat it as a pointer. Ensure it is a Smi so | |
| // the marker won't dereference it. | |
| ASSERT((*reinterpret_cast<uword*>(addr) & kSmiTagMask) == kSmiTag); | |
| ASSERT((*reinterpret_cast<uword*>(addr + kWordSize) & kSmiTagMask) == | |
| kSmiTag); | |
| } | |
| } | |
| void Object::VerifyBuiltinVtables() { | |
| #if defined(DEBUG) | |
| ASSERT(builtin_vtables_[kIllegalCid] == 0); | |
| ASSERT(builtin_vtables_[kFreeListElement] == 0); | |
| ASSERT(builtin_vtables_[kForwardingCorpse] == 0); | |
| ClassTable* table = IsolateGroup::Current()->class_table(); | |
| for (intptr_t cid = kObjectCid; cid < kNumPredefinedCids; cid++) { | |
| if (table->HasValidClassAt(cid)) { | |
| ASSERT(builtin_vtables_[cid] != 0); | |
| } | |
| } | |
| #endif | |
| } | |
| void Object::RegisterClass(const Class& cls, | |
| const String& name, | |
| const Library& lib) { | |
| ASSERT(name.Length() > 0); | |
| ASSERT(name.CharAt(0) != '_'); | |
| cls.set_name(name); | |
| lib.AddClass(cls); | |
| } | |
| void Object::RegisterPrivateClass(const Class& cls, | |
| const String& public_class_name, | |
| const Library& lib) { | |
| ASSERT(public_class_name.Length() > 0); | |
| ASSERT(public_class_name.CharAt(0) == '_'); | |
| String& str = String::Handle(); | |
| str = lib.PrivateName(public_class_name); | |
| cls.set_name(str); | |
| lib.AddClass(cls); | |
| } | |
| class VerificationWorkSet : public StackResource { | |
| public: | |
| explicit VerificationWorkSet(Thread* thread, Zone* zone) | |
| : StackResource(thread), | |
| thread_(thread), | |
| list_(GrowableObjectArray::Handle(zone)), | |
| stack_(GrowableObjectArray::Handle(zone)) { | |
| ASSERT(thread->forward_table_new() == nullptr); | |
| set_ = new WeakTable(); | |
| list_ = GrowableObjectArray::New(16); | |
| stack_ = GrowableObjectArray::New(16); | |
| thread->set_forward_table_new(set_); | |
| } | |
| ~VerificationWorkSet() { | |
| thread_->set_forward_table_new(nullptr); | |
| set_ = nullptr; | |
| } | |
| void Add(const Object& object) { | |
| if (!IsMarked(object.ptr())) { | |
| Mark(object.ptr()); | |
| list_.Add(object); | |
| } | |
| } | |
| bool TakeAndPush(Object* object) { | |
| if (list_.Length() == 0) { | |
| return false; | |
| } | |
| *object = list_.RemoveLast(); | |
| stack_.Add(*object); // Push | |
| return true; | |
| } | |
| void PopAndProcessCompletedClosuresAndContexts(Object* object) { | |
| if (stack_.Length() > 0) { | |
| *object = stack_.RemoveLast(); // Pop | |
| // Are we done processing nested context or closures? | |
| while (stack_.Length() > 0) { | |
| *object = stack_.At(stack_.Length() - 1); | |
| if (object->IsContext()) { | |
| stack_.RemoveLast(); | |
| } else if (object->IsClosure()) { | |
| object->SetDeeplyImmutable(); | |
| stack_.RemoveLast(); | |
| } | |
| } | |
| } | |
| } | |
| intptr_t StackLength() { return stack_.Length(); } | |
| private: | |
| bool IsMarked(ObjectPtr object) { | |
| return set_->GetValueExclusive(object) != 0; | |
| } | |
| void Mark(ObjectPtr object) { set_->SetValueExclusive(object, 1); } | |
| Thread* thread_; | |
| WeakTable* set_; | |
| GrowableObjectArray& list_; | |
| // Stack of objects that are currently being processed | |
| GrowableObjectArray& stack_; | |
| }; | |
| void Object::EnsureDeeplyImmutable(Zone* zone) const { | |
| auto thread = Thread::Current(); | |
| VerificationWorkSet workset(thread, zone); | |
| workset.Add(*this); | |
| Object& current = Object::Handle(zone); | |
| Function& function = Function::Handle(zone); | |
| Object& obj = Object::Handle(zone); | |
| ObjectStore* object_store = thread->isolate_group()->object_store(); | |
| const intptr_t bigint_cid = | |
| Class::Handle(zone, object_store->bigint_impl_class()).id(); | |
| while (workset.TakeAndPush(¤t)) { | |
| if (current.IsSmi() || current.IsNull() || current.IsCanonical() || | |
| current.IsDeeplyImmutable() || | |
| IsTypedDataBaseClassId(current.GetClassId()) || | |
| current.GetClassId() == bigint_cid) { | |
| workset.PopAndProcessCompletedClosuresAndContexts(&obj); | |
| continue; | |
| } | |
| if (current.IsClosure()) { | |
| const Closure& closure = Closure::Cast(current); | |
| function = closure.function(); | |
| if (!function.IsImplicitClosureFunction() && | |
| !function.captures_only_final_not_late_vars()) { | |
| Exceptions::ThrowArgumentError(String::Handle(String::New( | |
| "Only final not-late variables can be captured here."))); | |
| UNREACHABLE(); | |
| } | |
| obj = closure.RawContext(); | |
| workset.Add(obj); | |
| continue; | |
| } | |
| if (current.IsContext()) { | |
| const Context& context = Context::Cast(current); | |
| // Iterate through all elements of the context. | |
| for (intptr_t i = 0; i < context.num_variables(); i++) { | |
| obj = context.At(i); | |
| workset.Add(obj); | |
| } | |
| continue; | |
| } | |
| if (current.GetClassId() == ConstMap::kClassId) { | |
| continue; | |
| } | |
| Exceptions::ThrowArgumentError(String::Handle( | |
| String::NewFormatted("Only trivially-immutable values are allowed: %s.", | |
| current.ToCString()))); | |
| UNREACHABLE(); | |
| } | |
| RELEASE_ASSERT(workset.StackLength() == 0); | |
| } | |
| // Initialize a new isolate from source or from a snapshot. | |
| // | |
| // There are three possibilities: | |
| // 1. Running a Kernel binary. This function will bootstrap from the KERNEL | |
| // file. | |
| // 2. There is no vm snapshot. This function will bootstrap from source. | |
| // 3. There is a vm snapshot. The caller should initialize from the snapshot. | |
| // | |
| // A non-null kernel argument indicates (1). | |
| // A nullptr kernel indicates (2) or (3). | |
| ErrorPtr Object::Init(IsolateGroup* isolate_group, | |
| const uint8_t* kernel_buffer, | |
| intptr_t kernel_buffer_size) { | |
| Thread* thread = Thread::Current(); | |
| ASSERT(isolate_group == thread->isolate_group()); | |
| TIMELINE_DURATION(thread, Isolate, "Object::Init"); | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| const bool bootstrapping = false; | |
| #else | |
| Zone* zone = thread->zone(); | |
| const bool is_kernel = (kernel_buffer != nullptr); | |
| const bool bootstrapping = is_kernel; | |
| #endif // defined(DART_PRECOMPILED_RUNTIME). | |
| if (bootstrapping) { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| // Object::Init version when we are bootstrapping from source or from a | |
| // Kernel binary. | |
| // This will initialize isolate group object_store, shared by all isolates | |
| // running in the isolate group. | |
| ObjectStore* object_store = isolate_group->object_store(); | |
| SafepointWriteRwLocker ml(thread, isolate_group->program_lock()); | |
| Class& cls = Class::Handle(zone); | |
| Type& type = Type::Handle(zone); | |
| Array& array = Array::Handle(zone); | |
| WeakArray& weak_array = WeakArray::Handle(zone); | |
| Library& lib = Library::Handle(zone); | |
| TypeArguments& type_args = TypeArguments::Handle(zone); | |
| // All RawArray fields will be initialized to an empty array, therefore | |
| // initialize array class first. | |
| cls = Class::New<Array, RTN::Array>(isolate_group); | |
| object_store->set_array_class(cls); | |
| // VM classes that are parameterized (Array, ImmutableArray, | |
| // GrowableObjectArray, Map, ConstMap, | |
| // Set, ConstSet) are also pre-finalized, so | |
| // CalculateFieldOffsets() is not called, so we need to set the offset | |
| // of their type_arguments_ field, which is explicitly | |
| // declared in their respective Raw* classes. | |
| cls.set_type_arguments_field_offset(Array::type_arguments_offset(), | |
| RTN::Array::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(1); | |
| // Set up the growable object array class (Has to be done after the array | |
| // class is setup as one of its field is an array object). | |
| cls = Class::New<GrowableObjectArray, RTN::GrowableObjectArray>( | |
| isolate_group); | |
| object_store->set_growable_object_array_class(cls); | |
| cls.set_type_arguments_field_offset( | |
| GrowableObjectArray::type_arguments_offset(), | |
| RTN::GrowableObjectArray::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(1); | |
| // Initialize hash set for regexp_table_. | |
| const intptr_t kInitialCanonicalRegExpSize = 4; | |
| weak_array = HashTables::New<CanonicalRegExpSet>( | |
| kInitialCanonicalRegExpSize, Heap::kOld); | |
| object_store->set_regexp_table(weak_array); | |
| // Initialize hash set for canonical types. | |
| const intptr_t kInitialCanonicalTypeSize = 16; | |
| array = HashTables::New<CanonicalTypeSet>(kInitialCanonicalTypeSize, | |
| Heap::kOld); | |
| object_store->set_canonical_types(array); | |
| // Initialize hash set for canonical function types. | |
| const intptr_t kInitialCanonicalFunctionTypeSize = 16; | |
| array = HashTables::New<CanonicalFunctionTypeSet>( | |
| kInitialCanonicalFunctionTypeSize, Heap::kOld); | |
| object_store->set_canonical_function_types(array); | |
| // Initialize hash set for canonical record types. | |
| const intptr_t kInitialCanonicalRecordTypeSize = 16; | |
| array = HashTables::New<CanonicalRecordTypeSet>( | |
| kInitialCanonicalRecordTypeSize, Heap::kOld); | |
| object_store->set_canonical_record_types(array); | |
| // Initialize hash set for canonical type parameters. | |
| const intptr_t kInitialCanonicalTypeParameterSize = 4; | |
| array = HashTables::New<CanonicalTypeParameterSet>( | |
| kInitialCanonicalTypeParameterSize, Heap::kOld); | |
| object_store->set_canonical_type_parameters(array); | |
| // Initialize hash set for canonical_type_arguments_. | |
| const intptr_t kInitialCanonicalTypeArgumentsSize = 4; | |
| array = HashTables::New<CanonicalTypeArgumentsSet>( | |
| kInitialCanonicalTypeArgumentsSize, Heap::kOld); | |
| object_store->set_canonical_type_arguments(array); | |
| // Setup type class early in the process. | |
| const Class& type_cls = | |
| Class::Handle(zone, Class::New<Type, RTN::Type>(isolate_group)); | |
| const Class& function_type_cls = Class::Handle( | |
| zone, Class::New<FunctionType, RTN::FunctionType>(isolate_group)); | |
| const Class& record_type_cls = Class::Handle( | |
| zone, Class::New<RecordType, RTN::RecordType>(isolate_group)); | |
| const Class& type_parameter_cls = Class::Handle( | |
| zone, Class::New<TypeParameter, RTN::TypeParameter>(isolate_group)); | |
| const Class& library_prefix_cls = Class::Handle( | |
| zone, Class::New<LibraryPrefix, RTN::LibraryPrefix>(isolate_group)); | |
| // Pre-allocate the OneByteString class needed by the symbol table. | |
| cls = Class::NewStringClass(kOneByteStringCid, isolate_group); | |
| object_store->set_one_byte_string_class(cls); | |
| // Pre-allocate the TwoByteString class needed by the symbol table. | |
| cls = Class::NewStringClass(kTwoByteStringCid, isolate_group); | |
| object_store->set_two_byte_string_class(cls); | |
| // Set up the libraries array before initializing the core library. | |
| const GrowableObjectArray& libraries = | |
| GrowableObjectArray::Handle(zone, GrowableObjectArray::New(Heap::kOld)); | |
| object_store->set_libraries(libraries); | |
| // Pre-register the core library. | |
| Library::InitCoreLibrary(isolate_group); | |
| // Basic infrastructure has been setup, initialize the class dictionary. | |
| const Library& core_lib = Library::Handle(zone, Library::CoreLibrary()); | |
| ASSERT(!core_lib.IsNull()); | |
| const GrowableObjectArray& pending_classes = | |
| GrowableObjectArray::Handle(zone, GrowableObjectArray::New()); | |
| object_store->set_pending_classes(pending_classes); | |
| // Now that the symbol table is initialized and that the core dictionary as | |
| // well as the core implementation dictionary have been setup, preallocate | |
| // remaining classes and register them by name in the dictionaries. | |
| String& name = String::Handle(zone); | |
| cls = object_store->array_class(); // Was allocated above. | |
| RegisterPrivateClass(cls, Symbols::_List(), core_lib); | |
| pending_classes.Add(cls); | |
| // We cannot use NewNonParameterizedType(), because Array is | |
| // parameterized. Warning: class _List has not been patched yet. Its | |
| // declared number of type parameters is still 0. It will become 1 after | |
| // patching. The array type allocated below represents the raw type _List | |
| // and not _List<E> as we could expect. Use with caution. | |
| type = Type::New(Class::Handle(zone, cls.ptr()), | |
| Object::null_type_arguments(), Nullability::kNonNullable); | |
| type.SetIsFinalized(); | |
| type ^= type.Canonicalize(thread); | |
| object_store->set_array_type(type); | |
| cls = object_store->growable_object_array_class(); // Was allocated above. | |
| RegisterPrivateClass(cls, Symbols::_GrowableList(), core_lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<Array, RTN::Array>(kImmutableArrayCid, isolate_group); | |
| object_store->set_immutable_array_class(cls); | |
| cls.set_type_arguments_field_offset(Array::type_arguments_offset(), | |
| RTN::Array::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(1); | |
| ASSERT(object_store->immutable_array_class() != | |
| object_store->array_class()); | |
| cls.set_is_prefinalized(); | |
| RegisterPrivateClass(cls, Symbols::_ImmutableList(), core_lib); | |
| pending_classes.Add(cls); | |
| cls = object_store->one_byte_string_class(); // Was allocated above. | |
| RegisterPrivateClass(cls, Symbols::OneByteString(), core_lib); | |
| pending_classes.Add(cls); | |
| cls = object_store->two_byte_string_class(); // Was allocated above. | |
| RegisterPrivateClass(cls, Symbols::TwoByteString(), core_lib); | |
| pending_classes.Add(cls); | |
| // Pre-register the isolate library so the native class implementations can | |
| // be hooked up before compiling it. | |
| Library& isolate_lib = Library::Handle( | |
| zone, Library::LookupLibrary(thread, Symbols::DartIsolate())); | |
| if (isolate_lib.IsNull()) { | |
| isolate_lib = Library::NewLibraryHelper(Symbols::DartIsolate(), true); | |
| isolate_lib.SetLoadRequested(); | |
| isolate_lib.Register(thread); | |
| } | |
| object_store->set_bootstrap_library(ObjectStore::kIsolate, isolate_lib); | |
| ASSERT(!isolate_lib.IsNull()); | |
| ASSERT(isolate_lib.ptr() == Library::IsolateLibrary()); | |
| cls = Class::New<Capability, RTN::Capability>(isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_Capability(), isolate_lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<ReceivePort, RTN::ReceivePort>(isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_RawReceivePort(), isolate_lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<SendPort, RTN::SendPort>(isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_SendPort(), isolate_lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<TransferableTypedData, RTN::TransferableTypedData>( | |
| isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_TransferableTypedDataImpl(), | |
| isolate_lib); | |
| pending_classes.Add(cls); | |
| const Class& stacktrace_cls = Class::Handle( | |
| zone, Class::New<StackTrace, RTN::StackTrace>(isolate_group)); | |
| RegisterPrivateClass(stacktrace_cls, Symbols::_StackTrace(), core_lib); | |
| pending_classes.Add(stacktrace_cls); | |
| // Super type set below, after Object is allocated. | |
| cls = Class::New<RegExp, RTN::RegExp>(isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_RegExp(), core_lib); | |
| pending_classes.Add(cls); | |
| // Initialize the base interfaces used by the core VM classes. | |
| // Allocate and initialize the pre-allocated classes in the core library. | |
| // The script and token index of these pre-allocated classes is set up when | |
| // the corelib script is compiled. | |
| cls = Class::New<Instance, RTN::Instance>(kInstanceCid, isolate_group); | |
| object_store->set_object_class(cls); | |
| cls.set_name(Symbols::Object()); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_prefinalized(); | |
| cls.set_is_const(); | |
| core_lib.AddClass(cls); | |
| pending_classes.Add(cls); | |
| type = Type::NewNonParameterizedType(cls); | |
| ASSERT(type.IsCanonical()); | |
| object_store->set_object_type(type); | |
| type = type.ToNullability(Nullability::kNonNullable, Heap::kOld); | |
| ASSERT(type.IsCanonical()); | |
| object_store->set_non_nullable_object_type(type); | |
| type = type.ToNullability(Nullability::kNullable, Heap::kOld); | |
| ASSERT(type.IsCanonical()); | |
| object_store->set_nullable_object_type(type); | |
| cls = Class::New<Bool, RTN::Bool>(isolate_group); | |
| object_store->set_bool_class(cls); | |
| RegisterClass(cls, Symbols::Bool(), core_lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<Instance, RTN::Instance>(kNullCid, isolate_group); | |
| object_store->set_null_class(cls); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_prefinalized(); | |
| RegisterClass(cls, Symbols::Null(), core_lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<Instance, RTN::Instance>(kNeverCid, isolate_group); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_allocate_finalized(); | |
| cls.set_is_declaration_loaded(); | |
| cls.set_is_type_finalized(); | |
| cls.set_name(Symbols::Never()); | |
| object_store->set_never_class(cls); | |
| ASSERT(!library_prefix_cls.IsNull()); | |
| RegisterPrivateClass(library_prefix_cls, Symbols::_LibraryPrefix(), | |
| core_lib); | |
| pending_classes.Add(library_prefix_cls); | |
| RegisterPrivateClass(type_cls, Symbols::_Type(), core_lib); | |
| pending_classes.Add(type_cls); | |
| RegisterPrivateClass(function_type_cls, Symbols::_FunctionType(), core_lib); | |
| pending_classes.Add(function_type_cls); | |
| RegisterPrivateClass(record_type_cls, Symbols::_RecordType(), core_lib); | |
| pending_classes.Add(record_type_cls); | |
| RegisterPrivateClass(type_parameter_cls, Symbols::_TypeParameter(), | |
| core_lib); | |
| pending_classes.Add(type_parameter_cls); | |
| cls = Class::New<Integer, RTN::Integer>(isolate_group); | |
| object_store->set_integer_implementation_class(cls); | |
| RegisterPrivateClass(cls, Symbols::_IntegerImplementation(), core_lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<Smi, RTN::Smi>(isolate_group); | |
| object_store->set_smi_class(cls); | |
| RegisterPrivateClass(cls, Symbols::_Smi(), core_lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<Mint, RTN::Mint>(isolate_group); | |
| object_store->set_mint_class(cls); | |
| RegisterPrivateClass(cls, Symbols::_Mint(), core_lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<Double, RTN::Double>(isolate_group); | |
| object_store->set_double_class(cls); | |
| RegisterPrivateClass(cls, Symbols::_Double(), core_lib); | |
| pending_classes.Add(cls); | |
| // Class that represents the Dart class _Closure and C++ class Closure. | |
| cls = Class::New<Closure, RTN::Closure>(isolate_group); | |
| object_store->set_closure_class(cls); | |
| RegisterPrivateClass(cls, Symbols::_Closure(), core_lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<Record, RTN::Record>(isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_Record(), core_lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<WeakProperty, RTN::WeakProperty>(isolate_group); | |
| object_store->set_weak_property_class(cls); | |
| RegisterPrivateClass(cls, Symbols::_WeakProperty(), core_lib); | |
| cls = Class::New<WeakReference, RTN::WeakReference>(isolate_group); | |
| cls.set_type_arguments_field_offset( | |
| WeakReference::type_arguments_offset(), | |
| RTN::WeakReference::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(1); | |
| object_store->set_weak_reference_class(cls); | |
| RegisterPrivateClass(cls, Symbols::_WeakReference(), core_lib); | |
| // Pre-register the mirrors library so we can place the vm class | |
| // MirrorReference there rather than the core library. | |
| lib = Library::LookupLibrary(thread, Symbols::DartMirrors()); | |
| if (lib.IsNull()) { | |
| lib = Library::NewLibraryHelper(Symbols::DartMirrors(), true); | |
| lib.SetLoadRequested(); | |
| lib.Register(thread); | |
| } | |
| object_store->set_bootstrap_library(ObjectStore::kMirrors, lib); | |
| ASSERT(!lib.IsNull()); | |
| ASSERT(lib.ptr() == Library::MirrorsLibrary()); | |
| cls = Class::New<MirrorReference, RTN::MirrorReference>(isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_MirrorReference(), lib); | |
| // Pre-register dart:_compact_hash library so that we could place | |
| // collection classes (_Map, _ConstMap, _Set, _ConstSet) here. | |
| lib = Library::LookupLibrary(thread, Symbols::DartCompactHash()); | |
| if (lib.IsNull()) { | |
| lib = Library::NewLibraryHelper(Symbols::DartCompactHash(), true); | |
| lib.SetLoadRequested(); | |
| lib.Register(thread); | |
| } | |
| object_store->set_bootstrap_library(ObjectStore::kCompactHash, lib); | |
| ASSERT(!lib.IsNull()); | |
| ASSERT(lib.ptr() == Library::CompactHashLibrary()); | |
| cls = Class::New<LinkedHashBase, RTN::LinkedHashBase>( | |
| kLinkedHashBaseCid, isolate_group, /*register_class=*/true, | |
| /*is_abstract=*/true); | |
| cls.set_type_arguments_field_offset( | |
| LinkedHashBase::type_arguments_offset(), | |
| RTN::LinkedHashBase::type_arguments_offset()); | |
| cls.set_is_prefinalized(); | |
| isolate_group->class_table()->Register(cls); | |
| RegisterPrivateClass(cls, Symbols::_LinkedHashBase(), lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<Map, RTN::Map>(isolate_group); | |
| object_store->set_map_impl_class(cls); | |
| cls.set_type_arguments_field_offset(Map::type_arguments_offset(), | |
| RTN::Map::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(2); | |
| RegisterPrivateClass(cls, Symbols::_Map(), lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<Map, RTN::Map>(kConstMapCid, isolate_group); | |
| object_store->set_const_map_impl_class(cls); | |
| cls.set_type_arguments_field_offset(Map::type_arguments_offset(), | |
| RTN::Map::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(2); | |
| cls.set_is_prefinalized(); | |
| RegisterPrivateClass(cls, Symbols::_ConstMap(), lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<Set, RTN::Set>(isolate_group); | |
| object_store->set_set_impl_class(cls); | |
| cls.set_type_arguments_field_offset(Set::type_arguments_offset(), | |
| RTN::Set::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(1); | |
| RegisterPrivateClass(cls, Symbols::_Set(), lib); | |
| pending_classes.Add(cls); | |
| cls = Class::New<Set, RTN::Set>(kConstSetCid, isolate_group); | |
| object_store->set_const_set_impl_class(cls); | |
| cls.set_type_arguments_field_offset(Set::type_arguments_offset(), | |
| RTN::Set::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(1); | |
| cls.set_is_prefinalized(); | |
| RegisterPrivateClass(cls, Symbols::_ConstSet(), lib); | |
| pending_classes.Add(cls); | |
| // Pre-register the collection library. | |
| lib = Library::LookupLibrary(thread, Symbols::DartCollection()); | |
| if (lib.IsNull()) { | |
| lib = Library::NewLibraryHelper(Symbols::DartCollection(), true); | |
| lib.SetLoadRequested(); | |
| lib.Register(thread); | |
| } | |
| object_store->set_bootstrap_library(ObjectStore::kCollection, lib); | |
| // Pre-register the async library so we can place the vm class | |
| // FutureOr there rather than the core library. | |
| lib = Library::LookupLibrary(thread, Symbols::DartAsync()); | |
| if (lib.IsNull()) { | |
| lib = Library::NewLibraryHelper(Symbols::DartAsync(), true); | |
| lib.SetLoadRequested(); | |
| lib.Register(thread); | |
| } | |
| object_store->set_bootstrap_library(ObjectStore::kAsync, lib); | |
| ASSERT(!lib.IsNull()); | |
| ASSERT(lib.ptr() == Library::AsyncLibrary()); | |
| cls = Class::New<FutureOr, RTN::FutureOr>(isolate_group); | |
| cls.set_type_arguments_field_offset(FutureOr::type_arguments_offset(), | |
| RTN::FutureOr::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(1); | |
| RegisterClass(cls, Symbols::FutureOr(), lib); | |
| pending_classes.Add(cls); | |
| object_store->set_future_or_class(cls); | |
| cls = Class::New<SuspendState, RTN::SuspendState>(isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_SuspendState(), lib); | |
| pending_classes.Add(cls); | |
| // Pre-register the developer library so we can place the vm class | |
| // UserTag there rather than the core library. | |
| lib = Library::LookupLibrary(thread, Symbols::DartDeveloper()); | |
| if (lib.IsNull()) { | |
| lib = Library::NewLibraryHelper(Symbols::DartDeveloper(), true); | |
| lib.SetLoadRequested(); | |
| lib.Register(thread); | |
| } | |
| object_store->set_bootstrap_library(ObjectStore::kDeveloper, lib); | |
| ASSERT(!lib.IsNull()); | |
| ASSERT(lib.ptr() == Library::DeveloperLibrary()); | |
| cls = Class::New<UserTag, RTN::UserTag>(isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_UserTag(), lib); | |
| pending_classes.Add(cls); | |
| // Setup some default native field classes which can be extended for | |
| // specifying native fields in dart classes. | |
| Library::InitNativeWrappersLibrary(isolate_group, is_kernel); | |
| ASSERT(object_store->native_wrappers_library() != Library::null()); | |
| // Pre-register the typed_data library so the native class implementations | |
| // can be hooked up before compiling it. | |
| lib = Library::LookupLibrary(thread, Symbols::DartTypedData()); | |
| if (lib.IsNull()) { | |
| lib = Library::NewLibraryHelper(Symbols::DartTypedData(), true); | |
| lib.SetLoadRequested(); | |
| lib.Register(thread); | |
| } | |
| object_store->set_bootstrap_library(ObjectStore::kTypedData, lib); | |
| ASSERT(!lib.IsNull()); | |
| ASSERT(lib.ptr() == Library::TypedDataLibrary()); | |
| #define REGISTER_TYPED_DATA_CLASS(clazz) \ | |
| cls = Class::NewTypedDataClass(kTypedData##clazz##ArrayCid, isolate_group); \ | |
| RegisterPrivateClass(cls, Symbols::_##clazz##List(), lib); | |
| DART_CLASS_LIST_TYPED_DATA(REGISTER_TYPED_DATA_CLASS); | |
| #undef REGISTER_TYPED_DATA_CLASS | |
| #define REGISTER_TYPED_DATA_VIEW_CLASS(clazz) \ | |
| cls = \ | |
| Class::NewTypedDataViewClass(kTypedData##clazz##ViewCid, isolate_group); \ | |
| RegisterPrivateClass(cls, Symbols::_##clazz##View(), lib); \ | |
| pending_classes.Add(cls); \ | |
| cls = Class::NewUnmodifiableTypedDataViewClass( \ | |
| kUnmodifiableTypedData##clazz##ViewCid, isolate_group); \ | |
| RegisterPrivateClass(cls, Symbols::_Unmodifiable##clazz##View(), lib); \ | |
| pending_classes.Add(cls); | |
| CLASS_LIST_TYPED_DATA(REGISTER_TYPED_DATA_VIEW_CLASS); | |
| cls = Class::NewTypedDataViewClass(kByteDataViewCid, isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_ByteDataView(), lib); | |
| pending_classes.Add(cls); | |
| cls = Class::NewUnmodifiableTypedDataViewClass(kUnmodifiableByteDataViewCid, | |
| isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_UnmodifiableByteDataView(), lib); | |
| pending_classes.Add(cls); | |
| #undef REGISTER_TYPED_DATA_VIEW_CLASS | |
| #define REGISTER_EXT_TYPED_DATA_CLASS(clazz) \ | |
| cls = Class::NewExternalTypedDataClass(kExternalTypedData##clazz##Cid, \ | |
| isolate_group); \ | |
| RegisterPrivateClass(cls, Symbols::_External##clazz(), lib); | |
| cls = Class::New<Instance, RTN::Instance>(kByteBufferCid, isolate_group, | |
| /*register_class=*/false); | |
| cls.set_instance_size(0, 0); | |
| cls.set_next_field_offset(-kWordSize, -compiler::target::kWordSize); | |
| isolate_group->class_table()->Register(cls); | |
| RegisterPrivateClass(cls, Symbols::_ByteBuffer(), lib); | |
| pending_classes.Add(cls); | |
| CLASS_LIST_TYPED_DATA(REGISTER_EXT_TYPED_DATA_CLASS); | |
| #undef REGISTER_EXT_TYPED_DATA_CLASS | |
| // Register Float32x4, Int32x4, and Float64x2 in the object store. | |
| cls = Class::New<Float32x4, RTN::Float32x4>(isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_Float32x4(), lib); | |
| pending_classes.Add(cls); | |
| object_store->set_float32x4_class(cls); | |
| cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group, | |
| /*register_class=*/true, | |
| /*is_abstract=*/true); | |
| RegisterClass(cls, Symbols::Float32x4(), lib); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_prefinalized(); | |
| type = Type::NewNonParameterizedType(cls); | |
| object_store->set_float32x4_type(type); | |
| cls = Class::New<Int32x4, RTN::Int32x4>(isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_Int32x4(), lib); | |
| pending_classes.Add(cls); | |
| object_store->set_int32x4_class(cls); | |
| cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group, | |
| /*register_class=*/true, | |
| /*is_abstract=*/true); | |
| RegisterClass(cls, Symbols::Int32x4(), lib); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_prefinalized(); | |
| type = Type::NewNonParameterizedType(cls); | |
| object_store->set_int32x4_type(type); | |
| cls = Class::New<Float64x2, RTN::Float64x2>(isolate_group); | |
| RegisterPrivateClass(cls, Symbols::_Float64x2(), lib); | |
| pending_classes.Add(cls); | |
| object_store->set_float64x2_class(cls); | |
| cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group, | |
| /*register_class=*/true, | |
| /*is_abstract=*/true); | |
| RegisterClass(cls, Symbols::Float64x2(), lib); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_prefinalized(); | |
| type = Type::NewNonParameterizedType(cls); | |
| object_store->set_float64x2_type(type); | |
| // Set the super type of class StackTrace to Object type so that the | |
| // 'toString' method is implemented. | |
| type = object_store->object_type(); | |
| stacktrace_cls.set_super_type(type); | |
| // Abstract class that represents the Dart class Type. | |
| // Note that this class is implemented by Dart class _AbstractType. | |
| cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group, | |
| /*register_class=*/true, | |
| /*is_abstract=*/true); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_prefinalized(); | |
| RegisterClass(cls, Symbols::Type(), core_lib); | |
| pending_classes.Add(cls); | |
| type = Type::NewNonParameterizedType(cls); | |
| object_store->set_type_type(type); | |
| // Abstract class that represents the Dart class Function. | |
| cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group, | |
| /*register_class=*/true, | |
| /*is_abstract=*/true); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_prefinalized(); | |
| RegisterClass(cls, Symbols::Function(), core_lib); | |
| pending_classes.Add(cls); | |
| type = Type::NewNonParameterizedType(cls); | |
| object_store->set_function_type(type); | |
| // Abstract class that represents the Dart class Record. | |
| cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group, | |
| /*register_class=*/true, | |
| /*is_abstract=*/true); | |
| RegisterClass(cls, Symbols::Record(), core_lib); | |
| pending_classes.Add(cls); | |
| object_store->set_record_class(cls); | |
| cls = Class::New<Number, RTN::Number>(isolate_group); | |
| RegisterClass(cls, Symbols::Number(), core_lib); | |
| pending_classes.Add(cls); | |
| type = Type::NewNonParameterizedType(cls); | |
| object_store->set_number_type(type); | |
| type = type.ToNullability(Nullability::kNullable, Heap::kOld); | |
| object_store->set_nullable_number_type(type); | |
| cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group, | |
| /*register_class=*/true, | |
| /*is_abstract=*/true); | |
| RegisterClass(cls, Symbols::Int(), core_lib); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_prefinalized(); | |
| pending_classes.Add(cls); | |
| type = Type::NewNonParameterizedType(cls); | |
| object_store->set_int_type(type); | |
| type = type.ToNullability(Nullability::kNonNullable, Heap::kOld); | |
| object_store->set_non_nullable_int_type(type); | |
| type = type.ToNullability(Nullability::kNullable, Heap::kOld); | |
| object_store->set_nullable_int_type(type); | |
| cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group, | |
| /*register_class=*/true, | |
| /*is_abstract=*/true); | |
| RegisterClass(cls, Symbols::Double(), core_lib); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_prefinalized(); | |
| pending_classes.Add(cls); | |
| type = Type::NewNonParameterizedType(cls); | |
| object_store->set_double_type(type); | |
| type = type.ToNullability(Nullability::kNullable, Heap::kOld); | |
| object_store->set_nullable_double_type(type); | |
| name = Symbols::_String().ptr(); | |
| cls = Class::New<Instance, RTN::Instance>(kIllegalCid, isolate_group, | |
| /*register_class=*/true, | |
| /*is_abstract=*/true); | |
| RegisterClass(cls, name, core_lib); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_prefinalized(); | |
| pending_classes.Add(cls); | |
| type = Type::NewNonParameterizedType(cls); | |
| object_store->set_string_type(type); | |
| cls = object_store->bool_class(); | |
| type = Type::NewNonParameterizedType(cls); | |
| object_store->set_bool_type(type); | |
| cls = object_store->smi_class(); | |
| type = Type::NewNonParameterizedType(cls); | |
| object_store->set_smi_type(type); | |
| cls = object_store->mint_class(); | |
| type = Type::NewNonParameterizedType(cls); | |
| object_store->set_mint_type(type); | |
| // The classes 'void' and 'dynamic' are phony classes to make type checking | |
| // more regular; they live in the VM isolate. The class 'void' is not | |
| // registered in the class dictionary because its name is a reserved word. | |
| // The class 'dynamic' is registered in the class dictionary because its | |
| // name is a built-in identifier (this is wrong). The corresponding types | |
| // are stored in the object store. | |
| cls = object_store->null_class(); | |
| type = | |
| Type::New(cls, Object::null_type_arguments(), Nullability::kNullable); | |
| type.SetIsFinalized(); | |
| type ^= type.Canonicalize(thread); | |
| object_store->set_null_type(type); | |
| cls.set_declaration_type(type); | |
| ASSERT(type.IsNullable()); | |
| // Consider removing when/if Null becomes an ordinary class. | |
| type = object_store->object_type(); | |
| cls.set_super_type(type); | |
| cls = object_store->never_class(); | |
| type = Type::New(cls, Object::null_type_arguments(), | |
| Nullability::kNonNullable); | |
| type.SetIsFinalized(); | |
| type ^= type.Canonicalize(thread); | |
| object_store->set_never_type(type); | |
| type_args = TypeArguments::New(1); | |
| type_args.SetTypeAt(0, type); | |
| type_args = type_args.Canonicalize(thread); | |
| object_store->set_type_argument_never(type_args); | |
| // Create and cache commonly used type arguments <int>, <double>, | |
| // <String>, <String, dynamic> and <String, String>. | |
| type_args = TypeArguments::New(1); | |
| type = object_store->int_type(); | |
| type_args.SetTypeAt(0, type); | |
| type_args = type_args.Canonicalize(thread); | |
| object_store->set_type_argument_int(type_args); | |
| type_args = TypeArguments::New(1); | |
| type = object_store->double_type(); | |
| type_args.SetTypeAt(0, type); | |
| type_args = type_args.Canonicalize(thread); | |
| object_store->set_type_argument_double(type_args); | |
| type_args = TypeArguments::New(1); | |
| type = object_store->string_type(); | |
| type_args.SetTypeAt(0, type); | |
| type_args = type_args.Canonicalize(thread); | |
| object_store->set_type_argument_string(type_args); | |
| type_args = TypeArguments::New(2); | |
| type = object_store->string_type(); | |
| type_args.SetTypeAt(0, type); | |
| type_args.SetTypeAt(1, Object::dynamic_type()); | |
| type_args = type_args.Canonicalize(thread); | |
| object_store->set_type_argument_string_dynamic(type_args); | |
| type_args = TypeArguments::New(2); | |
| type = object_store->string_type(); | |
| type_args.SetTypeAt(0, type); | |
| type_args.SetTypeAt(1, type); | |
| type_args = type_args.Canonicalize(thread); | |
| object_store->set_type_argument_string_string(type_args); | |
| lib = Library::LookupLibrary(thread, Symbols::DartFfi()); | |
| if (lib.IsNull()) { | |
| lib = Library::NewLibraryHelper(Symbols::DartFfi(), true); | |
| lib.SetLoadRequested(); | |
| lib.Register(thread); | |
| } | |
| object_store->set_bootstrap_library(ObjectStore::kFfi, lib); | |
| cls = Class::New<Instance, RTN::Instance>(kFfiNativeTypeCid, isolate_group); | |
| cls.set_num_type_arguments_unsafe(0); | |
| cls.set_is_prefinalized(); | |
| pending_classes.Add(cls); | |
| object_store->set_ffi_native_type_class(cls); | |
| RegisterClass(cls, Symbols::FfiNativeType(), lib); | |
| #define REGISTER_FFI_TYPE_MARKER(clazz) \ | |
| cls = Class::New<Instance, RTN::Instance>(kFfi##clazz##Cid, isolate_group); \ | |
| cls.set_num_type_arguments_unsafe(0); \ | |
| cls.set_is_prefinalized(); \ | |
| pending_classes.Add(cls); \ | |
| RegisterClass(cls, Symbols::Ffi##clazz(), lib); | |
| CLASS_LIST_FFI_TYPE_MARKER(REGISTER_FFI_TYPE_MARKER); | |
| #undef REGISTER_FFI_TYPE_MARKER | |
| cls = Class::New<Instance, RTN::Instance>(kFfiNativeFunctionCid, | |
| isolate_group); | |
| cls.set_type_arguments_field_offset(Instance::NextFieldOffset(), | |
| RTN::Instance::NextFieldOffset()); | |
| cls.set_num_type_arguments_unsafe(1); | |
| cls.set_is_prefinalized(); | |
| pending_classes.Add(cls); | |
| RegisterClass(cls, Symbols::FfiNativeFunction(), lib); | |
| cls = Class::NewPointerClass(kPointerCid, isolate_group); | |
| object_store->set_ffi_pointer_class(cls); | |
| pending_classes.Add(cls); | |
| RegisterClass(cls, Symbols::FfiPointer(), lib); | |
| cls = Class::New<DynamicLibrary, RTN::DynamicLibrary>(kDynamicLibraryCid, | |
| isolate_group); | |
| cls.set_instance_size(DynamicLibrary::InstanceSize(), | |
| compiler::target::RoundedAllocationSize( | |
| RTN::DynamicLibrary::InstanceSize())); | |
| cls.set_is_prefinalized(); | |
| pending_classes.Add(cls); | |
| RegisterClass(cls, Symbols::FfiDynamicLibrary(), lib); | |
| cls = Class::New<NativeFinalizer, RTN::NativeFinalizer>(isolate_group); | |
| object_store->set_native_finalizer_class(cls); | |
| RegisterPrivateClass(cls, Symbols::_NativeFinalizer(), lib); | |
| cls = Class::New<Finalizer, RTN::Finalizer>(isolate_group); | |
| cls.set_type_arguments_field_offset( | |
| Finalizer::type_arguments_offset(), | |
| RTN::Finalizer::type_arguments_offset()); | |
| cls.set_num_type_arguments_unsafe(1); | |
| object_store->set_finalizer_class(cls); | |
| pending_classes.Add(cls); | |
| RegisterPrivateClass(cls, Symbols::_FinalizerImpl(), core_lib); | |
| // Pre-register the internal library so we can place the vm class | |
| // FinalizerEntry there rather than the core library. | |
| lib = Library::LookupLibrary(thread, Symbols::DartInternal()); | |
| if (lib.IsNull()) { | |
| lib = Library::NewLibraryHelper(Symbols::DartInternal(), true); | |
| lib.SetLoadRequested(); | |
| lib.Register(thread); | |
| } | |
| object_store->set_bootstrap_library(ObjectStore::kInternal, lib); | |
| ASSERT(!lib.IsNull()); | |
| ASSERT(lib.ptr() == Library::InternalLibrary()); | |
| cls = Class::New<FinalizerEntry, RTN::FinalizerEntry>(isolate_group); | |
| object_store->set_finalizer_entry_class(cls); | |
| pending_classes.Add(cls); | |
| RegisterClass(cls, Symbols::FinalizerEntry(), lib); | |
| lib = Library::LookupLibrary(thread, Symbols::DartVM()); | |
| if (lib.IsNull()) { | |
| lib = Library::NewLibraryHelper(Symbols::DartVM(), true); | |
| lib.SetLoadRequested(); | |
| lib.Register(thread); | |
| } | |
| object_store->set_bootstrap_library(ObjectStore::kVM, lib); | |
| // Finish the initialization by compiling the bootstrap scripts containing | |
| // the base interfaces and the implementation of the internal classes. | |
| const Error& error = Error::Handle( | |
| zone, Bootstrap::DoBootstrapping(kernel_buffer, kernel_buffer_size)); | |
| if (!error.IsNull()) { | |
| return error.ptr(); | |
| } | |
| isolate_group->class_table()->CopySizesFromClassObjects(); | |
| ClassFinalizer::VerifyBootstrapClasses(); | |
| // Adds static const fields (class ids) to the class 'ClassID'); | |
| lib = Library::LookupLibrary(thread, Symbols::DartInternal()); | |
| ASSERT(!lib.IsNull()); | |
| cls = lib.LookupClassAllowPrivate(Symbols::ClassID()); | |
| ASSERT(!cls.IsNull()); | |
| const bool injected = cls.InjectCIDFields(); | |
| ASSERT(injected); | |
| // Set up recognized state of all functions (core, math and typed data). | |
| MethodRecognizer::InitializeState(); | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| return Error::null(); | |
| } | |
| void Object::Print() const { | |
| THR_Print("%s\n", ToCString()); | |
| } | |
| StringPtr Object::DictionaryName() const { | |
| return String::null(); | |
| } | |
| bool Object::ShouldHaveShallowImmutabilityBitSet(classid_t class_id) { | |
| if (class_id < kNumPredefinedCids) { | |
| return IsShallowlyImmutableCid(class_id); | |
| } | |
| return false; | |
| } | |
| bool Object::ShouldHaveDeeplyImmutabilityBitSet(classid_t class_id) { | |
| if (class_id < kNumPredefinedCids) { | |
| return IsDeeplyImmutableCid(class_id); | |
| } else { | |
| return Class::IsDeeplyImmutable( | |
| IsolateGroup::Current()->class_table()->At(class_id)); | |
| } | |
| } | |
| void Object::InitializeObject(uword address, | |
| intptr_t class_id, | |
| intptr_t size, | |
| bool compressed, | |
| uword ptr_field_start_offset, | |
| uword ptr_field_end_offset) { | |
| // Note: we skip the header word here to avoid a racy read in the concurrent | |
| // marker from observing the null object when it reads into a heap page | |
| // allocated after marking started. | |
| uword cur = address + sizeof(UntaggedObject); | |
| uword ptr_field_start = address + ptr_field_start_offset; | |
| uword ptr_field_end = address + ptr_field_end_offset; | |
| uword end = address + size; | |
| // The start of pointer fields should always be past the object header, even | |
| // if there are no pointer fields (ptr_field_end < ptr_field_start). | |
| ASSERT(cur <= ptr_field_start); | |
| // The start of pointer fields can be at the end for empty payload objects. | |
| ASSERT(ptr_field_start <= end); | |
| // The end of pointer fields should always be before the end, as the end of | |
| // pointer fields is inclusive (the address of the last field to initialize). | |
| ASSERT(ptr_field_end < end); | |
| bool needs_init = true; | |
| if (IsTypedDataBaseClassId(class_id) || class_id == kArrayCid) { | |
| // If the size is greater than both Heap::kNewAllocatableSize and | |
| // kAllocatablePageSize, the object must have been allocated to a new | |
| // large page, which must already have been zero initialized by the OS. | |
| // Note that zero is a GC-safe value. | |
| // | |
| // For arrays, the caller will then initialize the fields to null with | |
| // safepoint checks to avoid blocking for the full duration of | |
| // initializing this array. | |
| needs_init = | |
| Heap::IsAllocatableInNewSpace(size) || IsAllocatableViaFreeLists(size); | |
| } | |
| if (needs_init) { | |
| // Initialize the memory prior to any pointer fields with 0. (This loop | |
| // and the next will be a no-op if the object has no pointer fields.) | |
| uword initial_value = 0; | |
| while (cur < ptr_field_start) { | |
| *reinterpret_cast<uword*>(cur) = initial_value; | |
| cur += kWordSize; | |
| } | |
| // Initialize any pointer fields with Object::null(). | |
| initial_value = static_cast<uword>(Roots::null_obj()); | |
| #if defined(DART_COMPRESSED_POINTERS) | |
| if (compressed) { | |
| initial_value &= 0xFFFFFFFF; | |
| initial_value |= initial_value << 32; | |
| } | |
| const bool has_pointer_fields = ptr_field_start <= ptr_field_end; | |
| // If there are compressed pointer fields and the first compressed pointer | |
| // field is not at a word start, then initialize it to Object::null(). | |
| if (compressed && has_pointer_fields && | |
| (ptr_field_start % kWordSize != 0)) { | |
| *reinterpret_cast<compressed_uword*>(ptr_field_start) = initial_value; | |
| } | |
| #endif | |
| while (cur <= ptr_field_end) { | |
| *reinterpret_cast<uword*>(cur) = initial_value; | |
| cur += kWordSize; | |
| } | |
| // Initialize the memory after any pointer fields with 0, unless this is | |
| // an instructions object in which case we use the break instruction. | |
| initial_value = class_id == kInstructionsCid ? kBreakInstructionFiller : 0; | |
| #if defined(DART_COMPRESSED_POINTERS) | |
| // If there are compressed pointer fields and the last compressed pointer | |
| // field is the start of a word, then initialize the other part of the word | |
| // to the new initial value. | |
| // | |
| // (We're guaranteed there's always space in the object after the last | |
| // pointer field in this case since objects are allocated in multiples of | |
| // the word size.) | |
| if (compressed && has_pointer_fields && (ptr_field_end % kWordSize == 0)) { | |
| *reinterpret_cast<compressed_uword*>(ptr_field_end + | |
| kCompressedWordSize) = initial_value; | |
| } | |
| #endif | |
| while (cur < end) { | |
| *reinterpret_cast<uword*>(cur) = initial_value; | |
| cur += kWordSize; | |
| } | |
| } else { | |
| // Check that MemorySanitizer understands this is initialized. | |
| MSAN_CHECK_INITIALIZED(reinterpret_cast<void*>(address), size); | |
| #if defined(DEBUG) | |
| const uword initial_value = 0; | |
| while (cur < end) { | |
| ASSERT_EQUAL(*reinterpret_cast<uword*>(cur), initial_value); | |
| cur += kWordSize; | |
| } | |
| #endif | |
| } | |
| InitializeHeader(address, class_id, size); | |
| #if defined(HOST_HAS_FAST_WRITE_WRITE_FENCE) | |
| StoreStoreFence(); | |
| #endif | |
| } | |
| void Object::CheckHandle() const { | |
| #if defined(DEBUG) | |
| if (ptr_ != Object::null()) { | |
| intptr_t cid = ptr_->GetClassId(); | |
| if (cid >= kNumPredefinedCids) { | |
| cid = kInstanceCid; | |
| } | |
| ASSERT(vtable() == builtin_vtables_[cid]); | |
| } | |
| #endif | |
| } | |
| ObjectPtr Object::Allocate(intptr_t cls_id, | |
| intptr_t size, | |
| Heap::Space space, | |
| bool compressed, | |
| uword ptr_field_start_offset, | |
| uword ptr_field_end_offset) { | |
| ASSERT(Utils::IsAligned(size, kObjectAlignment)); | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->execution_state() == Thread::kThreadInVM); | |
| ASSERT(thread->no_safepoint_scope_depth() == 0); | |
| ASSERT(thread->no_callback_scope_depth() == 0); | |
| Heap* heap = thread->heap(); | |
| uword address = heap->Allocate(thread, size, space); | |
| if (UNLIKELY(address == 0)) { | |
| // SuspendLongJumpScope during Dart entry ensures that if a longjmp base is | |
| // available, it is the innermost error handler, so check for a longjmp base | |
| // before checking for an exit frame. | |
| if (thread->long_jump_base() != nullptr) { | |
| Report::LongJump(Object::out_of_memory_error()); | |
| UNREACHABLE(); | |
| } else if (thread->top_exit_frame_info() != 0) { | |
| Exceptions::ThrowOOM(); | |
| UNREACHABLE(); | |
| } else { | |
| // Nowhere to propagate an exception to. | |
| OUT_OF_MEMORY(); | |
| } | |
| } | |
| ObjectPtr raw_obj; | |
| NoSafepointScope no_safepoint(thread); | |
| InitializeObject(address, cls_id, size, compressed, ptr_field_start_offset, | |
| ptr_field_end_offset); | |
| raw_obj = static_cast<ObjectPtr>(address + kHeapObjectTag); | |
| ASSERT(cls_id == UntaggedObject::ClassIdTag::decode(raw_obj->untag()->tags_)); | |
| if (raw_obj->IsOldObject() && UNLIKELY(thread->is_marking())) { | |
| // Black allocation. Prevents a data race between the mutator and | |
| // concurrent marker on ARM and ARM64 (the marker may observe a | |
| // publishing store of this object before the stores that initialize its | |
| // slots), and helps the collection to finish sooner. | |
| // release: Setting the mark bit must not be ordered after a publishing | |
| // store of this object. Compare Scavenger::ScavengePointer. | |
| raw_obj->untag()->SetMarkBitRelease(); | |
| heap->old_space()->AllocateBlack(size); | |
| } | |
| #if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER) | |
| HeapProfileSampler& heap_sampler = thread->heap_sampler(); | |
| if (heap_sampler.HasOutstandingSample()) { | |
| thread->IncrementNoCallbackScopeDepth(); | |
| void* data = heap_sampler.InvokeCallbackForLastSample(cls_id); | |
| heap->SetHeapSamplingData(raw_obj, data); | |
| thread->DecrementNoCallbackScopeDepth(); | |
| } | |
| #endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER) | |
| #if !defined(PRODUCT) | |
| auto class_table = thread->isolate_group()->class_table(); | |
| if (class_table->ShouldTraceAllocationFor(cls_id)) { | |
| uint32_t hash = | |
| HeapSnapshotWriter::GetHeapSnapshotIdentityHash(thread, raw_obj); | |
| Profiler::SampleAllocation(thread, cls_id, hash); | |
| } | |
| #endif // !defined(PRODUCT) | |
| return raw_obj; | |
| } | |
| class WriteBarrierUpdateVisitor : public ObjectPointerVisitor { | |
| public: | |
| explicit WriteBarrierUpdateVisitor(Thread* thread, ObjectPtr obj) | |
| : ObjectPointerVisitor(thread->isolate_group()), | |
| thread_(thread), | |
| old_obj_(obj) { | |
| ASSERT(old_obj_->IsOldObject()); | |
| } | |
| void VisitPointers(ObjectPtr* from, ObjectPtr* to) override { | |
| if (old_obj_->IsArray()) { | |
| for (ObjectPtr* slot = from; slot <= to; ++slot) { | |
| ObjectPtr value = *slot; | |
| if (value->IsHeapObject()) { | |
| old_obj_->untag()->CheckArrayPointerStore(slot, value, thread_); | |
| } | |
| } | |
| } else { | |
| for (ObjectPtr* slot = from; slot <= to; ++slot) { | |
| ObjectPtr value = *slot; | |
| if (value->IsHeapObject()) { | |
| old_obj_->untag()->CheckHeapPointerStore(value, thread_); | |
| } | |
| } | |
| } | |
| } | |
| #if defined(DART_COMPRESSED_POINTERS) | |
| void VisitCompressedPointers(uword heap_base, | |
| CompressedObjectPtr* from, | |
| CompressedObjectPtr* to) override { | |
| if (old_obj_->IsArray()) { | |
| for (CompressedObjectPtr* slot = from; slot <= to; ++slot) { | |
| ObjectPtr value = slot->Decompress(heap_base); | |
| if (value->IsHeapObject()) { | |
| old_obj_->untag()->CheckArrayPointerStore(slot, value, thread_); | |
| } | |
| } | |
| } else { | |
| for (CompressedObjectPtr* slot = from; slot <= to; ++slot) { | |
| ObjectPtr value = slot->Decompress(heap_base); | |
| if (value->IsHeapObject()) { | |
| old_obj_->untag()->CheckHeapPointerStore(value, thread_); | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| private: | |
| Thread* thread_; | |
| ObjectPtr old_obj_; | |
| DISALLOW_COPY_AND_ASSIGN(WriteBarrierUpdateVisitor); | |
| }; | |
| #if defined(DEBUG) | |
| bool Object::IsZoneHandle() const { | |
| return VMHandles::IsZoneHandle(reinterpret_cast<uword>(this)); | |
| } | |
| bool Object::IsReadOnlyHandle() const { | |
| return Roots::IsReadOnlyHandle(reinterpret_cast<uword>(this)); | |
| } | |
| bool Object::IsNotTemporaryScopedHandle() const { | |
| return (IsZoneHandle() || IsReadOnlyHandle()); | |
| } | |
| #endif | |
| ObjectPtr Object::Clone(const Object& orig, | |
| Heap::Space space, | |
| bool load_with_relaxed_atomics) { | |
| ASSERT(orig.ptr()->IsHeapObject()); | |
| // Generic function types should be cloned with FunctionType::Clone. | |
| ASSERT(!orig.IsFunctionType() || !FunctionType::Cast(orig).IsGeneric()); | |
| const Class& cls = Class::Handle(orig.clazz()); | |
| intptr_t size = orig.ptr()->untag()->HeapSize(); | |
| // All fields (including non-SmiPtr fields) will be initialized with Smi 0, | |
| // but the contents of the original object are copied over before the thread | |
| // is allowed to reach a safepoint. | |
| ObjectPtr raw_clone = | |
| Object::Allocate(cls.id(), size, space, cls.HasCompressedPointers(), | |
| from_offset<Object>(), to_offset<Object>()); | |
| NoSafepointScope no_safepoint; | |
| // Copy the body of the original into the clone. | |
| uword orig_addr = UntaggedObject::ToAddr(orig.ptr()); | |
| uword clone_addr = UntaggedObject::ToAddr(raw_clone); | |
| const intptr_t kHeaderSizeInBytes = sizeof(UntaggedObject); | |
| if (load_with_relaxed_atomics) { | |
| auto orig_atomics_ptr = reinterpret_cast<std::atomic<uword>*>(orig_addr); | |
| auto clone_ptr = reinterpret_cast<uword*>(clone_addr); | |
| for (intptr_t i = kHeaderSizeInBytes / kWordSize; i < size / kWordSize; | |
| i++) { | |
| *(clone_ptr + i) = | |
| (orig_atomics_ptr + i)->load(std::memory_order_relaxed); | |
| } | |
| } else { | |
| memmove(reinterpret_cast<uint8_t*>(clone_addr + kHeaderSizeInBytes), | |
| reinterpret_cast<uint8_t*>(orig_addr + kHeaderSizeInBytes), | |
| size - kHeaderSizeInBytes); | |
| } | |
| if (IsTypedDataClassId(raw_clone->GetClassIdOfHeapObject())) { | |
| auto raw_typed_data = TypedData::RawCast(raw_clone); | |
| raw_typed_data.untag()->RecomputeDataField(); | |
| } | |
| // Add clone to store buffer, if needed. | |
| if (!raw_clone->IsOldObject()) { | |
| // No need to remember an object in new space. | |
| return raw_clone; | |
| } | |
| WriteBarrierUpdateVisitor visitor(Thread::Current(), raw_clone); | |
| raw_clone->untag()->VisitPointers(&visitor); | |
| return raw_clone; | |
| } | |
| bool Class::HasCompressedPointers() const { | |
| const intptr_t cid = id(); | |
| switch (cid) { | |
| case kByteBufferCid: | |
| return ByteBuffer::ContainsCompressedPointers(); | |
| #define HANDLE_CASE(clazz) \ | |
| case k##clazz##Cid: \ | |
| return dart::clazz::ContainsCompressedPointers(); | |
| CLASS_LIST(HANDLE_CASE) | |
| #undef HANDLE_CASE | |
| #define HANDLE_CASE(clazz) \ | |
| case kTypedData##clazz##Cid: \ | |
| return dart::TypedData::ContainsCompressedPointers(); \ | |
| case kTypedData##clazz##ViewCid: \ | |
| case kUnmodifiableTypedData##clazz##ViewCid: \ | |
| return dart::TypedDataView::ContainsCompressedPointers(); \ | |
| case kExternalTypedData##clazz##Cid: \ | |
| return dart::ExternalTypedData::ContainsCompressedPointers(); | |
| CLASS_LIST_TYPED_DATA(HANDLE_CASE) | |
| #undef HANDLE_CASE | |
| default: | |
| if (cid >= kNumPredefinedCids) { | |
| return dart::Instance::ContainsCompressedPointers(); | |
| } | |
| } | |
| FATAL("Unsupported class for compressed pointers translation: %s (id=%" Pd | |
| ", kNumPredefinedCids=%" Pd ")\n", | |
| ToCString(), cid, kNumPredefinedCids); | |
| return false; | |
| } | |
| StringPtr Class::Name() const { | |
| return untag()->name(); | |
| } | |
| StringPtr Class::ScrubbedName() const { | |
| return Symbols::New(Thread::Current(), ScrubbedNameCString()); | |
| } | |
| const char* Class::ScrubbedNameCString() const { | |
| return String::ScrubName(String::Handle(Name())); | |
| } | |
| StringPtr Class::UserVisibleName() const { | |
| #if !defined(PRODUCT) | |
| ASSERT(untag()->user_name() != String::null()); | |
| return untag()->user_name(); | |
| #endif // !defined(PRODUCT) | |
| // No caching in PRODUCT, regenerate. | |
| return Symbols::New(Thread::Current(), GenerateUserVisibleName()); | |
| } | |
| const char* Class::UserVisibleNameCString() const { | |
| #if !defined(PRODUCT) | |
| ASSERT(untag()->user_name() != String::null()); | |
| return String::Handle(untag()->user_name()).ToCString(); | |
| #endif // !defined(PRODUCT) | |
| return GenerateUserVisibleName(); // No caching in PRODUCT, regenerate. | |
| } | |
| const char* Class::NameCString(NameVisibility name_visibility) const { | |
| switch (name_visibility) { | |
| case Object::kInternalName: | |
| return String::Handle(Name()).ToCString(); | |
| case Object::kScrubbedName: | |
| return ScrubbedNameCString(); | |
| case Object::kUserVisibleName: | |
| return UserVisibleNameCString(); | |
| default: | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| } | |
| ClassPtr Class::Mixin() const { | |
| if (is_transformed_mixin_application()) { | |
| const Array& interfaces = Array::Handle(this->interfaces()); | |
| ASSERT(interfaces.Length() > 0); | |
| const Type& mixin_type = | |
| Type::Handle(Type::RawCast(interfaces.At(interfaces.Length() - 1))); | |
| return mixin_type.type_class(); | |
| } | |
| return ptr(); | |
| } | |
| TypePtr Class::RareType() const { | |
| if (!IsGeneric()) { | |
| return DeclarationType(); | |
| } | |
| ASSERT(is_declaration_loaded()); | |
| Thread* const thread = Thread::Current(); | |
| Zone* const zone = thread->zone(); | |
| const auto& inst_to_bounds = | |
| TypeArguments::Handle(zone, DefaultTypeArguments(zone)); | |
| ASSERT(inst_to_bounds.ptr() != Object::empty_type_arguments().ptr()); | |
| auto& type = Type::Handle( | |
| zone, Type::New(*this, inst_to_bounds, Nullability::kNonNullable)); | |
| type ^= ClassFinalizer::FinalizeType(type); | |
| return type.ptr(); | |
| } | |
| template <class FakeObject, class TargetFakeObject> | |
| ClassPtr Class::New(IsolateGroup* isolate_group, bool register_class) { | |
| ASSERT(isolate_group->class_table()->At(kClassCid) != Class::null()); | |
| const auto& result = Class::Handle(Object::Allocate<Class>(Heap::kOld)); | |
| Object::VerifyBuiltinVtable<FakeObject>(FakeObject::kClassId); | |
| NOT_IN_PRECOMPILED(result.set_token_pos(TokenPosition::kNoSource)); | |
| NOT_IN_PRECOMPILED(result.set_end_token_pos(TokenPosition::kNoSource)); | |
| result.set_instance_size(FakeObject::InstanceSize(), | |
| compiler::target::RoundedAllocationSize( | |
| TargetFakeObject::InstanceSize())); | |
| result.set_type_arguments_field_offset_in_words(kNoTypeArguments, | |
| RTN::Class::kNoTypeArguments); | |
| const intptr_t host_next_field_offset = FakeObject::NextFieldOffset(); | |
| const intptr_t target_next_field_offset = TargetFakeObject::NextFieldOffset(); | |
| result.set_next_field_offset(host_next_field_offset, | |
| target_next_field_offset); | |
| COMPILE_ASSERT((FakeObject::kClassId != kInstanceCid)); | |
| result.set_id(FakeObject::kClassId); | |
| NOT_IN_PRECOMPILED(result.set_implementor_cid(kIllegalCid)); | |
| result.set_num_type_arguments_unsafe(0); | |
| result.set_num_native_fields(0); | |
| result.set_state_bits(0); | |
| if (IsInternalOnlyClassId(FakeObject::kClassId) || | |
| (FakeObject::kClassId == kTypeArgumentsCid)) { | |
| // VM internal classes are done. There is no finalization needed or | |
| // possible in this case. | |
| result.set_is_declaration_loaded(); | |
| result.set_is_type_finalized(); | |
| result.set_is_allocate_finalized(); | |
| } else if (FakeObject::kClassId != kClosureCid) { | |
| // VM backed classes are almost ready: run checks and resolve class | |
| // references, but do not recompute size. | |
| result.set_is_prefinalized(); | |
| } | |
| if (FakeObject::kClassId < kNumPredefinedCids && | |
| IsDeeplyImmutableCid(FakeObject::kClassId)) { | |
| result.set_is_deeply_immutable(true); | |
| } | |
| NOT_IN_PRECOMPILED(result.set_kernel_offset(0)); | |
| result.InitEmptyFields(); | |
| if (register_class) { | |
| isolate_group->class_table()->Register(result); | |
| } | |
| return result.ptr(); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) || defined(DART_DYNAMIC_MODULES) | |
| static void ReportTooManyTypeArguments(const Class& cls) { | |
| Report::MessageF(Report::kError, Script::Handle(cls.script()), | |
| cls.token_pos(), Report::AtLocation, | |
| "too many type parameters declared in class '%s' or in its " | |
| "super classes", | |
| String::Handle(cls.Name()).ToCString()); | |
| UNREACHABLE(); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) || defined(DART_DYNAMIC_MODULES) | |
| void Class::set_num_type_arguments(intptr_t value) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| UNREACHABLE(); | |
| #else | |
| if (!Utils::IsInt(16, value)) { | |
| ReportTooManyTypeArguments(*this); | |
| } | |
| // We allow concurrent calculation of the number of type arguments. If two | |
| // threads perform this operation it doesn't matter which one wins. | |
| DEBUG_ONLY(intptr_t old_value = num_type_arguments()); | |
| DEBUG_ASSERT(old_value == kUnknownNumTypeArguments || old_value == value); | |
| StoreNonPointer<int16_t, int16_t, std::memory_order_relaxed>( | |
| &untag()->num_type_arguments_, value); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| } | |
| void Class::set_num_type_arguments_unsafe(intptr_t value) const { | |
| StoreNonPointer(&untag()->num_type_arguments_, value); | |
| } | |
| void Class::set_has_pragma(bool value) const { | |
| set_state_bits(HasPragmaBit::update(value, state_bits())); | |
| } | |
| void Class::set_is_isolate_unsendable(bool value) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(IsIsolateUnsendableBit::update(value, state_bits())); | |
| } | |
| void Class::set_is_isolate_unsendable_due_to_pragma(bool value) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits( | |
| IsIsolateUnsendableDueToPragmaBit::update(value, state_bits())); | |
| } | |
| void Class::set_is_deeply_immutable(bool value) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(IsDeeplyImmutableBit::update(value, state_bits())); | |
| } | |
| void Class::set_is_future_subtype(bool value) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(IsFutureSubtypeBit::update(value, state_bits())); | |
| } | |
| void Class::set_can_be_future(bool value) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(CanBeFutureBit::update(value, state_bits())); | |
| } | |
| void Class::set_has_dynamically_extendable_subtypes(bool value) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits( | |
| HasDynamicallyExtendableSubtypesBit::update(value, state_bits())); | |
| } | |
| // Initialize class fields of type Array with empty array. | |
| void Class::InitEmptyFields() const { | |
| if (Object::empty_array().ptr() == Array::null()) { | |
| // The empty array has not been initialized yet. | |
| return; | |
| } | |
| untag()->set_interfaces(Object::empty_array().ptr()); | |
| untag()->set_constants(Object::null_array().ptr()); | |
| set_functions(Object::empty_array()); | |
| set_fields(Object::empty_array()); | |
| set_invocation_dispatcher_cache(Object::empty_array()); | |
| } | |
| ArrayPtr Class::OffsetToFieldMap( | |
| ClassTable* class_table /* = nullptr */) const { | |
| ASSERT(is_finalized()); | |
| if (untag()->offset_in_words_to_field<std::memory_order_acquire>() == | |
| Array::null()) { | |
| // Even if multiple threads are calling this concurrently, all of them would | |
| // compute the same array, so we intentionally don't acquire any locks here. | |
| const intptr_t length = untag()->host_instance_size_in_words_; | |
| const Array& array = Array::Handle(Array::New(length, Heap::kOld)); | |
| Class& cls = Class::Handle(this->ptr()); | |
| Array& fields = Array::Handle(); | |
| Field& f = Field::Handle(); | |
| while (!cls.IsNull()) { | |
| fields = cls.fields(); | |
| for (intptr_t i = 0; i < fields.Length(); ++i) { | |
| f ^= fields.At(i); | |
| if (f.is_instance()) { | |
| array.SetAt(f.HostOffset() >> kCompressedWordSizeLog2, f); | |
| } | |
| } | |
| cls = cls.SuperClass(class_table); | |
| } | |
| untag()->set_offset_in_words_to_field<std::memory_order_release>( | |
| array.ptr()); | |
| } | |
| return untag()->offset_in_words_to_field<std::memory_order_acquire>(); | |
| } | |
| bool Class::HasInstanceFields() const { | |
| const Array& field_array = Array::Handle(fields()); | |
| Field& field = Field::Handle(); | |
| for (intptr_t i = 0; i < field_array.Length(); ++i) { | |
| field ^= field_array.At(i); | |
| if (!field.is_static()) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| class FunctionName { | |
| public: | |
| FunctionName(const String& name, String* tmp_string) | |
| : name_(name), tmp_string_(tmp_string) {} | |
| bool Matches(const Function& function) const { | |
| if (name_.IsSymbol()) { | |
| return name_.ptr() == function.name(); | |
| } else { | |
| *tmp_string_ = function.name(); | |
| return name_.Equals(*tmp_string_); | |
| } | |
| } | |
| intptr_t Hash() const { return name_.Hash(); } | |
| private: | |
| const String& name_; | |
| String* tmp_string_; | |
| }; | |
| // Traits for looking up Functions by name. | |
| class ClassFunctionsTraits { | |
| public: | |
| static const char* Name() { return "ClassFunctionsTraits"; } | |
| static bool ReportStats() { return false; } | |
| // Called when growing the table. | |
| static bool IsMatch(const Object& a, const Object& b) { | |
| ASSERT(a.IsFunction() && b.IsFunction()); | |
| // Function objects are always canonical. | |
| return a.ptr() == b.ptr(); | |
| } | |
| static bool IsMatch(const FunctionName& name, const Object& obj) { | |
| return name.Matches(Function::Cast(obj)); | |
| } | |
| static uword Hash(const Object& key) { | |
| return String::HashRawSymbol(Function::Cast(key).name()); | |
| } | |
| static uword Hash(const FunctionName& name) { return name.Hash(); } | |
| }; | |
| typedef UnorderedHashSet<ClassFunctionsTraits> ClassFunctionsSet; | |
| void Class::SetFunctions(const Array& value) const { | |
| ASSERT(!value.IsNull()); | |
| const intptr_t len = value.Length(); | |
| #if defined(DEBUG) | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); | |
| if (is_finalized()) { | |
| Function& function = Function::Handle(); | |
| FunctionType& signature = FunctionType::Handle(); | |
| for (intptr_t i = 0; i < len; ++i) { | |
| function ^= value.At(i); | |
| signature = function.signature(); | |
| ASSERT(signature.IsFinalized()); | |
| } | |
| } | |
| #endif | |
| set_functions(value); | |
| if (len >= kFunctionLookupHashThreshold) { | |
| ClassFunctionsSet set(HashTables::New<ClassFunctionsSet>(len, Heap::kOld)); | |
| Function& func = Function::Handle(); | |
| for (intptr_t i = 0; i < len; ++i) { | |
| func ^= value.At(i); | |
| // Verify that all the functions in the array have this class as owner. | |
| ASSERT(func.Owner() == ptr()); | |
| set.Insert(func); | |
| } | |
| untag()->set_functions_hash_table(set.Release().ptr()); | |
| } else { | |
| untag()->set_functions_hash_table(Array::null()); | |
| } | |
| } | |
| void Class::AddFunction(const Function& function) const { | |
| #if defined(DEBUG) | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->IsDartMutatorThread()); | |
| ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(!is_finalized() || | |
| FunctionType::Handle(function.signature()).IsFinalized()); | |
| #endif | |
| const Array& arr = Array::Handle(functions()); | |
| const Array& new_array = | |
| Array::Handle(Array::Grow(arr, arr.Length() + 1, Heap::kOld)); | |
| new_array.SetAt(arr.Length(), function); | |
| set_functions(new_array); | |
| // Add to hash table, if any. | |
| const intptr_t new_len = new_array.Length(); | |
| if (new_len == kFunctionLookupHashThreshold) { | |
| // Transition to using hash table. | |
| SetFunctions(new_array); | |
| } else if (new_len > kFunctionLookupHashThreshold) { | |
| ClassFunctionsSet set(untag()->functions_hash_table()); | |
| set.Insert(function); | |
| untag()->set_functions_hash_table(set.Release().ptr()); | |
| } | |
| } | |
| intptr_t Class::FindFunctionIndex(const Function& needle) const { | |
| Thread* thread = Thread::Current(); | |
| if (EnsureIsFinalized(thread) != Error::null()) { | |
| return -1; | |
| } | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_FUNCTION_HANDLESCOPE(thread); | |
| Array& funcs = thread->ArrayHandle(); | |
| Function& function = thread->FunctionHandle(); | |
| funcs = current_functions(); | |
| ASSERT(!funcs.IsNull()); | |
| const intptr_t len = funcs.Length(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| function ^= funcs.At(i); | |
| if (needle.ptr() == function.ptr()) { | |
| return i; | |
| } | |
| } | |
| // No function found. | |
| return -1; | |
| } | |
| FunctionPtr Class::FunctionFromIndex(intptr_t idx) const { | |
| const Array& funcs = Array::Handle(current_functions()); | |
| if ((idx < 0) || (idx >= funcs.Length())) { | |
| return Function::null(); | |
| } | |
| Function& func = Function::Handle(); | |
| func ^= funcs.At(idx); | |
| ASSERT(!func.IsNull()); | |
| return func.ptr(); | |
| } | |
| FunctionPtr Class::ImplicitClosureFunctionFromIndex(intptr_t idx) const { | |
| Function& func = Function::Handle(FunctionFromIndex(idx)); | |
| if (func.IsNull() || !func.HasImplicitClosureFunction()) { | |
| return Function::null(); | |
| } | |
| func = func.ImplicitClosureFunction(); | |
| ASSERT(!func.IsNull()); | |
| return func.ptr(); | |
| } | |
| intptr_t Class::FindImplicitClosureFunctionIndex(const Function& needle) const { | |
| Thread* thread = Thread::Current(); | |
| if (EnsureIsFinalized(thread) != Error::null()) { | |
| return -1; | |
| } | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_FUNCTION_HANDLESCOPE(thread); | |
| Array& funcs = thread->ArrayHandle(); | |
| Function& function = thread->FunctionHandle(); | |
| funcs = current_functions(); | |
| ASSERT(!funcs.IsNull()); | |
| Function& implicit_closure = Function::Handle(thread->zone()); | |
| const intptr_t len = funcs.Length(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| function ^= funcs.At(i); | |
| implicit_closure = function.implicit_closure_function(); | |
| if (implicit_closure.IsNull()) { | |
| // Skip non-implicit closure functions. | |
| continue; | |
| } | |
| if (needle.ptr() == implicit_closure.ptr()) { | |
| return i; | |
| } | |
| } | |
| // No function found. | |
| return -1; | |
| } | |
| intptr_t Class::FindInvocationDispatcherFunctionIndex( | |
| const Function& needle) const { | |
| Thread* thread = Thread::Current(); | |
| if (EnsureIsFinalized(thread) != Error::null()) { | |
| return -1; | |
| } | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_OBJECT_HANDLESCOPE(thread); | |
| Array& funcs = thread->ArrayHandle(); | |
| Object& object = thread->ObjectHandle(); | |
| funcs = invocation_dispatcher_cache(); | |
| ASSERT(!funcs.IsNull()); | |
| const intptr_t len = funcs.Length(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| object = funcs.At(i); | |
| // The invocation_dispatcher_cache is a table with some entries that | |
| // are functions. | |
| if (object.IsFunction()) { | |
| if (Function::Cast(object).ptr() == needle.ptr()) { | |
| return i; | |
| } | |
| } | |
| } | |
| // No function found. | |
| return -1; | |
| } | |
| FunctionPtr Class::InvocationDispatcherFunctionFromIndex(intptr_t idx) const { | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_OBJECT_HANDLESCOPE(thread); | |
| Array& dispatcher_cache = thread->ArrayHandle(); | |
| Object& object = thread->ObjectHandle(); | |
| dispatcher_cache = invocation_dispatcher_cache(); | |
| object = dispatcher_cache.At(idx); | |
| if (!object.IsFunction()) { | |
| return Function::null(); | |
| } | |
| return Function::Cast(object).ptr(); | |
| } | |
| void Class::set_state_bits(intptr_t bits) const { | |
| StoreNonPointer<uint32_t, uint32_t, std::memory_order_release>( | |
| &untag()->state_bits_, static_cast<uint32_t>(bits)); | |
| } | |
| void Class::set_library(const Library& value) const { | |
| untag()->set_library(value.ptr()); | |
| } | |
| void Class::set_type_parameters(const TypeParameters& value) const { | |
| ASSERT((num_type_arguments() == kUnknownNumTypeArguments) || | |
| is_declared_in_bytecode() || is_prefinalized()); | |
| untag()->set_type_parameters(value.ptr()); | |
| } | |
| void Class::set_functions(const Array& value) const { | |
| // Ensure all writes to the [Function]s are visible by the time the array | |
| // is visible. | |
| untag()->set_functions<std::memory_order_release>(value.ptr()); | |
| } | |
| void Class::set_fields(const Array& value) const { | |
| // Ensure all writes to the [Field]s are visible by the time the array | |
| // is visible. | |
| untag()->set_fields<std::memory_order_release>(value.ptr()); | |
| } | |
| void Class::set_invocation_dispatcher_cache(const Array& cache) const { | |
| // Ensure all writes to the cache are visible by the time the array | |
| // is visible. | |
| untag()->set_invocation_dispatcher_cache<std::memory_order_release>( | |
| cache.ptr()); | |
| } | |
| void Class::set_declaration_instance_type_arguments( | |
| const TypeArguments& value) const { | |
| ASSERT(value.IsNull() || (value.IsCanonical() && value.IsOld())); | |
| ASSERT((declaration_instance_type_arguments() == TypeArguments::null()) || | |
| (declaration_instance_type_arguments() == value.ptr())); | |
| untag()->set_declaration_instance_type_arguments<std::memory_order_release>( | |
| value.ptr()); | |
| } | |
| TypeArgumentsPtr Class::GetDeclarationInstanceTypeArguments() const { | |
| const intptr_t num_type_arguments = NumTypeArguments(); | |
| if (num_type_arguments == 0) { | |
| return TypeArguments::null(); | |
| } | |
| if (declaration_instance_type_arguments() != TypeArguments::null()) { | |
| return declaration_instance_type_arguments(); | |
| } | |
| Thread* thread = Thread::Current(); | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| if (declaration_instance_type_arguments() != TypeArguments::null()) { | |
| return declaration_instance_type_arguments(); | |
| } | |
| Zone* zone = thread->zone(); | |
| auto& args = TypeArguments::Handle(zone); | |
| auto& type = AbstractType::Handle(zone); | |
| const intptr_t num_type_parameters = NumTypeParameters(thread); | |
| if (num_type_arguments == num_type_parameters) { | |
| type = DeclarationType(); | |
| args = Type::Cast(type).arguments(); | |
| } else { | |
| type = super_type(); | |
| const auto& super_args = TypeArguments::Handle( | |
| zone, Type::Cast(type).GetInstanceTypeArguments(thread)); | |
| if ((num_type_parameters == 0) || | |
| (!super_args.IsNull() && (super_args.Length() == num_type_arguments))) { | |
| args = super_args.ptr(); | |
| } else { | |
| args = TypeArguments::New(num_type_arguments); | |
| const intptr_t offset = num_type_arguments - num_type_parameters; | |
| for (intptr_t i = 0; i < offset; ++i) { | |
| type = super_args.TypeAtNullSafe(i); | |
| args.SetTypeAt(i, type); | |
| } | |
| type = DeclarationType(); | |
| const auto& decl_args = | |
| TypeArguments::Handle(zone, Type::Cast(type).arguments()); | |
| for (intptr_t i = 0; i < num_type_parameters; ++i) { | |
| type = decl_args.TypeAt(i); | |
| args.SetTypeAt(offset + i, type); | |
| } | |
| } | |
| } | |
| args = args.Canonicalize(thread); | |
| set_declaration_instance_type_arguments(args); | |
| return args.ptr(); | |
| } | |
| TypeArgumentsPtr Class::GetInstanceTypeArguments( | |
| Thread* thread, | |
| const TypeArguments& type_arguments, | |
| bool canonicalize) const { | |
| const intptr_t num_type_arguments = NumTypeArguments(); | |
| if (num_type_arguments == 0) { | |
| return TypeArguments::null(); | |
| } | |
| Zone* zone = thread->zone(); | |
| auto& args = TypeArguments::Handle(zone); | |
| const intptr_t num_type_parameters = NumTypeParameters(thread); | |
| ASSERT(type_arguments.IsNull() || | |
| type_arguments.Length() == num_type_parameters); | |
| if (num_type_arguments == num_type_parameters) { | |
| args = type_arguments.ptr(); | |
| } else { | |
| args = GetDeclarationInstanceTypeArguments(); | |
| if (num_type_parameters == 0) { | |
| return args.ptr(); | |
| } | |
| args = args.InstantiateFrom( | |
| TypeArguments::Handle( | |
| zone, type_arguments.ToInstantiatorTypeArguments(thread, *this)), | |
| Object::null_type_arguments(), kAllFree, Heap::kOld); | |
| } | |
| if (canonicalize) { | |
| args = args.Canonicalize(thread); | |
| } | |
| return args.ptr(); | |
| } | |
| intptr_t Class::NumTypeParameters(Thread* thread) const { | |
| if (!is_declaration_loaded()) { | |
| ASSERT(is_prefinalized()); | |
| const intptr_t cid = id(); | |
| if ((cid == kArrayCid) || (cid == kImmutableArrayCid) || | |
| (cid == kGrowableObjectArrayCid)) { | |
| return 1; // List's type parameter may not have been parsed yet. | |
| } | |
| return 0; | |
| } | |
| if (type_parameters() == TypeParameters::null()) { | |
| return 0; | |
| } | |
| REUSABLE_TYPE_PARAMETERS_HANDLESCOPE(thread); | |
| TypeParameters& type_params = thread->TypeParametersHandle(); | |
| type_params = type_parameters(); | |
| return type_params.Length(); | |
| } | |
| intptr_t Class::ComputeNumTypeArguments() const { | |
| ASSERT(is_declaration_loaded()); | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| auto isolate_group = thread->isolate_group(); | |
| const intptr_t num_type_params = NumTypeParameters(); | |
| if ((super_type() == AbstractType::null()) || | |
| (super_type() == isolate_group->object_store()->object_type())) { | |
| return num_type_params; | |
| } | |
| const auto& sup_type = Type::Handle(zone, super_type()); | |
| const auto& sup_class = Class::Handle(zone, sup_type.type_class()); | |
| const intptr_t sup_class_num_type_args = sup_class.NumTypeArguments(); | |
| if (num_type_params == 0) { | |
| return sup_class_num_type_args; | |
| } | |
| const auto& sup_type_args = TypeArguments::Handle(zone, sup_type.arguments()); | |
| if (sup_type_args.IsNull()) { | |
| // The super type is raw or the super class is non generic. | |
| // In either case, overlapping is not possible. | |
| return sup_class_num_type_args + num_type_params; | |
| } | |
| const intptr_t sup_type_args_length = sup_type_args.Length(); | |
| // Determine the maximum overlap of a prefix of the vector consisting of the | |
| // type parameters of this class with a suffix of the vector consisting of the | |
| // type arguments of the super type of this class. | |
| // The number of own type arguments of this class is the number of its type | |
| // parameters minus the number of type arguments in the overlap. | |
| // Attempt to overlap the whole vector of type parameters; reduce the size | |
| // of the vector (keeping the first type parameter) until it fits or until | |
| // its size is zero. | |
| auto& sup_type_arg = AbstractType::Handle(zone); | |
| for (intptr_t num_overlapping_type_args = | |
| (num_type_params < sup_type_args_length) ? num_type_params | |
| : sup_type_args_length; | |
| num_overlapping_type_args > 0; num_overlapping_type_args--) { | |
| intptr_t i = 0; | |
| for (; i < num_overlapping_type_args; i++) { | |
| sup_type_arg = sup_type_args.TypeAt(sup_type_args_length - | |
| num_overlapping_type_args + i); | |
| ASSERT(!sup_type_arg.IsNull()); | |
| if (!sup_type_arg.IsTypeParameter()) break; | |
| // The only type parameters appearing in the type arguments of the super | |
| // type are those declared by this class. Their finalized indices depend | |
| // on the number of type arguments being computed here. Therefore, they | |
| // cannot possibly be finalized yet. | |
| ASSERT(!TypeParameter::Cast(sup_type_arg).IsFinalized()); | |
| if (TypeParameter::Cast(sup_type_arg).index() != i || | |
| TypeParameter::Cast(sup_type_arg).IsNullable()) { | |
| break; | |
| } | |
| } | |
| if (i == num_overlapping_type_args) { | |
| // Overlap found. | |
| return sup_class_num_type_args + num_type_params - | |
| num_overlapping_type_args; | |
| } | |
| } | |
| // No overlap found. | |
| return sup_class_num_type_args + num_type_params; | |
| } | |
| intptr_t Class::NumTypeArguments() const { | |
| // Return cached value if already calculated. | |
| intptr_t num_type_args = num_type_arguments(); | |
| if (num_type_args != kUnknownNumTypeArguments) { | |
| return num_type_args; | |
| } | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| return 0; | |
| #else | |
| num_type_args = ComputeNumTypeArguments(); | |
| ASSERT(num_type_args != kUnknownNumTypeArguments); | |
| set_num_type_arguments(num_type_args); | |
| return num_type_args; | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| TypeArgumentsPtr Class::DefaultTypeArguments(Zone* zone) const { | |
| if (type_parameters() == TypeParameters::null()) { | |
| return Object::empty_type_arguments().ptr(); | |
| } | |
| return TypeParameters::Handle(zone, type_parameters()).defaults(); | |
| } | |
| ClassPtr Class::SuperClass(ClassTable* class_table /* = nullptr */) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| if (class_table == nullptr) { | |
| class_table = thread->isolate_group()->class_table(); | |
| } | |
| if (super_type() == AbstractType::null()) { | |
| if (id() == kTypeArgumentsCid) { | |
| // Pretend TypeArguments objects are Dart instances. | |
| return class_table->At(kInstanceCid); | |
| } | |
| return Class::null(); | |
| } | |
| const AbstractType& sup_type = AbstractType::Handle(zone, super_type()); | |
| const intptr_t type_class_id = sup_type.type_class_id(); | |
| return class_table->At(type_class_id); | |
| } | |
| void Class::set_super_type(const Type& value) const { | |
| ASSERT(value.IsNull() || !value.IsDynamicType()); | |
| untag()->set_super_type(value.ptr()); | |
| } | |
| TypeParameterPtr Class::TypeParameterAt(intptr_t index, | |
| Nullability nullability) const { | |
| ASSERT(index >= 0 && index < NumTypeParameters()); | |
| TypeParameter& type_param = | |
| TypeParameter::Handle(TypeParameter::New(*this, 0, index, nullability)); | |
| // Finalize type parameter only if its declaring class is | |
| // finalized and available in the current class table. | |
| if (is_type_finalized() && (type_param.parameterized_class() == ptr())) { | |
| type_param ^= ClassFinalizer::FinalizeType(type_param); | |
| } | |
| return type_param.ptr(); | |
| } | |
| intptr_t Class::UnboxedFieldSizeInBytesByCid(intptr_t cid) { | |
| switch (cid) { | |
| case kDoubleCid: | |
| return sizeof(UntaggedDouble::value_); | |
| case kFloat32x4Cid: | |
| return sizeof(UntaggedFloat32x4::value_); | |
| case kFloat64x2Cid: | |
| return sizeof(UntaggedFloat64x2::value_); | |
| case kInt32x4Cid: | |
| return sizeof(UntaggedInt32x4::value_); | |
| default: | |
| return sizeof(UntaggedMint::value_); | |
| } | |
| } | |
| UnboxedFieldBitmap Class::CalculateFieldOffsets() const { | |
| Array& flds = Array::Handle(fields()); | |
| const Class& super = Class::Handle(SuperClass()); | |
| intptr_t host_offset = 0; | |
| UnboxedFieldBitmap host_bitmap{}; | |
| // Target offsets might differ if the word size are different | |
| intptr_t target_offset = 0; | |
| intptr_t host_type_args_field_offset = kNoTypeArguments; | |
| intptr_t target_type_args_field_offset = RTN::Class::kNoTypeArguments; | |
| if (super.IsNull()) { | |
| host_offset = Instance::NextFieldOffset(); | |
| target_offset = RTN::Instance::NextFieldOffset(); | |
| ASSERT(host_offset > 0); | |
| ASSERT(target_offset > 0); | |
| } else { | |
| ASSERT(super.is_finalized() || super.is_prefinalized()); | |
| host_type_args_field_offset = super.host_type_arguments_field_offset(); | |
| target_type_args_field_offset = super.target_type_arguments_field_offset(); | |
| host_offset = super.host_next_field_offset(); | |
| ASSERT(host_offset > 0); | |
| target_offset = super.target_next_field_offset(); | |
| ASSERT(target_offset > 0); | |
| // We should never call CalculateFieldOffsets for native wrapper | |
| // classes, assert this. | |
| ASSERT(num_native_fields() == 0); | |
| const intptr_t num_native_fields = super.num_native_fields(); | |
| set_num_native_fields(num_native_fields); | |
| if (num_native_fields > 0 || is_isolate_unsendable_due_to_pragma()) { | |
| set_is_isolate_unsendable(true); | |
| } | |
| host_bitmap = IsolateGroup::Current()->class_table()->GetUnboxedFieldsMapAt( | |
| super.id()); | |
| } | |
| // If the super class is parameterized, use the same type_arguments field, | |
| // otherwise, if this class is the first in the super chain to be | |
| // parameterized, introduce a new type_arguments field. | |
| if (host_type_args_field_offset == kNoTypeArguments) { | |
| ASSERT(target_type_args_field_offset == RTN::Class::kNoTypeArguments); | |
| if (IsGeneric()) { | |
| // The instance needs a type_arguments field. | |
| host_type_args_field_offset = host_offset; | |
| target_type_args_field_offset = target_offset; | |
| host_offset += kCompressedWordSize; | |
| target_offset += compiler::target::kCompressedWordSize; | |
| } | |
| } else { | |
| ASSERT(target_type_args_field_offset != RTN::Class::kNoTypeArguments); | |
| } | |
| set_type_arguments_field_offset(host_type_args_field_offset, | |
| target_type_args_field_offset); | |
| ASSERT(host_offset > 0); | |
| ASSERT(target_offset > 0); | |
| Field& field = Field::Handle(); | |
| const intptr_t len = flds.Length(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| field ^= flds.At(i); | |
| // Offset is computed only for instance fields. | |
| if (!field.is_static()) { | |
| ASSERT(field.HostOffset() == 0); | |
| ASSERT(field.TargetOffset() == 0); | |
| field.SetOffset(host_offset, target_offset); | |
| if (field.is_unboxed()) { | |
| const intptr_t field_size = | |
| UnboxedFieldSizeInBytesByCid(field.guarded_cid()); | |
| const intptr_t host_num_words = field_size / kCompressedWordSize; | |
| const intptr_t host_next_offset = host_offset + field_size; | |
| const intptr_t host_next_position = | |
| host_next_offset / kCompressedWordSize; | |
| const intptr_t target_next_offset = target_offset + field_size; | |
| const intptr_t target_next_position = | |
| target_next_offset / compiler::target::kCompressedWordSize; | |
| // The bitmap has fixed length. Checks if the offset position is smaller | |
| // than its length. If it is not, than the field should be boxed | |
| if (host_next_position <= UnboxedFieldBitmap::Length() && | |
| target_next_position <= UnboxedFieldBitmap::Length()) { | |
| for (intptr_t j = 0; j < host_num_words; j++) { | |
| // Activate the respective bit in the bitmap, indicating that the | |
| // content is not a pointer | |
| host_bitmap.Set(host_offset / kCompressedWordSize); | |
| host_offset += kCompressedWordSize; | |
| } | |
| ASSERT(host_offset == host_next_offset); | |
| target_offset = target_next_offset; | |
| } else { | |
| // Make the field boxed | |
| field.set_is_unboxed(false); | |
| host_offset += kCompressedWordSize; | |
| target_offset += compiler::target::kCompressedWordSize; | |
| } | |
| } else { | |
| host_offset += kCompressedWordSize; | |
| target_offset += compiler::target::kCompressedWordSize; | |
| } | |
| } | |
| } | |
| const intptr_t host_instance_size = RoundedAllocationSize(host_offset); | |
| const intptr_t target_instance_size = | |
| compiler::target::RoundedAllocationSize(target_offset); | |
| if (!Utils::IsInt(32, target_instance_size)) { | |
| // Many parts of the compiler assume offsets can be represented with | |
| // int32_t. | |
| FATAL("Too many fields in %s\n", UserVisibleNameCString()); | |
| } | |
| set_instance_size(host_instance_size, target_instance_size); | |
| set_next_field_offset(host_offset, target_offset); | |
| return host_bitmap; | |
| } | |
| void Class::AddInvocationDispatcher(const String& target_name, | |
| const Array& args_desc, | |
| const Function& dispatcher) const { | |
| auto thread = Thread::Current(); | |
| ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(target_name.ptr() == dispatcher.name()); | |
| DispatcherSet dispatchers(invocation_dispatcher_cache() == | |
| Array::empty_array().ptr() | |
| ? HashTables::New<DispatcherSet>(4, Heap::kOld) | |
| : invocation_dispatcher_cache()); | |
| dispatchers.Insert(dispatcher); | |
| set_invocation_dispatcher_cache(dispatchers.Release()); | |
| } | |
| FunctionPtr Class::GetInvocationDispatcher(const String& target_name, | |
| const Array& args_desc, | |
| UntaggedFunction::Kind kind, | |
| bool create_if_absent, | |
| bool is_dynamically_callable) const { | |
| ASSERT(kind == UntaggedFunction::kNoSuchMethodDispatcher || | |
| kind == UntaggedFunction::kInvokeFieldDispatcher || | |
| kind == UntaggedFunction::kDynamicInvocationForwarder); | |
| auto thread = Thread::Current(); | |
| auto Z = thread->zone(); | |
| auto& function = Function::Handle(Z); | |
| // First we'll try to find it without using locks. | |
| DispatcherKey key(target_name, args_desc, kind); | |
| if (invocation_dispatcher_cache() != Array::empty_array().ptr()) { | |
| DispatcherSet dispatchers(Z, invocation_dispatcher_cache()); | |
| function ^= dispatchers.GetOrNull(key); | |
| dispatchers.Release(); | |
| } | |
| if (!function.IsNull() || !create_if_absent) { | |
| return function.ptr(); | |
| } | |
| // If we failed to find it and possibly need to create it, use a write lock. | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| // Try to find it again & return if it was added in the meantime. | |
| if (invocation_dispatcher_cache() != Array::empty_array().ptr()) { | |
| DispatcherSet dispatchers(Z, invocation_dispatcher_cache()); | |
| function ^= dispatchers.GetOrNull(key); | |
| dispatchers.Release(); | |
| } | |
| if (!function.IsNull()) return function.ptr(); | |
| // Otherwise create it & add it. | |
| function = CreateInvocationDispatcher(target_name, args_desc, kind, | |
| is_dynamically_callable); | |
| AddInvocationDispatcher(target_name, args_desc, function); | |
| return function.ptr(); | |
| } | |
| FunctionPtr Class::CreateInvocationDispatcher( | |
| const String& target_name, | |
| const Array& args_desc, | |
| UntaggedFunction::Kind kind, | |
| bool is_dynamically_callable) const { | |
| ASSERT(target_name.ptr() != Symbols::DynamicImplicitCall().ptr()); | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| FunctionType& signature = FunctionType::Handle(zone, FunctionType::New()); | |
| Function& invocation = Function::Handle( | |
| zone, Function::New( | |
| signature, | |
| String::Handle(zone, Symbols::New(thread, target_name)), kind, | |
| false, // Not static. | |
| false, // Not const. | |
| false, // Not abstract. | |
| false, // Not external. | |
| false, // Not native. | |
| *this, TokenPosition::kMinSource)); | |
| ArgumentsDescriptor desc(args_desc); | |
| const intptr_t type_args_len = desc.TypeArgsLen(); | |
| if (type_args_len > 0) { | |
| // Make dispatcher function generic, since type arguments are passed. | |
| const auto& type_parameters = | |
| TypeParameters::Handle(zone, TypeParameters::New(type_args_len)); | |
| // Allow any type, as any type checking is compiled into the dispatcher. | |
| auto& bound = Type::Handle( | |
| zone, IsolateGroup::Current()->object_store()->nullable_object_type()); | |
| for (intptr_t i = 0; i < type_args_len; i++) { | |
| // The name of the type parameter does not matter, as a type error using | |
| // it should never be thrown. | |
| type_parameters.SetNameAt(i, Symbols::OptimizedOut()); | |
| type_parameters.SetBoundAt(i, bound); | |
| // Type arguments will always be provided, so the default is not used. | |
| type_parameters.SetDefaultAt(i, Object::dynamic_type()); | |
| } | |
| signature.SetTypeParameters(type_parameters); | |
| } | |
| signature.set_num_fixed_parameters(desc.PositionalCount()); | |
| signature.SetNumOptionalParameters(desc.NamedCount(), | |
| false); // Not positional. | |
| signature.set_parameter_types( | |
| Array::Handle(zone, Array::New(desc.Count(), Heap::kOld))); | |
| invocation.CreateNameArray(); | |
| signature.CreateNameArrayIncludingFlags(); | |
| // Receiver. | |
| signature.SetParameterTypeAt(0, Object::dynamic_type()); | |
| invocation.SetParameterNameAt(0, Symbols::This()); | |
| // Remaining positional parameters. | |
| for (intptr_t i = 1; i < desc.PositionalCount(); i++) { | |
| signature.SetParameterTypeAt(i, Object::dynamic_type()); | |
| char name[64]; | |
| Utils::SNPrint(name, 64, ":p%" Pd, i); | |
| invocation.SetParameterNameAt( | |
| i, String::Handle(zone, Symbols::New(thread, name))); | |
| } | |
| // Named parameters. | |
| for (intptr_t i = 0; i < desc.NamedCount(); i++) { | |
| const intptr_t param_index = desc.PositionAt(i); | |
| const auto& param_name = String::Handle(zone, desc.NameAt(i)); | |
| signature.SetParameterTypeAt(param_index, Object::dynamic_type()); | |
| signature.SetParameterNameAt(param_index, param_name); | |
| } | |
| signature.FinalizeNameArray(); | |
| signature.set_result_type(Object::dynamic_type()); | |
| invocation.set_is_debuggable(false); | |
| invocation.set_is_visible(false); | |
| invocation.set_is_reflectable(false); | |
| invocation.set_saved_args_desc(args_desc); | |
| invocation.set_is_dynamically_callable(is_dynamically_callable); | |
| signature ^= ClassFinalizer::FinalizeType(signature); | |
| invocation.SetSignature(signature); | |
| #if defined(DART_DYNAMIC_MODULES) | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| const bool attach_bytecode = true; | |
| #else | |
| const bool attach_bytecode = is_declared_in_bytecode(); | |
| #endif | |
| if (attach_bytecode) { | |
| switch (kind) { | |
| case UntaggedFunction::kNoSuchMethodDispatcher: | |
| invocation.AttachBytecode(Object::nsm_dispatcher_bytecode()); | |
| break; | |
| case UntaggedFunction::kInvokeFieldDispatcher: | |
| invocation.AttachBytecode(Object::invoke_field_bytecode()); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| return invocation.ptr(); | |
| } | |
| // Method extractors are used to create implicit closures from methods. | |
| // When an expression obj.M is evaluated for the first time and receiver obj | |
| // does not have a getter called M but has a method called M then an extractor | |
| // is created and injected as a getter (under the name get:M) into the class | |
| // owning method M. | |
| FunctionPtr Function::CreateMethodExtractor(const String& getter_name) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| ASSERT(Field::IsGetterName(getter_name)); | |
| const Function& closure_function = | |
| Function::Handle(zone, ImplicitClosureFunction()); | |
| const Class& owner = Class::Handle(zone, closure_function.Owner()); | |
| FunctionType& signature = FunctionType::Handle(zone, FunctionType::New()); | |
| const Function& extractor = Function::Handle( | |
| zone, | |
| Function::New(signature, | |
| String::Handle(zone, Symbols::New(thread, getter_name)), | |
| UntaggedFunction::kMethodExtractor, | |
| false, // Not static. | |
| false, // Not const. | |
| is_abstract(), | |
| false, // Not external. | |
| false, // Not native. | |
| owner, TokenPosition::kMethodExtractor)); | |
| // Initialize signature: receiver is a single fixed parameter. | |
| const intptr_t kNumParameters = 1; | |
| signature.set_num_fixed_parameters(kNumParameters); | |
| signature.SetNumOptionalParameters(0, false); | |
| signature.set_parameter_types(Object::synthetic_getter_parameter_types()); | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| extractor.set_positional_parameter_names( | |
| Object::synthetic_getter_parameter_names()); | |
| #endif | |
| signature.set_result_type(Object::dynamic_type()); | |
| extractor.InheritKernelOffsetFrom(*this); | |
| extractor.set_extracted_method_closure(closure_function); | |
| extractor.set_is_debuggable(false); | |
| extractor.set_is_visible(false); | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| extractor.SetIsDynamicallyOverridden(IsDynamicallyOverridden()); | |
| #endif | |
| signature ^= ClassFinalizer::FinalizeType(signature); | |
| extractor.SetSignature(signature); | |
| #if defined(DART_DYNAMIC_MODULES) | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| const bool attach_bytecode = true; | |
| #else | |
| const bool attach_bytecode = is_declared_in_bytecode(); | |
| #endif | |
| if (attach_bytecode) { | |
| // Bytecode method extractor may be used to create a closure instance | |
| // with a compiled closure function, so layout of the closure object | |
| // has to be compatible between them. | |
| // 'Closure::HasInstantiatorTypeArgumentsField' involves a heavy signature | |
| // type inspection which is not feasible to perform in the method | |
| // extractor. So it is performed here to select a variant of the bytecode | |
| // method extractor. | |
| if (Closure::HasInstantiatorTypeArgumentsField(closure_function)) { | |
| extractor.AttachBytecode(Object::method_extractor_with_ita_bytecode()); | |
| } else { | |
| extractor.AttachBytecode(Object::method_extractor_without_ita_bytecode()); | |
| } | |
| } | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| owner.AddFunction(extractor); | |
| return extractor.ptr(); | |
| } | |
| FunctionPtr Function::GetMethodExtractor(const String& getter_name) const { | |
| ASSERT(Field::IsGetterName(getter_name)); | |
| const Function& closure_function = | |
| Function::Handle(ImplicitClosureFunction()); | |
| const Class& owner = Class::Handle(closure_function.Owner()); | |
| Thread* thread = Thread::Current(); | |
| if (owner.EnsureIsFinalized(thread) != Error::null()) { | |
| return Function::null(); | |
| } | |
| IsolateGroup* group = thread->isolate_group(); | |
| Function& result = Function::Handle( | |
| Resolver::ResolveDynamicFunction(thread->zone(), owner, getter_name)); | |
| if (result.IsNull()) { | |
| SafepointWriteRwLocker ml(thread, group->program_lock()); | |
| result = owner.LookupDynamicFunctionUnsafe(getter_name); | |
| if (result.IsNull()) { | |
| result = CreateMethodExtractor(getter_name); | |
| } | |
| } | |
| ASSERT(result.kind() == UntaggedFunction::kMethodExtractor); | |
| return result.ptr(); | |
| } | |
| // Record field getters are used to access fields of arbitrary | |
| // record instances dynamically. | |
| FunctionPtr Class::CreateRecordFieldGetter(const String& getter_name) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| ASSERT(IsRecordClass()); | |
| ASSERT(Field::IsGetterName(getter_name)); | |
| FunctionType& signature = FunctionType::Handle(zone, FunctionType::New()); | |
| const Function& getter = Function::Handle( | |
| zone, | |
| Function::New(signature, | |
| String::Handle(zone, Symbols::New(thread, getter_name)), | |
| UntaggedFunction::kRecordFieldGetter, | |
| false, // Not static. | |
| false, // Not const. | |
| false, // Not abstract. | |
| false, // Not external. | |
| false, // Not native. | |
| *this, TokenPosition::kMinSource)); | |
| // Initialize signature: receiver is a single fixed parameter. | |
| const intptr_t kNumParameters = 1; | |
| signature.set_num_fixed_parameters(kNumParameters); | |
| signature.SetNumOptionalParameters(0, false); | |
| signature.set_parameter_types(Object::synthetic_getter_parameter_types()); | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| getter.set_positional_parameter_names( | |
| Object::synthetic_getter_parameter_names()); | |
| #endif | |
| signature.set_result_type(Object::dynamic_type()); | |
| getter.set_is_debuggable(false); | |
| getter.set_is_visible(false); | |
| signature ^= ClassFinalizer::FinalizeType(signature); | |
| getter.SetSignature(signature); | |
| AddFunction(getter); | |
| return getter.ptr(); | |
| } | |
| FunctionPtr Class::GetRecordFieldGetter(const String& getter_name) const { | |
| ASSERT(IsRecordClass()); | |
| ASSERT(Field::IsGetterName(getter_name)); | |
| Thread* thread = Thread::Current(); | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| Function& result = Function::Handle(thread->zone(), | |
| LookupDynamicFunctionUnsafe(getter_name)); | |
| if (result.IsNull()) { | |
| result = CreateRecordFieldGetter(getter_name); | |
| } | |
| ASSERT(result.kind() == UntaggedFunction::kRecordFieldGetter); | |
| return result.ptr(); | |
| } | |
| bool FindPragmaInMetadata(Thread* T, | |
| const Object& metadata_obj, | |
| const String& pragma_name, | |
| bool multiple, | |
| Object* options) { | |
| auto IG = T->isolate_group(); | |
| auto Z = T->zone(); | |
| // If there is a compile-time error while evaluating the metadata, we will | |
| // simply claim there was no @pragma annotation. | |
| if (metadata_obj.IsNull() || metadata_obj.IsLanguageError()) { | |
| return false; | |
| } | |
| ASSERT(metadata_obj.IsArray()); | |
| auto& metadata = Array::Cast(metadata_obj); | |
| auto& pragma_class = Class::Handle(Z, IG->object_store()->pragma_class()); | |
| if (pragma_class.IsNull()) { | |
| // Precompiler may drop pragma class. | |
| return false; | |
| } | |
| auto& pragma_name_field = | |
| Field::Handle(Z, pragma_class.LookupField(Symbols::name())); | |
| auto& pragma_options_field = | |
| Field::Handle(Z, pragma_class.LookupField(Symbols::options())); | |
| auto& pragma = Object::Handle(Z); | |
| bool found = false; | |
| auto& options_value = Object::Handle(Z); | |
| auto& results = GrowableObjectArray::Handle(Z); | |
| if (multiple) { | |
| ASSERT(options != nullptr); | |
| results ^= GrowableObjectArray::New(1); | |
| } | |
| for (intptr_t i = 0; i < metadata.Length(); ++i) { | |
| pragma = metadata.At(i); | |
| if (pragma.clazz() != pragma_class.ptr() || | |
| Instance::Cast(pragma).GetField(pragma_name_field) != | |
| pragma_name.ptr()) { | |
| continue; | |
| } | |
| options_value = Instance::Cast(pragma).GetField(pragma_options_field); | |
| found = true; | |
| if (multiple) { | |
| results.Add(options_value); | |
| continue; | |
| } | |
| if (options != nullptr) { | |
| *options = options_value.ptr(); | |
| } | |
| return true; | |
| } | |
| if (found && options != nullptr) { | |
| *options = results.ptr(); | |
| } | |
| return false; | |
| } | |
| bool Library::FindPragma(Thread* T, | |
| bool only_core, | |
| const Object& obj, | |
| const String& pragma_name, | |
| bool multiple, | |
| Object* options) { | |
| auto Z = T->zone(); | |
| auto& lib = Library::Handle(Z); | |
| if (obj.IsLibrary()) { | |
| lib = Library::Cast(obj).ptr(); | |
| } else if (obj.IsClass()) { | |
| auto& klass = Class::Cast(obj); | |
| if (!klass.has_pragma()) return false; | |
| lib = klass.library(); | |
| } else if (obj.IsFunction()) { | |
| auto& function = Function::Cast(obj); | |
| if (!function.has_pragma()) return false; | |
| lib = Class::Handle(Z, function.Owner()).library(); | |
| } else if (obj.IsField()) { | |
| auto& field = Field::Cast(obj); | |
| if (!field.has_pragma()) return false; | |
| lib = Class::Handle(Z, field.Owner()).library(); | |
| } else { | |
| UNREACHABLE(); | |
| } | |
| if (only_core && !lib.is_dart_scheme()) { | |
| return false; | |
| } | |
| Object& metadata_obj = Object::Handle(Z, lib.GetMetadata(obj)); | |
| if (metadata_obj.IsUnwindError()) { | |
| Report::LongJump(UnwindError::Cast(metadata_obj)); | |
| } | |
| return FindPragmaInMetadata(T, metadata_obj, pragma_name, multiple, options); | |
| } | |
| bool Function::IsDynamicInvocationForwarderName(const String& name) { | |
| return IsDynamicInvocationForwarderName(name.ptr()); | |
| } | |
| bool Function::IsDynamicInvocationForwarderName(StringPtr name) { | |
| return String::StartsWith(name, Symbols::DynamicPrefix().ptr()); | |
| } | |
| StringPtr Function::DemangleDynamicInvocationForwarderName(const String& name) { | |
| if (name.ptr() == Symbols::DynamicImplicitCall().ptr()) { | |
| return Symbols::call().ptr(); | |
| } | |
| const intptr_t kDynamicPrefixLength = 4; // "dyn:" | |
| ASSERT(Symbols::DynamicPrefix().Length() == kDynamicPrefixLength); | |
| return Symbols::New(Thread::Current(), name, kDynamicPrefixLength, | |
| name.Length() - kDynamicPrefixLength); | |
| } | |
| const String& Function::DropImplicitCallPrefix(const String& name) { | |
| if (name.ptr() == Symbols::DynamicImplicitCall().ptr()) { | |
| return Symbols::DynamicCall(); | |
| } | |
| return name; | |
| } | |
| StringPtr Function::CreateDynamicInvocationForwarderName(const String& name) { | |
| return Symbols::FromConcat(Thread::Current(), Symbols::DynamicPrefix(), name); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) || defined(DART_DYNAMIC_MODULES) | |
| FunctionPtr Function::CreateDynamicInvocationForwarder( | |
| const String& mangled_name) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| Function& forwarder = Function::Handle(zone); | |
| // Load with relaxed atomics to prevent data race with updating original's | |
| // properties that are overridden below for the copy anyway. | |
| forwarder ^= Object::Clone(*this, Heap::kOld, | |
| /*load_with_relaxed_atomics=*/true); | |
| forwarder.reset_unboxed_parameters_and_return(); | |
| forwarder.set_name(mangled_name); | |
| forwarder.set_is_native(false); | |
| // TODO(dartbug.com/37737): Currently, we intentionally keep the recognized | |
| // kind when creating the dynamic invocation forwarder. | |
| forwarder.set_kind(UntaggedFunction::kDynamicInvocationForwarder); | |
| forwarder.set_modifier(UntaggedFunction::kNoModifier); | |
| forwarder.set_is_debuggable(false); | |
| // TODO(vegorov) for error reporting reasons it is better to make this | |
| // function visible and instead use a TailCall to invoke the target. | |
| // Our TailCall instruction is not ready for such usage though it | |
| // blocks inlining and can't take Function-s only Code objects. | |
| forwarder.set_is_visible(false); | |
| #if defined(DART_DYNAMIC_MODULES) | |
| if (HasBytecode()) { | |
| forwarder.ClearBytecode(); | |
| } | |
| #endif | |
| forwarder.ClearICDataArray(); | |
| forwarder.ClearCode(); | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| forwarder.set_usage_counter(0); | |
| forwarder.set_deoptimization_counter(0); | |
| forwarder.set_optimized_instruction_count(0); | |
| forwarder.set_inlining_depth(0); | |
| forwarder.set_optimized_call_site_count(0); | |
| #endif | |
| forwarder.InheritKernelOffsetFrom(*this); | |
| forwarder.SetForwardingTarget(*this); | |
| #if defined(DART_DYNAMIC_MODULES) | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| // Allow the creation of a lazily created interpreted dynamic invocation | |
| // forwarders for compiled code that does not already have one created, | |
| // for example, when the only dynamic callers are in interpreted code. | |
| const bool attach_bytecode = true; | |
| #else | |
| const bool attach_bytecode = | |
| is_declared_in_bytecode() && !thread->isolate_group()->modular_aot_mode(); | |
| #endif | |
| if (attach_bytecode) { | |
| forwarder.AttachBytecode(Object::dynamic_invocation_forwarder_bytecode()); | |
| } | |
| #endif | |
| if (thread->isolate_group()->modular_aot_mode()) { | |
| forwarder.SetInstructionsSafe(StubCode::DynamicInvocationForwarder()); | |
| } | |
| return forwarder.ptr(); | |
| } | |
| FunctionPtr Function::GetDynamicInvocationForwarder( | |
| const String& mangled_name) const { | |
| ASSERT(IsDynamicInvocationForwarderName(mangled_name)); | |
| ASSERT(mangled_name.ptr() != Symbols::DynamicImplicitCall().ptr()); | |
| auto thread = Thread::Current(); | |
| auto zone = thread->zone(); | |
| const Class& owner = Class::Handle(zone, Owner()); | |
| Function& result = Function::Handle(zone); | |
| // First we'll try to find it without using locks. | |
| result = owner.GetInvocationDispatcher( | |
| mangled_name, Array::null_array(), | |
| UntaggedFunction::kDynamicInvocationForwarder, | |
| /*create_if_absent=*/false); | |
| if (!result.IsNull()) return result.ptr(); | |
| const bool needs_dyn_forwarder = NeedsDynamicInvocationForwarder(); | |
| if (!needs_dyn_forwarder) { | |
| return ptr(); | |
| } | |
| // If we failed to find it and possibly need to create it, use a write lock. | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| // Try to find it again & return if it was added in the mean time. | |
| result = owner.GetInvocationDispatcher( | |
| mangled_name, Array::null_array(), | |
| UntaggedFunction::kDynamicInvocationForwarder, | |
| /*create_if_absent=*/false); | |
| if (!result.IsNull()) return result.ptr(); | |
| // Otherwise create it & add it. | |
| result = CreateDynamicInvocationForwarder(mangled_name); | |
| owner.AddInvocationDispatcher(mangled_name, Array::null_array(), result); | |
| return result.ptr(); | |
| } | |
| bool Function::NeedsDynamicInvocationForwarder() const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| // Right now closures do not need a dyn:* forwarder. | |
| // See https://github.com/dart-lang/sdk/issues/40813 | |
| if (IsClosureFunction()) return false; | |
| // Method extractors have no parameters to check and return value is a closure | |
| // and therefore not an unboxed primitive type. | |
| if (IsMethodExtractor()) { | |
| return false; | |
| } | |
| // Record field getters have no parameters to check and 'dynamic' return type. | |
| if (IsRecordFieldGetter()) { | |
| return false; | |
| } | |
| // Invoke field dispatchers are dynamically generated, will invoke a getter to | |
| // obtain the field value and then invoke ".call()" on the result. | |
| // Those dynamically generated dispathers don't have proper kernel metadata | |
| // associated with them - we can therefore not query if there are dynamic | |
| // calls to them or not and are therefore conservative. | |
| if (IsInvokeFieldDispatcher()) { | |
| return true; | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| // The dyn:* forwarders perform unboxing of parameters before calling the | |
| // actual target (which accepts unboxed parameters) and boxes return values | |
| // of the return value. | |
| if (HasUnboxedParameters() || HasUnboxedReturnValue()) { | |
| return true; | |
| } | |
| if (MaxNumberOfParametersInRegisters(zone) > 0) { | |
| return true; | |
| } | |
| #endif | |
| // There are no parameters to type check for getters and if the return value | |
| // is boxed, then the dyn:* forwarder is not needed. | |
| if (IsImplicitGetterFunction()) { | |
| return false; | |
| } | |
| // Covariant parameters (both explicitly covariant and generic-covariant-impl) | |
| // are checked in the body of a function and therefore don't need checks in a | |
| // dynamic invocation forwarder. So dynamic invocation forwarder is only | |
| // needed if there are non-covariant parameters of non-top type. | |
| if (IsImplicitSetterFunction()) { | |
| const auto& field = Field::Handle(zone, accessor_field()); | |
| return !(field.is_covariant() || field.is_generic_covariant_impl()); | |
| } | |
| const auto& type_params = TypeParameters::Handle(zone, type_parameters()); | |
| if (!type_params.IsNull()) { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| if (thread->isolate_group()->modular_aot_mode()) { | |
| // Generic methods need dynamic invocation forwarder in order to | |
| // pass default type arguments. | |
| return true; | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| auto& bound = AbstractType::Handle(zone); | |
| for (intptr_t i = 0, n = type_params.Length(); i < n; ++i) { | |
| bound = type_params.BoundAt(i); | |
| if (!bound.IsTopType() && !type_params.IsGenericCovariantImplAt(i)) { | |
| return true; | |
| } | |
| } | |
| } | |
| const intptr_t num_params = NumParameters(); | |
| BitVector is_covariant(zone, num_params); | |
| BitVector is_generic_covariant_impl(zone, num_params); | |
| ReadParameterCovariance(&is_covariant, &is_generic_covariant_impl); | |
| auto& type = AbstractType::Handle(zone); | |
| for (intptr_t i = NumImplicitParameters(); i < num_params; ++i) { | |
| type = ParameterTypeAt(i); | |
| if (!type.IsTopType() && !is_generic_covariant_impl.Contains(i) && | |
| !is_covariant.Contains(i)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| void Function::ReadParameterCovariance( | |
| BitVector* is_covariant, | |
| BitVector* is_generic_covariant_impl) const { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| if (is_declared_in_bytecode()) { | |
| bytecode::BytecodeReader::ReadParameterCovariance( | |
| *this, is_covariant, is_generic_covariant_impl); | |
| return; | |
| } | |
| #endif | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| kernel::ReadParameterCovariance(*this, is_covariant, | |
| is_generic_covariant_impl); | |
| #endif | |
| } | |
| #endif | |
| bool AbstractType::InstantiateAndTestSubtype( | |
| AbstractType* subtype, | |
| AbstractType* supertype, | |
| const TypeArguments& instantiator_type_args, | |
| const TypeArguments& function_type_args) { | |
| if (!subtype->IsInstantiated()) { | |
| *subtype = subtype->InstantiateFrom( | |
| instantiator_type_args, function_type_args, kAllFree, Heap::kOld); | |
| } | |
| if (!supertype->IsInstantiated()) { | |
| *supertype = supertype->InstantiateFrom( | |
| instantiator_type_args, function_type_args, kAllFree, Heap::kOld); | |
| } | |
| return subtype->IsSubtypeOf(*supertype, Heap::kOld); | |
| } | |
| ArrayPtr Class::invocation_dispatcher_cache() const { | |
| return untag()->invocation_dispatcher_cache<std::memory_order_acquire>(); | |
| } | |
| void Class::Finalize() const { | |
| auto thread = Thread::Current(); | |
| auto isolate_group = thread->isolate_group(); | |
| ASSERT(!thread->isolate_group()->all_classes_finalized()); | |
| ASSERT(!is_finalized()); | |
| // Prefinalized classes have a VM internal representation and no Dart fields. | |
| // Their instance size is precomputed and field offsets are known. | |
| if (!is_prefinalized()) { | |
| // Compute offsets of instance fields, instance size and bitmap for unboxed | |
| // fields. | |
| const auto host_bitmap = CalculateFieldOffsets(); | |
| if (ptr() == isolate_group->class_table()->At(id())) { | |
| if (!ClassTable::IsTopLevelCid(id())) { | |
| // Unless class is top-level, which don't get instantiated, | |
| // sets the new size in the class table. | |
| isolate_group->class_table()->UpdateClassSize(id(), ptr()); | |
| isolate_group->class_table()->SetUnboxedFieldsMapAt(id(), host_bitmap); | |
| } | |
| } | |
| } | |
| #if defined(DEBUG) | |
| if (is_const()) { | |
| // Double-check that all fields are final (CFE should guarantee that if it | |
| // marks the class as having a constant constructor). | |
| auto Z = thread->zone(); | |
| const auto& super_class = Class::Handle(Z, SuperClass()); | |
| ASSERT(super_class.IsNull() || super_class.is_const()); | |
| const auto& fields = Array::Handle(Z, this->fields()); | |
| auto& field = Field::Handle(Z); | |
| for (intptr_t i = 0; i < fields.Length(); ++i) { | |
| field ^= fields.At(i); | |
| ASSERT(field.is_static() || field.is_final()); | |
| } | |
| } | |
| #endif | |
| set_is_finalized(); | |
| } | |
| #if defined(DEBUG) | |
| static bool IsMutatorOrAtDeoptSafepoint() { | |
| Thread* thread = Thread::Current(); | |
| return thread->IsDartMutatorThread() || thread->OwnsDeoptSafepoint(); | |
| } | |
| #endif | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| class CHACodeArray : public WeakCodeReferences { | |
| public: | |
| explicit CHACodeArray(const Class& cls) | |
| : WeakCodeReferences(WeakArray::Handle(cls.dependent_code())), | |
| cls_(cls) {} | |
| virtual void UpdateArrayTo(const WeakArray& value) { | |
| cls_.set_dependent_code(value); | |
| } | |
| virtual void ReportDeoptimization(const Code& code) { | |
| if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) { | |
| Function& function = Function::Handle(code.function()); | |
| THR_Print("Deoptimizing %s because CHA optimized (%s).\n", | |
| function.ToFullyQualifiedCString(), cls_.ToCString()); | |
| } | |
| } | |
| virtual void ReportSwitchingCode(const Code& code) { | |
| if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) { | |
| Function& function = Function::Handle(code.function()); | |
| THR_Print( | |
| "Switching %s to unoptimized code because CHA invalid" | |
| " (%s)\n", | |
| function.ToFullyQualifiedCString(), cls_.ToCString()); | |
| } | |
| } | |
| private: | |
| const Class& cls_; | |
| DISALLOW_COPY_AND_ASSIGN(CHACodeArray); | |
| }; | |
| void Class::RegisterCHACode(const Code& code) { | |
| if (FLAG_trace_cha) { | |
| THR_Print("RegisterCHACode '%s' depends on class '%s'\n", | |
| Function::Handle(code.function()).ToQualifiedCString(), | |
| ToCString()); | |
| } | |
| DEBUG_ASSERT(IsMutatorOrAtDeoptSafepoint()); | |
| ASSERT(code.is_optimized()); | |
| CHACodeArray a(*this); | |
| a.Register(code); | |
| } | |
| void Class::DisableCHAOptimizedCode(const Class& subclass) { | |
| DEBUG_ASSERT( | |
| IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| CHACodeArray a(*this); | |
| if (FLAG_trace_deoptimization && a.HasCodes()) { | |
| if (subclass.IsNull()) { | |
| THR_Print("Deopt for CHA (all)\n"); | |
| } else { | |
| THR_Print("Deopt for CHA (new subclass %s)\n", subclass.ToCString()); | |
| } | |
| } | |
| a.DisableCode(/*are_mutators_stopped=*/false); | |
| } | |
| void Class::DisableAllCHAOptimizedCode() { | |
| DisableCHAOptimizedCode(Class::Handle()); | |
| } | |
| WeakArrayPtr Class::dependent_code() const { | |
| DEBUG_ASSERT( | |
| IsolateGroup::Current()->program_lock()->IsCurrentThreadReader()); | |
| return untag()->dependent_code(); | |
| } | |
| void Class::set_dependent_code(const WeakArray& array) const { | |
| DEBUG_ASSERT( | |
| IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| untag()->set_dependent_code(array.ptr()); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| bool Class::TraceAllocation(IsolateGroup* isolate_group) const { | |
| #ifndef PRODUCT | |
| auto class_table = isolate_group->class_table(); | |
| return class_table->ShouldTraceAllocationFor(id()); | |
| #else | |
| return false; | |
| #endif | |
| } | |
| void Class::SetTraceAllocation(bool trace_allocation) const { | |
| #ifndef PRODUCT | |
| auto isolate_group = IsolateGroup::Current(); | |
| const bool changed = trace_allocation != this->TraceAllocation(isolate_group); | |
| if (changed) { | |
| auto class_table = isolate_group->class_table(); | |
| class_table->SetTraceAllocationFor(id(), trace_allocation); | |
| #ifdef TARGET_ARCH_IA32 | |
| DisableAllocationStub(); | |
| #endif | |
| } | |
| #else | |
| UNREACHABLE(); | |
| #endif | |
| } | |
| // Conventions: | |
| // * For throwing a NSM in a library or top-level class (i.e., level is | |
| // kTopLevel), if a method was found but was incompatible, we pass the | |
| // signature of the found method as a string, otherwise the null instance. | |
| // * Otherwise, for throwing a NSM in a class klass we use its runtime type as | |
| // receiver, i.e., klass.RareType(). | |
| static ObjectPtr ThrowNoSuchMethod(const Instance& receiver, | |
| const String& function_name, | |
| const Array& arguments, | |
| const Array& argument_names, | |
| const InvocationMirror::Level level, | |
| const InvocationMirror::Kind kind) { | |
| const Smi& invocation_type = | |
| Smi::Handle(Smi::New(InvocationMirror::EncodeType(level, kind))); | |
| ASSERT(!receiver.IsNull() || level == InvocationMirror::Level::kTopLevel); | |
| ASSERT(level != InvocationMirror::Level::kTopLevel || receiver.IsString()); | |
| const Array& args = Array::Handle(Array::New(7)); | |
| args.SetAt(0, receiver); | |
| args.SetAt(1, function_name); | |
| args.SetAt(2, invocation_type); | |
| args.SetAt(3, Object::smi_zero()); // Type arguments length. | |
| args.SetAt(4, Object::null_type_arguments()); | |
| args.SetAt(5, arguments); | |
| args.SetAt(6, argument_names); | |
| const Library& libcore = Library::Handle(Library::CoreLibrary()); | |
| const Class& cls = | |
| Class::Handle(libcore.LookupClass(Symbols::NoSuchMethodError())); | |
| ASSERT(!cls.IsNull()); | |
| const auto& error = cls.EnsureIsFinalized(Thread::Current()); | |
| ASSERT(error == Error::null()); | |
| const Function& throwNew = | |
| Function::Handle(cls.LookupFunctionAllowPrivate(Symbols::ThrowNew())); | |
| return DartEntry::InvokeFunction(throwNew, args); | |
| } | |
| static ObjectPtr ThrowTypeError(const TokenPosition token_pos, | |
| const Instance& src_value, | |
| const AbstractType& dst_type, | |
| const String& dst_name) { | |
| const Array& args = Array::Handle(Array::New(4)); | |
| const Smi& pos = Smi::Handle(Smi::New(token_pos.Serialize())); | |
| args.SetAt(0, pos); | |
| args.SetAt(1, src_value); | |
| args.SetAt(2, dst_type); | |
| args.SetAt(3, dst_name); | |
| const Library& libcore = Library::Handle(Library::CoreLibrary()); | |
| const Class& cls = | |
| Class::Handle(libcore.LookupClassAllowPrivate(Symbols::TypeError())); | |
| const auto& error = cls.EnsureIsFinalized(Thread::Current()); | |
| ASSERT(error == Error::null()); | |
| const Function& throwNew = | |
| Function::Handle(cls.LookupFunctionAllowPrivate(Symbols::ThrowNew())); | |
| return DartEntry::InvokeFunction(throwNew, args); | |
| } | |
| static bool WriteQualifiedMemberName(Zone* zone, | |
| BaseTextBuffer* buffer, | |
| const Object& member) { | |
| if (member.IsFunction()) { | |
| const auto& fun = Function::Cast(member); | |
| const auto& cls = Class::Handle(zone, fun.Owner()); | |
| if (WriteQualifiedMemberName(zone, buffer, cls)) { | |
| buffer->AddString("."); | |
| } | |
| buffer->AddString(fun.UserVisibleNameCString()); | |
| if (!fun.IsRegularFunction()) { | |
| buffer->Printf(" (kind %s)", Function::KindToCString(fun.kind())); | |
| } | |
| } else if (member.IsField()) { | |
| const auto& field = Field::Cast(member); | |
| const auto& cls = Class::Handle(field.Owner()); | |
| if (WriteQualifiedMemberName(zone, buffer, cls)) { | |
| buffer->AddString("."); | |
| } | |
| const auto& name = String::Handle(field.name()); | |
| buffer->AddString(name.ToCString()); | |
| } else if (member.IsClass()) { | |
| const Class& cls = Class::Cast(member); | |
| const Library& lib = Library::Handle(cls.library()); | |
| if (!lib.IsNull()) { | |
| const String& name = String::Handle(lib.url()); | |
| buffer->Printf("%s::", name.ToCString()); | |
| } | |
| if (!cls.IsTopLevel()) { | |
| buffer->AddString(cls.UserVisibleNameCString()); | |
| } | |
| return !cls.IsTopLevel(); | |
| } | |
| return false; | |
| } | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| DART_WARN_UNUSED_RESULT | |
| static bool VerifyEntryPointHelper(const Object& object, | |
| EntryPointPragma expected) { | |
| // Annotations are discarded in the AOT snapshot, so we can't determine | |
| // precisely if this member was marked as an entry-point. Instead, we use | |
| // "has_pragma()" as a proxy, since that bit is usually retained. | |
| if (object.IsClass()) { | |
| return Class::Cast(object).has_pragma() && | |
| expected == EntryPointPragma::kAlways; | |
| } | |
| if (object.IsField()) { | |
| return Field::Cast(object).has_pragma() && | |
| expected != EntryPointPragma::kCallOnly; | |
| } | |
| if (!object.IsFunction()) { | |
| FATAL("Unexpected annotated node %s", object.ToCString()); | |
| } | |
| const auto& f = Function::Cast(object); | |
| if (!f.has_pragma()) return false; | |
| // For non-closurization uses, if the function does not have code | |
| // attached, that means it was not properly annotated to allow the use. | |
| if (f.IsGetterFunction()) { | |
| return EntryPointPragmaUtils::AllowsGet(expected) && f.HasCode(); | |
| } | |
| if (f.IsSetterFunction()) { | |
| return EntryPointPragmaUtils::AllowsSet(expected) && f.HasCode(); | |
| } | |
| if (EntryPointPragmaUtils::AllowsCall(expected)) { | |
| return f.HasCode(); | |
| } | |
| if (f.IsConstructor()) { | |
| // We're not checking for a call, which is the only allowed access. | |
| return false; | |
| } | |
| if (EntryPointPragmaUtils::AllowsGet(expected)) { | |
| // For non-getter functions, a 'get' entry point expectation denotes | |
| // closurization. The precompiler saves the implicit closure | |
| // function information if the function is properly annotated. | |
| return f.HasImplicitClosureFunction(); | |
| } | |
| return false; | |
| } | |
| #endif | |
| DART_WARN_UNUSED_RESULT | |
| static ErrorPtr VerifyEntryPoint(const Library& lib, | |
| const Object& member, | |
| const Object& annotated, | |
| EntryPointPragma expected) { | |
| ASSERT(expected != EntryPointPragma::kNever); | |
| auto* const thread = Thread::Current(); | |
| auto* const zone = thread->zone(); | |
| // Special cases for certain types of functions that should delegate to | |
| // different members within the same class. | |
| if (member.IsFunction()) { | |
| const auto& fun = Function::Cast(member); | |
| if (fun.IsMethodExtractor()) { | |
| ASSERT(expected == EntryPointPragma::kGetterOnly); | |
| // To be able to call a method extractor, the original method needs to | |
| // be annotated for closure retrieval. | |
| const auto& closure = | |
| Function::Handle(zone, fun.extracted_method_closure()); | |
| return VerifyEntryPoint( | |
| lib, closure, annotated.IsNull() ? annotated : closure, expected); | |
| } else if (fun.IsImplicitClosureFunction()) { | |
| // Check the annotations on the parent function instead. | |
| const auto& parent = Function::Handle(zone, fun.parent_function()); | |
| return VerifyEntryPoint( | |
| lib, parent, annotated.IsNull() ? annotated : parent, expected); | |
| } else if (fun.IsImplicitGetterOrSetter()) { | |
| ASSERT(fun.IsImplicitSetterFunction() || | |
| expected == EntryPointPragma::kGetterOnly); | |
| ASSERT(!fun.IsImplicitSetterFunction() || | |
| expected == EntryPointPragma::kSetterOnly); | |
| // For implicit getters or setters, the field must be properly annotated. | |
| const auto& field = Field::Handle(zone, fun.accessor_field()); | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (!fun.HasCode()) { | |
| return VerifyEntryPoint(lib, field, Object::null_object(), expected); | |
| } | |
| #endif | |
| return VerifyEntryPoint(lib, field, | |
| annotated.IsNull() ? annotated : field, expected); | |
| } | |
| } | |
| // For method and fields, the expected pragma should always be more specific. | |
| ASSERT_EQUAL(member.IsClass(), expected == EntryPointPragma::kAlways); | |
| // A null annotated object is used to signal an error should always be thrown. | |
| if (!annotated.IsNull()) { | |
| bool is_marked_entrypoint = false; | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| is_marked_entrypoint = VerifyEntryPointHelper(annotated, expected); | |
| #else | |
| const auto& metadata = Object::Handle(zone, lib.GetMetadata(annotated)); | |
| if (metadata.IsError()) { | |
| return Error::RawCast(metadata.ptr()); | |
| } | |
| ASSERT(!metadata.IsNull() && metadata.IsArray()); | |
| const EntryPointPragma pragma = | |
| FindEntryPointPragma(thread->isolate_group(), Array::Cast(metadata), | |
| &Field::Handle(zone), &Object::Handle(zone)); | |
| ASSERT(pragma != EntryPointPragma::kCallOnly || annotated.IsFunction()); | |
| is_marked_entrypoint = | |
| pragma == EntryPointPragma::kAlways || pragma == expected; | |
| #endif | |
| if (is_marked_entrypoint) { | |
| return Error::null(); | |
| } | |
| } | |
| ZoneTextBuffer buffer(zone); | |
| switch (expected) { | |
| case EntryPointPragma::kAlways: | |
| buffer.AddString("ERROR: To access '"); | |
| WriteQualifiedMemberName(zone, &buffer, member); | |
| buffer.AddString("' from native code, it must be annotated.\n"); | |
| break; | |
| case EntryPointPragma::kCallOnly: | |
| ASSERT(member.IsFunction()); | |
| buffer.AddString("ERROR: To invoke '"); | |
| WriteQualifiedMemberName(zone, &buffer, member); | |
| buffer.AddString("' from native code, it must be annotated.\n"); | |
| break; | |
| case EntryPointPragma::kGetterOnly: | |
| if (member.IsField() || | |
| (member.IsFunction() && Function::Cast(member).IsGetterFunction())) { | |
| buffer.AddString("ERROR: To retrieve the value of '"); | |
| WriteQualifiedMemberName(zone, &buffer, member); | |
| buffer.AddString("' from native code, it must be annotated.\n"); | |
| } else { | |
| const auto& function = Function::Cast(member); | |
| // Other types of functions cannot be closurized. | |
| if (!function.IsRegularFunction()) { | |
| // Other types of functions reaching here are internal errors. | |
| buffer.AddString("Cannot get closure value for method '"); | |
| WriteQualifiedMemberName(zone, &buffer, function); | |
| buffer.Printf("' of kind %s", | |
| Function::KindToCString(function.kind())); | |
| FATAL("%s", buffer.buffer()); | |
| } | |
| buffer.AddString("ERROR: To closurize '"); | |
| WriteQualifiedMemberName(zone, &buffer, function); | |
| buffer.AddString("' from native code, it must be annotated.\n"); | |
| } | |
| break; | |
| case EntryPointPragma::kSetterOnly: | |
| if (member.IsField() || | |
| (member.IsFunction() && Function::Cast(member).IsSetterFunction())) { | |
| buffer.AddString("ERROR: To set the value of '"); | |
| WriteQualifiedMemberName(zone, &buffer, member); | |
| buffer.AddString("' from native code, it must be annotated.\n"); | |
| } else { | |
| // Other types of functions reaching here are internal errors. | |
| buffer.AddString("Cannot set value of '"); | |
| WriteQualifiedMemberName(zone, &buffer, member); | |
| buffer.AddString("'"); | |
| FATAL("%s", buffer.buffer()); | |
| } | |
| break; | |
| default: | |
| FATAL("Unexpected EntryPointPragma value %" Pd, | |
| static_cast<intptr_t>(expected)); | |
| } | |
| buffer.AddString( | |
| "ERROR: See https://github.com/dart-lang/sdk/blob/master/runtime/" | |
| "docs/compiler/aot/entry_point_pragma.md\n"); | |
| OS::PrintErr("%s", buffer.buffer()); | |
| return ApiError::New(String::Handle(String::New(buffer.buffer()))); | |
| } | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| DART_WARN_UNUSED_RESULT | |
| static ErrorPtr EntryPointFunctionInvocationError(const Function& function) { | |
| // Ensures a failure by passing null for the annotated object. | |
| return VerifyEntryPoint(Library::Handle(Library::null()), function, | |
| Object::null_object(), EntryPointPragma::kCallOnly); | |
| } | |
| #endif | |
| ObjectPtr Class::InvokeGetter(const String& getter_name, | |
| bool check_is_entrypoint, | |
| bool respect_reflectable, | |
| bool for_invocation) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| CHECK_ERROR(EnsureIsFinalized(thread)); | |
| // Note static fields do not have implicit getters. | |
| const Field& field = Field::Handle(zone, LookupStaticField(getter_name)); | |
| if (field.IsNull() || field.IsUninitialized()) { | |
| const String& internal_getter_name = | |
| String::Handle(zone, Field::GetterName(getter_name)); | |
| Function& getter = | |
| Function::Handle(zone, LookupStaticFunction(internal_getter_name)); | |
| if (getter.IsNull() || (respect_reflectable && !getter.is_reflectable())) { | |
| if (for_invocation) { | |
| // LookupStaticFunction(getter_name) has already failed in Invoke(). | |
| // Instead of throwing an NSM, indicate that no getter was found | |
| // by returning a value that cannot be returned by a getter (here, | |
| // the sentinel value). | |
| return Object::sentinel().ptr(); | |
| } else if (getter.IsNull()) { | |
| getter = LookupStaticFunction(getter_name); | |
| if (!getter.IsNull()) { | |
| if (getter.SafeToClosurize()) { | |
| if (check_is_entrypoint) { | |
| CHECK_ERROR( | |
| getter.VerifyEntryPoint(EntryPointPragma::kGetterOnly)); | |
| } | |
| // Looking for a getter but found a regular method: closurize it. | |
| const Function& closure_function = | |
| Function::Handle(zone, getter.ImplicitClosureFunction()); | |
| return closure_function.ImplicitStaticClosure(); | |
| } | |
| } | |
| } | |
| return ThrowNoSuchMethod(AbstractType::Handle(zone, RareType()), | |
| getter_name, Object::null_array(), | |
| Object::null_array(), InvocationMirror::kStatic, | |
| InvocationMirror::kGetter); | |
| } | |
| if (check_is_entrypoint) { | |
| CHECK_ERROR(getter.VerifyEntryPoint(EntryPointPragma::kGetterOnly)); | |
| } | |
| // Invoke the getter and return the result. | |
| return DartEntry::InvokeFunction(getter, Object::empty_array()); | |
| } | |
| if (check_is_entrypoint) { | |
| CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kGetterOnly)); | |
| } | |
| return field.StaticValue(); | |
| } | |
| ObjectPtr Class::InvokeSetter(const String& setter_name, | |
| const Instance& value, | |
| bool check_is_entrypoint, | |
| bool respect_reflectable) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| CHECK_ERROR(EnsureIsFinalized(thread)); | |
| // Check for real fields and user-defined setters. | |
| const Field& field = Field::Handle(zone, LookupStaticField(setter_name)); | |
| const String& internal_setter_name = | |
| String::Handle(zone, Field::SetterName(setter_name)); | |
| if (field.IsNull()) { | |
| const Function& setter = | |
| Function::Handle(zone, LookupStaticFunction(internal_setter_name)); | |
| const int kNumArgs = 1; | |
| const Array& args = Array::Handle(zone, Array::New(kNumArgs)); | |
| args.SetAt(0, value); | |
| if (setter.IsNull() || (respect_reflectable && !setter.is_reflectable())) { | |
| return ThrowNoSuchMethod(AbstractType::Handle(zone, RareType()), | |
| internal_setter_name, args, Object::null_array(), | |
| InvocationMirror::kStatic, | |
| InvocationMirror::kSetter); | |
| } | |
| const auto& type = AbstractType::Handle(zone, setter.ParameterTypeAt(0)); | |
| if (!value.RuntimeTypeIsSubtypeOf(type, Object::null_type_arguments(), | |
| Object::null_type_arguments())) { | |
| const String& argument_name = | |
| String::Handle(zone, setter.ParameterNameAt(0)); | |
| return ThrowTypeError(setter.token_pos(), value, type, argument_name); | |
| } | |
| if (check_is_entrypoint) { | |
| CHECK_ERROR(setter.VerifyEntryPoint(EntryPointPragma::kSetterOnly)); | |
| } | |
| // Invoke the setter and return the result. | |
| return DartEntry::InvokeFunction(setter, args); | |
| } | |
| if (field.is_final() || (respect_reflectable && !field.is_reflectable())) { | |
| const int kNumArgs = 1; | |
| const Array& args = Array::Handle(zone, Array::New(kNumArgs)); | |
| args.SetAt(0, value); | |
| return ThrowNoSuchMethod(AbstractType::Handle(zone, RareType()), | |
| internal_setter_name, args, Object::null_array(), | |
| InvocationMirror::kStatic, | |
| InvocationMirror::kSetter); | |
| } | |
| const auto& type = AbstractType::Handle(zone, field.type()); | |
| if (!value.RuntimeTypeIsSubtypeOf(type, Object::null_type_arguments(), | |
| Object::null_type_arguments())) { | |
| const String& argument_name = String::Handle(zone, field.name()); | |
| return ThrowTypeError(field.token_pos(), value, type, argument_name); | |
| } | |
| if (check_is_entrypoint) { | |
| CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kSetterOnly)); | |
| } | |
| field.SetStaticValue(value); | |
| return value.ptr(); | |
| } | |
| // Creates a new array of boxed arguments suitable for invoking the callable | |
| // from the original boxed arguments for a static call. Also sets the contents | |
| // of the handle pointed to by [callable_args_desc_array_out] to an appropriate | |
| // arguments descriptor array for the new arguments. | |
| // | |
| // Assumes [arg_names] are consistent with [static_args_descriptor]. | |
| static ArrayPtr CreateCallableArgumentsFromStatic( | |
| Zone* zone, | |
| const Instance& receiver, | |
| const Array& static_args, | |
| const Array& arg_names, | |
| const ArgumentsDescriptor& static_args_descriptor) { | |
| const intptr_t num_static_type_args = static_args_descriptor.TypeArgsLen(); | |
| const intptr_t num_static_args = static_args_descriptor.Count(); | |
| // Double check that the static args descriptor expects boxed arguments | |
| // and the static args descriptor is consistent with the static arguments. | |
| ASSERT_EQUAL(static_args_descriptor.Size(), num_static_args); | |
| ASSERT_EQUAL(static_args.Length(), | |
| num_static_args + (num_static_type_args > 0 ? 1 : 0)); | |
| // Add an additional slot to store the callable as the receiver. | |
| const auto& callable_args = | |
| Array::Handle(zone, Array::New(static_args.Length() + 1)); | |
| const intptr_t first_arg_index = static_args_descriptor.FirstArgIndex(); | |
| auto& temp = Object::Handle(zone); | |
| // Copy the static args into the corresponding slots of the callable args. | |
| if (num_static_type_args > 0) { | |
| temp = static_args.At(0); | |
| callable_args.SetAt(0, temp); | |
| } | |
| for (intptr_t i = first_arg_index; i < static_args.Length(); i++) { | |
| temp = static_args.At(i); | |
| callable_args.SetAt(i + 1, temp); | |
| } | |
| // Set the receiver slot in the callable args. | |
| callable_args.SetAt(first_arg_index, receiver); | |
| return callable_args.ptr(); | |
| } | |
| ObjectPtr Class::Invoke(const String& function_name, | |
| const Array& args, | |
| const Array& arg_names, | |
| bool check_is_entrypoint, | |
| bool respect_reflectable) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| CHECK_ERROR(EnsureIsFinalized(thread)); | |
| // We don't pass any explicit type arguments, which will be understood as | |
| // using dynamic for any function type arguments by lower layers. | |
| const int kTypeArgsLen = 0; | |
| const Array& args_descriptor_array = Array::Handle( | |
| zone, ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(), | |
| arg_names, Heap::kNew)); | |
| ArgumentsDescriptor args_descriptor(args_descriptor_array); | |
| Function& function = | |
| Function::Handle(zone, LookupStaticFunction(function_name)); | |
| if (function.IsNull()) { | |
| // Didn't find a method: try to find a getter and invoke call on its result. | |
| const Object& getter_result = | |
| Object::Handle(zone, InvokeGetter(function_name, check_is_entrypoint, | |
| respect_reflectable, | |
| /*for_invocation=*/true)); | |
| if (getter_result.IsError()) { | |
| return getter_result.ptr(); | |
| } else if (getter_result.ptr() != Object::sentinel().ptr()) { | |
| const auto& call_args_descriptor_array = Array::Handle( | |
| zone, ArgumentsDescriptor::NewBoxed(args_descriptor.TypeArgsLen(), | |
| args_descriptor.Count() + 1, | |
| arg_names, Heap::kNew)); | |
| const auto& call_args = Array::Handle( | |
| zone, | |
| CreateCallableArgumentsFromStatic(zone, Instance::Cast(getter_result), | |
| args, arg_names, args_descriptor)); | |
| return DartEntry::InvokeClosure(thread, call_args, | |
| call_args_descriptor_array); | |
| } | |
| } | |
| if (function.IsNull() || | |
| !function.AreValidArguments(args_descriptor, nullptr) || | |
| (respect_reflectable && !function.is_reflectable())) { | |
| return ThrowNoSuchMethod( | |
| AbstractType::Handle(zone, RareType()), function_name, args, arg_names, | |
| InvocationMirror::kStatic, InvocationMirror::kMethod); | |
| } | |
| // This is a static function, so we pass an empty instantiator tav. | |
| ASSERT(function.is_static()); | |
| ObjectPtr type_error = function.DoArgumentTypesMatch( | |
| args, args_descriptor, Object::empty_type_arguments()); | |
| if (type_error != Error::null()) { | |
| return type_error; | |
| } | |
| if (check_is_entrypoint) { | |
| CHECK_ERROR(function.VerifyEntryPoint(EntryPointPragma::kCallOnly)); | |
| } | |
| return DartEntry::InvokeFunction(function, args, args_descriptor_array); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| static ObjectPtr LoadExpressionEvaluationFunction( | |
| Thread* thread, | |
| const ExternalTypedData& kernel_buffer, | |
| const Class& klass) { | |
| Zone* zone = thread->zone(); | |
| #if defined(DART_DYNAMIC_MODULES) | |
| if (Dart_IsBytecode( | |
| reinterpret_cast<const uint8_t*>(kernel_buffer.DataAddr(0)), | |
| kernel_buffer.LengthInBytes())) { | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| bytecode::BytecodeLoader loader(thread, kernel_buffer); | |
| loader.SetExpressionEvaluationRealClass(klass); | |
| loader.LoadBytecode(); | |
| return loader.GetExpressionEvaluationFunction(); | |
| } | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| std::unique_ptr<kernel::Program> kernel_pgm = | |
| kernel::Program::ReadFromTypedData(kernel_buffer); | |
| if (kernel_pgm == nullptr) { | |
| return ApiError::New(String::Handle( | |
| zone, String::New("Kernel isolate returned ill-formed kernel."))); | |
| } | |
| auto& result = Object::Handle(zone); | |
| { | |
| kernel::KernelLoader loader(kernel_pgm.get(), | |
| /*uri_to_source_table=*/nullptr); | |
| result = loader.LoadExpressionEvaluationFunction(klass); | |
| kernel_pgm.reset(); | |
| } | |
| if (result.IsError()) return result.ptr(); | |
| return Function::Cast(result).ptr(); | |
| } | |
| static bool EvaluationFunctionNeedsReceiver(Thread* thread, | |
| Zone* zone, | |
| const Function& eval_function) { | |
| auto parsed_function = new ParsedFunction( | |
| thread, Function::ZoneHandle(zone, eval_function.ptr())); | |
| parsed_function->EnsureKernelScopes(); | |
| return parsed_function->is_receiver_used(); | |
| } | |
| static ObjectPtr EvaluateCompiledExpressionHelper( | |
| Zone* zone, | |
| const Function& eval_function, | |
| const Array& type_definitions, | |
| const Array& arguments, | |
| const TypeArguments& type_arguments) { | |
| // type_arguments is null if all type arguments are dynamic. | |
| if (type_definitions.Length() == 0 || type_arguments.IsNull()) { | |
| return DartEntry::InvokeFunction(eval_function, arguments); | |
| } | |
| intptr_t num_type_args = type_arguments.Length(); | |
| const auto& real_arguments = | |
| Array::Handle(zone, Array::New(arguments.Length() + 1)); | |
| real_arguments.SetAt(0, type_arguments); | |
| Object& arg = Object::Handle(zone); | |
| for (intptr_t i = 0; i < arguments.Length(); ++i) { | |
| arg = arguments.At(i); | |
| real_arguments.SetAt(i + 1, arg); | |
| } | |
| const Array& args_desc = | |
| Array::Handle(zone, ArgumentsDescriptor::NewBoxed( | |
| num_type_args, arguments.Length(), Heap::kNew)); | |
| return DartEntry::InvokeFunction(eval_function, real_arguments, args_desc); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| ObjectPtr Library::EvaluateCompiledExpression( | |
| const ExternalTypedData& kernel_buffer, | |
| const Array& type_definitions, | |
| const Array& arguments, | |
| const TypeArguments& type_arguments) const { | |
| const auto& klass = Class::Handle(toplevel_class()); | |
| return klass.EvaluateCompiledExpression(kernel_buffer, type_definitions, | |
| arguments, type_arguments); | |
| } | |
| ObjectPtr Class::EvaluateCompiledExpression( | |
| const ExternalTypedData& kernel_buffer, | |
| const Array& type_definitions, | |
| const Array& arguments, | |
| const TypeArguments& type_arguments) const { | |
| auto thread = Thread::Current(); | |
| const auto& library = Library::Handle(thread->zone(), this->library()); | |
| return Instance::EvaluateCompiledExpression( | |
| thread, Instance::null_object(), library, *this, kernel_buffer, | |
| type_definitions, arguments, type_arguments); | |
| } | |
| ObjectPtr Instance::EvaluateCompiledExpression( | |
| const Class& klass, | |
| const ExternalTypedData& kernel_buffer, | |
| const Array& type_definitions, | |
| const Array& arguments, | |
| const TypeArguments& type_arguments) const { | |
| auto thread = Thread::Current(); | |
| auto zone = thread->zone(); | |
| const auto& library = Library::Handle(zone, klass.library()); | |
| return Instance::EvaluateCompiledExpression(thread, *this, library, klass, | |
| kernel_buffer, type_definitions, | |
| arguments, type_arguments); | |
| } | |
| ObjectPtr Instance::EvaluateCompiledExpression( | |
| Thread* thread, | |
| const Object& receiver, | |
| const Library& library, | |
| const Class& klass, | |
| const ExternalTypedData& kernel_buffer, | |
| const Array& type_definitions, | |
| const Array& arguments, | |
| const TypeArguments& type_arguments) { | |
| Zone* zone = thread->zone(); | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| const auto& error_str = String::Handle( | |
| zone, | |
| String::New("Expression evaluation not available in precompiled mode.")); | |
| return ApiError::New(error_str); | |
| #else | |
| if (IsInternalOnlyClassId(klass.id()) || (klass.id() == kTypeArgumentsCid)) { | |
| const auto& exception = Instance::Handle( | |
| zone, String::New("Expressions can be evaluated only with regular Dart " | |
| "instances/classes.")); | |
| return UnhandledException::New(exception, StackTrace::null_instance()); | |
| } | |
| const auto& result = Object::Handle( | |
| zone, LoadExpressionEvaluationFunction(thread, kernel_buffer, klass)); | |
| if (result.IsError()) return result.ptr(); | |
| const auto& eval_function = Function::Cast(result); | |
| #if defined(DEBUG) | |
| for (intptr_t i = 0; i < arguments.Length(); ++i) { | |
| ASSERT(arguments.At(i) != Object::optimized_out().ptr()); | |
| ASSERT(arguments.At(i) != Object::sentinel().ptr()); | |
| } | |
| #endif // defined(DEBUG) | |
| auto& all_arguments = Array::Handle(zone, arguments.ptr()); | |
| if (!eval_function.is_static()) { | |
| // `this` may be optimized out (e.g. not accessible from breakpoint due to | |
| // not being captured by closure). We allow this as long as the evaluation | |
| // function doesn't actually need `this`. | |
| if (receiver.IsNull() || receiver.ptr() == Object::optimized_out().ptr()) { | |
| if (EvaluationFunctionNeedsReceiver(thread, zone, eval_function)) { | |
| return Object::optimized_out().ptr(); | |
| } | |
| } | |
| all_arguments = Array::New(1 + arguments.Length()); | |
| auto& param = PassiveObject::Handle(); | |
| all_arguments.SetAt(0, receiver); | |
| for (intptr_t i = 0; i < arguments.Length(); i++) { | |
| param = arguments.At(i); | |
| all_arguments.SetAt(i + 1, param); | |
| } | |
| } | |
| return EvaluateCompiledExpressionHelper(zone, eval_function, type_definitions, | |
| all_arguments, type_arguments); | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| void Class::EnsureDeclarationLoaded() const { | |
| if (!is_declaration_loaded()) { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| // Loading of class declaration can be postponed until needed | |
| // if class comes from bytecode. | |
| if (is_declared_in_bytecode()) { | |
| Thread* thread = Thread::Current(); | |
| SafepointWriteRwLocker ml(thread, | |
| thread->isolate_group()->program_lock()); | |
| if (is_declaration_loaded()) { | |
| return; | |
| } | |
| bytecode::BytecodeReader::LoadClassDeclaration(*this); | |
| ASSERT(is_declaration_loaded()); | |
| ASSERT(is_type_finalized()); | |
| return; | |
| } | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| FATAL("Unable to use class %s which is not loaded yet.", ToCString()); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| } | |
| // Ensure that top level parsing of the class has been done. | |
| ErrorPtr Class::EnsureIsFinalized(Thread* thread) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| RELEASE_ASSERT(is_finalized()); | |
| return Error::null(); | |
| #else | |
| { | |
| SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| if (is_finalized()) { | |
| return Error::null(); | |
| } | |
| } | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| if (is_finalized()) { | |
| return Error::null(); | |
| } | |
| LeaveCompilerScope ncs(thread); | |
| ASSERT(thread != nullptr); | |
| const Error& error = | |
| Error::Handle(thread->zone(), ClassFinalizer::LoadClassMembers(*this)); | |
| if (!error.IsNull()) { | |
| ASSERT(thread == Thread::Current()); | |
| if (thread->long_jump_base() != nullptr) { | |
| Report::LongJump(error); | |
| UNREACHABLE(); | |
| } | |
| } | |
| return error.ptr(); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| } | |
| // Ensure that code outdated by finalized class is cleaned up, new instance of | |
| // this class is ready to be allocated. | |
| ErrorPtr Class::EnsureIsAllocateFinalized(Thread* thread) const { | |
| ASSERT(!IsNull()); | |
| if (is_allocate_finalized()) { | |
| return Error::null(); | |
| } | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| if (is_allocate_finalized()) { | |
| return Error::null(); | |
| } | |
| ASSERT(thread != nullptr); | |
| Error& error = Error::Handle(thread->zone(), EnsureIsFinalized(thread)); | |
| if (!error.IsNull()) { | |
| ASSERT(thread == Thread::Current()); | |
| if (thread->long_jump_base() != nullptr) { | |
| Report::LongJump(error); | |
| UNREACHABLE(); | |
| } | |
| } | |
| // May be allocate-finalized recursively during EnsureIsFinalized. | |
| if (is_allocate_finalized()) { | |
| return Error::null(); | |
| } | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| error ^= ClassFinalizer::AllocateFinalizeClass(*this); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) | |
| return error.ptr(); | |
| } | |
| void Class::SetFields(const Array& value) const { | |
| ASSERT(!value.IsNull()); | |
| #if defined(DEBUG) | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); | |
| // Verify that all the fields in the array have this class as owner. | |
| Field& field = Field::Handle(); | |
| intptr_t len = value.Length(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| field ^= value.At(i); | |
| ASSERT(field.IsOriginal()); | |
| ASSERT(field.Owner() == ptr()); | |
| } | |
| #endif | |
| // The value of static fields is already initialized to null. | |
| set_fields(value); | |
| } | |
| void Class::AddField(const Field& field) const { | |
| #if defined(DEBUG) | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); | |
| #endif | |
| const Array& arr = Array::Handle(fields()); | |
| const Array& new_arr = Array::Handle(Array::Grow(arr, arr.Length() + 1)); | |
| new_arr.SetAt(arr.Length(), field); | |
| SetFields(new_arr); | |
| } | |
| void Class::AddFields(const GrowableArray<const Field*>& new_fields) const { | |
| #if defined(DEBUG) | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); | |
| #endif | |
| const intptr_t num_new_fields = new_fields.length(); | |
| if (num_new_fields == 0) return; | |
| const Array& arr = Array::Handle(fields()); | |
| const intptr_t num_old_fields = arr.Length(); | |
| const Array& new_arr = Array::Handle( | |
| Array::Grow(arr, num_old_fields + num_new_fields, Heap::kOld)); | |
| for (intptr_t i = 0; i < num_new_fields; i++) { | |
| new_arr.SetAt(i + num_old_fields, *new_fields.At(i)); | |
| } | |
| SetFields(new_arr); | |
| } | |
| intptr_t Class::FindFieldIndex(const Field& needle) const { | |
| Thread* thread = Thread::Current(); | |
| if (EnsureIsFinalized(thread) != Error::null()) { | |
| return -1; | |
| } | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_FIELD_HANDLESCOPE(thread); | |
| Array& fields = thread->ArrayHandle(); | |
| Field& field = thread->FieldHandle(); | |
| fields = this->fields(); | |
| ASSERT(!fields.IsNull()); | |
| for (intptr_t i = 0, n = fields.Length(); i < n; ++i) { | |
| field ^= fields.At(i); | |
| if (needle.ptr() == field.ptr()) { | |
| return i; | |
| } | |
| } | |
| // Not found. | |
| return -1; | |
| } | |
| FieldPtr Class::FieldFromIndex(intptr_t idx) const { | |
| Array& fields = Array::Handle(this->fields()); | |
| if ((idx < 0) || (idx >= fields.Length())) { | |
| return Field::null(); | |
| } | |
| return Field::RawCast(fields.At(idx)); | |
| } | |
| bool Class::InjectCIDFields() const { | |
| if (library() != Library::InternalLibrary() || | |
| Name() != Symbols::ClassID().ptr()) { | |
| return false; | |
| } | |
| auto thread = Thread::Current(); | |
| auto isolate_group = thread->isolate_group(); | |
| auto zone = thread->zone(); | |
| Field& field = Field::Handle(zone); | |
| Smi& value = Smi::Handle(zone); | |
| String& field_name = String::Handle(zone); | |
| // clang-format off | |
| static const struct { | |
| const char* const field_name; | |
| const intptr_t cid; | |
| } cid_fields[] = { | |
| #define CLASS_LIST_WITH_NULL(V) \ | |
| V(Null) \ | |
| CLASS_LIST_NO_OBJECT(V) | |
| #define ADD_SET_FIELD(clazz) \ | |
| {"cid" #clazz, k##clazz##Cid}, | |
| CLASS_LIST_WITH_NULL(ADD_SET_FIELD) | |
| #undef ADD_SET_FIELD | |
| #undef CLASS_LIST_WITH_NULL | |
| #define ADD_SET_FIELD(clazz) \ | |
| {"cid" #clazz, kTypedData##clazz##Cid}, \ | |
| {"cid" #clazz "View", kTypedData##clazz##ViewCid}, \ | |
| {"cidExternal" #clazz, kExternalTypedData##clazz##Cid}, \ | |
| {"cidUnmodifiable" #clazz "View", kUnmodifiableTypedData##clazz##ViewCid}, \ | |
| CLASS_LIST_TYPED_DATA(ADD_SET_FIELD) | |
| #undef ADD_SET_FIELD | |
| // Used in const hashing to determine whether we're dealing with a | |
| // user-defined const. See lib/_internal/vm/lib/compact_hash.dart. | |
| {"numPredefinedCids", kNumPredefinedCids}, | |
| }; | |
| // clang-format on | |
| const AbstractType& field_type = Type::Handle(zone, Type::IntType()); | |
| for (size_t i = 0; i < ARRAY_SIZE(cid_fields); i++) { | |
| field_name = Symbols::New(thread, cid_fields[i].field_name); | |
| field = Field::New(field_name, /* is_static = */ true, | |
| /* is_final = */ false, | |
| /* is_const = */ true, | |
| /* is_reflectable = */ false, | |
| /* is_late = */ false, *this, field_type, | |
| TokenPosition::kMinSource, TokenPosition::kMinSource); | |
| value = Smi::New(cid_fields[i].cid); | |
| isolate_group->RegisterStaticField(field, value); | |
| AddField(field); | |
| } | |
| return true; | |
| } | |
| template <class FakeInstance, class TargetFakeInstance> | |
| ClassPtr Class::NewCommon(intptr_t index) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kClassCid) != | |
| Class::null()); | |
| const auto& result = Class::Handle(Object::Allocate<Class>(Heap::kOld)); | |
| // Here kIllegalCid means not-yet-assigned. | |
| Object::VerifyBuiltinVtable<FakeInstance>(index == kIllegalCid ? kInstanceCid | |
| : index); | |
| NOT_IN_PRECOMPILED(result.set_token_pos(TokenPosition::kNoSource)); | |
| NOT_IN_PRECOMPILED(result.set_end_token_pos(TokenPosition::kNoSource)); | |
| const intptr_t host_instance_size = FakeInstance::InstanceSize(); | |
| const intptr_t target_instance_size = compiler::target::RoundedAllocationSize( | |
| TargetFakeInstance::InstanceSize()); | |
| result.set_instance_size(host_instance_size, target_instance_size); | |
| result.set_type_arguments_field_offset_in_words(kNoTypeArguments, | |
| RTN::Class::kNoTypeArguments); | |
| const intptr_t host_next_field_offset = FakeInstance::NextFieldOffset(); | |
| const intptr_t target_next_field_offset = | |
| TargetFakeInstance::NextFieldOffset(); | |
| result.set_next_field_offset(host_next_field_offset, | |
| target_next_field_offset); | |
| result.set_id(index); | |
| NOT_IN_PRECOMPILED(result.set_implementor_cid(kIllegalCid)); | |
| result.set_num_type_arguments_unsafe(kUnknownNumTypeArguments); | |
| result.set_num_native_fields(0); | |
| result.set_state_bits(0); | |
| NOT_IN_PRECOMPILED(result.set_kernel_offset(0)); | |
| result.InitEmptyFields(); | |
| return result.ptr(); | |
| } | |
| template <class FakeInstance, class TargetFakeInstance> | |
| ClassPtr Class::New(intptr_t index, | |
| IsolateGroup* isolate_group, | |
| bool register_class, | |
| bool is_abstract) { | |
| Class& result = | |
| Class::Handle(NewCommon<FakeInstance, TargetFakeInstance>(index)); | |
| if (is_abstract) { | |
| result.set_is_abstract(); | |
| } | |
| if (register_class) { | |
| isolate_group->class_table()->Register(result); | |
| } | |
| return result.ptr(); | |
| } | |
| ClassPtr Class::New(const Library& lib, | |
| const String& name, | |
| const Script& script, | |
| TokenPosition token_pos, | |
| bool register_class) { | |
| Class& result = | |
| Class::Handle(NewCommon<Instance, RTN::Instance>(kIllegalCid)); | |
| result.set_library(lib); | |
| result.set_name(name); | |
| result.set_script(script); | |
| NOT_IN_PRECOMPILED(result.set_token_pos(token_pos)); | |
| // The size gets initialized to 0. Once the class gets finalized the class | |
| // finalizer will set the correct size. | |
| ASSERT(!result.is_finalized() && !result.is_prefinalized()); | |
| result.set_instance_size_in_words(0, 0); | |
| if (register_class) { | |
| IsolateGroup::Current()->RegisterClass(result); | |
| } | |
| return result.ptr(); | |
| } | |
| ClassPtr Class::NewInstanceClass() { | |
| return Class::New<Instance, RTN::Instance>(kIllegalCid, | |
| IsolateGroup::Current()); | |
| } | |
| ClassPtr Class::NewNativeWrapper(const Library& library, | |
| const String& name, | |
| int field_count) { | |
| Class& cls = Class::Handle(library.LookupClass(name)); | |
| if (cls.IsNull()) { | |
| cls = New(library, name, Script::Handle(), TokenPosition::kNoSource); | |
| cls.SetFields(Object::empty_array()); | |
| cls.SetFunctions(Object::empty_array()); | |
| // Set super class to Object. | |
| cls.set_super_type(Type::Handle(Type::ObjectType())); | |
| // Compute instance size. First word contains a pointer to a properly | |
| // sized typed array once the first native field has been set. | |
| const intptr_t host_instance_size = | |
| sizeof(UntaggedInstance) + kCompressedWordSize; | |
| #if defined(DART_PRECOMPILER) | |
| const intptr_t target_instance_size = | |
| compiler::target::Instance::InstanceSize() + | |
| compiler::target::kCompressedWordSize; | |
| #else | |
| const intptr_t target_instance_size = | |
| sizeof(UntaggedInstance) + compiler::target::kCompressedWordSize; | |
| #endif | |
| cls.set_instance_size( | |
| RoundedAllocationSize(host_instance_size), | |
| compiler::target::RoundedAllocationSize(target_instance_size)); | |
| cls.set_next_field_offset(host_instance_size, target_instance_size); | |
| cls.set_num_native_fields(field_count); | |
| cls.set_is_allocate_finalized(); | |
| // The signature of the constructor yet to be added to this class will have | |
| // to be finalized explicitly, since the class is prematurely marked as | |
| // 'is_allocate_finalized' and finalization of member types will not occur. | |
| cls.set_is_declaration_loaded(); | |
| cls.set_is_type_finalized(); | |
| cls.set_is_synthesized_class(); | |
| cls.set_is_isolate_unsendable(true); | |
| cls.set_is_deeply_immutable(true); | |
| NOT_IN_PRECOMPILED(cls.set_implementor_cid(kDynamicCid)); | |
| library.AddClass(cls); | |
| return cls.ptr(); | |
| } else { | |
| return Class::null(); | |
| } | |
| } | |
| ClassPtr Class::NewStringClass(intptr_t class_id, IsolateGroup* isolate_group) { | |
| intptr_t host_instance_size, target_instance_size; | |
| if (class_id == kOneByteStringCid) { | |
| host_instance_size = OneByteString::InstanceSize(); | |
| target_instance_size = compiler::target::RoundedAllocationSize( | |
| RTN::OneByteString::InstanceSize()); | |
| } else { | |
| ASSERT(class_id == kTwoByteStringCid); | |
| host_instance_size = TwoByteString::InstanceSize(); | |
| target_instance_size = compiler::target::RoundedAllocationSize( | |
| RTN::TwoByteString::InstanceSize()); | |
| } | |
| Class& result = Class::Handle(New<String, RTN::String>( | |
| class_id, isolate_group, /*register_class=*/false)); | |
| result.set_instance_size(host_instance_size, target_instance_size); | |
| const intptr_t host_next_field_offset = String::NextFieldOffset(); | |
| const intptr_t target_next_field_offset = RTN::String::NextFieldOffset(); | |
| result.set_next_field_offset(host_next_field_offset, | |
| target_next_field_offset); | |
| result.set_is_prefinalized(); | |
| ASSERT(IsDeeplyImmutableCid(class_id)); | |
| result.set_is_deeply_immutable(true); | |
| isolate_group->class_table()->Register(result); | |
| return result.ptr(); | |
| } | |
| ClassPtr Class::NewTypedDataClass(intptr_t class_id, | |
| IsolateGroup* isolate_group) { | |
| ASSERT(IsTypedDataClassId(class_id)); | |
| const intptr_t host_instance_size = TypedData::InstanceSize(); | |
| const intptr_t target_instance_size = | |
| compiler::target::RoundedAllocationSize(RTN::TypedData::InstanceSize()); | |
| Class& result = Class::Handle(New<TypedData, RTN::TypedData>( | |
| class_id, isolate_group, /*register_class=*/false)); | |
| result.set_instance_size(host_instance_size, target_instance_size); | |
| const intptr_t host_next_field_offset = TypedData::NextFieldOffset(); | |
| const intptr_t target_next_field_offset = RTN::TypedData::NextFieldOffset(); | |
| result.set_next_field_offset(host_next_field_offset, | |
| target_next_field_offset); | |
| result.set_is_prefinalized(); | |
| isolate_group->class_table()->Register(result); | |
| return result.ptr(); | |
| } | |
| ClassPtr Class::NewTypedDataViewClass(intptr_t class_id, | |
| IsolateGroup* isolate_group) { | |
| ASSERT(IsTypedDataViewClassId(class_id)); | |
| const intptr_t host_instance_size = TypedDataView::InstanceSize(); | |
| const intptr_t target_instance_size = compiler::target::RoundedAllocationSize( | |
| RTN::TypedDataView::InstanceSize()); | |
| Class& result = Class::Handle(New<TypedDataView, RTN::TypedDataView>( | |
| class_id, isolate_group, /*register_class=*/false)); | |
| result.set_instance_size(host_instance_size, target_instance_size); | |
| const intptr_t host_next_field_offset = TypedDataView::NextFieldOffset(); | |
| const intptr_t target_next_field_offset = | |
| RTN::TypedDataView::NextFieldOffset(); | |
| result.set_next_field_offset(host_next_field_offset, | |
| target_next_field_offset); | |
| result.set_is_prefinalized(); | |
| isolate_group->class_table()->Register(result); | |
| return result.ptr(); | |
| } | |
| ClassPtr Class::NewUnmodifiableTypedDataViewClass(intptr_t class_id, | |
| IsolateGroup* isolate_group) { | |
| ASSERT(IsUnmodifiableTypedDataViewClassId(class_id)); | |
| const intptr_t host_instance_size = TypedDataView::InstanceSize(); | |
| const intptr_t target_instance_size = compiler::target::RoundedAllocationSize( | |
| RTN::TypedDataView::InstanceSize()); | |
| Class& result = Class::Handle(New<TypedDataView, RTN::TypedDataView>( | |
| class_id, isolate_group, /*register_class=*/false)); | |
| result.set_instance_size(host_instance_size, target_instance_size); | |
| const intptr_t host_next_field_offset = TypedDataView::NextFieldOffset(); | |
| const intptr_t target_next_field_offset = | |
| RTN::TypedDataView::NextFieldOffset(); | |
| result.set_next_field_offset(host_next_field_offset, | |
| target_next_field_offset); | |
| result.set_is_prefinalized(); | |
| isolate_group->class_table()->Register(result); | |
| return result.ptr(); | |
| } | |
| ClassPtr Class::NewExternalTypedDataClass(intptr_t class_id, | |
| IsolateGroup* isolate_group) { | |
| ASSERT(IsExternalTypedDataClassId(class_id)); | |
| const intptr_t host_instance_size = ExternalTypedData::InstanceSize(); | |
| const intptr_t target_instance_size = compiler::target::RoundedAllocationSize( | |
| RTN::ExternalTypedData::InstanceSize()); | |
| Class& result = Class::Handle(New<ExternalTypedData, RTN::ExternalTypedData>( | |
| class_id, isolate_group, /*register_class=*/false)); | |
| const intptr_t host_next_field_offset = ExternalTypedData::NextFieldOffset(); | |
| const intptr_t target_next_field_offset = | |
| RTN::ExternalTypedData::NextFieldOffset(); | |
| result.set_instance_size(host_instance_size, target_instance_size); | |
| result.set_next_field_offset(host_next_field_offset, | |
| target_next_field_offset); | |
| result.set_is_prefinalized(); | |
| isolate_group->class_table()->Register(result); | |
| return result.ptr(); | |
| } | |
| ClassPtr Class::NewPointerClass(intptr_t class_id, | |
| IsolateGroup* isolate_group) { | |
| ASSERT(IsFfiPointerClassId(class_id)); | |
| intptr_t host_instance_size = Pointer::InstanceSize(); | |
| intptr_t target_instance_size = | |
| compiler::target::RoundedAllocationSize(RTN::Pointer::InstanceSize()); | |
| Class& result = Class::Handle(New<Pointer, RTN::Pointer>( | |
| class_id, isolate_group, /*register_class=*/false)); | |
| result.set_instance_size(host_instance_size, target_instance_size); | |
| result.set_type_arguments_field_offset(Pointer::type_arguments_offset(), | |
| RTN::Pointer::type_arguments_offset()); | |
| const intptr_t host_next_field_offset = Pointer::NextFieldOffset(); | |
| const intptr_t target_next_field_offset = RTN::Pointer::NextFieldOffset(); | |
| result.set_next_field_offset(host_next_field_offset, | |
| target_next_field_offset); | |
| result.set_is_prefinalized(); | |
| isolate_group->class_table()->Register(result); | |
| return result.ptr(); | |
| } | |
| void Class::set_name(const String& value) const { | |
| ASSERT(untag()->name() == String::null()); | |
| ASSERT(value.IsSymbol()); | |
| untag()->set_name(value.ptr()); | |
| #if !defined(PRODUCT) | |
| if (untag()->user_name() == String::null()) { | |
| // TODO(johnmccutchan): Eagerly set user name for VM isolate classes, | |
| // lazily set user name for the other classes. | |
| // Generate and set user_name. | |
| const String& user_name = String::Handle( | |
| Symbols::New(Thread::Current(), GenerateUserVisibleName())); | |
| set_user_name(user_name); | |
| } | |
| #endif // !defined(PRODUCT) | |
| } | |
| #if !defined(PRODUCT) | |
| void Class::set_user_name(const String& value) const { | |
| untag()->set_user_name(value.ptr()); | |
| } | |
| #endif // !defined(PRODUCT) | |
| #if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER) | |
| void Class::SetUserVisibleNameInClassTable() const { | |
| // Top level classes don't record a user visible name in the class table. | |
| if (IsTopLevel()) return; | |
| IsolateGroup* isolate_group = IsolateGroup::Current(); | |
| auto class_table = isolate_group->class_table(); | |
| if (class_table->UserVisibleNameFor(id()) == nullptr) { | |
| String& name = String::Handle(UserVisibleName()); | |
| class_table->SetUserVisibleNameFor(id(), name.ToMallocCString()); | |
| } | |
| } | |
| #endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER) | |
| const char* Class::GenerateUserVisibleName() const { | |
| if (FLAG_show_internal_names) { | |
| return String::Handle(Name()).ToCString(); | |
| } | |
| switch (id()) { | |
| case kFloat32x4Cid: | |
| return Symbols::Float32x4().ToCString(); | |
| case kFloat64x2Cid: | |
| return Symbols::Float64x2().ToCString(); | |
| case kInt32x4Cid: | |
| return Symbols::Int32x4().ToCString(); | |
| case kTypedDataInt8ArrayCid: | |
| case kExternalTypedDataInt8ArrayCid: | |
| return Symbols::Int8List().ToCString(); | |
| case kTypedDataUint8ArrayCid: | |
| case kExternalTypedDataUint8ArrayCid: | |
| return Symbols::Uint8List().ToCString(); | |
| case kTypedDataUint8ClampedArrayCid: | |
| case kExternalTypedDataUint8ClampedArrayCid: | |
| return Symbols::Uint8ClampedList().ToCString(); | |
| case kTypedDataInt16ArrayCid: | |
| case kExternalTypedDataInt16ArrayCid: | |
| return Symbols::Int16List().ToCString(); | |
| case kTypedDataUint16ArrayCid: | |
| case kExternalTypedDataUint16ArrayCid: | |
| return Symbols::Uint16List().ToCString(); | |
| case kTypedDataInt32ArrayCid: | |
| case kExternalTypedDataInt32ArrayCid: | |
| return Symbols::Int32List().ToCString(); | |
| case kTypedDataUint32ArrayCid: | |
| case kExternalTypedDataUint32ArrayCid: | |
| return Symbols::Uint32List().ToCString(); | |
| case kTypedDataInt64ArrayCid: | |
| case kExternalTypedDataInt64ArrayCid: | |
| return Symbols::Int64List().ToCString(); | |
| case kTypedDataUint64ArrayCid: | |
| case kExternalTypedDataUint64ArrayCid: | |
| return Symbols::Uint64List().ToCString(); | |
| case kTypedDataInt32x4ArrayCid: | |
| case kExternalTypedDataInt32x4ArrayCid: | |
| return Symbols::Int32x4List().ToCString(); | |
| case kTypedDataFloat32x4ArrayCid: | |
| case kExternalTypedDataFloat32x4ArrayCid: | |
| return Symbols::Float32x4List().ToCString(); | |
| case kTypedDataFloat64x2ArrayCid: | |
| case kExternalTypedDataFloat64x2ArrayCid: | |
| return Symbols::Float64x2List().ToCString(); | |
| case kTypedDataFloat32ArrayCid: | |
| case kExternalTypedDataFloat32ArrayCid: | |
| return Symbols::Float32List().ToCString(); | |
| case kTypedDataFloat64ArrayCid: | |
| case kExternalTypedDataFloat64ArrayCid: | |
| return Symbols::Float64List().ToCString(); | |
| case kPointerCid: | |
| return Symbols::FfiPointer().ToCString(); | |
| case kDynamicLibraryCid: | |
| return Symbols::FfiDynamicLibrary().ToCString(); | |
| case kNullCid: | |
| return Symbols::Null().ToCString(); | |
| case kDynamicCid: | |
| return Symbols::Dynamic().ToCString(); | |
| case kVoidCid: | |
| return Symbols::Void().ToCString(); | |
| case kNeverCid: | |
| return Symbols::Never().ToCString(); | |
| case kClassCid: | |
| return Symbols::Class().ToCString(); | |
| case kTypeParametersCid: | |
| return Symbols::TypeParameters().ToCString(); | |
| case kTypeArgumentsCid: | |
| return Symbols::TypeArguments().ToCString(); | |
| case kPatchClassCid: | |
| return Symbols::PatchClass().ToCString(); | |
| case kFunctionCid: | |
| return Symbols::Function().ToCString(); | |
| case kClosureDataCid: | |
| return Symbols::ClosureData().ToCString(); | |
| case kFfiTrampolineDataCid: | |
| return Symbols::FfiTrampolineData().ToCString(); | |
| case kFieldCid: | |
| return Symbols::Field().ToCString(); | |
| case kScriptCid: | |
| return Symbols::Script().ToCString(); | |
| case kLibraryCid: | |
| return Symbols::Library().ToCString(); | |
| case kLibraryPrefixCid: | |
| return Symbols::LibraryPrefix().ToCString(); | |
| case kNamespaceCid: | |
| return Symbols::Namespace().ToCString(); | |
| case kKernelProgramInfoCid: | |
| return Symbols::KernelProgramInfo().ToCString(); | |
| case kWeakSerializationReferenceCid: | |
| return Symbols::WeakSerializationReference().ToCString(); | |
| case kWeakArrayCid: | |
| return Symbols::WeakArray().ToCString(); | |
| case kCodeCid: | |
| return Symbols::Code().ToCString(); | |
| case kBytecodeCid: | |
| return Symbols::Bytecode().ToCString(); | |
| case kInstructionsCid: | |
| return Symbols::Instructions().ToCString(); | |
| case kInstructionsSectionCid: | |
| return Symbols::InstructionsSection().ToCString(); | |
| case kInstructionsTableCid: | |
| return Symbols::InstructionsTable().ToCString(); | |
| case kObjectPoolCid: | |
| return Symbols::ObjectPool().ToCString(); | |
| case kCodeSourceMapCid: | |
| return Symbols::CodeSourceMap().ToCString(); | |
| case kPcDescriptorsCid: | |
| return Symbols::PcDescriptors().ToCString(); | |
| case kCompressedStackMapsCid: | |
| return Symbols::CompressedStackMaps().ToCString(); | |
| case kLocalVarDescriptorsCid: | |
| return Symbols::LocalVarDescriptors().ToCString(); | |
| case kExceptionHandlersCid: | |
| return Symbols::ExceptionHandlers().ToCString(); | |
| case kContextCid: | |
| return Symbols::Context().ToCString(); | |
| case kContextScopeCid: | |
| return Symbols::ContextScope().ToCString(); | |
| case kSentinelCid: | |
| return Symbols::Sentinel().ToCString(); | |
| case kSingleTargetCacheCid: | |
| return Symbols::SingleTargetCache().ToCString(); | |
| case kICDataCid: | |
| return Symbols::ICData().ToCString(); | |
| case kMegamorphicCacheCid: | |
| return Symbols::MegamorphicCache().ToCString(); | |
| case kSubtypeTestCacheCid: | |
| return Symbols::SubtypeTestCache().ToCString(); | |
| case kLoadingUnitCid: | |
| return Symbols::LoadingUnit().ToCString(); | |
| case kApiErrorCid: | |
| return Symbols::ApiError().ToCString(); | |
| case kLanguageErrorCid: | |
| return Symbols::LanguageError().ToCString(); | |
| case kUnhandledExceptionCid: | |
| return Symbols::UnhandledException().ToCString(); | |
| case kUnwindErrorCid: | |
| return Symbols::UnwindError().ToCString(); | |
| case kIntegerCid: | |
| case kSmiCid: | |
| case kMintCid: | |
| return Symbols::Int().ToCString(); | |
| case kDoubleCid: | |
| return Symbols::Double().ToCString(); | |
| case kOneByteStringCid: | |
| case kTwoByteStringCid: | |
| return Symbols::_String().ToCString(); | |
| case kArrayCid: | |
| case kImmutableArrayCid: | |
| case kGrowableObjectArrayCid: | |
| return Symbols::List().ToCString(); | |
| } | |
| String& name = String::Handle(Name()); | |
| name = Symbols::New(Thread::Current(), String::ScrubName(name)); | |
| if (name.ptr() == Symbols::_Future().ptr() && | |
| library() == Library::AsyncLibrary()) { | |
| return Symbols::Future().ToCString(); | |
| } | |
| return name.ToCString(); | |
| } | |
| void Class::set_script(const Script& value) const { | |
| untag()->set_script(value.ptr()); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| KernelProgramInfoPtr Class::KernelProgramInfo() const { | |
| const auto& lib = Library::Handle(library()); | |
| return lib.kernel_program_info(); | |
| } | |
| void Class::set_token_pos(TokenPosition token_pos) const { | |
| ASSERT(!token_pos.IsClassifying()); | |
| StoreNonPointer(&untag()->token_pos_, token_pos); | |
| } | |
| void Class::set_end_token_pos(TokenPosition token_pos) const { | |
| ASSERT(!token_pos.IsClassifying()); | |
| StoreNonPointer(&untag()->end_token_pos_, token_pos); | |
| } | |
| void Class::set_implementor_cid(intptr_t value) const { | |
| ASSERT(value >= 0 && value < std::numeric_limits<classid_t>::max()); | |
| StoreNonPointer(&untag()->implementor_cid_, value); | |
| } | |
| void Class::ClearImplementor() const { | |
| // Check raw implementor_cid_ without normalization done by | |
| // implementor_cid() accessor. | |
| if (untag()->implementor_cid_ != kVoidCid) { | |
| set_implementor_cid(kIllegalCid); | |
| } | |
| } | |
| bool Class::NoteImplementor(const Class& implementor) const { | |
| ASSERT(!implementor.is_abstract()); | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| if (implementor_cid() == kDynamicCid) { | |
| return false; | |
| } else if (implementor_cid() == implementor.id()) { | |
| return false; | |
| } else if (implementor_cid() == kIllegalCid) { | |
| set_implementor_cid(implementor.id()); | |
| return true; // None -> One | |
| } else { | |
| set_implementor_cid(kDynamicCid); | |
| return true; // One -> Many | |
| } | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| uint32_t Class::Hash() const { | |
| return Class::Hash(ptr()); | |
| } | |
| uint32_t Class::Hash(ClassPtr obj) { | |
| return String::HashRawSymbol(obj.untag()->name()); | |
| } | |
| int32_t Class::SourceFingerprint() const { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| if (is_declared_in_bytecode()) { | |
| return 0; | |
| } | |
| return kernel::KernelSourceFingerprintHelper::CalculateClassFingerprint( | |
| *this); | |
| #else | |
| return 0; | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| void Class::set_is_implemented(bool value) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_is_implemented_unsafe(value); | |
| } | |
| void Class::set_is_implemented_unsafe(bool value) const { | |
| set_state_bits(ImplementedBit::update(value, state_bits())); | |
| } | |
| void Class::set_is_abstract() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(AbstractBit::update(true, state_bits())); | |
| } | |
| void Class::set_is_declaration_loaded() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_is_declaration_loaded_unsafe(); | |
| } | |
| void Class::set_is_declaration_loaded_unsafe() const { | |
| ASSERT(!is_declaration_loaded()); | |
| set_state_bits(ClassLoadingBits::update(UntaggedClass::kDeclarationLoaded, | |
| state_bits())); | |
| } | |
| void Class::set_is_type_finalized() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(is_declaration_loaded()); | |
| ASSERT(!is_type_finalized()); | |
| set_state_bits( | |
| ClassLoadingBits::update(UntaggedClass::kTypeFinalized, state_bits())); | |
| } | |
| void Class::set_is_synthesized_class() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_is_synthesized_class_unsafe(); | |
| } | |
| void Class::set_is_synthesized_class_unsafe() const { | |
| set_state_bits(SynthesizedClassBit::update(true, state_bits())); | |
| } | |
| void Class::set_is_enum_class() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(EnumBit::update(true, state_bits())); | |
| } | |
| void Class::set_is_const() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(ConstBit::update(true, state_bits())); | |
| } | |
| void Class::set_is_transformed_mixin_application() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(TransformedMixinApplicationBit::update(true, state_bits())); | |
| } | |
| void Class::set_is_sealed() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(SealedBit::update(true, state_bits())); | |
| } | |
| void Class::set_is_mixin_class() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(MixinClassBit::update(true, state_bits())); | |
| } | |
| void Class::set_is_base_class() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(BaseClassBit::update(true, state_bits())); | |
| } | |
| void Class::set_is_interface_class() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(InterfaceClassBit::update(true, state_bits())); | |
| } | |
| void Class::set_is_final() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(FinalBit::update(true, state_bits())); | |
| } | |
| void Class::set_is_fields_marked_nullable() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(FieldsMarkedNullableBit::update(true, state_bits())); | |
| } | |
| void Class::set_is_allocated(bool value) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_is_allocated_unsafe(value); | |
| } | |
| void Class::set_is_allocated_unsafe(bool value) const { | |
| set_state_bits(IsAllocatedBit::update(value, state_bits())); | |
| } | |
| void Class::set_is_loaded(bool value) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(IsLoadedBit::update(value, state_bits())); | |
| } | |
| #if defined(DART_DYNAMIC_MODULES) | |
| void Class::set_is_declared_in_bytecode(bool value) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| set_state_bits(IsDeclaredInBytecodeBit::update(value, state_bits())); | |
| } | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| void Class::set_is_finalized() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(!is_finalized()); | |
| set_is_finalized_unsafe(); | |
| } | |
| void Class::set_is_finalized_unsafe() const { | |
| set_state_bits( | |
| ClassFinalizedBits::update(UntaggedClass::kFinalized, state_bits())); | |
| } | |
| void Class::set_is_allocate_finalized() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(!is_allocate_finalized()); | |
| set_state_bits(ClassFinalizedBits::update(UntaggedClass::kAllocateFinalized, | |
| state_bits())); | |
| } | |
| void Class::set_is_prefinalized() const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(!is_finalized()); | |
| set_state_bits( | |
| ClassFinalizedBits::update(UntaggedClass::kPreFinalized, state_bits())); | |
| } | |
| void Class::set_interfaces(const Array& value) const { | |
| ASSERT(!value.IsNull()); | |
| untag()->set_interfaces(value.ptr()); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| void Class::AddDirectImplementor(const Class& implementor, | |
| bool is_mixin) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(is_implemented()); | |
| ASSERT(!implementor.IsNull()); | |
| GrowableObjectArray& direct_implementors = | |
| GrowableObjectArray::Handle(untag()->direct_implementors()); | |
| if (direct_implementors.IsNull()) { | |
| direct_implementors = GrowableObjectArray::New(4, Heap::kOld); | |
| untag()->set_direct_implementors(direct_implementors.ptr()); | |
| } | |
| #if defined(DEBUG) | |
| // Verify that the same class is not added twice. | |
| // The only exception is mixins: when mixin application is transformed, | |
| // mixin is added to the end of interfaces list and may be duplicated: | |
| // class X = A with B implements B; | |
| // This is rare and harmless. | |
| if (!is_mixin) { | |
| for (intptr_t i = 0; i < direct_implementors.Length(); i++) { | |
| ASSERT(direct_implementors.At(i) != implementor.ptr()); | |
| } | |
| } | |
| #endif | |
| direct_implementors.Add(implementor, Heap::kOld); | |
| } | |
| void Class::set_direct_implementors( | |
| const GrowableObjectArray& implementors) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| untag()->set_direct_implementors(implementors.ptr()); | |
| } | |
| void Class::AddDirectSubclass(const Class& subclass) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(!subclass.IsNull()); | |
| ASSERT(subclass.SuperClass() == ptr()); | |
| // Do not keep track of the direct subclasses of class Object. | |
| ASSERT(!IsObjectClass()); | |
| GrowableObjectArray& direct_subclasses = | |
| GrowableObjectArray::Handle(untag()->direct_subclasses()); | |
| if (direct_subclasses.IsNull()) { | |
| direct_subclasses = GrowableObjectArray::New(4, Heap::kOld); | |
| untag()->set_direct_subclasses(direct_subclasses.ptr()); | |
| } | |
| #if defined(DEBUG) | |
| // Verify that the same class is not added twice. | |
| for (intptr_t i = 0; i < direct_subclasses.Length(); i++) { | |
| ASSERT(direct_subclasses.At(i) != subclass.ptr()); | |
| } | |
| #endif | |
| direct_subclasses.Add(subclass, Heap::kOld); | |
| } | |
| void Class::set_direct_subclasses(const GrowableObjectArray& subclasses) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| untag()->set_direct_subclasses(subclasses.ptr()); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| ArrayPtr Class::constants() const { | |
| return untag()->constants(); | |
| } | |
| void Class::set_constants(const Array& value) const { | |
| untag()->set_constants(value.ptr()); | |
| } | |
| void Class::set_declaration_type(const Type& value) const { | |
| ASSERT(id() != kDynamicCid && id() != kVoidCid); | |
| ASSERT(!value.IsNull() && value.IsCanonical() && value.IsOld()); | |
| ASSERT((declaration_type() == Object::null()) || | |
| (declaration_type() == value.ptr())); // Set during own finalization. | |
| // Since DeclarationType is used as the runtime type of instances of a | |
| // non-generic class, its nullability must be kNonNullable. | |
| // The exception is DeclarationType of Null which is kNullable. | |
| ASSERT((value.type_class_id() == kNullCid) == value.IsNullable()); | |
| untag()->set_declaration_type<std::memory_order_release>(value.ptr()); | |
| } | |
| TypePtr Class::DeclarationType() const { | |
| ASSERT(is_declaration_loaded()); | |
| if (IsNullClass()) { | |
| return Type::NullType(); | |
| } | |
| if (IsDynamicClass()) { | |
| return Type::DynamicType(); | |
| } | |
| if (IsVoidClass()) { | |
| return Type::VoidType(); | |
| } | |
| if (declaration_type() != Type::null()) { | |
| return declaration_type(); | |
| } | |
| { | |
| auto thread = Thread::Current(); | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| if (declaration_type() != Type::null()) { | |
| return declaration_type(); | |
| } | |
| // For efficiency, the runtimeType intrinsic returns the type cached by | |
| // DeclarationType without checking its nullability. Therefore, we | |
| // consistently cache the kNonNullable version of the type. | |
| // The exception is type Null which is stored as kNullable. | |
| TypeArguments& type_args = TypeArguments::Handle(); | |
| const intptr_t num_type_params = NumTypeParameters(); | |
| if (num_type_params > 0) { | |
| type_args = TypeArguments::New(num_type_params); | |
| TypeParameter& type_param = TypeParameter::Handle(); | |
| for (intptr_t i = 0; i < num_type_params; i++) { | |
| type_param = TypeParameterAt(i); | |
| type_args.SetTypeAt(i, type_param); | |
| } | |
| } | |
| Type& type = | |
| Type::Handle(Type::New(*this, type_args, Nullability::kNonNullable)); | |
| type ^= ClassFinalizer::FinalizeType(type); | |
| set_declaration_type(type); | |
| return type.ptr(); | |
| } | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| void Class::set_allocation_stub(const Code& value) const { | |
| // Never clear the stub as it may still be a target, but will be GC-d if | |
| // not referenced. | |
| ASSERT(!value.IsNull()); | |
| ASSERT(untag()->allocation_stub() == Code::null()); | |
| untag()->set_allocation_stub(value.ptr()); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| void Class::DisableAllocationStub() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| { | |
| const Code& existing_stub = Code::Handle(allocation_stub()); | |
| if (existing_stub.IsNull()) { | |
| return; | |
| } | |
| } | |
| auto thread = Thread::Current(); | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| const Code& existing_stub = Code::Handle(allocation_stub()); | |
| if (existing_stub.IsNull()) { | |
| return; | |
| } | |
| ASSERT(!existing_stub.IsDisabled()); | |
| // Change the stub so that the next caller will regenerate the stub. | |
| existing_stub.DisableStubCode(NumTypeParameters() > 0); | |
| // Disassociate the existing stub from class. | |
| untag()->set_allocation_stub(Code::null()); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| bool Class::IsDartFunctionClass() const { | |
| return ptr() == Type::Handle(Type::DartFunctionType()).type_class(); | |
| } | |
| bool Class::IsFutureClass() const { | |
| // Looking up future_class in the object store would not work, because | |
| // this function is called during class finalization, before the object store | |
| // field would be initialized by InitKnownObjects(). | |
| return (Name() == Symbols::Future().ptr()) && | |
| (library() == Library::AsyncLibrary()); | |
| } | |
| // Checks if type T0 is a subtype of type T1. | |
| // Type T0 is specified by class 'cls' parameterized with 'type_arguments' and | |
| // by 'nullability', and type T1 is specified by 'other' and must have a type | |
| // class. | |
| // [type_arguments] should be a flattened instance type arguments vector. | |
| bool Class::IsSubtypeOf(const Class& cls, | |
| const TypeArguments& type_arguments, | |
| Nullability nullability, | |
| const AbstractType& other, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_equivalence) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" Class::IsSubtypeOf(%s %s, %s)\n", | |
| cls.ToCString(), type_arguments.ToCString(), | |
| other.ToCString()); | |
| // This function does not support Null, Never, dynamic, or void as type T0. | |
| classid_t this_cid = cls.id(); | |
| ASSERT(this_cid != kNullCid && this_cid != kNeverCid && | |
| this_cid != kDynamicCid && this_cid != kVoidCid); | |
| ASSERT(type_arguments.IsNull() || | |
| (type_arguments.Length() >= cls.NumTypeArguments())); | |
| // Type T1 must have a type class (e.g. not a type param or a function type). | |
| ASSERT(other.HasTypeClass()); | |
| const classid_t other_cid = other.type_class_id(); | |
| if (other_cid == kDynamicCid || other_cid == kVoidCid) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: true (right is top)\n"); | |
| return true; | |
| } | |
| // Left nullable: | |
| // if T0 is S0? then: | |
| // T0 <: T1 iff S0 <: T1 and Null <: T1 | |
| if ((nullability == Nullability::kNullable) && | |
| !Instance::NullIsAssignableTo(other)) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: false (nullability)\n"); | |
| return false; | |
| } | |
| // Right Object. | |
| if (other_cid == kObjectCid) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: true (right is Object)\n"); | |
| return true; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const Class& other_class = Class::Handle(zone, other.type_class()); | |
| const TypeArguments& other_type_arguments = | |
| TypeArguments::Handle(zone, other.arguments()); | |
| // Use the 'this_class' object as if it was the receiver of this method, but | |
| // instead of recursing, reset it to the super class and loop. | |
| Class& this_class = Class::Handle(zone, cls.ptr()); | |
| while (true) { | |
| // Apply additional subtyping rules if T0 or T1 are 'FutureOr'. | |
| // Left FutureOr: | |
| // if T0 is FutureOr<S0> then: | |
| // T0 <: T1 iff Future<S0> <: T1 and S0 <: T1 | |
| if (this_cid == kFutureOrCid) { | |
| // Check Future<S0> <: T1. | |
| ObjectStore* object_store = IsolateGroup::Current()->object_store(); | |
| const Class& future_class = | |
| Class::Handle(zone, object_store->future_class()); | |
| ASSERT(!future_class.IsNull() && future_class.NumTypeParameters() == 1 && | |
| this_class.NumTypeParameters() == 1); | |
| ASSERT(type_arguments.IsNull() || type_arguments.Length() >= 1); | |
| if (Class::IsSubtypeOf(future_class, type_arguments, | |
| Nullability::kNonNullable, other, space, | |
| function_type_equivalence)) { | |
| // Check S0 <: T1. | |
| const AbstractType& type_arg = | |
| AbstractType::Handle(zone, type_arguments.TypeAtNullSafe(0)); | |
| if (type_arg.IsSubtypeOf(other, space, function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: true (left is FutureOr)\n"); | |
| return true; | |
| } | |
| } | |
| } | |
| // Right FutureOr: | |
| // if T1 is FutureOr<S1> then: | |
| // T0 <: T1 iff any of the following hold: | |
| // either T0 <: Future<S1> | |
| // or T0 <: S1 | |
| // or T0 is X0 and X0 has bound S0 and S0 <: T1 (checked elsewhere) | |
| if (other_cid == kFutureOrCid) { | |
| const AbstractType& other_type_arg = | |
| AbstractType::Handle(zone, other_type_arguments.TypeAtNullSafe(0)); | |
| // Check if S1 is a top type. | |
| if (other_type_arg.IsTopType()) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: true (right is FutureOr top)\n"); | |
| return true; | |
| } | |
| // Check T0 <: Future<S1> when T0 is Future<S0>. | |
| if (this_class.IsFutureClass()) { | |
| const AbstractType& type_arg = | |
| AbstractType::Handle(zone, type_arguments.TypeAtNullSafe(0)); | |
| // If T0 is Future<S0>, then T0 <: Future<S1>, iff S0 <: S1. | |
| if (type_arg.IsSubtypeOf(other_type_arg, space, | |
| function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: true (left is Future, right is FutureOr)\n"); | |
| return true; | |
| } | |
| } | |
| // Check T0 <: Future<S1> when T0 is FutureOr<S0> is already done. | |
| // Check T0 <: S1. | |
| if (other_type_arg.HasTypeClass() && | |
| Class::IsSubtypeOf(this_class, type_arguments, nullability, | |
| other_type_arg, space, | |
| function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: true (right is FutureOr, subtype of arg)\n"); | |
| return true; | |
| } | |
| } | |
| // Check for reflexivity. | |
| if (this_class.ptr() == other_class.ptr()) { | |
| const intptr_t num_type_params = this_class.NumTypeParameters(); | |
| if (num_type_params == 0) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: true (same non-generic class)\n"); | |
| return true; | |
| } | |
| // Check for covariance. | |
| if (other_type_arguments.IsNull()) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: true (same class, dynamic type args)\n"); | |
| return true; | |
| } | |
| const intptr_t num_type_args = this_class.NumTypeArguments(); | |
| const intptr_t from_index = num_type_args - num_type_params; | |
| ASSERT(other_type_arguments.Length() == num_type_params); | |
| AbstractType& type = AbstractType::Handle(zone); | |
| AbstractType& other_type = AbstractType::Handle(zone); | |
| for (intptr_t i = 0; i < num_type_params; ++i) { | |
| type = type_arguments.TypeAtNullSafe(from_index + i); | |
| other_type = other_type_arguments.TypeAt(i); | |
| ASSERT(!type.IsNull() && !other_type.IsNull()); | |
| if (!type.IsSubtypeOf(other_type, space, function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (same class, type args mismatch)\n"); | |
| return false; | |
| } | |
| } | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: true (same class, matching type args)\n"); | |
| return true; | |
| } | |
| // _Closure <: Function | |
| if (this_class.IsClosureClass() && other_class.IsDartFunctionClass()) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: true (left is closure, right is Function)\n"); | |
| return true; | |
| } | |
| // Check for 'direct super type' specified in the implements clause | |
| // and check for transitivity at the same time. | |
| Array& interfaces = Array::Handle(zone, this_class.interfaces()); | |
| Type& interface = Type::Handle(zone); | |
| Class& interface_class = Class::Handle(zone); | |
| TypeArguments& interface_args = TypeArguments::Handle(zone); | |
| for (intptr_t i = 0; i < interfaces.Length(); i++) { | |
| interface ^= interfaces.At(i); | |
| ASSERT(interface.IsFinalized()); | |
| interface_class = interface.type_class(); | |
| interface_args = interface.arguments(); | |
| if (!interface_args.IsNull() && !interface_args.IsInstantiated()) { | |
| // This type class implements an interface that is parameterized with | |
| // generic type(s), e.g. it implements List<T>. | |
| // The uninstantiated type T must be instantiated using the type | |
| // parameters of this type before performing the type test. | |
| // The type arguments of this type that are referred to by the type | |
| // parameters of the interface are at the end of the type vector, | |
| // after the type arguments of the super type of this type. | |
| // The index of the type parameters is adjusted upon finalization. | |
| interface_args = interface_args.InstantiateFrom( | |
| type_arguments, Object::null_type_arguments(), kNoneFree, space); | |
| } | |
| interface_args = interface_class.GetInstanceTypeArguments( | |
| thread, interface_args, /*canonicalize=*/false); | |
| // In Dart 2, implementing Function has no meaning. | |
| // TODO(regis): Can we encounter and skip Object as well? | |
| if (interface_class.IsDartFunctionClass()) { | |
| continue; | |
| } | |
| if (Class::IsSubtypeOf(interface_class, interface_args, | |
| Nullability::kNonNullable, other, space, | |
| function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: true (interface found)\n"); | |
| return true; | |
| } | |
| } | |
| // "Recurse" up the class hierarchy until we have reached the top. | |
| this_class = this_class.SuperClass(); | |
| if (this_class.IsNull()) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: false (supertype not found)\n"); | |
| return false; | |
| } | |
| this_cid = this_class.id(); | |
| } | |
| UNREACHABLE(); | |
| return false; | |
| } | |
| bool Class::IsTopLevel() const { | |
| return Name() == Symbols::TopLevel().ptr(); | |
| } | |
| bool Class::IsPrivate() const { | |
| return Library::IsPrivate(String::Handle(Name())); | |
| } | |
| FunctionPtr Class::LookupDynamicFunctionUnsafe(const String& name) const { | |
| return LookupFunctionReadLocked(name, kInstance); | |
| } | |
| FunctionPtr Class::LookupStaticFunction(const String& name) const { | |
| Thread* thread = Thread::Current(); | |
| SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| return LookupFunctionReadLocked(name, kStatic); | |
| } | |
| FunctionPtr Class::LookupStaticFunctionAllowPrivate(const String& name) const { | |
| return LookupFunctionAllowPrivate(name, kStatic); | |
| } | |
| FunctionPtr Class::LookupConstructor(const String& name) const { | |
| Thread* thread = Thread::Current(); | |
| SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| return LookupFunctionReadLocked(name, kConstructor); | |
| } | |
| FunctionPtr Class::LookupConstructorAllowPrivate(const String& name) const { | |
| return LookupFunctionAllowPrivate(name, kConstructor); | |
| } | |
| FunctionPtr Class::LookupFactory(const String& name) const { | |
| Thread* thread = Thread::Current(); | |
| SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| return LookupFunctionReadLocked(name, kFactory); | |
| } | |
| FunctionPtr Class::LookupFactoryAllowPrivate(const String& name) const { | |
| return LookupFunctionAllowPrivate(name, kFactory); | |
| } | |
| FunctionPtr Class::LookupFunctionAllowPrivate(const String& name) const { | |
| return LookupFunctionAllowPrivate(name, kAny); | |
| } | |
| FunctionPtr Class::LookupFunction(const String& name) const { | |
| Thread* thread = Thread::Current(); | |
| SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| return LookupFunctionReadLocked(name); | |
| } | |
| FunctionPtr Class::LookupFunctionReadLocked(const String& name) const { | |
| return LookupFunctionReadLocked(name, kAny); | |
| } | |
| FunctionPtr Class::CheckFunctionType(const Function& func, MemberKind kind) { | |
| if ((kind == kInstance) || (kind == kInstanceAllowAbstract)) { | |
| if (func.IsDynamicFunction(kind == kInstanceAllowAbstract)) { | |
| return func.ptr(); | |
| } | |
| } else if (kind == kStatic) { | |
| if (func.IsStaticFunction()) { | |
| return func.ptr(); | |
| } | |
| } else if (kind == kConstructor) { | |
| if (func.IsGenerativeConstructor()) { | |
| ASSERT(!func.is_static()); | |
| return func.ptr(); | |
| } | |
| } else if (kind == kFactory) { | |
| if (func.IsFactory()) { | |
| ASSERT(func.is_static()); | |
| return func.ptr(); | |
| } | |
| } else if (kind == kAny) { | |
| return func.ptr(); | |
| } | |
| return Function::null(); | |
| } | |
| FunctionPtr Class::LookupFunctionReadLocked(const String& name, | |
| MemberKind kind) const { | |
| ASSERT(!IsNull()); | |
| Thread* thread = Thread::Current(); | |
| RELEASE_ASSERT(is_finalized()); | |
| // Caller needs to ensure they grab program_lock because this method | |
| // can be invoked with either ReadRwLock or WriteRwLock. | |
| #if defined(DEBUG) | |
| ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadReader()); | |
| #endif | |
| ASSERT(functions() != Array::null()); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_FUNCTION_HANDLESCOPE(thread); | |
| Array& funcs = thread->ArrayHandle(); | |
| funcs = functions(); | |
| const intptr_t len = funcs.Length(); | |
| Function& function = thread->FunctionHandle(); | |
| if (len >= kFunctionLookupHashThreshold) { | |
| // TODO(dartbug.com/36097): We require currently a read lock in the resolver | |
| // to avoid read-write race access to this hash table. | |
| // If we want to increase resolver speed by avoiding the need for read lock, | |
| // we could make change this hash table to be lock-free for the reader. | |
| const Array& hash_table = | |
| Array::Handle(thread->zone(), untag()->functions_hash_table()); | |
| if (!hash_table.IsNull()) { | |
| ClassFunctionsSet set(hash_table.ptr()); | |
| REUSABLE_STRING_HANDLESCOPE(thread); | |
| function ^= set.GetOrNull(FunctionName(name, &(thread->StringHandle()))); | |
| // No mutations. | |
| ASSERT(set.Release().ptr() == hash_table.ptr()); | |
| return function.IsNull() ? Function::null() | |
| : CheckFunctionType(function, kind); | |
| } | |
| } | |
| if (name.IsSymbol()) { | |
| // Quick Symbol compare. | |
| NoSafepointScope no_safepoint; | |
| for (intptr_t i = 0; i < len; i++) { | |
| function ^= funcs.At(i); | |
| if (function.name() == name.ptr()) { | |
| return CheckFunctionType(function, kind); | |
| } | |
| } | |
| } else { | |
| REUSABLE_STRING_HANDLESCOPE(thread); | |
| String& function_name = thread->StringHandle(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| function ^= funcs.At(i); | |
| function_name = function.name(); | |
| if (function_name.Equals(name)) { | |
| return CheckFunctionType(function, kind); | |
| } | |
| } | |
| } | |
| // No function found. | |
| return Function::null(); | |
| } | |
| FunctionPtr Class::LookupFunctionAllowPrivate(const String& name, | |
| MemberKind kind) const { | |
| ASSERT(!IsNull()); | |
| Thread* thread = Thread::Current(); | |
| RELEASE_ASSERT(is_finalized()); | |
| SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_FUNCTION_HANDLESCOPE(thread); | |
| REUSABLE_STRING_HANDLESCOPE(thread); | |
| Array& funcs = thread->ArrayHandle(); | |
| funcs = current_functions(); | |
| ASSERT(!funcs.IsNull()); | |
| const intptr_t len = funcs.Length(); | |
| Function& function = thread->FunctionHandle(); | |
| String& function_name = thread->StringHandle(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| function ^= funcs.At(i); | |
| function_name = function.name(); | |
| if (String::EqualsIgnoringPrivateKey(function_name, name)) { | |
| return CheckFunctionType(function, kind); | |
| } | |
| } | |
| // No function found. | |
| return Function::null(); | |
| } | |
| FieldPtr Class::LookupInstanceField(const String& name) const { | |
| return LookupField(name, kInstance); | |
| } | |
| FieldPtr Class::LookupStaticField(const String& name) const { | |
| return LookupField(name, kStatic); | |
| } | |
| FieldPtr Class::LookupField(const String& name) const { | |
| return LookupField(name, kAny); | |
| } | |
| FieldPtr Class::LookupField(const String& name, MemberKind kind) const { | |
| ASSERT(!IsNull()); | |
| Thread* thread = Thread::Current(); | |
| if (EnsureIsFinalized(thread) != Error::null()) { | |
| return Field::null(); | |
| } | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_FIELD_HANDLESCOPE(thread); | |
| REUSABLE_STRING_HANDLESCOPE(thread); | |
| Array& flds = thread->ArrayHandle(); | |
| flds = fields(); | |
| ASSERT(!flds.IsNull()); | |
| intptr_t len = flds.Length(); | |
| Field& field = thread->FieldHandle(); | |
| if (name.IsSymbol()) { | |
| // Use fast raw pointer string compare for symbols. | |
| for (intptr_t i = 0; i < len; i++) { | |
| field ^= flds.At(i); | |
| if (name.ptr() == field.name()) { | |
| if (kind == kInstance) { | |
| return field.is_static() ? Field::null() : field.ptr(); | |
| } else if (kind == kStatic) { | |
| return field.is_static() ? field.ptr() : Field::null(); | |
| } | |
| ASSERT(kind == kAny); | |
| return field.ptr(); | |
| } | |
| } | |
| } else { | |
| String& field_name = thread->StringHandle(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| field ^= flds.At(i); | |
| field_name = field.name(); | |
| if (name.Equals(field_name)) { | |
| if (kind == kInstance) { | |
| return field.is_static() ? Field::null() : field.ptr(); | |
| } else if (kind == kStatic) { | |
| return field.is_static() ? field.ptr() : Field::null(); | |
| } | |
| ASSERT(kind == kAny); | |
| return field.ptr(); | |
| } | |
| } | |
| } | |
| return Field::null(); | |
| } | |
| FieldPtr Class::LookupFieldAllowPrivate(const String& name, | |
| bool instance_only) const { | |
| ASSERT(!IsNull()); | |
| // Use slow string compare, ignoring privacy name mangling. | |
| Thread* thread = Thread::Current(); | |
| if (EnsureIsFinalized(thread) != Error::null()) { | |
| return Field::null(); | |
| } | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_FIELD_HANDLESCOPE(thread); | |
| REUSABLE_STRING_HANDLESCOPE(thread); | |
| Array& flds = thread->ArrayHandle(); | |
| flds = fields(); | |
| ASSERT(!flds.IsNull()); | |
| intptr_t len = flds.Length(); | |
| Field& field = thread->FieldHandle(); | |
| String& field_name = thread->StringHandle(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| field ^= flds.At(i); | |
| field_name = field.name(); | |
| if (field.is_static() && instance_only) { | |
| // If we only care about instance fields, skip statics. | |
| continue; | |
| } | |
| if (String::EqualsIgnoringPrivateKey(field_name, name)) { | |
| return field.ptr(); | |
| } | |
| } | |
| return Field::null(); | |
| } | |
| FieldPtr Class::LookupInstanceFieldAllowPrivate(const String& name) const { | |
| Field& field = Field::Handle(LookupFieldAllowPrivate(name, true)); | |
| if (!field.IsNull() && !field.is_static()) { | |
| return field.ptr(); | |
| } | |
| return Field::null(); | |
| } | |
| FieldPtr Class::LookupStaticFieldAllowPrivate(const String& name) const { | |
| Field& field = Field::Handle(LookupFieldAllowPrivate(name)); | |
| if (!field.IsNull() && field.is_static()) { | |
| return field.ptr(); | |
| } | |
| return Field::null(); | |
| } | |
| const char* Class::ToCString() const { | |
| ZoneTextBuffer buffer(Thread::Current()->zone()); | |
| const Library& lib = Library::Handle(library()); | |
| if (!lib.IsNull()) { | |
| buffer.Printf("%s ", lib.ToCString()); | |
| } | |
| buffer.Printf("Class: %s", String::Handle(Name()).ToCString()); | |
| return buffer.buffer(); | |
| } | |
| // Thomas Wang, Integer Hash Functions. | |
| // https://gist.github.com/badboy/6267743 | |
| // "64 bit to 32 bit Hash Functions" | |
| static uword Hash64To32(uint64_t v) { | |
| v = ~v + (v << 18); | |
| v = v ^ (v >> 31); | |
| v = v * 21; | |
| v = v ^ (v >> 11); | |
| v = v + (v << 6); | |
| v = v ^ (v >> 22); | |
| return static_cast<uint32_t>(v); | |
| } | |
| InstancePtr Class::LookupCanonicalInstance(Zone* zone, | |
| const Instance& value) const { | |
| ASSERT(this->ptr() == value.clazz()); | |
| ASSERT(is_finalized() || is_prefinalized()); | |
| Instance& canonical_value = Instance::Handle(zone); | |
| if (this->constants() != Array::null()) { | |
| CanonicalInstancesSet constants(zone, this->constants()); | |
| canonical_value ^= constants.GetOrNull(CanonicalInstanceKey(value)); | |
| this->set_constants(constants.Release()); | |
| } | |
| return canonical_value.ptr(); | |
| } | |
| InstancePtr Class::InsertCanonicalConstant(Zone* zone, | |
| const Instance& constant) const { | |
| ASSERT(constant.IsCanonical()); | |
| ASSERT(this->ptr() == constant.clazz()); | |
| Instance& canonical_value = Instance::Handle(zone); | |
| if (this->constants() == Array::null()) { | |
| CanonicalInstancesSet constants( | |
| HashTables::New<CanonicalInstancesSet>(128, Heap::kOld)); | |
| canonical_value ^= constants.InsertNewOrGet(CanonicalInstanceKey(constant)); | |
| this->set_constants(constants.Release()); | |
| } else { | |
| CanonicalInstancesSet constants(Thread::Current()->zone(), | |
| this->constants()); | |
| canonical_value ^= constants.InsertNewOrGet(CanonicalInstanceKey(constant)); | |
| this->set_constants(constants.Release()); | |
| } | |
| return canonical_value.ptr(); | |
| } | |
| // Scoped mapping FunctionType -> FunctionType. | |
| // Used for tracking and updating nested generic function types | |
| // and their type parameters. | |
| class FunctionTypeMapping : public ValueObject { | |
| public: | |
| FunctionTypeMapping(Zone* zone, | |
| FunctionTypeMapping** mapping, | |
| const FunctionType& from, | |
| const FunctionType& to) | |
| : zone_(zone), parent_(*mapping), from_(from), to_(to) { | |
| // Add self to the linked list. | |
| *mapping = this; | |
| } | |
| const FunctionType* Find(const Object& from) const { | |
| if (!from.IsFunctionType()) { | |
| return nullptr; | |
| } | |
| for (const FunctionTypeMapping* scope = this; scope != nullptr; | |
| scope = scope->parent_) { | |
| if (scope->from_.ptr() == from.ptr()) { | |
| return &(scope->to_); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| TypeParameterPtr MapTypeParameter(const TypeParameter& type_param) const { | |
| ASSERT(type_param.IsFunctionTypeParameter()); | |
| const FunctionType* new_owner = Find( | |
| FunctionType::Handle(zone_, type_param.parameterized_function_type())); | |
| if (new_owner != nullptr) { | |
| return new_owner->TypeParameterAt(type_param.index() - type_param.base(), | |
| type_param.nullability()); | |
| } | |
| return type_param.ptr(); | |
| } | |
| bool ContainsOwnersOfTypeParameters(const TypeParameter& p1, | |
| const TypeParameter& p2) const { | |
| auto& from = FunctionType::Handle(zone_, p1.parameterized_function_type()); | |
| const FunctionType* to = Find(from); | |
| if (to != nullptr) { | |
| return to->ptr() == p2.parameterized_function_type(); | |
| } | |
| from = p2.parameterized_function_type(); | |
| to = Find(from); | |
| if (to != nullptr) { | |
| return to->ptr() == p1.parameterized_function_type(); | |
| } | |
| return false; | |
| } | |
| private: | |
| Zone* zone_; | |
| const FunctionTypeMapping* const parent_; | |
| const FunctionType& from_; | |
| const FunctionType& to_; | |
| }; | |
| intptr_t TypeParameters::Length() const { | |
| if (IsNull() || untag()->names() == Array::null()) return 0; | |
| return Smi::Value(untag()->names()->untag()->length()); | |
| } | |
| void TypeParameters::set_names(const Array& value) const { | |
| ASSERT(!value.IsNull()); | |
| untag()->set_names(value.ptr()); | |
| } | |
| StringPtr TypeParameters::NameAt(intptr_t index) const { | |
| const Array& names_array = Array::Handle(names()); | |
| return String::RawCast(names_array.At(index)); | |
| } | |
| void TypeParameters::SetNameAt(intptr_t index, const String& value) const { | |
| const Array& names_array = Array::Handle(names()); | |
| names_array.SetAt(index, value); | |
| } | |
| void TypeParameters::set_flags(const Array& value) const { | |
| untag()->set_flags(value.ptr()); | |
| } | |
| void TypeParameters::set_bounds(const TypeArguments& value) const { | |
| // A null value represents a vector of dynamic. | |
| untag()->set_bounds(value.ptr()); | |
| } | |
| AbstractTypePtr TypeParameters::BoundAt(intptr_t index) const { | |
| const TypeArguments& upper_bounds = TypeArguments::Handle(bounds()); | |
| return upper_bounds.IsNull() ? Type::DynamicType() | |
| : upper_bounds.TypeAt(index); | |
| } | |
| void TypeParameters::SetBoundAt(intptr_t index, | |
| const AbstractType& value) const { | |
| const TypeArguments& upper_bounds = TypeArguments::Handle(bounds()); | |
| upper_bounds.SetTypeAt(index, value); | |
| } | |
| bool TypeParameters::AllDynamicBounds() const { | |
| return bounds() == TypeArguments::null(); | |
| } | |
| void TypeParameters::set_defaults(const TypeArguments& value) const { | |
| // The null value represents a vector of dynamic. | |
| untag()->set_defaults(value.ptr()); | |
| } | |
| AbstractTypePtr TypeParameters::DefaultAt(intptr_t index) const { | |
| const TypeArguments& default_type_args = TypeArguments::Handle(defaults()); | |
| return default_type_args.IsNull() ? Type::DynamicType() | |
| : default_type_args.TypeAt(index); | |
| } | |
| void TypeParameters::SetDefaultAt(intptr_t index, | |
| const AbstractType& value) const { | |
| const TypeArguments& default_type_args = TypeArguments::Handle(defaults()); | |
| default_type_args.SetTypeAt(index, value); | |
| } | |
| bool TypeParameters::AllDynamicDefaults() const { | |
| return defaults() == TypeArguments::null(); | |
| } | |
| void TypeParameters::AllocateFlags(Heap::Space space) const { | |
| const intptr_t len = (Length() + kFlagsPerSmiMask) >> kFlagsPerSmiShift; | |
| const Array& flags_array = Array::Handle(Array::New(len, space)); | |
| // Initialize flags to 0. | |
| const Smi& zero = Smi::Handle(Smi::New(0)); | |
| for (intptr_t i = 0; i < len; i++) { | |
| flags_array.SetAt(i, zero); | |
| } | |
| set_flags(flags_array); | |
| } | |
| void TypeParameters::OptimizeFlags() const { | |
| if (untag()->flags() == Array::null()) return; // Already optimized. | |
| const intptr_t len = (Length() + kFlagsPerSmiMask) >> kFlagsPerSmiShift; | |
| const Array& flags_array = Array::Handle(flags()); | |
| const Smi& zero = Smi::Handle(Smi::New(0)); | |
| for (intptr_t i = 0; i < len; i++) { | |
| if (flags_array.At(i) != zero.ptr()) return; | |
| } | |
| set_flags(Object::null_array()); | |
| } | |
| bool TypeParameters::IsGenericCovariantImplAt(intptr_t index) const { | |
| if (untag()->flags() == Array::null()) return false; | |
| const intptr_t flag = Smi::Value( | |
| Smi::RawCast(Array::Handle(flags()).At(index >> kFlagsPerSmiShift))); | |
| return (flag >> (index & kFlagsPerSmiMask)) != 0; | |
| } | |
| void TypeParameters::SetIsGenericCovariantImplAt(intptr_t index, | |
| bool value) const { | |
| const Array& flg = Array::Handle(flags()); | |
| intptr_t flag = Smi::Value(Smi::RawCast(flg.At(index >> kFlagsPerSmiShift))); | |
| if (value) { | |
| flag |= 1 << (index % kFlagsPerSmiMask); | |
| } else { | |
| flag &= ~(1 << (index % kFlagsPerSmiMask)); | |
| } | |
| flg.SetAt(index >> kFlagsPerSmiShift, Smi::Handle(Smi::New(flag))); | |
| } | |
| void TypeParameters::Print(Thread* thread, | |
| Zone* zone, | |
| bool are_class_type_parameters, | |
| intptr_t base, | |
| NameVisibility name_visibility, | |
| BaseTextBuffer* printer) const { | |
| String& name = String::Handle(zone); | |
| AbstractType& type = AbstractType::Handle(zone); | |
| const intptr_t num_type_params = Length(); | |
| for (intptr_t i = 0; i < num_type_params; i++) { | |
| if (are_class_type_parameters) { | |
| name = NameAt(i); | |
| printer->AddString(name.ToCString()); | |
| } else { | |
| printer->AddString(TypeParameter::CanonicalNameCString( | |
| are_class_type_parameters, base, base + i)); | |
| } | |
| if (FLAG_show_internal_names || !AllDynamicBounds()) { | |
| type = BoundAt(i); | |
| // Do not print default bound. | |
| if (!type.IsNull() && | |
| (FLAG_show_internal_names || !type.IsNullableObjectType())) { | |
| printer->AddString(" extends "); | |
| type.PrintName(name_visibility, printer); | |
| if (FLAG_show_internal_names && !AllDynamicDefaults()) { | |
| type = DefaultAt(i); | |
| if (!type.IsNull() && | |
| (FLAG_show_internal_names || !type.IsDynamicType())) { | |
| printer->AddString(" defaults to "); | |
| type.PrintName(name_visibility, printer); | |
| } | |
| } | |
| } | |
| } | |
| if (i != num_type_params - 1) { | |
| printer->AddString(", "); | |
| } | |
| } | |
| } | |
| const char* TypeParameters::ToCString() const { | |
| if (IsNull()) { | |
| return "TypeParameters: null"; | |
| } | |
| auto thread = Thread::Current(); | |
| auto zone = thread->zone(); | |
| ZoneTextBuffer buffer(zone); | |
| buffer.AddString("TypeParameters: "); | |
| Print(thread, zone, true, 0, kInternalName, &buffer); | |
| return buffer.buffer(); | |
| } | |
| TypeParametersPtr TypeParameters::New(Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kTypeParametersCid) != | |
| Class::null()); | |
| return Object::Allocate<TypeParameters>(space); | |
| } | |
| TypeParametersPtr TypeParameters::New(intptr_t count, Heap::Space space) { | |
| const TypeParameters& result = | |
| TypeParameters::Handle(TypeParameters::New(space)); | |
| // Create an [ Array ] of [ String ] objects to represent the names. | |
| // Create a [ TypeArguments ] vector representing the bounds. | |
| // Create a [ TypeArguments ] vector representing the defaults. | |
| // Create an [ Array ] of [ Smi] objects to represent the flags. | |
| const Array& names_array = Array::Handle(Array::New(count, space)); | |
| result.set_names(names_array); | |
| TypeArguments& type_args = TypeArguments::Handle(); | |
| type_args = TypeArguments::New(count, Heap::kNew); // Will get canonicalized. | |
| result.set_bounds(type_args); | |
| type_args = TypeArguments::New(count, Heap::kNew); // Will get canonicalized. | |
| result.set_defaults(type_args); | |
| result.AllocateFlags(space); // Will get optimized. | |
| return result.ptr(); | |
| } | |
| intptr_t TypeArguments::ComputeNullability() const { | |
| if (IsNull()) return 0; | |
| const intptr_t num_types = Length(); | |
| intptr_t result = 0; | |
| if (num_types <= kNullabilityMaxTypes) { | |
| AbstractType& type = AbstractType::Handle(); | |
| for (intptr_t i = 0; i < num_types; i++) { | |
| type = TypeAt(i); | |
| intptr_t type_bits = 0; | |
| if (!type.IsNull()) { | |
| switch (type.nullability()) { | |
| case Nullability::kNullable: | |
| type_bits = kNullableBit; | |
| break; | |
| case Nullability::kNonNullable: | |
| type_bits = kNonNullableBit; | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| result |= (type_bits << (i * kNullabilityBitsPerType)); | |
| } | |
| } | |
| set_nullability(result); | |
| return result; | |
| } | |
| void TypeArguments::set_nullability(intptr_t value) const { | |
| untag()->set_nullability(Smi::New(value)); | |
| } | |
| uword TypeArguments::HashForRange(intptr_t from_index, intptr_t len) const { | |
| if (IsNull()) return kAllDynamicHash; | |
| if (IsRaw(from_index, len)) return kAllDynamicHash; | |
| uint32_t result = 0; | |
| AbstractType& type = AbstractType::Handle(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| type = TypeAt(from_index + i); | |
| ASSERT(!type.IsNull()); | |
| result = CombineHashes(result, type.Hash()); | |
| } | |
| result = FinalizeHash(result, kHashBits); | |
| return result; | |
| } | |
| uword TypeArguments::ComputeHash() const { | |
| if (IsNull()) return kAllDynamicHash; | |
| const uword result = HashForRange(0, Length()); | |
| ASSERT(result != 0); | |
| SetHash(result); | |
| return result; | |
| } | |
| TypeArgumentsPtr TypeArguments::Prepend(Zone* zone, | |
| const TypeArguments& other, | |
| intptr_t other_length, | |
| intptr_t total_length) const { | |
| if (other_length == 0) { | |
| ASSERT(IsCanonical()); | |
| return ptr(); | |
| } else if (other_length == total_length) { | |
| ASSERT(other.IsCanonical()); | |
| return other.ptr(); | |
| } else if (IsNull() && other.IsNull()) { | |
| return TypeArguments::null(); | |
| } | |
| const TypeArguments& result = | |
| TypeArguments::Handle(zone, TypeArguments::New(total_length, Heap::kNew)); | |
| AbstractType& type = AbstractType::Handle(zone); | |
| for (intptr_t i = 0; i < other_length; i++) { | |
| type = other.IsNull() ? Type::DynamicType() : other.TypeAt(i); | |
| result.SetTypeAt(i, type); | |
| } | |
| for (intptr_t i = other_length; i < total_length; i++) { | |
| type = IsNull() ? Type::DynamicType() : TypeAt(i - other_length); | |
| result.SetTypeAt(i, type); | |
| } | |
| return result.Canonicalize(Thread::Current()); | |
| } | |
| InstantiationMode TypeArguments::GetInstantiationMode(Zone* zone, | |
| const Function* function, | |
| const Class* cls) const { | |
| if (IsNull() || IsInstantiated()) { | |
| return InstantiationMode::kIsInstantiated; | |
| } | |
| if (function != nullptr) { | |
| if (CanShareFunctionTypeArguments(*function)) { | |
| return InstantiationMode::kSharesFunctionTypeArguments; | |
| } | |
| if (cls == nullptr) { | |
| cls = &Class::Handle(zone, function->Owner()); | |
| } | |
| } | |
| if (cls != nullptr) { | |
| if (CanShareInstantiatorTypeArguments(*cls)) { | |
| return InstantiationMode::kSharesInstantiatorTypeArguments; | |
| } | |
| } | |
| return InstantiationMode::kNeedsInstantiation; | |
| } | |
| StringPtr TypeArguments::Name() const { | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| PrintSubvectorName(0, Length(), kInternalName, &printer); | |
| return Symbols::New(thread, printer.buffer()); | |
| } | |
| StringPtr TypeArguments::UserVisibleName() const { | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| PrintSubvectorName(0, Length(), kUserVisibleName, &printer); | |
| return Symbols::New(thread, printer.buffer()); | |
| } | |
| void TypeArguments::PrintSubvectorName(intptr_t from_index, | |
| intptr_t len, | |
| NameVisibility name_visibility, | |
| BaseTextBuffer* printer) const { | |
| printer->AddString("<"); | |
| AbstractType& type = AbstractType::Handle(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| if (from_index + i < Length()) { | |
| type = TypeAt(from_index + i); | |
| if (type.IsNull()) { | |
| printer->AddString("null"); // Unfinalized vector. | |
| } else { | |
| type.PrintName(name_visibility, printer); | |
| } | |
| } else { | |
| printer->AddString("dynamic"); | |
| } | |
| if (i < len - 1) { | |
| printer->AddString(", "); | |
| } | |
| } | |
| printer->AddString(">"); | |
| } | |
| void TypeArguments::PrintTo(BaseTextBuffer* buffer) const { | |
| buffer->AddString("TypeArguments: "); | |
| if (IsNull()) { | |
| return buffer->AddString("null"); | |
| } | |
| buffer->Printf("(H%" Px ")", Smi::Value(untag()->hash())); | |
| auto& type_at = AbstractType::Handle(); | |
| for (intptr_t i = 0; i < Length(); i++) { | |
| type_at = TypeAt(i); | |
| buffer->Printf(" [%s]", type_at.IsNull() ? "null" : type_at.ToCString()); | |
| } | |
| } | |
| bool TypeArguments::IsSubvectorEquivalent( | |
| const TypeArguments& other, | |
| intptr_t from_index, | |
| intptr_t len, | |
| TypeEquality kind, | |
| FunctionTypeMapping* function_type_equivalence) const { | |
| if (this->ptr() == other.ptr()) { | |
| return true; | |
| } | |
| if (kind == TypeEquality::kCanonical) { | |
| if (IsNull()) { | |
| return other.IsRaw(from_index, len); | |
| } else if (other.IsNull()) { | |
| return IsRaw(from_index, len); | |
| } | |
| if (Length() != other.Length()) { | |
| return false; | |
| } | |
| } | |
| AbstractType& type = AbstractType::Handle(); | |
| AbstractType& other_type = AbstractType::Handle(); | |
| for (intptr_t i = from_index; i < from_index + len; i++) { | |
| type = IsNull() ? Type::DynamicType() : TypeAt(i); | |
| ASSERT(!type.IsNull()); | |
| other_type = other.IsNull() ? Type::DynamicType() : other.TypeAt(i); | |
| ASSERT(!other_type.IsNull()); | |
| if (!type.IsEquivalent(other_type, kind, function_type_equivalence)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool TypeArguments::IsDynamicTypes(bool raw_instantiated, | |
| intptr_t from_index, | |
| intptr_t len) const { | |
| ASSERT(Length() >= (from_index + len)); | |
| AbstractType& type = AbstractType::Handle(); | |
| Class& type_class = Class::Handle(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| type = TypeAt(from_index + i); | |
| if (type.IsNull()) { | |
| return false; | |
| } | |
| if (!type.HasTypeClass()) { | |
| if (raw_instantiated && type.IsTypeParameter()) { | |
| // An uninstantiated type parameter is equivalent to dynamic. | |
| continue; | |
| } | |
| return false; | |
| } | |
| type_class = type.type_class(); | |
| if (!type_class.IsDynamicClass()) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| TypeArguments::Cache::Cache(Zone* zone, const TypeArguments& source) | |
| : zone_(ASSERT_NOTNULL(zone)), | |
| cache_container_(&source), | |
| data_(Array::Handle(source.instantiations())), | |
| smi_handle_(Smi::Handle(zone)) { | |
| ASSERT(IsolateGroup::Current() | |
| ->type_arguments_canonicalization_mutex() | |
| ->IsOwnedByCurrentThread()); | |
| } | |
| TypeArguments::Cache::Cache(Zone* zone, const Array& array) | |
| : zone_(ASSERT_NOTNULL(zone)), | |
| cache_container_(nullptr), | |
| data_(Array::Handle(array.ptr())), | |
| smi_handle_(Smi::Handle(zone)) { | |
| ASSERT(IsolateGroup::Current() | |
| ->type_arguments_canonicalization_mutex() | |
| ->IsOwnedByCurrentThread()); | |
| } | |
| bool TypeArguments::Cache::IsHash(const Array& array) { | |
| return array.Length() > kMaxLinearCacheSize; | |
| } | |
| intptr_t TypeArguments::Cache::NumOccupied(const Array& array) { | |
| return NumOccupiedBits::decode( | |
| RawSmiValue(Smi::RawCast(array.AtAcquire(kMetadataIndex)))); | |
| } | |
| #if defined(DEBUG) | |
| bool TypeArguments::Cache::IsValidStorageLocked(const Array& array) { | |
| // We only require the mutex be held so we don't need to use acquire/release | |
| // semantics to access and set the number of occupied entries in the header. | |
| ASSERT(IsolateGroup::Current() | |
| ->type_arguments_canonicalization_mutex() | |
| ->IsOwnedByCurrentThread()); | |
| // Quick check against the empty linear cache. | |
| if (array.ptr() == EmptyStorage().ptr()) return true; | |
| const intptr_t num_occupied = NumOccupied(array); | |
| // We should be using the same shared value for an empty cache. | |
| if (num_occupied == 0) return false; | |
| const intptr_t storage_len = array.Length(); | |
| // All caches have the metadata followed by a series of entries. | |
| if ((storage_len % kEntrySize) != kHeaderSize) return false; | |
| const intptr_t num_entries = NumEntries(array); | |
| // Linear caches contain at least one unoccupied entry, and hash-based caches | |
| // grow prior to hitting 100% occupancy. | |
| if (num_occupied >= num_entries) return false; | |
| // In a linear cache, all entries with indexes smaller than [num_occupied] | |
| // should be occupied and ones greater than or equal should be unoccupied. | |
| const bool is_linear_cache = IsLinear(array); | |
| // The capacity of a hash-based cache must be a power of two (see | |
| // EnsureCapacityLocked as to why). | |
| if (!is_linear_cache) { | |
| if (!Utils::IsPowerOfTwo(num_entries)) return false; | |
| const intptr_t metadata = | |
| RawSmiValue(Smi::RawCast(array.AtAcquire(kMetadataIndex))); | |
| if ((1 << EntryCountLog2Bits::decode(metadata)) != num_entries) { | |
| return false; | |
| } | |
| } | |
| for (intptr_t i = 0; i < num_entries; i++) { | |
| const intptr_t index = kHeaderSize + i * kEntrySize; | |
| if (array.At(index + kSentinelIndex) == Sentinel()) { | |
| if (is_linear_cache && i < num_occupied) return false; | |
| continue; | |
| } | |
| if (is_linear_cache && i >= num_occupied) return false; | |
| // The elements of an occupied entry are all TypeArguments values. | |
| for (intptr_t j = index; j < index + kEntrySize; j++) { | |
| if (!array.At(j)->IsHeapObject()) return false; | |
| if (array.At(j) == Object::null()) continue; // null is a valid TAV. | |
| if (!array.At(j)->IsTypeArguments()) return false; | |
| } | |
| } | |
| return true; | |
| } | |
| #endif | |
| bool TypeArguments::Cache::IsOccupied(intptr_t entry) const { | |
| InstantiationsCacheTable table(data_); | |
| ASSERT(entry >= 0 && entry < table.Length()); | |
| return table.At(entry).Get<kSentinelIndex>() != Sentinel(); | |
| } | |
| TypeArgumentsPtr TypeArguments::Cache::Retrieve(intptr_t entry) const { | |
| ASSERT(IsOccupied(entry)); | |
| InstantiationsCacheTable table(data_); | |
| return table.At(entry).Get<kInstantiatedTypeArgsIndex>(); | |
| } | |
| intptr_t TypeArguments::Cache::NumEntries(const Array& array) { | |
| InstantiationsCacheTable table(array); | |
| return table.Length(); | |
| } | |
| TypeArguments::Cache::KeyLocation TypeArguments::Cache::FindKeyOrUnused( | |
| const Array& array, | |
| const TypeArguments& instantiator_tav, | |
| const TypeArguments& function_tav) { | |
| const bool is_hash = IsHash(array); | |
| InstantiationsCacheTable table(array); | |
| const intptr_t num_entries = table.Length(); | |
| // For a linear cache, start at the first entry and probe linearly. This can | |
| // be done because a linear cache always has at least one unoccupied entry | |
| // after all the occupied ones. | |
| intptr_t probe = 0; | |
| intptr_t probe_distance = 1; | |
| if (is_hash) { | |
| // For a hash-based cache, instead start at an entry determined by the hash | |
| // of the keys. | |
| auto hash = FinalizeHash( | |
| CombineHashes(instantiator_tav.Hash(), function_tav.Hash())); | |
| probe = hash & (num_entries - 1); | |
| } | |
| while (true) { | |
| const auto& tuple = table.At(probe); | |
| if (tuple.Get<kSentinelIndex>() == Sentinel()) break; | |
| if ((tuple.Get<kInstantiatorTypeArgsIndex>() == instantiator_tav.ptr()) && | |
| (tuple.Get<kFunctionTypeArgsIndex>() == function_tav.ptr())) { | |
| return {probe, true}; | |
| } | |
| // Advance probe by the current probing distance. | |
| probe = probe + probe_distance; | |
| if (is_hash) { | |
| // Wrap around if the probe goes off the end of the entries array. | |
| probe = probe & (num_entries - 1); | |
| // We had a collision, so increase the probe distance. See comment in | |
| // EnsureCapacityLocked for an explanation of how this hits all slots. | |
| probe_distance++; | |
| } | |
| } | |
| // We should always get the next slot for a linear cache. | |
| ASSERT(is_hash || probe == NumOccupied(array)); | |
| return {probe, false}; | |
| } | |
| TypeArguments::Cache::KeyLocation TypeArguments::Cache::AddEntry( | |
| intptr_t entry, | |
| const TypeArguments& instantiator_tav, | |
| const TypeArguments& function_tav, | |
| const TypeArguments& instantiated_tav) const { | |
| // We don't do mutating operations in tests without a TypeArguments object. | |
| ASSERT(cache_container_ != nullptr); | |
| #if defined(DEBUG) | |
| auto loc = FindKeyOrUnused(instantiator_tav, function_tav); | |
| ASSERT_EQUAL(loc.entry, entry); | |
| ASSERT(!loc.present); | |
| #endif | |
| // Double-check we got the expected entry index when adding to a linear array. | |
| ASSERT(!IsLinear() || entry == NumOccupied()); | |
| const intptr_t new_occupied = NumOccupied() + 1; | |
| const bool storage_changed = EnsureCapacity(new_occupied); | |
| // Note that this call to IsLinear() may return a different result than the | |
| // earlier, since EnsureCapacity() may have swapped to hash-based storage. | |
| if (storage_changed && !IsLinear()) { | |
| // The capacity of the array has changed, and the capacity is used when | |
| // probing further into the array due to collisions. Thus, we need to redo | |
| // the entry index calculation. | |
| auto loc = FindKeyOrUnused(instantiator_tav, function_tav); | |
| ASSERT(!loc.present); | |
| entry = loc.entry; | |
| } | |
| // Go ahead and increment the number of occupied entries prior to adding the | |
| // entry. Use a store-release barrier in case of concurrent readers. | |
| const intptr_t metadata = RawSmiValue(Smi::RawCast(data_.At(kMetadataIndex))); | |
| smi_handle_ = Smi::New(NumOccupiedBits::update(new_occupied, metadata)); | |
| data_.SetAtRelease(kMetadataIndex, smi_handle_); | |
| InstantiationsCacheTable table(data_); | |
| const auto& tuple = table.At(entry); | |
| // The parts of the tuple that aren't used for sentinel checking are only | |
| // retrieved if the entry is occupied. Entries in the cache are never deleted, | |
| // so once the entry is marked as occupied, the contents of that entry never | |
| // change. Thus, we don't need store-release barriers here. | |
| tuple.Set<kFunctionTypeArgsIndex>(function_tav); | |
| tuple.Set<kInstantiatedTypeArgsIndex>(instantiated_tav); | |
| // For the sentinel position, though, we do. | |
| static_assert( | |
| kSentinelIndex == kInstantiatorTypeArgsIndex, | |
| "the sentinel position is not protected with a store-release barrier"); | |
| tuple.Set<kInstantiatorTypeArgsIndex, std::memory_order_release>( | |
| instantiator_tav); | |
| if (storage_changed) { | |
| // Only check for validity on growth, just to keep the overhead on DEBUG | |
| // builds down. | |
| DEBUG_ASSERT(IsValidStorageLocked(data_)); | |
| // Update the container of the original cache to point to the new one. | |
| cache_container_->set_instantiations(data_); | |
| } | |
| return {entry, true}; | |
| } | |
| SmiPtr TypeArguments::Cache::Sentinel() { | |
| return Smi::New(kSentinelValue); | |
| } | |
| bool TypeArguments::Cache::EnsureCapacity(intptr_t new_occupied) const { | |
| ASSERT(new_occupied > NumOccupied()); | |
| // How many entries are in the current array (including unoccupied entries). | |
| const intptr_t current_capacity = NumEntries(); | |
| // Early returns for cases where no growth is needed. | |
| const bool is_linear = IsLinear(); | |
| if (is_linear) { | |
| // We need at least one unoccupied entry in addition to the occupied ones. | |
| if (current_capacity > new_occupied) return false; | |
| } else { | |
| if (LoadFactor(new_occupied, current_capacity) < kMaxLoadFactor) { | |
| return false; | |
| } | |
| } | |
| if (new_occupied <= kMaxLinearCacheEntries) { | |
| ASSERT(is_linear); | |
| // Not enough room for both the new entry and at least one unoccupied | |
| // entry, so grow the tuple capacity of the linear cache by about 50%, | |
| // ensuring that space for at least one new tuple is added, capping the | |
| // total number of occupied entries to the max allowed. | |
| const intptr_t new_capacity = | |
| Utils::Minimum(current_capacity + (current_capacity >> 1), | |
| kMaxLinearCacheEntries) + | |
| 1; | |
| const intptr_t cache_size = kHeaderSize + new_capacity * kEntrySize; | |
| ASSERT(cache_size <= kMaxLinearCacheSize); | |
| data_ = Array::Grow(data_, cache_size, Heap::kOld); | |
| ASSERT(!data_.IsNull()); | |
| // No need to adjust the number of occupied entries or old entries, as they | |
| // are copied over by Array::Grow. Just mark any new entries as unoccupied. | |
| smi_handle_ = Sentinel(); | |
| InstantiationsCacheTable table(data_); | |
| for (intptr_t i = current_capacity; i < new_capacity; i++) { | |
| const auto& tuple = table.At(i); | |
| tuple.Set<kSentinelIndex>(smi_handle_); | |
| } | |
| return true; | |
| } | |
| // Either we're converting a linear cache into a hash-based cache, or the | |
| // load factor of the hash-based cache has increased to the point where we | |
| // need to grow it. | |
| const intptr_t new_capacity = | |
| is_linear ? kNumInitialHashCacheEntries : 2 * current_capacity; | |
| // Because we use quadratic (actually triangle number) probing it is | |
| // important that the size is a power of two (otherwise we could fail to | |
| // find an empty slot). This is described in Knuth's The Art of Computer | |
| // Programming Volume 2, Chapter 6.4, exercise 20 (solution in the | |
| // appendix, 2nd edition). | |
| ASSERT(Utils::IsPowerOfTwo(new_capacity)); | |
| ASSERT(LoadFactor(new_occupied, new_capacity) < kMaxLoadFactor); | |
| const intptr_t new_size = kHeaderSize + new_capacity * kEntrySize; | |
| const auto& new_data = | |
| Array::Handle(zone_, Array::NewUninitialized(new_size, Heap::kOld)); | |
| ASSERT(!new_data.IsNull()); | |
| // First set up the metadata in new_data. | |
| const intptr_t metadata = RawSmiValue(Smi::RawCast(data_.At(kMetadataIndex))); | |
| smi_handle_ = Smi::New(EntryCountLog2Bits::update( | |
| Utils::ShiftForPowerOfTwo(new_capacity), metadata)); | |
| new_data.SetAt(kMetadataIndex, smi_handle_); | |
| // Then mark all the entries in new_data as unoccupied. | |
| smi_handle_ = Sentinel(); | |
| InstantiationsCacheTable to_table(new_data); | |
| for (const auto& tuple : to_table) { | |
| tuple.Set<kSentinelIndex>(smi_handle_); | |
| } | |
| // Finally, copy over the entries. | |
| auto& instantiator_tav = TypeArguments::Handle(zone_); | |
| auto& function_tav = TypeArguments::Handle(zone_); | |
| auto& result_tav = TypeArguments::Handle(zone_); | |
| const InstantiationsCacheTable from_table(data_); | |
| for (const auto& from_tuple : from_table) { | |
| // Skip unoccupied entries. | |
| if (from_tuple.Get<kSentinelIndex>() == Sentinel()) continue; | |
| instantiator_tav ^= from_tuple.Get<kInstantiatorTypeArgsIndex>(); | |
| function_tav = from_tuple.Get<kFunctionTypeArgsIndex>(); | |
| result_tav = from_tuple.Get<kInstantiatedTypeArgsIndex>(); | |
| // Since new_data has a different total capacity, we can't use the old | |
| // entry indexes, but must recalculate them. | |
| auto loc = FindKeyOrUnused(new_data, instantiator_tav, function_tav); | |
| ASSERT(!loc.present); | |
| const auto& to_tuple = to_table.At(loc.entry); | |
| to_tuple.Set<kInstantiatorTypeArgsIndex>(instantiator_tav); | |
| to_tuple.Set<kFunctionTypeArgsIndex>(function_tav); | |
| to_tuple.Set<kInstantiatedTypeArgsIndex>(result_tav); | |
| } | |
| data_ = new_data.ptr(); | |
| return true; | |
| } | |
| bool TypeArguments::HasInstantiations() const { | |
| return instantiations() != Cache::EmptyStorage().ptr(); | |
| } | |
| ArrayPtr TypeArguments::instantiations() const { | |
| // We rely on the fact that any loads from the array are dependent loads and | |
| // avoid the load-acquire barrier here. | |
| return untag()->instantiations(); | |
| } | |
| void TypeArguments::set_instantiations(const Array& value) const { | |
| // We have to ensure that initializing stores to the array are available | |
| // when releasing the pointer to the array pointer. | |
| // => We have to use store-release here. | |
| ASSERT(!value.IsNull()); | |
| untag()->set_instantiations<std::memory_order_release>(value.ptr()); | |
| } | |
| bool TypeArguments::HasCount(intptr_t count) const { | |
| if (IsNull()) { | |
| return true; | |
| } | |
| return Length() == count; | |
| } | |
| intptr_t TypeArguments::Length() const { | |
| if (IsNull()) { | |
| return 0; | |
| } | |
| return Smi::Value(untag()->length()); | |
| } | |
| intptr_t TypeArguments::nullability() const { | |
| if (IsNull()) { | |
| return 0; | |
| } | |
| return Smi::Value(untag()->nullability()); | |
| } | |
| AbstractTypePtr TypeArguments::TypeAt(intptr_t index) const { | |
| ASSERT(!IsNull()); | |
| ASSERT((index >= 0) && (index < Length())); | |
| return untag()->element(index); | |
| } | |
| AbstractTypePtr TypeArguments::TypeAtNullSafe(intptr_t index) const { | |
| if (IsNull()) { | |
| // null vector represents infinite list of dynamics | |
| return Type::dynamic_type().ptr(); | |
| } | |
| ASSERT((index >= 0) && (index < Length())); | |
| return TypeAt(index); | |
| } | |
| void TypeArguments::SetTypeAt(intptr_t index, const AbstractType& value) const { | |
| ASSERT(!IsCanonical()); | |
| ASSERT((index >= 0) && (index < Length())); | |
| return untag()->set_element(index, value.ptr()); | |
| } | |
| bool TypeArguments::IsSubvectorInstantiated( | |
| intptr_t from_index, | |
| intptr_t len, | |
| Genericity genericity, | |
| intptr_t num_free_fun_type_params) const { | |
| ASSERT(!IsNull()); | |
| AbstractType& type = AbstractType::Handle(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| type = TypeAt(from_index + i); | |
| // If this type argument T is null, the type A containing T in its flattened | |
| // type argument vector V is recursive and is still being finalized. | |
| // T is the type argument of a super type of A. T is being instantiated | |
| // during finalization of V, which is also the instantiator. T depends | |
| // solely on the type parameters of A and will be replaced by a non-null | |
| // type before A is marked as finalized. | |
| if (!type.IsNull() && | |
| !type.IsInstantiated(genericity, num_free_fun_type_params)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool TypeArguments::IsUninstantiatedIdentity() const { | |
| AbstractType& type = AbstractType::Handle(); | |
| const intptr_t num_types = Length(); | |
| for (intptr_t i = 0; i < num_types; i++) { | |
| type = TypeAt(i); | |
| if (type.IsNull()) { | |
| return false; // Still unfinalized, too early to tell. | |
| } | |
| if (!type.IsTypeParameter()) { | |
| return false; | |
| } | |
| const TypeParameter& type_param = TypeParameter::Cast(type); | |
| ASSERT(type_param.IsFinalized()); | |
| if ((type_param.index() != i) || type_param.IsFunctionTypeParameter()) { | |
| return false; | |
| } | |
| // Instantiating nullable type parameters may change | |
| // nullability of a type, so type arguments vector containing such type | |
| // parameters cannot be substituted with instantiator type arguments. | |
| if (type_param.IsNullable()) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| // Note that it is not necessary to verify at runtime that the instantiator | |
| // type vector is long enough, since this uninstantiated vector contains as | |
| // many different type parameters as it is long. | |
| } | |
| // Return true if this uninstantiated type argument vector, once instantiated | |
| // at runtime, is a prefix of the type argument vector of its instantiator. | |
| // A runtime check may be required, as indicated by with_runtime_check. | |
| bool TypeArguments::CanShareInstantiatorTypeArguments( | |
| const Class& instantiator_class, | |
| bool* with_runtime_check) const { | |
| ASSERT(!IsInstantiated()); | |
| if (with_runtime_check != nullptr) { | |
| *with_runtime_check = false; | |
| } | |
| const intptr_t num_type_args = Length(); | |
| const intptr_t num_instantiator_type_args = | |
| instantiator_class.NumTypeArguments(); | |
| if (num_type_args > num_instantiator_type_args) { | |
| // This vector cannot be a prefix of a shorter vector. | |
| return false; | |
| } | |
| const intptr_t num_instantiator_type_params = | |
| instantiator_class.NumTypeParameters(); | |
| const intptr_t first_type_param_offset = | |
| num_instantiator_type_args - num_instantiator_type_params; | |
| // At compile time, the type argument vector of the instantiator consists of | |
| // the type argument vector of its super type, which may refer to the type | |
| // parameters of the instantiator class, followed by (or overlapping partially | |
| // or fully with) the type parameters of the instantiator class in declaration | |
| // order. | |
| // In other words, the only variables are the type parameters of the | |
| // instantiator class. | |
| // This uninstantiated type argument vector is also expressed in terms of the | |
| // type parameters of the instantiator class. Therefore, in order to be a | |
| // prefix once instantiated at runtime, every one of its type argument must be | |
| // equal to the type argument of the instantiator vector at the same index. | |
| // As a first requirement, the last num_instantiator_type_params type | |
| // arguments of this type argument vector must refer to the corresponding type | |
| // parameters of the instantiator class. | |
| AbstractType& type_arg = AbstractType::Handle(); | |
| for (intptr_t i = first_type_param_offset; i < num_type_args; i++) { | |
| type_arg = TypeAt(i); | |
| if (!type_arg.IsTypeParameter()) { | |
| return false; | |
| } | |
| const TypeParameter& type_param = TypeParameter::Cast(type_arg); | |
| ASSERT(type_param.IsFinalized()); | |
| if ((type_param.index() != i) || type_param.IsFunctionTypeParameter()) { | |
| return false; | |
| } | |
| // Instantiating nullable type parameters may change nullability | |
| // of a type, so type arguments vector containing such type parameters | |
| // cannot be substituted with instantiator type arguments, unless we check | |
| // at runtime the nullability of the first 1 or 2 type arguments of the | |
| // instantiator. | |
| // Note that the presence of non-overlapping super type arguments (i.e. | |
| // first_type_param_offset > 0) will prevent this optimization. | |
| if (type_param.IsNullable()) { | |
| if (with_runtime_check == nullptr || i >= kNullabilityMaxTypes) { | |
| return false; | |
| } | |
| *with_runtime_check = true; | |
| } | |
| } | |
| // As a second requirement, the type arguments corresponding to the super type | |
| // must be identical. Overlapping ones have already been checked starting at | |
| // first_type_param_offset. | |
| if (first_type_param_offset == 0) { | |
| return true; | |
| } | |
| Type& super_type = Type::Handle(instantiator_class.super_type()); | |
| const TypeArguments& super_type_args = | |
| TypeArguments::Handle(super_type.GetInstanceTypeArguments( | |
| Thread::Current(), /*canonicalize=*/false)); | |
| if (super_type_args.IsNull()) { | |
| ASSERT(!IsUninstantiatedIdentity()); | |
| return false; | |
| } | |
| AbstractType& super_type_arg = AbstractType::Handle(); | |
| for (intptr_t i = 0; (i < first_type_param_offset) && (i < num_type_args); | |
| i++) { | |
| type_arg = TypeAt(i); | |
| super_type_arg = super_type_args.TypeAt(i); | |
| if (!type_arg.Equals(super_type_arg)) { | |
| ASSERT(!IsUninstantiatedIdentity()); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| // Return true if this uninstantiated type argument vector, once instantiated | |
| // at runtime, is a prefix of the enclosing function type arguments. | |
| // A runtime check may be required, as indicated by with_runtime_check. | |
| bool TypeArguments::CanShareFunctionTypeArguments( | |
| const Function& function, | |
| bool* with_runtime_check) const { | |
| ASSERT(!IsInstantiated()); | |
| if (with_runtime_check != nullptr) { | |
| *with_runtime_check = false; | |
| } | |
| const intptr_t num_type_args = Length(); | |
| const intptr_t num_parent_type_args = function.NumParentTypeArguments(); | |
| const intptr_t num_function_type_params = function.NumTypeParameters(); | |
| const intptr_t num_function_type_args = | |
| num_parent_type_args + num_function_type_params; | |
| if (num_type_args > num_function_type_args) { | |
| // This vector cannot be a prefix of a shorter vector. | |
| return false; | |
| } | |
| AbstractType& type_arg = AbstractType::Handle(); | |
| for (intptr_t i = 0; i < num_type_args; i++) { | |
| type_arg = TypeAt(i); | |
| if (!type_arg.IsTypeParameter()) { | |
| return false; | |
| } | |
| const TypeParameter& type_param = TypeParameter::Cast(type_arg); | |
| ASSERT(type_param.IsFinalized()); | |
| if ((type_param.index() != i) || !type_param.IsFunctionTypeParameter()) { | |
| return false; | |
| } | |
| // Instantiating nullable type parameters may change nullability | |
| // of a type, so type arguments vector containing such type parameters | |
| // cannot be substituted with the enclosing function type arguments, unless | |
| // we check at runtime the nullability of the first 1 or 2 type arguments of | |
| // the enclosing function type arguments. | |
| if (type_param.IsNullable()) { | |
| if (with_runtime_check == nullptr || i >= kNullabilityMaxTypes) { | |
| return false; | |
| } | |
| *with_runtime_check = true; | |
| } | |
| } | |
| return true; | |
| } | |
| TypeArgumentsPtr TypeArguments::TruncatedTo(intptr_t length) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const TypeArguments& result = | |
| TypeArguments::Handle(zone, TypeArguments::New(length)); | |
| AbstractType& type = AbstractType::Handle(zone); | |
| for (intptr_t i = 0; i < length; i++) { | |
| type = TypeAt(i); | |
| result.SetTypeAt(i, type); | |
| } | |
| return result.Canonicalize(thread); | |
| } | |
| bool TypeArguments::IsFinalized() const { | |
| ASSERT(!IsNull()); | |
| AbstractType& type = AbstractType::Handle(); | |
| const intptr_t num_types = Length(); | |
| for (intptr_t i = 0; i < num_types; i++) { | |
| type = TypeAt(i); | |
| if (!type.IsFinalized()) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| TypeArgumentsPtr TypeArguments::InstantiateFrom( | |
| const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments, | |
| intptr_t num_free_fun_type_params, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_mapping, | |
| intptr_t num_parent_type_args_adjustment) const { | |
| ASSERT(!IsInstantiated()); | |
| if ((instantiator_type_arguments.IsNull() || | |
| instantiator_type_arguments.Length() == Length()) && | |
| IsUninstantiatedIdentity()) { | |
| return instantiator_type_arguments.ptr(); | |
| } | |
| const intptr_t num_types = Length(); | |
| TypeArguments& instantiated_array = | |
| TypeArguments::Handle(TypeArguments::New(num_types, space)); | |
| AbstractType& type = AbstractType::Handle(); | |
| for (intptr_t i = 0; i < num_types; i++) { | |
| type = TypeAt(i); | |
| // If this type argument T is null, the type A containing T in its flattened | |
| // type argument vector V is recursive and is still being finalized. | |
| // T is the type argument of a super type of A. T is being instantiated | |
| // during finalization of V, which is also the instantiator. T depends | |
| // solely on the type parameters of A and will be replaced by a non-null | |
| // type before A is marked as finalized. | |
| if (!type.IsNull() && !type.IsInstantiated()) { | |
| type = type.InstantiateFrom( | |
| instantiator_type_arguments, function_type_arguments, | |
| num_free_fun_type_params, space, function_type_mapping, | |
| num_parent_type_args_adjustment); | |
| // A returned null type indicates a failed instantiation in dead code that | |
| // must be propagated up to the caller, the optimizing compiler. | |
| if (type.IsNull()) { | |
| return Object::empty_type_arguments().ptr(); | |
| } | |
| } | |
| instantiated_array.SetTypeAt(i, type); | |
| } | |
| return instantiated_array.ptr(); | |
| } | |
| TypeArgumentsPtr TypeArguments::UpdateFunctionTypes( | |
| intptr_t num_parent_type_args_adjustment, | |
| intptr_t num_free_fun_type_params, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_mapping) const { | |
| Zone* zone = Thread::Current()->zone(); | |
| TypeArguments* updated_args = nullptr; | |
| AbstractType& type = AbstractType::Handle(zone); | |
| AbstractType& updated = AbstractType::Handle(zone); | |
| for (intptr_t i = 0, n = Length(); i < n; ++i) { | |
| type = TypeAt(i); | |
| updated = type.UpdateFunctionTypes(num_parent_type_args_adjustment, | |
| num_free_fun_type_params, space, | |
| function_type_mapping); | |
| if (type.ptr() != updated.ptr()) { | |
| if (updated_args == nullptr) { | |
| updated_args = | |
| &TypeArguments::Handle(zone, TypeArguments::New(n, space)); | |
| for (intptr_t j = 0; j < i; ++j) { | |
| type = TypeAt(j); | |
| updated_args->SetTypeAt(j, type); | |
| } | |
| } | |
| } | |
| if (updated_args != nullptr) { | |
| updated_args->SetTypeAt(i, updated); | |
| } | |
| } | |
| return (updated_args != nullptr) ? updated_args->ptr() : ptr(); | |
| } | |
| #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| // A local flag used only in object_test.cc that, when true, causes a failure | |
| // when a cache entry for the given instantiator and function type arguments | |
| // already exists. Used to check that the InstantiateTypeArguments stub found | |
| // the cache entry instead of calling the runtime. | |
| bool TESTING_runtime_fail_on_existing_cache_entry = false; | |
| #endif | |
| TypeArgumentsPtr TypeArguments::InstantiateAndCanonicalizeFrom( | |
| const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments) const { | |
| auto thread = Thread::Current(); | |
| auto zone = thread->zone(); | |
| SafepointMutexLocker ml( | |
| thread->isolate_group()->type_arguments_canonicalization_mutex()); | |
| ASSERT(!IsInstantiated()); | |
| ASSERT(instantiator_type_arguments.IsNull() || | |
| instantiator_type_arguments.IsCanonical()); | |
| ASSERT(function_type_arguments.IsNull() || | |
| function_type_arguments.IsCanonical()); | |
| // Lookup instantiators and if found, return instantiated result. | |
| Cache cache(zone, *this); | |
| auto const loc = cache.FindKeyOrUnused(instantiator_type_arguments, | |
| function_type_arguments); | |
| if (loc.present) { | |
| #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| if (TESTING_runtime_fail_on_existing_cache_entry) { | |
| TextBuffer buffer(1024); | |
| buffer.Printf("for\n"); | |
| buffer.Printf(" * uninstantiated type arguments %s\n", ToCString()); | |
| buffer.Printf(" * instantiation type arguments: %s (hash: %" Pu ")\n", | |
| instantiator_type_arguments.ToCString(), | |
| instantiator_type_arguments.Hash()); | |
| buffer.Printf(" * function type arguments: %s (hash: %" Pu ")\n", | |
| function_type_arguments.ToCString(), | |
| function_type_arguments.Hash()); | |
| buffer.Printf(" * number of occupied entries in cache: %" Pd "\n", | |
| cache.NumOccupied()); | |
| buffer.Printf(" * number of total entries in cache: %" Pd "\n", | |
| cache.NumEntries()); | |
| buffer.Printf("expected to find entry %" Pd | |
| " of cache in stub, but reached runtime", | |
| loc.entry); | |
| FATAL("%s", buffer.buffer()); | |
| } | |
| #endif | |
| return cache.Retrieve(loc.entry); | |
| } | |
| // Cache lookup failed. Instantiate the type arguments. | |
| TypeArguments& result = TypeArguments::Handle(zone); | |
| result = InstantiateFrom(instantiator_type_arguments, function_type_arguments, | |
| kAllFree, Heap::kOld); | |
| // Canonicalize type arguments. | |
| result = result.Canonicalize(thread); | |
| // InstantiateAndCanonicalizeFrom is not reentrant. It cannot have been called | |
| // indirectly, so the prior_instantiations array cannot have grown. | |
| ASSERT(cache.data_.ptr() == instantiations()); | |
| cache.AddEntry(loc.entry, instantiator_type_arguments, | |
| function_type_arguments, result); | |
| return result.ptr(); | |
| } | |
| TypeArgumentsPtr TypeArguments::New(intptr_t len, Heap::Space space) { | |
| if (len < 0 || len > kMaxElements) { | |
| // This should be caught before we reach here. | |
| FATAL("Fatal error in TypeArguments::New: invalid len %" Pd "\n", len); | |
| } | |
| TypeArguments& result = TypeArguments::Handle(); | |
| { | |
| auto raw = Object::Allocate<TypeArguments>(space, len); | |
| NoSafepointScope no_safepoint; | |
| result = raw; | |
| // Length must be set before we start storing into the array. | |
| result.SetLength(len); | |
| result.SetHash(0); | |
| result.set_nullability(0); | |
| } | |
| // The array used as storage for an empty linear cache should be initialized. | |
| ASSERT(Cache::EmptyStorage().ptr() != Array::null()); | |
| result.set_instantiations(Cache::EmptyStorage()); | |
| return result.ptr(); | |
| } | |
| void TypeArguments::SetLength(intptr_t value) const { | |
| ASSERT(!IsCanonical()); | |
| // This is only safe because we create a new Smi, which does not cause | |
| // heap allocation. | |
| untag()->set_length(Smi::New(value)); | |
| } | |
| TypeArgumentsPtr TypeArguments::Canonicalize(Thread* thread) const { | |
| if (IsNull() || IsCanonical()) { | |
| ASSERT(IsOld()); | |
| return this->ptr(); | |
| } | |
| const intptr_t num_types = Length(); | |
| if (num_types == 0) { | |
| return TypeArguments::empty_type_arguments().ptr(); | |
| } else if (IsRaw(0, num_types)) { | |
| return TypeArguments::null(); | |
| } | |
| Zone* zone = thread->zone(); | |
| auto isolate_group = thread->isolate_group(); | |
| ObjectStore* object_store = isolate_group->object_store(); | |
| TypeArguments& result = TypeArguments::Handle(zone); | |
| { | |
| SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex()); | |
| CanonicalTypeArgumentsSet table(zone, | |
| object_store->canonical_type_arguments()); | |
| result ^= table.GetOrNull(CanonicalTypeArgumentsKey(*this)); | |
| object_store->set_canonical_type_arguments(table.Release()); | |
| } | |
| if (result.IsNull()) { | |
| // Canonicalize each type argument. | |
| AbstractType& type_arg = AbstractType::Handle(zone); | |
| GrowableHandlePtrArray<const AbstractType> canonicalized_types(zone, | |
| num_types); | |
| for (intptr_t i = 0; i < num_types; i++) { | |
| type_arg = TypeAt(i); | |
| type_arg = type_arg.Canonicalize(thread); | |
| canonicalized_types.Add(type_arg); | |
| } | |
| SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex()); | |
| CanonicalTypeArgumentsSet table(zone, | |
| object_store->canonical_type_arguments()); | |
| // Since we canonicalized some type arguments above we need to lookup | |
| // in the table again to make sure we don't already have an equivalent | |
| // canonical entry. | |
| result ^= table.GetOrNull(CanonicalTypeArgumentsKey(*this)); | |
| if (result.IsNull()) { | |
| for (intptr_t i = 0; i < num_types; i++) { | |
| SetTypeAt(i, canonicalized_types.At(i)); | |
| } | |
| // Make sure we have an old space object and add it to the table. | |
| if (this->IsNew()) { | |
| result ^= Object::Clone(*this, Heap::kOld); | |
| } else { | |
| result = this->ptr(); | |
| } | |
| ASSERT(result.IsOld()); | |
| result.ComputeNullability(); | |
| result.SetCanonical(); // Mark object as being canonical. | |
| // Now add this TypeArgument into the canonical list of type arguments. | |
| bool present = table.Insert(result); | |
| ASSERT(!present); | |
| } | |
| object_store->set_canonical_type_arguments(table.Release()); | |
| } | |
| ASSERT(result.Equals(*this)); | |
| ASSERT(!result.IsNull()); | |
| ASSERT(result.IsTypeArguments()); | |
| ASSERT(result.IsCanonical()); | |
| return result.ptr(); | |
| } | |
| TypeArgumentsPtr TypeArguments::FromInstanceTypeArguments( | |
| Thread* thread, | |
| const Class& cls) const { | |
| if (IsNull()) { | |
| return ptr(); | |
| } | |
| const intptr_t num_type_arguments = cls.NumTypeArguments(); | |
| const intptr_t num_type_parameters = cls.NumTypeParameters(thread); | |
| ASSERT(Length() >= num_type_arguments); | |
| if (Length() == num_type_parameters) { | |
| return ptr(); | |
| } | |
| if (num_type_parameters == 0) { | |
| return TypeArguments::null(); | |
| } | |
| Zone* zone = thread->zone(); | |
| const auto& args = | |
| TypeArguments::Handle(zone, TypeArguments::New(num_type_parameters)); | |
| const intptr_t offset = num_type_arguments - num_type_parameters; | |
| auto& type = AbstractType::Handle(zone); | |
| for (intptr_t i = 0; i < num_type_parameters; ++i) { | |
| type = TypeAt(offset + i); | |
| args.SetTypeAt(i, type); | |
| } | |
| return args.ptr(); | |
| } | |
| TypeArgumentsPtr TypeArguments::ToInstantiatorTypeArguments( | |
| Thread* thread, | |
| const Class& cls) const { | |
| if (IsNull()) { | |
| return ptr(); | |
| } | |
| const intptr_t num_type_arguments = cls.NumTypeArguments(); | |
| const intptr_t num_type_parameters = cls.NumTypeParameters(thread); | |
| ASSERT(Length() == num_type_parameters); | |
| if (num_type_arguments == num_type_parameters) { | |
| return ptr(); | |
| } | |
| Zone* zone = thread->zone(); | |
| const auto& args = | |
| TypeArguments::Handle(zone, TypeArguments::New(num_type_arguments)); | |
| const intptr_t offset = num_type_arguments - num_type_parameters; | |
| auto& type = AbstractType::Handle(zone); | |
| for (intptr_t i = 0; i < num_type_parameters; ++i) { | |
| type = TypeAt(i); | |
| args.SetTypeAt(offset + i, type); | |
| } | |
| return args.ptr(); | |
| } | |
| void TypeArguments::EnumerateURIs(URIs* uris) const { | |
| if (IsNull()) { | |
| return; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| AbstractType& type = AbstractType::Handle(zone); | |
| const intptr_t num_types = Length(); | |
| for (intptr_t i = 0; i < num_types; i++) { | |
| type = TypeAt(i); | |
| type.EnumerateURIs(uris); | |
| } | |
| } | |
| const char* TypeArguments::ToCString() const { | |
| if (IsNull()) { | |
| return "TypeArguments: null"; // Optimizing the frequent case. | |
| } | |
| ZoneTextBuffer buffer(Thread::Current()->zone()); | |
| PrintTo(&buffer); | |
| return buffer.buffer(); | |
| } | |
| const char* PatchClass::ToCString() const { | |
| const Class& cls = Class::Handle(wrapped_class()); | |
| const char* cls_name = cls.ToCString(); | |
| return OS::SCreate(Thread::Current()->zone(), "PatchClass for %s", cls_name); | |
| } | |
| PatchClassPtr PatchClass::New(const Class& wrapped_class, | |
| const KernelProgramInfo& info, | |
| const Script& script) { | |
| const PatchClass& result = PatchClass::Handle(PatchClass::New()); | |
| result.set_wrapped_class(wrapped_class); | |
| NOT_IN_PRECOMPILED_RUNTIME( | |
| result.untag()->set_kernel_program_info(info.ptr())); | |
| result.set_script(script); | |
| result.set_kernel_library_index(-1); | |
| return result.ptr(); | |
| } | |
| PatchClassPtr PatchClass::New() { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kPatchClassCid) != | |
| Class::null()); | |
| return Object::Allocate<PatchClass>(Heap::kOld); | |
| } | |
| void PatchClass::set_wrapped_class(const Class& value) const { | |
| untag()->set_wrapped_class(value.ptr()); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| void PatchClass::set_kernel_program_info(const KernelProgramInfo& info) const { | |
| untag()->set_kernel_program_info(info.ptr()); | |
| } | |
| #endif | |
| void PatchClass::set_script(const Script& value) const { | |
| untag()->set_script(value.ptr()); | |
| } | |
| uword Function::Hash() const { | |
| uint32_t hash = String::HashRawSymbol(name()); | |
| if (IsNonImplicitClosureFunction()) { | |
| hash = CombineHashes(hash, token_pos().Hash()); | |
| } | |
| if (Owner()->IsClass()) { | |
| hash = CombineHashes(hash, Class::Hash(Class::RawCast(Owner()))); | |
| } | |
| return hash; | |
| } | |
| bool Function::HasBreakpoint() const { | |
| #if defined(PRODUCT) | |
| return false; | |
| #else | |
| auto thread = Thread::Current(); | |
| return thread->isolate_group()->debugger()->HasBreakpoint(thread, *this); | |
| #endif | |
| } | |
| void Function::InstallOptimizedCode(const Code& code) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| // We may not have previous code if FLAG_precompile is set. | |
| // Hot-reload may have already disabled the current code. | |
| if (HasCode() && !Code::Handle(CurrentCode()).IsDisabled()) { | |
| Code::Handle(CurrentCode()).DisableDartCode(); | |
| } | |
| AttachCode(code); | |
| } | |
| void Function::SetInstructions(const Code& value) const { | |
| // Ensure that nobody is executing this function when we install it. | |
| if (untag()->code() != Code::null() && HasCode()) { | |
| GcSafepointOperationScope safepoint(Thread::Current()); | |
| SetInstructionsSafe(value); | |
| } else { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| SetInstructionsSafe(value); | |
| } | |
| } | |
| void Function::SetInstructionsSafe(const Code& value) const { | |
| untag()->set_code<std::memory_order_release>(value.ptr()); | |
| StoreNonPointer(&untag()->entry_point_, value.EntryPoint()); | |
| StoreNonPointer(&untag()->unchecked_entry_point_, | |
| value.UncheckedEntryPoint()); | |
| } | |
| void Function::AttachCode(const Code& value) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| SetInstructions(value); | |
| ASSERT(Function::Handle(value.function()).IsNull() || | |
| (value.function() == this->ptr())); | |
| } | |
| bool Function::HasCode() const { | |
| NoSafepointScope no_safepoint; | |
| ASSERT(untag()->code() != Code::null()); | |
| return untag()->code() != StubCode::LazyCompile().ptr(); | |
| } | |
| #if defined(DART_DYNAMIC_MODULES) | |
| void Function::AttachBytecode(const Bytecode& value) const { | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(!value.IsNull()); | |
| // Finish setting up code before activating it. | |
| if (!value.IsSharedBytecodeStub()) { | |
| value.set_function(*this); | |
| } | |
| ASSERT(untag()->ic_data_array_or_bytecode() == Object::null()); | |
| untag()->set_ic_data_array_or_bytecode(value.ptr()); | |
| // Set the code entry_point to InterpretCall stub. | |
| SetInstructions(StubCode::InterpretCall()); | |
| } | |
| void Function::ClearBytecode() const { | |
| ASSERT(HasBytecode()); | |
| untag()->set_ic_data_array_or_bytecode(Object::null()); | |
| ClearCode(); | |
| } | |
| bool Function::IsInterpreted(FunctionPtr function) { | |
| return function->untag()->code() == StubCode::InterpretCall().ptr(); | |
| } | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| bool Function::HasCode(FunctionPtr function) { | |
| NoSafepointScope no_safepoint; | |
| ASSERT(function->untag()->code() != Code::null()); | |
| return function->untag()->code() != StubCode::LazyCompile().ptr(); | |
| } | |
| void Function::ClearCode() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| UNREACHABLE(); | |
| #else | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| ClearCodeSafe(); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| } | |
| void Function::ClearCodeSafe() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| UNREACHABLE(); | |
| #else | |
| // This may get called when lazily creating dynamic invocation forwarders | |
| // in the precompiled runtime, which can be done with dynamic modules. | |
| // While the original function from which the forwarder is cloned may have | |
| // been compiled, the forwarder is instead interpreted once complete. | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| untag()->set_unoptimized_code(Code::null()); | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| SetInstructionsSafe(StubCode::LazyCompile()); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| } | |
| void Function::EnsureHasCompiledUnoptimizedCode() const { | |
| ASSERT(!ForceOptimize()); | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->IsDartMutatorThread()); | |
| DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame()); | |
| Zone* zone = thread->zone(); | |
| const Error& error = | |
| Error::Handle(zone, EnsureHasCompiledUnoptimizedCodeNoThrow()); | |
| if (!error.IsNull()) { | |
| Exceptions::PropagateError(error); | |
| } | |
| } | |
| ErrorPtr Function::EnsureHasCompiledUnoptimizedCodeNoThrow() const { | |
| ASSERT(!ForceOptimize()); | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->IsDartMutatorThread()); | |
| return Compiler::EnsureUnoptimizedCode(thread, *this); | |
| } | |
| void Function::SwitchToUnoptimizedCode() const { | |
| ASSERT(HasOptimizedCode()); | |
| ASSERT(!ForceOptimize()); | |
| Thread* thread = Thread::Current(); | |
| DEBUG_ASSERT( | |
| thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); | |
| Zone* zone = thread->zone(); | |
| // TODO(35224): DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame()); | |
| const Code& current_code = Code::Handle(zone, CurrentCode()); | |
| if (FLAG_trace_deoptimization_verbose) { | |
| THR_Print("Disabling optimized code: '%s' entry: %#" Px "\n", | |
| ToFullyQualifiedCString(), current_code.EntryPoint()); | |
| } | |
| current_code.DisableDartCode(); | |
| const Error& error = | |
| Error::Handle(zone, Compiler::EnsureUnoptimizedCode(thread, *this)); | |
| if (!error.IsNull()) { | |
| Exceptions::PropagateError(error); | |
| } | |
| const Code& unopt_code = Code::Handle(zone, unoptimized_code()); | |
| unopt_code.Enable(); | |
| AttachCode(unopt_code); | |
| } | |
| void Function::SwitchToLazyCompiledUnoptimizedCode() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| if (!HasOptimizedCode()) { | |
| return; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| ASSERT(thread->IsDartMutatorThread()); | |
| const Code& current_code = Code::Handle(zone, CurrentCode()); | |
| TIR_Print("Disabling optimized code for %s\n", ToCString()); | |
| current_code.DisableDartCode(); | |
| const Code& unopt_code = Code::Handle(zone, unoptimized_code()); | |
| if (unopt_code.IsNull()) { | |
| // Set the lazy compile stub code. | |
| TIR_Print("Switched to lazy compile stub for %s\n", ToCString()); | |
| SetInstructions(StubCode::LazyCompile()); | |
| return; | |
| } | |
| TIR_Print("Switched to unoptimized code for %s\n", ToCString()); | |
| AttachCode(unopt_code); | |
| unopt_code.Enable(); | |
| #endif | |
| } | |
| void Function::set_unoptimized_code(const Code& value) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| DEBUG_ASSERT(IsMutatorOrAtDeoptSafepoint()); | |
| ASSERT(value.IsNull() || !value.is_optimized()); | |
| untag()->set_unoptimized_code(value.ptr()); | |
| #endif | |
| } | |
| ContextScopePtr Function::context_scope() const { | |
| if (IsClosureFunction()) { | |
| const Object& obj = Object::Handle(untag()->data()); | |
| ASSERT(!obj.IsNull()); | |
| return ClosureData::Cast(obj).context_scope(); | |
| } | |
| return ContextScope::null(); | |
| } | |
| void Function::set_context_scope(const ContextScope& value) const { | |
| if (IsClosureFunction()) { | |
| const Object& obj = Object::Handle(untag()->data()); | |
| ASSERT(!obj.IsNull()); | |
| ClosureData::Cast(obj).set_context_scope(value); | |
| return; | |
| } | |
| UNREACHABLE(); | |
| } | |
| Function::AwaiterLink Function::awaiter_link() const { | |
| if (IsClosureFunction()) { | |
| const Object& obj = Object::Handle(untag()->data()); | |
| ASSERT(!obj.IsNull()); | |
| return ClosureData::Cast(obj).awaiter_link(); | |
| } | |
| UNREACHABLE(); | |
| return {}; | |
| } | |
| void Function::set_awaiter_link(Function::AwaiterLink link) const { | |
| if (IsClosureFunction()) { | |
| const Object& obj = Object::Handle(untag()->data()); | |
| ASSERT(!obj.IsNull()); | |
| ClosureData::Cast(obj).set_awaiter_link(link); | |
| return; | |
| } | |
| UNREACHABLE(); | |
| } | |
| bool Function::captures_only_final_not_late_vars() const { | |
| if (IsClosureFunction()) { | |
| const Object& obj = Object::Handle(untag()->data()); | |
| ASSERT(!obj.IsNull()); | |
| return ClosureData::Cast(obj).captures_only_final_not_late_vars(); | |
| } | |
| UNREACHABLE(); | |
| } | |
| void Function::set_captures_only_final_not_late_vars(bool value) const { | |
| if (IsClosureFunction()) { | |
| const Object& obj = Object::Handle(untag()->data()); | |
| ASSERT(!obj.IsNull()); | |
| ClosureData::Cast(obj).set_captures_only_final_not_late_vars(value); | |
| return; | |
| } | |
| UNREACHABLE(); | |
| } | |
| ClosurePtr Function::implicit_static_closure() const { | |
| if (IsImplicitStaticClosureFunction()) { | |
| const Object& obj = Object::Handle(untag()->data()); | |
| ASSERT(!obj.IsNull()); | |
| return ClosureData::Cast(obj).implicit_static_closure(); | |
| } | |
| return Closure::null(); | |
| } | |
| void Function::set_implicit_static_closure(const Closure& closure) const { | |
| if (IsImplicitStaticClosureFunction()) { | |
| const Object& obj = Object::Handle(untag()->data()); | |
| ASSERT(!obj.IsNull()); | |
| ClosureData::Cast(obj).set_implicit_static_closure(closure); | |
| return; | |
| } | |
| UNREACHABLE(); | |
| } | |
| FunctionPtr Function::extracted_method_closure() const { | |
| ASSERT(kind() == UntaggedFunction::kMethodExtractor); | |
| const Object& obj = Object::Handle(untag()->data()); | |
| ASSERT(obj.IsFunction()); | |
| return Function::Cast(obj).ptr(); | |
| } | |
| void Function::set_extracted_method_closure(const Function& value) const { | |
| ASSERT(kind() == UntaggedFunction::kMethodExtractor); | |
| ASSERT(untag()->data() == Object::null()); | |
| set_data(value); | |
| } | |
| ArrayPtr Function::saved_args_desc() const { | |
| if (kind() == UntaggedFunction::kDynamicInvocationForwarder) { | |
| return Array::null(); | |
| } | |
| ASSERT(kind() == UntaggedFunction::kNoSuchMethodDispatcher || | |
| kind() == UntaggedFunction::kInvokeFieldDispatcher); | |
| return Array::RawCast(untag()->data()); | |
| } | |
| void Function::set_saved_args_desc(const Array& value) const { | |
| ASSERT(kind() == UntaggedFunction::kNoSuchMethodDispatcher || | |
| kind() == UntaggedFunction::kInvokeFieldDispatcher); | |
| ASSERT(untag()->data() == Object::null()); | |
| set_data(value); | |
| } | |
| FieldPtr Function::accessor_field() const { | |
| ASSERT(kind() == UntaggedFunction::kImplicitGetter || | |
| kind() == UntaggedFunction::kImplicitSetter || | |
| kind() == UntaggedFunction::kImplicitStaticGetter || | |
| kind() == UntaggedFunction::kFieldInitializer); | |
| return Field::RawCast(untag()->data()); | |
| } | |
| void Function::set_accessor_field(const Field& value) const { | |
| ASSERT(kind() == UntaggedFunction::kImplicitGetter || | |
| kind() == UntaggedFunction::kImplicitSetter || | |
| kind() == UntaggedFunction::kImplicitStaticGetter || | |
| kind() == UntaggedFunction::kFieldInitializer); | |
| // Top level classes may be finalized multiple times. | |
| ASSERT(untag()->data() == Object::null() || untag()->data() == value.ptr()); | |
| set_data(value); | |
| } | |
| FunctionPtr Function::parent_function() const { | |
| if (!IsClosureFunction()) return Function::null(); | |
| Object& obj = Object::Handle(untag()->data()); | |
| ASSERT(!obj.IsNull()); | |
| return ClosureData::Cast(obj).parent_function(); | |
| } | |
| void Function::set_parent_function(const Function& value) const { | |
| ASSERT(IsClosureFunction()); | |
| const Object& obj = Object::Handle(untag()->data()); | |
| ASSERT(!obj.IsNull()); | |
| ClosureData::Cast(obj).set_parent_function(value); | |
| } | |
| TypeArgumentsPtr Function::DefaultTypeArguments(Zone* zone) const { | |
| if (type_parameters() == TypeParameters::null()) { | |
| return Object::empty_type_arguments().ptr(); | |
| } | |
| return TypeParameters::Handle(zone, type_parameters()).defaults(); | |
| } | |
| InstantiationMode Function::default_type_arguments_instantiation_mode() const { | |
| if (!IsClosureFunction()) { | |
| UNREACHABLE(); | |
| } | |
| return ClosureData::DefaultTypeArgumentsInstantiationMode( | |
| ClosureData::RawCast(data())); | |
| } | |
| void Function::set_default_type_arguments_instantiation_mode( | |
| InstantiationMode value) const { | |
| if (!IsClosureFunction()) { | |
| UNREACHABLE(); | |
| } | |
| const auto& closure_data = ClosureData::Handle(ClosureData::RawCast(data())); | |
| ASSERT(!closure_data.IsNull()); | |
| closure_data.set_default_type_arguments_instantiation_mode(value); | |
| } | |
| // Enclosing outermost function of this local function. | |
| FunctionPtr Function::GetOutermostFunction() const { | |
| FunctionPtr parent = parent_function(); | |
| if (parent == Object::null()) { | |
| return ptr(); | |
| } | |
| Function& function = Function::Handle(); | |
| do { | |
| function = parent; | |
| parent = function.parent_function(); | |
| } while (parent != Object::null()); | |
| return function.ptr(); | |
| } | |
| FunctionPtr Function::implicit_closure_function() const { | |
| if (IsClosureFunction() || IsDispatcherOrImplicitAccessor() || | |
| IsFieldInitializer() || IsFfiCallbackTrampoline() || | |
| IsMethodExtractor()) { | |
| return Function::null(); | |
| } | |
| const Object& obj = Object::Handle(data()); | |
| ASSERT(obj.IsNull() || obj.IsScript() || obj.IsFunction() || obj.IsArray()); | |
| if (obj.IsNull() || obj.IsScript()) { | |
| return Function::null(); | |
| } | |
| if (obj.IsFunction()) { | |
| return Function::Cast(obj).ptr(); | |
| } | |
| ASSERT(is_native()); | |
| ASSERT(obj.IsArray()); | |
| const Object& res = Object::Handle(Array::Cast(obj).AtAcquire(1)); | |
| return res.IsNull() ? Function::null() : Function::Cast(res).ptr(); | |
| } | |
| void Function::set_implicit_closure_function(const Function& value) const { | |
| DEBUG_ASSERT( | |
| IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(!IsClosureFunction()); | |
| const Object& old_data = Object::Handle(data()); | |
| if (is_old_native()) { | |
| ASSERT(old_data.IsArray()); | |
| const auto& pair = Array::Cast(old_data); | |
| ASSERT(pair.AtAcquire(NativeFunctionData::kTearOff) == Object::null() || | |
| value.IsNull()); | |
| pair.SetAtRelease(NativeFunctionData::kTearOff, value); | |
| } else { | |
| ASSERT(old_data.IsNull() || value.IsNull()); | |
| set_data(value); | |
| } | |
| } | |
| void Function::SetFfiCSignature(const FunctionType& sig) const { | |
| ASSERT(IsFfiCallbackTrampoline()); | |
| const Object& obj = Object::Handle(data()); | |
| ASSERT(!obj.IsNull()); | |
| FfiTrampolineData::Cast(obj).set_c_signature(sig); | |
| } | |
| FunctionTypePtr Function::FfiCSignature() const { | |
| auto* const zone = Thread::Current()->zone(); | |
| if (IsFfiCallbackTrampoline()) { | |
| const Object& obj = Object::Handle(zone, data()); | |
| ASSERT(!obj.IsNull()); | |
| return FfiTrampolineData::Cast(obj).c_signature(); | |
| } | |
| auto& pragma_value = Instance::Handle(zone); | |
| if (is_ffi_native()) { | |
| pragma_value = GetNativeAnnotation(); | |
| } else if (IsFfiCallClosure()) { | |
| pragma_value = GetFfiCallClosurePragmaValue(); | |
| } else { | |
| UNREACHABLE(); | |
| } | |
| const auto& type_args = | |
| TypeArguments::Handle(zone, pragma_value.GetTypeArguments()); | |
| ASSERT(type_args.Length() == 1); | |
| const auto& native_type = | |
| FunctionType::Cast(AbstractType::ZoneHandle(zone, type_args.TypeAt(0))); | |
| return native_type.ptr(); | |
| } | |
| bool Function::FfiCSignatureContainsHandles() const { | |
| const FunctionType& c_signature = FunctionType::Handle(FfiCSignature()); | |
| return c_signature.ContainsHandles(); | |
| } | |
| bool FunctionType::ContainsHandles() const { | |
| const intptr_t num_params = num_fixed_parameters(); | |
| for (intptr_t i = 0; i < num_params; i++) { | |
| const bool is_handle = | |
| AbstractType::Handle(ParameterTypeAt(i)).type_class_id() == | |
| kFfiHandleCid; | |
| if (is_handle) { | |
| return true; | |
| } | |
| } | |
| return AbstractType::Handle(result_type()).type_class_id() == kFfiHandleCid; | |
| } | |
| // Keep consistent with BaseMarshaller::IsCompound. | |
| bool Function::FfiCSignatureReturnsStruct() const { | |
| ASSERT(IsFfiCallbackTrampoline()); | |
| Zone* zone = Thread::Current()->zone(); | |
| const auto& c_signature = FunctionType::Handle(zone, FfiCSignature()); | |
| const auto& type = AbstractType::Handle(zone, c_signature.result_type()); | |
| if (IsFfiTypeClassId(type.type_class_id())) { | |
| return false; | |
| } | |
| const auto& cls = Class::Handle(zone, type.type_class()); | |
| const auto& superClass = Class::Handle(zone, cls.SuperClass()); | |
| const bool is_abi_specific_int = | |
| String::Handle(zone, superClass.UserVisibleName()) | |
| .Equals(Symbols::AbiSpecificInteger()); | |
| if (is_abi_specific_int) { | |
| return false; | |
| } | |
| #ifdef DEBUG | |
| const bool is_struct = String::Handle(zone, superClass.UserVisibleName()) | |
| .Equals(Symbols::Struct()); | |
| const bool is_union = String::Handle(zone, superClass.UserVisibleName()) | |
| .Equals(Symbols::Union()); | |
| ASSERT(is_struct || is_union); | |
| #endif | |
| return true; | |
| } | |
| bool Function::FfiCSignatureReturnsHandle() const { | |
| ASSERT(IsFfiCallbackTrampoline()); | |
| const auto& c_signature = FunctionType::Handle(FfiCSignature()); | |
| const auto& type = AbstractType::Handle(c_signature.result_type()); | |
| return type.type_class_id() == kFfiHandleCid; | |
| } | |
| int32_t Function::FfiCallbackId() const { | |
| ASSERT(IsFfiCallbackTrampoline()); | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const auto& obj = Object::Handle(zone, data()); | |
| ASSERT(!obj.IsNull()); | |
| const auto& trampoline_data = FfiTrampolineData::Cast(obj); | |
| if (trampoline_data.callback_id() == -1) { | |
| static constexpr intptr_t kInitialCallbackIdsReserved = 16; | |
| auto isolate_group = thread->isolate_group(); | |
| SafepointWriteRwLocker ml(thread, isolate_group->program_lock()); | |
| if (trampoline_data.callback_id() == -1) { | |
| auto object_store = isolate_group->object_store(); | |
| auto& code_array = | |
| GrowableObjectArray::Handle(zone, object_store->ffi_callback_code()); | |
| if (code_array.IsNull()) { | |
| code_array = | |
| GrowableObjectArray::New(kInitialCallbackIdsReserved, Heap::kOld); | |
| object_store->set_ffi_callback_code(code_array); | |
| } | |
| const intptr_t callback_id = code_array.Length(); | |
| code_array.Add(Code::null_object(), Heap::kOld); | |
| trampoline_data.set_callback_id(callback_id); | |
| } | |
| } | |
| ASSERT(trampoline_data.callback_id() != -1); | |
| return trampoline_data.callback_id(); | |
| } | |
| bool Function::FfiIsLeaf() const { | |
| Zone* zone = Thread::Current()->zone(); | |
| auto& pragma_value = Instance::Handle(zone); | |
| if (is_ffi_native()) { | |
| pragma_value = GetNativeAnnotation(); | |
| } else if (IsFfiCallClosure()) { | |
| pragma_value = GetFfiCallClosurePragmaValue(); | |
| } else { | |
| UNREACHABLE(); | |
| } | |
| const auto& pragma_value_class = Class::Handle(zone, pragma_value.clazz()); | |
| const auto& is_leaf_field = Field::Handle( | |
| zone, pragma_value_class.LookupFieldAllowPrivate(Symbols::isLeaf())); | |
| ASSERT(!is_leaf_field.IsNull()); | |
| return Bool::Handle(zone, Bool::RawCast(pragma_value.GetField(is_leaf_field))) | |
| .value(); | |
| } | |
| FunctionPtr Function::FfiCallbackTarget() const { | |
| ASSERT(IsFfiCallbackTrampoline()); | |
| const Object& obj = Object::Handle(data()); | |
| ASSERT(!obj.IsNull()); | |
| return FfiTrampolineData::Cast(obj).callback_target(); | |
| } | |
| void Function::SetFfiCallbackTarget(const Function& target) const { | |
| ASSERT(IsFfiCallbackTrampoline()); | |
| const Object& obj = Object::Handle(data()); | |
| ASSERT(!obj.IsNull()); | |
| FfiTrampolineData::Cast(obj).set_callback_target(target); | |
| } | |
| InstancePtr Function::FfiCallbackExceptionalReturn() const { | |
| ASSERT(IsFfiCallbackTrampoline()); | |
| const Object& obj = Object::Handle(data()); | |
| ASSERT(!obj.IsNull()); | |
| return FfiTrampolineData::Cast(obj).callback_exceptional_return(); | |
| } | |
| void Function::SetFfiCallbackExceptionalReturn(const Instance& value) const { | |
| ASSERT(IsFfiCallbackTrampoline()); | |
| const Object& obj = Object::Handle(data()); | |
| ASSERT(!obj.IsNull()); | |
| FfiTrampolineData::Cast(obj).set_callback_exceptional_return(value); | |
| } | |
| FfiCallbackKind Function::GetFfiCallbackKind() const { | |
| ASSERT(IsFfiCallbackTrampoline()); | |
| const Object& obj = Object::Handle(data()); | |
| ASSERT(!obj.IsNull()); | |
| return FfiTrampolineData::Cast(obj).ffi_function_kind(); | |
| } | |
| void Function::SetFfiCallbackKind(FfiCallbackKind value) const { | |
| ASSERT(IsFfiCallbackTrampoline()); | |
| const Object& obj = Object::Handle(data()); | |
| ASSERT(!obj.IsNull()); | |
| FfiTrampolineData::Cast(obj).set_ffi_function_kind(value); | |
| } | |
| const char* Function::KindToCString(UntaggedFunction::Kind kind) { | |
| return UntaggedFunction::KindToCString(kind); | |
| } | |
| FunctionPtr Function::ForwardingTarget() const { | |
| ASSERT(kind() == UntaggedFunction::kDynamicInvocationForwarder); | |
| return Function::RawCast(WeakSerializationReference::Unwrap(data())); | |
| } | |
| void Function::SetForwardingTarget(const Function& target) const { | |
| ASSERT(kind() == UntaggedFunction::kDynamicInvocationForwarder); | |
| set_data(target); | |
| } | |
| // This field is heavily overloaded: | |
| // kernel eval function: Array[0] = Script | |
| // Array[1] = KernelProgramInfo | |
| // Array[2] = Kernel index of enclosing library | |
| // method extractor: Function extracted closure function | |
| // implicit getter: Field | |
| // implicit setter: Field | |
| // impl. static final gttr: Field | |
| // field initializer: Field | |
| // noSuchMethod dispatcher: Array arguments descriptor | |
| // invoke-field dispatcher: Array arguments descriptor | |
| // closure function: ClosureData | |
| // native function: Array[0] = String native name | |
| // Array[1] = Function implicit closure function | |
| // regular function: Function for implicit closure function | |
| // constructor, factory: Function for implicit closure function | |
| // ffi trampoline function: FfiTrampolineData (Dart->C) | |
| // dyn inv forwarder: Forwarding target, a WSR pointing to it or null | |
| // (null can only occur if forwarding target was | |
| // dropped) | |
| void Function::set_data(const Object& value) const { | |
| untag()->set_data<std::memory_order_release>(value.ptr()); | |
| } | |
| #if defined(DART_DYNAMIC_MODULES) && !defined(PRODUCT) && \ | |
| !defined(DART_PRECOMPILED_RUNTIME) | |
| bool Function::IsReloadedImplicitClosure(FunctionPtr ptr) { | |
| if (!IsImplicitClosureFunction(ptr)) { | |
| return false; | |
| } | |
| return ClosureData::ReloadedImplicitClosure( | |
| ClosureData::RawCast(ptr->untag()->data())); | |
| } | |
| void Function::SetIsReloadedImplicitClosure(bool value) const { | |
| if (IsImplicitClosureFunction()) { | |
| const Object& obj = Object::Handle(untag()->data()); | |
| ASSERT(!obj.IsNull()); | |
| ClosureData::Cast(obj).set_reloaded_implicit_closure(value); | |
| return; | |
| } | |
| UNREACHABLE(); | |
| } | |
| #endif | |
| 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()); | |
| } | |
| StringPtr Function::native_name() const { | |
| ASSERT(is_native()); | |
| const Object& obj = Object::Handle(data()); | |
| ASSERT(obj.IsArray()); | |
| return String::RawCast(Array::Cast(obj).At(0)); | |
| } | |
| void Function::set_native_name(const String& value) const { | |
| ASSERT(is_native()); | |
| const auto& pair = Array::Cast(Object::Handle(data())); | |
| ASSERT(pair.At(0) == Object::null()); | |
| pair.SetAt(NativeFunctionData::kNativeName, value); | |
| } | |
| InstancePtr Function::GetNativeAnnotation() const { | |
| ASSERT(is_ffi_native()); | |
| Zone* zone = Thread::Current()->zone(); | |
| auto& pragma_value = Object::Handle(zone); | |
| Library::FindPragma(dart::Thread::Current(), /*only_core=*/false, | |
| Object::Handle(zone, ptr()), | |
| String::Handle(zone, Symbols::vm_ffi_native().ptr()), | |
| /*multiple=*/false, &pragma_value); | |
| auto const& native_instance = Instance::Cast(pragma_value); | |
| ASSERT(!native_instance.IsNull()); | |
| #if defined(DEBUG) | |
| const auto& native_class = Class::Handle(zone, native_instance.clazz()); | |
| ASSERT(String::Handle(zone, native_class.UserVisibleName()) | |
| .Equals(Symbols::FfiNative())); | |
| #endif | |
| return native_instance.ptr(); | |
| } | |
| bool Function::is_old_native() const { | |
| return is_native() && !is_external(); | |
| } | |
| bool Function::is_ffi_native() const { | |
| return is_native() && is_external(); | |
| } | |
| void Function::SetSignature(const FunctionType& value) const { | |
| set_signature(value); | |
| ASSERT(NumImplicitParameters() == value.num_implicit_parameters()); | |
| if (IsClosureFunction() && value.IsGeneric()) { | |
| Zone* zone = Thread::Current()->zone(); | |
| const TypeParameters& type_params = | |
| TypeParameters::Handle(zone, value.type_parameters()); | |
| const TypeArguments& defaults = | |
| TypeArguments::Handle(zone, type_params.defaults()); | |
| auto mode = defaults.GetInstantiationMode(zone, this); | |
| set_default_type_arguments_instantiation_mode(mode); | |
| } | |
| } | |
| TypeParameterPtr FunctionType::TypeParameterAt(intptr_t index, | |
| Nullability nullability) const { | |
| ASSERT(index >= 0 && index < NumTypeParameters()); | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| TypeParameter& type_param = TypeParameter::Handle( | |
| zone, TypeParameter::New(*this, NumParentTypeArguments(), | |
| NumParentTypeArguments() + index, nullability)); | |
| type_param.SetIsFinalized(); | |
| if (IsFinalized()) { | |
| type_param ^= type_param.Canonicalize(thread); | |
| } | |
| return type_param.ptr(); | |
| } | |
| void FunctionType::set_result_type(const AbstractType& value) const { | |
| ASSERT(!value.IsNull()); | |
| untag()->set_result_type(value.ptr()); | |
| } | |
| AbstractTypePtr Function::ParameterTypeAt(intptr_t index) const { | |
| const Array& types = Array::Handle(parameter_types()); | |
| return AbstractType::RawCast(types.At(index)); | |
| } | |
| AbstractTypePtr FunctionType::ParameterTypeAt(intptr_t index) const { | |
| const Array& parameter_types = Array::Handle(untag()->parameter_types()); | |
| return AbstractType::RawCast(parameter_types.At(index)); | |
| } | |
| void FunctionType::SetParameterTypeAt(intptr_t index, | |
| const AbstractType& value) const { | |
| ASSERT(!value.IsNull()); | |
| const Array& parameter_types = Array::Handle(untag()->parameter_types()); | |
| parameter_types.SetAt(index, value); | |
| } | |
| void FunctionType::set_parameter_types(const Array& value) const { | |
| ASSERT(value.IsNull() || value.Length() > 0); | |
| untag()->set_parameter_types(value.ptr()); | |
| } | |
| StringPtr Function::ParameterNameAt(intptr_t index) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (signature() == FunctionType::null()) { | |
| // Without the signature, we're guaranteed not to have any name information. | |
| return Symbols::OptimizedOut().ptr(); | |
| } | |
| #endif | |
| const intptr_t num_fixed = num_fixed_parameters(); | |
| if (HasOptionalNamedParameters() && index >= num_fixed) { | |
| const Array& parameter_names = | |
| Array::Handle(signature()->untag()->named_parameter_names()); | |
| return String::RawCast(parameter_names.At(index - num_fixed)); | |
| } | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| return Symbols::OptimizedOut().ptr(); | |
| #else | |
| const Array& names = Array::Handle(untag()->positional_parameter_names()); | |
| return String::RawCast(names.At(index)); | |
| #endif | |
| } | |
| void Function::SetParameterNameAt(intptr_t index, const String& value) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| ASSERT(!value.IsNull() && value.IsSymbol()); | |
| if (HasOptionalNamedParameters() && index >= num_fixed_parameters()) { | |
| // These should be set on the signature, not the function. | |
| UNREACHABLE(); | |
| } | |
| const Array& parameter_names = | |
| Array::Handle(untag()->positional_parameter_names()); | |
| parameter_names.SetAt(index, value); | |
| #endif | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| void Function::set_positional_parameter_names(const Array& value) const { | |
| ASSERT(value.ptr() == Object::empty_array().ptr() || value.Length() > 0); | |
| untag()->set_positional_parameter_names(value.ptr()); | |
| } | |
| #endif | |
| StringPtr FunctionType::ParameterNameAt(intptr_t index) const { | |
| const intptr_t num_fixed = num_fixed_parameters(); | |
| if (!HasOptionalNamedParameters() || index < num_fixed) { | |
| // The positional parameter names are stored on the function, not here. | |
| UNREACHABLE(); | |
| } | |
| const Array& parameter_names = | |
| Array::Handle(untag()->named_parameter_names()); | |
| return String::RawCast(parameter_names.At(index - num_fixed)); | |
| } | |
| void FunctionType::SetParameterNameAt(intptr_t index, | |
| const String& value) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| UNREACHABLE(); | |
| #else | |
| ASSERT(!value.IsNull() && value.IsSymbol()); | |
| const intptr_t num_fixed = num_fixed_parameters(); | |
| if (!HasOptionalNamedParameters() || index < num_fixed) { | |
| UNREACHABLE(); | |
| } | |
| const Array& parameter_names = | |
| Array::Handle(untag()->named_parameter_names()); | |
| parameter_names.SetAt(index - num_fixed, value); | |
| #endif | |
| } | |
| void FunctionType::set_named_parameter_names(const Array& value) const { | |
| ASSERT(value.ptr() == Object::empty_array().ptr() || value.Length() > 0); | |
| untag()->set_named_parameter_names(value.ptr()); | |
| } | |
| void Function::CreateNameArray(Heap::Space space) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| const intptr_t num_positional_params = | |
| num_fixed_parameters() + NumOptionalPositionalParameters(); | |
| if (num_positional_params == 0) { | |
| set_positional_parameter_names(Object::empty_array()); | |
| } else { | |
| set_positional_parameter_names( | |
| Array::Handle(Array::New(num_positional_params, space))); | |
| } | |
| #endif | |
| } | |
| void FunctionType::CreateNameArrayIncludingFlags(Heap::Space space) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| UNREACHABLE(); | |
| #else | |
| const intptr_t num_named_parameters = NumOptionalNamedParameters(); | |
| if (num_named_parameters == 0) { | |
| return set_named_parameter_names(Object::empty_array()); | |
| } | |
| // Currently, we only store flags for named parameters. | |
| const intptr_t last_index = (num_named_parameters - 1) / | |
| compiler::target::kNumParameterFlagsPerElement; | |
| const intptr_t num_flag_slots = last_index + 1; | |
| intptr_t num_total_slots = num_named_parameters + num_flag_slots; | |
| auto& array = Array::Handle(Array::New(num_total_slots, space)); | |
| // Set flag slots to Smi 0 before handing off. | |
| auto& empty_flags_smi = Smi::Handle(Smi::New(0)); | |
| for (intptr_t i = num_named_parameters; i < num_total_slots; i++) { | |
| array.SetAt(i, empty_flags_smi); | |
| } | |
| set_named_parameter_names(array); | |
| #endif | |
| } | |
| intptr_t FunctionType::GetRequiredFlagIndex(intptr_t index, | |
| intptr_t* flag_mask) const { | |
| // If these calculations change, also change | |
| // FlowGraphBuilder::BuildClosureCallHasRequiredNamedArgumentsCheck. | |
| ASSERT(HasOptionalNamedParameters()); | |
| ASSERT(flag_mask != nullptr); | |
| ASSERT(index >= num_fixed_parameters()); | |
| index -= num_fixed_parameters(); | |
| *flag_mask = (1 << compiler::target::kRequiredNamedParameterFlag) | |
| << ((static_cast<uintptr_t>(index) % | |
| compiler::target::kNumParameterFlagsPerElement) * | |
| compiler::target::kNumParameterFlags); | |
| return NumOptionalNamedParameters() + | |
| index / compiler::target::kNumParameterFlagsPerElement; | |
| } | |
| bool Function::HasRequiredNamedParameters() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (signature() == FunctionType::null()) { | |
| // Signatures for functions with required named parameters are not dropped. | |
| return false; | |
| } | |
| #endif | |
| return FunctionType::Handle(signature()).HasRequiredNamedParameters(); | |
| } | |
| bool Function::IsRequiredAt(intptr_t index) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (signature() == FunctionType::null()) { | |
| // Signature is not dropped in aot when any named parameter is required. | |
| return false; | |
| } | |
| #endif | |
| if (!HasOptionalNamedParameters() || index < num_fixed_parameters()) { | |
| return false; | |
| } | |
| const FunctionType& sig = FunctionType::Handle(signature()); | |
| return sig.IsRequiredAt(index); | |
| } | |
| bool FunctionType::IsRequiredAt(intptr_t index) const { | |
| if (!HasOptionalNamedParameters() || index < num_fixed_parameters()) { | |
| return false; | |
| } | |
| intptr_t flag_mask; | |
| const intptr_t flag_index = GetRequiredFlagIndex(index, &flag_mask); | |
| const Array& parameter_names = | |
| Array::Handle(untag()->named_parameter_names()); | |
| if (flag_index >= parameter_names.Length()) { | |
| return false; | |
| } | |
| const intptr_t flags = | |
| Smi::Value(Smi::RawCast(parameter_names.At(flag_index))); | |
| return (flags & flag_mask) != 0; | |
| } | |
| void FunctionType::SetIsRequiredAt(intptr_t index) const { | |
| #if defined(DART_PRECOMPILER_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| UNREACHABLE(); | |
| #else | |
| intptr_t flag_mask; | |
| const intptr_t flag_index = GetRequiredFlagIndex(index, &flag_mask); | |
| const Array& parameter_names = | |
| Array::Handle(untag()->named_parameter_names()); | |
| ASSERT(flag_index < parameter_names.Length()); | |
| const intptr_t flags = | |
| Smi::Value(Smi::RawCast(parameter_names.At(flag_index))); | |
| parameter_names.SetAt(flag_index, Smi::Handle(Smi::New(flags | flag_mask))); | |
| #endif | |
| } | |
| void FunctionType::FinalizeNameArray() const { | |
| #if defined(DART_PRECOMPILER_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| UNREACHABLE(); | |
| #else | |
| const intptr_t num_named_parameters = NumOptionalNamedParameters(); | |
| if (num_named_parameters == 0) { | |
| ASSERT(untag()->named_parameter_names() == Object::empty_array().ptr()); | |
| return; | |
| } | |
| const Array& parameter_names = | |
| Array::Handle(untag()->named_parameter_names()); | |
| // Truncate the parameter names array to remove unused flags from the end. | |
| intptr_t last_used = parameter_names.Length() - 1; | |
| for (; last_used >= num_named_parameters; --last_used) { | |
| if (Smi::Value(Smi::RawCast(parameter_names.At(last_used))) != 0) { | |
| break; | |
| } | |
| } | |
| parameter_names.Truncate(last_used + 1); | |
| #endif | |
| } | |
| bool FunctionType::HasRequiredNamedParameters() const { | |
| const intptr_t num_named_params = NumOptionalNamedParameters(); | |
| if (num_named_params == 0) return false; | |
| // Check for flag slots in the named parameter names array. | |
| const auto& parameter_names = Array::Handle(named_parameter_names()); | |
| ASSERT(!parameter_names.IsNull()); | |
| return parameter_names.Length() > num_named_params; | |
| } | |
| static void ReportTooManyTypeParameters(const FunctionType& sig) { | |
| Report::MessageF(Report::kError, Script::Handle(), TokenPosition::kNoSource, | |
| Report::AtLocation, | |
| "too many type parameters declared in signature '%s' or in " | |
| "its enclosing signatures", | |
| sig.ToUserVisibleCString()); | |
| UNREACHABLE(); | |
| } | |
| void FunctionType::SetTypeParameters(const TypeParameters& value) const { | |
| untag()->set_type_parameters(value.ptr()); | |
| const intptr_t count = value.Length(); | |
| if (!UntaggedFunctionType::PackedNumTypeParameters::is_valid(count)) { | |
| ReportTooManyTypeParameters(*this); | |
| } | |
| untag()->packed_type_parameter_counts_.Update<PackedNumTypeParameters>(count); | |
| } | |
| void FunctionType::SetNumParentTypeArguments(intptr_t value) const { | |
| ASSERT(value >= 0); | |
| if (!PackedNumParentTypeArguments::is_valid(value)) { | |
| ReportTooManyTypeParameters(*this); | |
| } | |
| untag()->packed_type_parameter_counts_.Update<PackedNumParentTypeArguments>( | |
| value); | |
| } | |
| bool Function::IsGeneric() const { | |
| return FunctionType::IsGeneric(signature()); | |
| } | |
| intptr_t Function::NumTypeParameters() const { | |
| return FunctionType::NumTypeParametersOf(signature()); | |
| } | |
| intptr_t Function::NumParentTypeArguments() const { | |
| return FunctionType::NumParentTypeArgumentsOf(signature()); | |
| } | |
| intptr_t Function::NumTypeArguments() const { | |
| return FunctionType::NumTypeArgumentsOf(signature()); | |
| } | |
| intptr_t Function::num_fixed_parameters() const { | |
| return FunctionType::NumFixedParametersOf(signature()); | |
| } | |
| bool Function::HasOptionalParameters() const { | |
| return FunctionType::HasOptionalParameters(signature()); | |
| } | |
| bool Function::HasOptionalNamedParameters() const { | |
| return FunctionType::HasOptionalNamedParameters(signature()); | |
| } | |
| bool Function::HasOptionalPositionalParameters() const { | |
| return FunctionType::HasOptionalPositionalParameters(signature()); | |
| } | |
| intptr_t Function::NumOptionalParameters() const { | |
| return FunctionType::NumOptionalParametersOf(signature()); | |
| } | |
| intptr_t Function::NumOptionalPositionalParameters() const { | |
| return FunctionType::NumOptionalPositionalParametersOf(signature()); | |
| } | |
| intptr_t Function::NumOptionalNamedParameters() const { | |
| return FunctionType::NumOptionalNamedParametersOf(signature()); | |
| } | |
| intptr_t Function::NumParameters() const { | |
| return FunctionType::NumParametersOf(signature()); | |
| } | |
| TypeParameterPtr Function::TypeParameterAt(intptr_t index, | |
| Nullability nullability) const { | |
| const FunctionType& sig = FunctionType::Handle(signature()); | |
| return sig.TypeParameterAt(index, nullability); | |
| } | |
| void Function::set_kind(UntaggedFunction::Kind value) const { | |
| untag()->kind_tag_.Update<KindBits>(value); | |
| } | |
| void Function::set_modifier(UntaggedFunction::AsyncModifier value) const { | |
| untag()->kind_tag_.Update<ModifierBits>(value); | |
| } | |
| void Function::set_recognized_kind(MethodRecognizer::Kind value) const { | |
| // Prevent multiple settings of kind. | |
| ASSERT((value == MethodRecognizer::kUnknown) || !IsRecognized()); | |
| untag()->kind_tag_.Update<RecognizedBits>(value); | |
| } | |
| void Function::set_token_pos(TokenPosition token_pos) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| ASSERT(!token_pos.IsClassifying() || IsMethodExtractor()); | |
| StoreNonPointer(&untag()->token_pos_, token_pos); | |
| #endif | |
| } | |
| void Function::set_kind_tag(uint32_t value) const { | |
| untag()->kind_tag_ = value; | |
| } | |
| bool Function::is_eval_function() const { | |
| if (data()->IsArray()) { | |
| const intptr_t len = Array::LengthOf(Array::RawCast(data())); | |
| return len == static_cast<intptr_t>(EvalFunctionData::kLength); | |
| } | |
| return false; | |
| } | |
| bool Function::IsOptimizable() const { | |
| if (FLAG_precompiled_mode) { | |
| return true; | |
| } | |
| if (ForceOptimize()) return true; | |
| if (is_old_native()) { | |
| // Native methods don't need to be optimized. | |
| return false; | |
| } | |
| if (is_optimizable() && (script() != Script::null())) { | |
| // Additional check needed for implicit getters. | |
| return (unoptimized_code() == Object::null()) || | |
| (Code::Handle(unoptimized_code()).Size() < | |
| FLAG_huge_method_cutoff_in_code_size); | |
| } | |
| return false; | |
| } | |
| bool Function::IsTypedDataViewFactory() const { | |
| switch (recognized_kind()) { | |
| case MethodRecognizer::kTypedData_ByteDataView_factory: | |
| case MethodRecognizer::kTypedData_Int8ArrayView_factory: | |
| case MethodRecognizer::kTypedData_Uint8ArrayView_factory: | |
| case MethodRecognizer::kTypedData_Uint8ClampedArrayView_factory: | |
| case MethodRecognizer::kTypedData_Int16ArrayView_factory: | |
| case MethodRecognizer::kTypedData_Uint16ArrayView_factory: | |
| case MethodRecognizer::kTypedData_Int32ArrayView_factory: | |
| case MethodRecognizer::kTypedData_Uint32ArrayView_factory: | |
| case MethodRecognizer::kTypedData_Int64ArrayView_factory: | |
| case MethodRecognizer::kTypedData_Uint64ArrayView_factory: | |
| case MethodRecognizer::kTypedData_Float32ArrayView_factory: | |
| case MethodRecognizer::kTypedData_Float64ArrayView_factory: | |
| case MethodRecognizer::kTypedData_Float32x4ArrayView_factory: | |
| case MethodRecognizer::kTypedData_Int32x4ArrayView_factory: | |
| case MethodRecognizer::kTypedData_Float64x2ArrayView_factory: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| bool Function::IsUnmodifiableTypedDataViewFactory() const { | |
| switch (recognized_kind()) { | |
| case MethodRecognizer::kTypedData_UnmodifiableByteDataView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableInt8ArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableUint8ArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableUint8ClampedArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableInt16ArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableUint16ArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableInt32ArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableUint32ArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableInt64ArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableUint64ArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableFloat32ArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableFloat64ArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableFloat32x4ArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableInt32x4ArrayView_factory: | |
| case MethodRecognizer::kTypedData_UnmodifiableFloat64x2ArrayView_factory: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| static bool InVmTests(const Function& function) { | |
| #if defined(TESTING) | |
| return true; | |
| #else | |
| auto* zone = Thread::Current()->zone(); | |
| const auto& cls = Class::Handle(zone, function.Owner()); | |
| const auto& lib = Library::Handle(zone, cls.library()); | |
| const auto& url = String::Handle(zone, lib.url()); | |
| const bool in_vm_tests = | |
| strstr(url.ToCString(), "runtime/tests/vm/") != nullptr; | |
| return in_vm_tests; | |
| #endif | |
| } | |
| bool Function::ForceOptimize() const { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| if (RecognizedKindForceOptimize() || IsFfiCallClosure() || | |
| IsFfiCallbackTrampoline() || is_ffi_native() || | |
| IsTypedDataViewFactory() || IsUnmodifiableTypedDataViewFactory()) { | |
| return true; | |
| } | |
| if (!has_pragma()) return false; | |
| const bool has_vm_pragma = Library::FindPragma( | |
| Thread::Current(), false, *this, Symbols::vm_force_optimize()); | |
| if (!has_vm_pragma) return false; | |
| // For run_vm_tests and runtime/tests/vm allow marking arbitrary functions as | |
| // force-optimize via `@pragma('vm:force-optimize')`. | |
| return InVmTests(*this); | |
| #else | |
| // These are not supposed to be called in AOT runtime. | |
| UNREACHABLE(); | |
| return false; | |
| #endif | |
| } | |
| bool Function::IsPreferInline() const { | |
| if (!has_pragma()) return false; | |
| return Library::FindPragma(Thread::Current(), /*only_core=*/false, *this, | |
| Symbols::vm_prefer_inline()); | |
| } | |
| bool Function::IsIdempotent() const { | |
| if (!has_pragma()) return false; | |
| #if defined(TESTING) | |
| const bool kAllowOnlyForCoreLibFunctions = false; | |
| #else | |
| const bool kAllowOnlyForCoreLibFunctions = true; | |
| #endif // defined(TESTING) | |
| return Library::FindPragma(Thread::Current(), kAllowOnlyForCoreLibFunctions, | |
| *this, Symbols::vm_idempotent()); | |
| } | |
| bool Function::IsCachableIdempotent() const { | |
| if (!has_pragma()) return false; | |
| const bool has_vm_pragma = | |
| Library::FindPragma(Thread::Current(), /*only_core=*/false, *this, | |
| Symbols::vm_cachable_idempotent()); | |
| if (!has_vm_pragma) return false; | |
| // For run_vm_tests and runtime/tests/vm allow marking arbitrary functions. | |
| return InVmTests(*this); | |
| } | |
| bool Function::IsExternalEffect() const { | |
| if (!has_pragma()) return false; | |
| return Library::FindPragma(dart::Thread::Current(), /*only_core=*/false, | |
| *this, Symbols::external_effect()); | |
| } | |
| bool Function::IsFfiCallClosure() const { | |
| if (!IsNonImplicitClosureFunction()) return false; | |
| if (!has_pragma()) return false; | |
| return Library::FindPragma(Thread::Current(), /*only_core=*/false, *this, | |
| Symbols::vm_ffi_call_closure()); | |
| } | |
| InstancePtr Function::GetFfiCallClosurePragmaValue() const { | |
| ASSERT(IsFfiCallClosure()); | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| auto& pragma_value = Object::Handle(zone); | |
| Library::FindPragma(thread, /*only_core=*/false, *this, | |
| Symbols::vm_ffi_call_closure(), | |
| /*multiple=*/false, &pragma_value); | |
| ASSERT(!pragma_value.IsNull()); | |
| return Instance::Cast(pragma_value).ptr(); | |
| } | |
| bool Function::RecognizedKindForceOptimize() const { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| switch (recognized_kind()) { | |
| // Uses unboxed/untagged data not supported in unoptimized, or uses | |
| // LoadIndexed/StoreIndexed/MemoryCopy instructions with typed data | |
| // arrays, which requires optimization for payload extraction. | |
| case MethodRecognizer::kObjectArrayGetIndexed: | |
| case MethodRecognizer::kGrowableArrayGetIndexed: | |
| #define TYPED_DATA_GET_INDEXED_CASES(clazz) \ | |
| case MethodRecognizer::k##clazz##ArrayGetIndexed: \ | |
| FALL_THROUGH; \ | |
| case MethodRecognizer::kExternal##clazz##ArrayGetIndexed: \ | |
| FALL_THROUGH; \ | |
| case MethodRecognizer::k##clazz##ArrayViewGetIndexed: \ | |
| FALL_THROUGH; | |
| DART_CLASS_LIST_TYPED_DATA(TYPED_DATA_GET_INDEXED_CASES) | |
| #undef TYPED_DATA_GET_INDEXED_CASES | |
| case MethodRecognizer::kCopyRangeFromUint8ListToOneByteString: | |
| case MethodRecognizer::kFinalizerBase_getIsolateFinalizers: | |
| case MethodRecognizer::kFinalizerBase_setIsolate: | |
| case MethodRecognizer::kFinalizerBase_setIsolateFinalizers: | |
| case MethodRecognizer::kFinalizerEntry_getExternalSize: | |
| case MethodRecognizer::kProfiler_getCurrentTag: | |
| case MethodRecognizer::kUserTag_defaultTag: | |
| case MethodRecognizer::kExtensionStreamHasListener: | |
| case MethodRecognizer::kTimeline_isDartStreamEnabled: | |
| case MethodRecognizer::kFfiLoadInt8: | |
| case MethodRecognizer::kFfiLoadInt16: | |
| case MethodRecognizer::kFfiLoadInt32: | |
| case MethodRecognizer::kFfiLoadInt64: | |
| case MethodRecognizer::kFfiLoadUint8: | |
| case MethodRecognizer::kFfiLoadUint16: | |
| case MethodRecognizer::kFfiLoadUint32: | |
| case MethodRecognizer::kFfiLoadUint64: | |
| case MethodRecognizer::kFfiLoadFloat: | |
| case MethodRecognizer::kFfiLoadFloatUnaligned: | |
| case MethodRecognizer::kFfiLoadDouble: | |
| case MethodRecognizer::kFfiLoadDoubleUnaligned: | |
| case MethodRecognizer::kFfiLoadPointer: | |
| case MethodRecognizer::kFfiStoreInt8: | |
| case MethodRecognizer::kFfiStoreInt16: | |
| case MethodRecognizer::kFfiStoreInt32: | |
| case MethodRecognizer::kFfiStoreInt64: | |
| case MethodRecognizer::kFfiStoreUint8: | |
| case MethodRecognizer::kFfiStoreUint16: | |
| case MethodRecognizer::kFfiStoreUint32: | |
| case MethodRecognizer::kFfiStoreUint64: | |
| case MethodRecognizer::kFfiStoreFloat: | |
| case MethodRecognizer::kFfiStoreFloatUnaligned: | |
| case MethodRecognizer::kFfiStoreDouble: | |
| case MethodRecognizer::kFfiStoreDoubleUnaligned: | |
| case MethodRecognizer::kFfiStorePointer: | |
| case MethodRecognizer::kFfiFromAddress: | |
| case MethodRecognizer::kFfiGetAddress: | |
| case MethodRecognizer::kFfiAsExternalTypedDataInt8: | |
| case MethodRecognizer::kFfiAsExternalTypedDataInt16: | |
| case MethodRecognizer::kFfiAsExternalTypedDataInt32: | |
| case MethodRecognizer::kFfiAsExternalTypedDataInt64: | |
| case MethodRecognizer::kFfiAsExternalTypedDataUint8: | |
| case MethodRecognizer::kFfiAsExternalTypedDataUint16: | |
| case MethodRecognizer::kFfiAsExternalTypedDataUint32: | |
| case MethodRecognizer::kFfiAsExternalTypedDataUint64: | |
| case MethodRecognizer::kFfiAsExternalTypedDataFloat: | |
| case MethodRecognizer::kFfiAsExternalTypedDataDouble: | |
| case MethodRecognizer::kGetNativeField: | |
| case MethodRecognizer::kRecord_fieldNames: | |
| case MethodRecognizer::kRecord_numFields: | |
| case MethodRecognizer::kStringBaseCodeUnitAt: | |
| case MethodRecognizer::kUtf8DecoderScan: | |
| case MethodRecognizer::kDouble_hashCode: | |
| case MethodRecognizer::kTypedList_GetInt8: | |
| case MethodRecognizer::kTypedList_SetInt8: | |
| case MethodRecognizer::kTypedList_GetUint8: | |
| case MethodRecognizer::kTypedList_SetUint8: | |
| case MethodRecognizer::kTypedList_GetInt16: | |
| case MethodRecognizer::kTypedList_SetInt16: | |
| case MethodRecognizer::kTypedList_GetUint16: | |
| case MethodRecognizer::kTypedList_SetUint16: | |
| case MethodRecognizer::kTypedList_GetInt32: | |
| case MethodRecognizer::kTypedList_SetInt32: | |
| case MethodRecognizer::kTypedList_GetUint32: | |
| case MethodRecognizer::kTypedList_SetUint32: | |
| case MethodRecognizer::kTypedList_GetInt64: | |
| case MethodRecognizer::kTypedList_SetInt64: | |
| case MethodRecognizer::kTypedList_GetUint64: | |
| case MethodRecognizer::kTypedList_SetUint64: | |
| case MethodRecognizer::kTypedList_GetFloat32: | |
| case MethodRecognizer::kTypedList_SetFloat32: | |
| case MethodRecognizer::kTypedList_GetFloat64: | |
| case MethodRecognizer::kTypedList_SetFloat64: | |
| case MethodRecognizer::kTypedList_GetInt32x4: | |
| case MethodRecognizer::kTypedList_SetInt32x4: | |
| case MethodRecognizer::kTypedList_GetFloat32x4: | |
| case MethodRecognizer::kTypedList_SetFloat32x4: | |
| case MethodRecognizer::kTypedList_GetFloat64x2: | |
| case MethodRecognizer::kTypedList_SetFloat64x2: | |
| case MethodRecognizer::kTypedData_memMove1: | |
| case MethodRecognizer::kTypedData_memMove2: | |
| case MethodRecognizer::kTypedData_memMove4: | |
| case MethodRecognizer::kTypedData_memMove8: | |
| case MethodRecognizer::kTypedData_memMove16: | |
| case MethodRecognizer::kMemCopy: | |
| // Prevent the GC from running so that the operation is atomic from | |
| // a GC point of view. Always double check implementation in | |
| // kernel_to_il.cc that no GC can happen in between the relevant IL | |
| // instructions. | |
| // TODO(https://dartbug.com/48527): Support inlining. | |
| case MethodRecognizer::kFinalizerBase_exchangeEntriesCollectedWithNull: | |
| // Both unboxed/untagged data and atomic-to-GC operation. | |
| case MethodRecognizer::kFinalizerEntry_allocate: | |
| return true; | |
| // Only force-optimize when the hardware fast path is available, since the | |
| // fall-through Dart body contains integer-arithmetic instance calls which | |
| // are not allowed inside force-optimized functions. | |
| case MethodRecognizer::kInteger_oneBitCount: | |
| return UnaryInt64OpInstr::IsSupported(Token::kPOPCNT); | |
| case MethodRecognizer::kInteger_trailingZeroBitCount: | |
| return UnaryInt64OpInstr::IsSupported(Token::kCTZ); | |
| default: | |
| return false; | |
| } | |
| #else | |
| // These are not supposed to be called in AOT runtime. | |
| UNREACHABLE(); | |
| return false; | |
| #endif | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| bool Function::CanBeInlined() const { | |
| if (ForceOptimize()) { | |
| if (IsFfiCallClosure() || IsFfiCallbackTrampoline() || is_ffi_native()) { | |
| // We currently don't support inlining FFI trampolines. Some of them | |
| // are naturally non-inlinable because they contain a try/catch block, | |
| // but this condition is broader than strictly necessary. | |
| // The work necessary for inlining FFI trampolines is tracked by | |
| // http://dartbug.com/45055. | |
| return false; | |
| } | |
| if (CompilerState::Current().is_aot()) { | |
| return true; | |
| } | |
| // Inlining of force-optimized functions requires target function to be | |
| // idempotent becase if deoptimization is needed in inlined body, the | |
| // execution of the force-optimized will be restarted at the beginning of | |
| // the function. | |
| ASSERT(!IsPreferInline() || IsIdempotent()); | |
| return IsIdempotent(); | |
| } | |
| if (HasBreakpoint()) { | |
| return false; | |
| } | |
| return is_inlinable(); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| intptr_t Function::NumImplicitParameters() const { | |
| const UntaggedFunction::Kind k = kind(); | |
| if ((k == UntaggedFunction::kClosureFunction) || | |
| (k == UntaggedFunction::kImplicitClosureFunction) || | |
| (k == UntaggedFunction::kFfiTrampoline)) { | |
| return 1; // Closure object. | |
| } | |
| if (!is_static()) { | |
| // Closure functions defined inside instance (i.e. non-static) functions are | |
| // marked as non-static, but they do not have a receiver. | |
| // Closures are handled above. | |
| ASSERT((k != UntaggedFunction::kClosureFunction) && | |
| (k != UntaggedFunction::kImplicitClosureFunction)); | |
| return 1; // Receiver. | |
| } | |
| return 0; // No implicit parameters. | |
| } | |
| bool Function::AreValidArgumentCounts(intptr_t num_type_arguments, | |
| intptr_t num_arguments, | |
| intptr_t num_named_arguments, | |
| String* error_message) const { | |
| if ((num_type_arguments != 0) && | |
| (num_type_arguments != NumTypeParameters())) { | |
| if (error_message != nullptr) { | |
| const intptr_t kMessageBufferSize = 64; | |
| char message_buffer[kMessageBufferSize]; | |
| Utils::SNPrint(message_buffer, kMessageBufferSize, | |
| "%" Pd " type arguments passed, but %" Pd " expected", | |
| num_type_arguments, NumTypeParameters()); | |
| // Allocate in old space because it can be invoked in background | |
| // optimizing compilation. | |
| *error_message = String::New(message_buffer, Heap::kOld); | |
| } | |
| return false; // Too many type arguments. | |
| } | |
| if (num_named_arguments > NumOptionalNamedParameters()) { | |
| if (error_message != nullptr) { | |
| const intptr_t kMessageBufferSize = 64; | |
| char message_buffer[kMessageBufferSize]; | |
| Utils::SNPrint(message_buffer, kMessageBufferSize, | |
| "%" Pd " named passed, at most %" Pd " expected", | |
| num_named_arguments, NumOptionalNamedParameters()); | |
| // Allocate in old space because it can be invoked in background | |
| // optimizing compilation. | |
| *error_message = String::New(message_buffer, Heap::kOld); | |
| } | |
| return false; // Too many named arguments. | |
| } | |
| const intptr_t num_pos_args = num_arguments - num_named_arguments; | |
| const intptr_t num_opt_pos_params = NumOptionalPositionalParameters(); | |
| const intptr_t num_pos_params = num_fixed_parameters() + num_opt_pos_params; | |
| if (num_pos_args > num_pos_params) { | |
| if (error_message != nullptr) { | |
| const intptr_t kMessageBufferSize = 64; | |
| char message_buffer[kMessageBufferSize]; | |
| // Hide implicit parameters to the user. | |
| const intptr_t num_hidden_params = NumImplicitParameters(); | |
| Utils::SNPrint(message_buffer, kMessageBufferSize, | |
| "%" Pd "%s passed, %s%" Pd " expected", | |
| num_pos_args - num_hidden_params, | |
| num_opt_pos_params > 0 ? " positional" : "", | |
| num_opt_pos_params > 0 ? "at most " : "", | |
| num_pos_params - num_hidden_params); | |
| // Allocate in old space because it can be invoked in background | |
| // optimizing compilation. | |
| *error_message = String::New(message_buffer, Heap::kOld); | |
| } | |
| return false; // Too many fixed and/or positional arguments. | |
| } | |
| if (num_pos_args < num_fixed_parameters()) { | |
| if (error_message != nullptr) { | |
| const intptr_t kMessageBufferSize = 64; | |
| char message_buffer[kMessageBufferSize]; | |
| // Hide implicit parameters to the user. | |
| const intptr_t num_hidden_params = NumImplicitParameters(); | |
| Utils::SNPrint(message_buffer, kMessageBufferSize, | |
| "%" Pd "%s passed, %s%" Pd " expected", | |
| num_pos_args - num_hidden_params, | |
| num_opt_pos_params > 0 ? " positional" : "", | |
| num_opt_pos_params > 0 ? "at least " : "", | |
| num_fixed_parameters() - num_hidden_params); | |
| // Allocate in old space because it can be invoked in background | |
| // optimizing compilation. | |
| *error_message = String::New(message_buffer, Heap::kOld); | |
| } | |
| return false; // Too few fixed and/or positional arguments. | |
| } | |
| return true; | |
| } | |
| bool Function::AreValidArguments(intptr_t num_type_arguments, | |
| intptr_t num_arguments, | |
| const Array& argument_names, | |
| String* error_message) const { | |
| const Array& args_desc_array = Array::Handle(ArgumentsDescriptor::NewBoxed( | |
| num_type_arguments, num_arguments, argument_names, Heap::kNew)); | |
| ArgumentsDescriptor args_desc(args_desc_array); | |
| return AreValidArguments(args_desc, error_message); | |
| } | |
| bool Function::AreValidArguments(const ArgumentsDescriptor& args_desc, | |
| String* error_message) const { | |
| const intptr_t num_type_arguments = args_desc.TypeArgsLen(); | |
| const intptr_t num_arguments = args_desc.Count(); | |
| const intptr_t num_named_arguments = args_desc.NamedCount(); | |
| if (!AreValidArgumentCounts(num_type_arguments, num_arguments, | |
| num_named_arguments, error_message)) { | |
| return false; | |
| } | |
| // Verify that all argument names are valid parameter names. | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| String& argument_name = String::Handle(zone); | |
| String& parameter_name = String::Handle(zone); | |
| const intptr_t num_positional_args = num_arguments - num_named_arguments; | |
| const intptr_t num_parameters = NumParameters(); | |
| for (intptr_t i = 0; i < num_named_arguments; i++) { | |
| argument_name = args_desc.NameAt(i); | |
| ASSERT(argument_name.IsSymbol()); | |
| bool found = false; | |
| for (intptr_t j = num_positional_args; j < num_parameters; j++) { | |
| parameter_name = ParameterNameAt(j); | |
| ASSERT(parameter_name.IsSymbol()); | |
| if (argument_name.Equals(parameter_name)) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (!found) { | |
| if (error_message != nullptr) { | |
| const intptr_t kMessageBufferSize = 64; | |
| char message_buffer[kMessageBufferSize]; | |
| Utils::SNPrint(message_buffer, kMessageBufferSize, | |
| "no optional formal parameter named '%s'", | |
| argument_name.ToCString()); | |
| *error_message = String::New(message_buffer); | |
| } | |
| return false; | |
| } | |
| } | |
| // Verify that all required named parameters are filled. | |
| for (intptr_t j = num_parameters - NumOptionalNamedParameters(); | |
| j < num_parameters; j++) { | |
| if (IsRequiredAt(j)) { | |
| parameter_name = ParameterNameAt(j); | |
| ASSERT(parameter_name.IsSymbol()); | |
| bool found = false; | |
| for (intptr_t i = 0; i < num_named_arguments; i++) { | |
| argument_name = args_desc.NameAt(i); | |
| ASSERT(argument_name.IsSymbol()); | |
| if (argument_name.Equals(parameter_name)) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (!found) { | |
| if (error_message != nullptr) { | |
| const intptr_t kMessageBufferSize = 64; | |
| char message_buffer[kMessageBufferSize]; | |
| Utils::SNPrint(message_buffer, kMessageBufferSize, | |
| "missing required named parameter '%s'", | |
| parameter_name.ToCString()); | |
| *error_message = String::New(message_buffer); | |
| } | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| // Retrieves the function type arguments, if any. This could be explicitly | |
| // passed type from the arguments array, delayed type arguments in closures, | |
| // or instantiated bounds for the type parameters if no other source for | |
| // function type arguments are found. | |
| static TypeArgumentsPtr RetrieveFunctionTypeArguments( | |
| Thread* thread, | |
| Zone* zone, | |
| const Function& function, | |
| const Instance& receiver, | |
| const TypeArguments& instantiator_type_args, | |
| const Array& args, | |
| const ArgumentsDescriptor& args_desc) { | |
| ASSERT(!function.IsNull()); | |
| const intptr_t kNumCurrentTypeArgs = function.NumTypeParameters(); | |
| const intptr_t kNumParentTypeArgs = function.NumParentTypeArguments(); | |
| const intptr_t kNumTypeArgs = kNumCurrentTypeArgs + kNumParentTypeArgs; | |
| // Non-generic functions don't receive type arguments. | |
| if (kNumTypeArgs == 0) return Object::empty_type_arguments().ptr(); | |
| // Closure functions require that the receiver be provided (and is a closure). | |
| ASSERT(!function.IsClosureFunction() || receiver.IsClosure()); | |
| // Only closure functions should have possibly generic parents. | |
| ASSERT(function.IsClosureFunction() || kNumParentTypeArgs == 0); | |
| const auto& parent_type_args = | |
| function.IsClosureFunction() | |
| ? TypeArguments::Handle( | |
| zone, Closure::Cast(receiver).function_type_arguments()) | |
| : Object::empty_type_arguments(); | |
| // We don't try to instantiate the parent type parameters to their bounds | |
| // if not provided or check any closed-over type arguments against the parent | |
| // type parameter bounds (since they have been type checked already). | |
| if (kNumCurrentTypeArgs == 0) return parent_type_args.ptr(); | |
| auto& function_type_args = TypeArguments::Handle(zone); | |
| // First check for delayed type arguments before using either provided or | |
| // default type arguments. | |
| bool has_delayed_type_args = false; | |
| if (function.IsClosureFunction()) { | |
| const auto& closure = Closure::Cast(receiver); | |
| function_type_args = closure.delayed_type_arguments(); | |
| has_delayed_type_args = | |
| function_type_args.ptr() != Object::empty_type_arguments().ptr(); | |
| } | |
| if (args_desc.TypeArgsLen() > 0) { | |
| // We should never end up here when the receiver is a closure with delayed | |
| // type arguments unless this dynamically called closure function was | |
| // retrieved directly from the closure instead of going through | |
| // DartEntry::ResolveCallable, which appropriately checks for this case. | |
| ASSERT(!has_delayed_type_args); | |
| function_type_args ^= args.At(0); | |
| } else if (!has_delayed_type_args) { | |
| // We have no explicitly provided function type arguments, so instantiate | |
| // the type parameters to bounds or replace as appropriate. | |
| function_type_args = function.DefaultTypeArguments(zone); | |
| auto const mode = | |
| function.IsClosureFunction() | |
| ? function.default_type_arguments_instantiation_mode() | |
| : function_type_args.GetInstantiationMode(zone, &function); | |
| switch (mode) { | |
| case InstantiationMode::kIsInstantiated: | |
| // Nothing left to do. | |
| break; | |
| case InstantiationMode::kNeedsInstantiation: | |
| function_type_args = function_type_args.InstantiateAndCanonicalizeFrom( | |
| instantiator_type_args, parent_type_args); | |
| break; | |
| case InstantiationMode::kSharesInstantiatorTypeArguments: | |
| function_type_args = instantiator_type_args.ptr(); | |
| break; | |
| case InstantiationMode::kSharesFunctionTypeArguments: | |
| function_type_args = parent_type_args.ptr(); | |
| break; | |
| } | |
| } | |
| return function_type_args.Prepend(zone, parent_type_args, kNumParentTypeArgs, | |
| kNumTypeArgs); | |
| } | |
| // Retrieves the instantiator type arguments, if any, from the receiver. | |
| static TypeArgumentsPtr RetrieveInstantiatorTypeArguments( | |
| Zone* zone, | |
| const Function& function, | |
| const Instance& receiver) { | |
| if (function.IsClosureFunction()) { | |
| ASSERT(receiver.IsClosure()); | |
| const auto& closure = Closure::Cast(receiver); | |
| return closure.instantiator_type_arguments(); | |
| } | |
| if (!receiver.IsNull()) { | |
| const auto& cls = Class::Handle(zone, receiver.clazz()); | |
| if (cls.NumTypeArguments() > 0) { | |
| return receiver.GetTypeArguments(); | |
| } | |
| } | |
| return Object::empty_type_arguments().ptr(); | |
| } | |
| ObjectPtr Function::DoArgumentTypesMatch( | |
| const Array& args, | |
| const ArgumentsDescriptor& args_desc) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (signature() == FunctionType::null()) { | |
| // Precompiler deleted signature because of missing entry point pragma. | |
| return EntryPointFunctionInvocationError(*this); | |
| } | |
| #endif | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| auto& receiver = Instance::Handle(zone); | |
| if (IsClosureFunction() || HasThisParameter()) { | |
| receiver ^= args.At(args_desc.FirstArgIndex()); | |
| } | |
| const auto& instantiator_type_arguments = TypeArguments::Handle( | |
| zone, RetrieveInstantiatorTypeArguments(zone, *this, receiver)); | |
| return Function::DoArgumentTypesMatch(args, args_desc, | |
| instantiator_type_arguments); | |
| } | |
| ObjectPtr Function::DoArgumentTypesMatch( | |
| const Array& args, | |
| const ArgumentsDescriptor& args_desc, | |
| const TypeArguments& instantiator_type_arguments) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (signature() == FunctionType::null()) { | |
| // Precompiler deleted signature because of missing entry point pragma. | |
| return EntryPointFunctionInvocationError(*this); | |
| } | |
| #endif | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| auto& receiver = Instance::Handle(zone); | |
| if (IsClosureFunction() || HasThisParameter()) { | |
| receiver ^= args.At(args_desc.FirstArgIndex()); | |
| } | |
| const auto& function_type_arguments = TypeArguments::Handle( | |
| zone, RetrieveFunctionTypeArguments(thread, zone, *this, receiver, | |
| instantiator_type_arguments, args, | |
| args_desc)); | |
| return Function::DoArgumentTypesMatch( | |
| args, args_desc, instantiator_type_arguments, function_type_arguments); | |
| } | |
| ObjectPtr Function::DoArgumentTypesMatch( | |
| const Array& args, | |
| const ArgumentsDescriptor& args_desc, | |
| const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (signature() == FunctionType::null()) { | |
| // Precompiler deleted signature because of missing entry point pragma. | |
| return EntryPointFunctionInvocationError(*this); | |
| } | |
| #endif | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| // Perform any non-covariant bounds checks on the provided function type | |
| // arguments to make sure they are appropriate subtypes of the bounds. | |
| const intptr_t kNumLocalTypeArgs = NumTypeParameters(); | |
| if (kNumLocalTypeArgs > 0) { | |
| const intptr_t kNumParentTypeArgs = NumParentTypeArguments(); | |
| ASSERT(function_type_arguments.HasCount(kNumParentTypeArgs + | |
| kNumLocalTypeArgs)); | |
| const auto& params = TypeParameters::Handle(zone, type_parameters()); | |
| // No checks are needed if all bounds are dynamic. | |
| if (!params.AllDynamicBounds()) { | |
| auto& param = AbstractType::Handle(zone); | |
| auto& bound = AbstractType::Handle(zone); | |
| for (intptr_t i = 0; i < kNumLocalTypeArgs; i++) { | |
| bound = params.BoundAt(i); | |
| // Only perform non-covariant checks where the bound is not | |
| // the top type. | |
| if (params.IsGenericCovariantImplAt(i) || bound.IsTopType()) { | |
| continue; | |
| } | |
| param = TypeParameterAt(i); | |
| if (!AbstractType::InstantiateAndTestSubtype( | |
| ¶m, &bound, instantiator_type_arguments, | |
| function_type_arguments)) { | |
| const auto& names = Array::Handle(zone, params.names()); | |
| auto& name = String::Handle(zone); | |
| name ^= names.At(i); | |
| return Error::RawCast( | |
| ThrowTypeError(token_pos(), param, bound, name)); | |
| } | |
| } | |
| } | |
| } else { | |
| ASSERT(function_type_arguments.HasCount(NumParentTypeArguments())); | |
| } | |
| AbstractType& type = AbstractType::Handle(zone); | |
| Instance& argument = Instance::Handle(zone); | |
| auto check_argument = [](const Instance& argument, const AbstractType& type, | |
| const TypeArguments& instantiator_type_args, | |
| const TypeArguments& function_type_args) -> bool { | |
| // If the argument type is the top type, no need to check. | |
| if (type.IsTopType()) return true; | |
| return argument.IsInstanceOf(type, instantiator_type_args, | |
| function_type_args); | |
| }; | |
| // Check types of the provided arguments against the expected parameter types. | |
| const intptr_t arg_offset = args_desc.FirstArgIndex(); | |
| // Only check explicit arguments. | |
| const intptr_t arg_start = arg_offset + NumImplicitParameters(); | |
| const intptr_t end_positional_args = arg_offset + args_desc.PositionalCount(); | |
| for (intptr_t arg_index = arg_start; arg_index < end_positional_args; | |
| ++arg_index) { | |
| argument ^= args.At(arg_index); | |
| // Adjust for type arguments when they're present. | |
| const intptr_t param_index = arg_index - arg_offset; | |
| type = ParameterTypeAt(param_index); | |
| if (!check_argument(argument, type, instantiator_type_arguments, | |
| function_type_arguments)) { | |
| auto& name = String::Handle(zone, ParameterNameAt(param_index)); | |
| if (!type.IsInstantiated()) { | |
| type = | |
| type.InstantiateFrom(instantiator_type_arguments, | |
| function_type_arguments, kAllFree, Heap::kNew); | |
| } | |
| return ThrowTypeError(token_pos(), argument, type, name); | |
| } | |
| } | |
| const intptr_t num_named_arguments = args_desc.NamedCount(); | |
| if (num_named_arguments == 0) { | |
| return Error::null(); | |
| } | |
| const int num_parameters = NumParameters(); | |
| const int num_fixed_params = num_fixed_parameters(); | |
| String& argument_name = String::Handle(zone); | |
| String& parameter_name = String::Handle(zone); | |
| // Check types of named arguments against expected parameter type. | |
| for (intptr_t named_index = 0; named_index < num_named_arguments; | |
| named_index++) { | |
| argument_name = args_desc.NameAt(named_index); | |
| ASSERT(argument_name.IsSymbol()); | |
| argument ^= args.At(arg_offset + args_desc.PositionAt(named_index)); | |
| // Try to find the named parameter that matches the provided argument. | |
| // Even when annotated with @required, named parameters are still stored | |
| // as if they were optional and so come after the fixed parameters. | |
| // Currently O(n^2) as there's no guarantee from either the CFE or the | |
| // VM that named parameters and named arguments are sorted in the same way. | |
| intptr_t param_index = num_fixed_params; | |
| for (; param_index < num_parameters; param_index++) { | |
| parameter_name = ParameterNameAt(param_index); | |
| ASSERT(parameter_name.IsSymbol()); | |
| if (!parameter_name.Equals(argument_name)) continue; | |
| type = ParameterTypeAt(param_index); | |
| if (!check_argument(argument, type, instantiator_type_arguments, | |
| function_type_arguments)) { | |
| auto& name = String::Handle(zone, ParameterNameAt(param_index)); | |
| if (!type.IsInstantiated()) { | |
| type = type.InstantiateFrom(instantiator_type_arguments, | |
| function_type_arguments, kAllFree, | |
| Heap::kNew); | |
| } | |
| return ThrowTypeError(token_pos(), argument, type, name); | |
| } | |
| break; | |
| } | |
| // Only should fail if AreValidArguments returns a false positive. | |
| ASSERT(param_index < num_parameters); | |
| } | |
| return Error::null(); | |
| } | |
| // Helper allocating a C string buffer in the zone, printing the fully qualified | |
| // name of a function in it, and replacing ':' by '_' to make sure the | |
| // constructed name is a valid C++ identifier for debugging purpose. | |
| // Set 'chars' to allocated buffer and return number of written characters. | |
| enum QualifiedFunctionLibKind { | |
| kQualifiedFunctionLibKindLibUrl, | |
| kQualifiedFunctionLibKindLibName | |
| }; | |
| static intptr_t ConstructFunctionFullyQualifiedCString( | |
| const Function& function, | |
| char** chars, | |
| intptr_t reserve_len, | |
| bool with_lib, | |
| QualifiedFunctionLibKind lib_kind) { | |
| Zone* zone = Thread::Current()->zone(); | |
| const char* name = String::Handle(zone, function.name()).ToCString(); | |
| const char* function_format = (reserve_len == 0) ? "%s" : "%s_"; | |
| reserve_len += Utils::SNPrint(nullptr, 0, function_format, name); | |
| const Function& parent = Function::Handle(zone, function.parent_function()); | |
| intptr_t written = 0; | |
| if (parent.IsNull()) { | |
| const Class& function_class = Class::Handle(zone, function.Owner()); | |
| ASSERT(!function_class.IsNull()); | |
| const char* class_name = | |
| String::Handle(zone, function_class.Name()).ToCString(); | |
| ASSERT(class_name != nullptr); | |
| const char* library_name = nullptr; | |
| const char* lib_class_format = nullptr; | |
| if (with_lib) { | |
| const Library& library = Library::Handle(zone, function_class.library()); | |
| ASSERT(!library.IsNull()); | |
| switch (lib_kind) { | |
| case kQualifiedFunctionLibKindLibUrl: | |
| library_name = String::Handle(zone, library.url()).ToCString(); | |
| break; | |
| case kQualifiedFunctionLibKindLibName: | |
| library_name = String::Handle(zone, library.name()).ToCString(); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| ASSERT(library_name != nullptr); | |
| lib_class_format = (library_name[0] == '\0') ? "%s%s_" : "%s_%s_"; | |
| } else { | |
| library_name = ""; | |
| lib_class_format = "%s%s."; | |
| } | |
| reserve_len += | |
| Utils::SNPrint(nullptr, 0, lib_class_format, library_name, class_name); | |
| ASSERT(chars != nullptr); | |
| *chars = zone->Alloc<char>(reserve_len + 1); | |
| written = Utils::SNPrint(*chars, reserve_len + 1, lib_class_format, | |
| library_name, class_name); | |
| } else { | |
| written = ConstructFunctionFullyQualifiedCString(parent, chars, reserve_len, | |
| with_lib, lib_kind); | |
| } | |
| ASSERT(*chars != nullptr); | |
| char* next = *chars + written; | |
| written += Utils::SNPrint(next, reserve_len + 1, function_format, name); | |
| // Replace ":" with "_". | |
| while (true) { | |
| next = strchr(next, ':'); | |
| if (next == nullptr) break; | |
| *next = '_'; | |
| } | |
| return written; | |
| } | |
| const char* Function::ToFullyQualifiedCString() const { | |
| char* chars = nullptr; | |
| ConstructFunctionFullyQualifiedCString(*this, &chars, 0, true, | |
| kQualifiedFunctionLibKindLibUrl); | |
| return chars; | |
| } | |
| const char* Function::ToLibNamePrefixedQualifiedCString() const { | |
| char* chars = nullptr; | |
| ConstructFunctionFullyQualifiedCString(*this, &chars, 0, true, | |
| kQualifiedFunctionLibKindLibName); | |
| return chars; | |
| } | |
| const char* Function::ToQualifiedCString() const { | |
| char* chars = nullptr; | |
| ConstructFunctionFullyQualifiedCString(*this, &chars, 0, false, | |
| kQualifiedFunctionLibKindLibUrl); | |
| return chars; | |
| } | |
| AbstractTypePtr FunctionType::InstantiateFrom( | |
| const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments, | |
| intptr_t num_free_fun_type_params, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_mapping, | |
| intptr_t num_parent_type_args_adjustment) const { | |
| ASSERT(IsFinalized()); | |
| Zone* zone = Thread::Current()->zone(); | |
| const intptr_t num_parent_type_args = NumParentTypeArguments(); | |
| bool delete_type_parameters = false; | |
| if (num_free_fun_type_params == kCurrentAndEnclosingFree) { | |
| // See the comment on kCurrentAndEnclosingFree to understand why we don't | |
| // adjust 'num_free_fun_type_params' downward in this case. | |
| num_free_fun_type_params = kAllFree; | |
| delete_type_parameters = true; | |
| } else { | |
| ASSERT(!IsInstantiated(kAny, num_free_fun_type_params)); | |
| // We only consider the function type parameters declared by the parents | |
| // of this signature function as free. | |
| if (num_parent_type_args < num_free_fun_type_params) { | |
| num_free_fun_type_params = num_parent_type_args; | |
| } | |
| } | |
| // The number of parent type parameters that remain uninstantiated. | |
| const intptr_t remaining_parent_type_params = | |
| num_free_fun_type_params < num_parent_type_args | |
| ? num_parent_type_args - num_free_fun_type_params | |
| : 0; | |
| // Adjust number of parent type arguments for all nested substituted types. | |
| num_parent_type_args_adjustment = | |
| remaining_parent_type_params + | |
| (delete_type_parameters ? 0 : NumTypeParameters()); | |
| FunctionType& sig = FunctionType::Handle( | |
| FunctionType::New(remaining_parent_type_params, nullability(), space)); | |
| AbstractType& type = AbstractType::Handle(zone); | |
| FunctionTypeMapping scope(zone, &function_type_mapping, *this, sig); | |
| // Copy the type parameters and instantiate their bounds and defaults. | |
| if (!delete_type_parameters) { | |
| const TypeParameters& type_params = | |
| TypeParameters::Handle(zone, type_parameters()); | |
| if (!type_params.IsNull()) { | |
| const TypeParameters& sig_type_params = | |
| TypeParameters::Handle(zone, TypeParameters::New()); | |
| // No need to set names that are ignored in a signature, however, the | |
| // length of the names array defines the number of type parameters. | |
| sig_type_params.set_names(Array::Handle(zone, type_params.names())); | |
| sig_type_params.set_flags(Array::Handle(zone, type_params.flags())); | |
| sig.SetTypeParameters(sig_type_params); | |
| TypeArguments& type_args = TypeArguments::Handle(zone); | |
| type_args = type_params.bounds(); | |
| if (!type_args.IsNull() && !type_args.IsInstantiated()) { | |
| type_args = type_args.InstantiateFrom( | |
| instantiator_type_arguments, function_type_arguments, | |
| num_free_fun_type_params, space, function_type_mapping, | |
| num_parent_type_args_adjustment); | |
| } | |
| sig_type_params.set_bounds(type_args); | |
| type_args = type_params.defaults(); | |
| if (!type_args.IsNull() && !type_args.IsInstantiated()) { | |
| type_args = type_args.InstantiateFrom( | |
| instantiator_type_arguments, function_type_arguments, | |
| num_free_fun_type_params, space, function_type_mapping, | |
| num_parent_type_args_adjustment); | |
| } | |
| sig_type_params.set_defaults(type_args); | |
| } | |
| } | |
| type = result_type(); | |
| if (!type.IsInstantiated()) { | |
| type = type.InstantiateFrom( | |
| instantiator_type_arguments, function_type_arguments, | |
| num_free_fun_type_params, space, function_type_mapping, | |
| num_parent_type_args_adjustment); | |
| // A returned null type indicates a failed instantiation in dead code that | |
| // must be propagated up to the caller, the optimizing compiler. | |
| if (type.IsNull()) { | |
| return FunctionType::null(); | |
| } | |
| } | |
| sig.set_result_type(type); | |
| const intptr_t num_params = NumParameters(); | |
| sig.set_num_implicit_parameters(num_implicit_parameters()); | |
| sig.set_num_fixed_parameters(num_fixed_parameters()); | |
| sig.SetNumOptionalParameters(NumOptionalParameters(), | |
| HasOptionalPositionalParameters()); | |
| sig.set_parameter_types(Array::Handle(Array::New(num_params, space))); | |
| for (intptr_t i = 0; i < num_params; i++) { | |
| type = ParameterTypeAt(i); | |
| if (!type.IsInstantiated()) { | |
| type = type.InstantiateFrom( | |
| instantiator_type_arguments, function_type_arguments, | |
| num_free_fun_type_params, space, function_type_mapping, | |
| num_parent_type_args_adjustment); | |
| // A returned null type indicates a failed instantiation in dead code that | |
| // must be propagated up to the caller, the optimizing compiler. | |
| if (type.IsNull()) { | |
| return FunctionType::null(); | |
| } | |
| } | |
| sig.SetParameterTypeAt(i, type); | |
| } | |
| sig.set_named_parameter_names(Array::Handle(zone, named_parameter_names())); | |
| if (delete_type_parameters) { | |
| ASSERT(sig.IsInstantiated(kFunctions)); | |
| } | |
| sig.SetIsFinalized(); | |
| // Canonicalization is not part of instantiation. | |
| return sig.ptr(); | |
| } | |
| AbstractTypePtr FunctionType::UpdateFunctionTypes( | |
| intptr_t num_parent_type_args_adjustment, | |
| intptr_t num_free_fun_type_params, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_mapping) const { | |
| ASSERT(num_parent_type_args_adjustment >= 0); | |
| ASSERT(IsFinalized()); | |
| Zone* zone = Thread::Current()->zone(); | |
| const intptr_t old_num_parent_type_args = NumParentTypeArguments(); | |
| // From now on, adjust all type parameter types | |
| // which belong to this or nested function types. | |
| if (num_free_fun_type_params > old_num_parent_type_args) { | |
| num_free_fun_type_params = old_num_parent_type_args; | |
| } | |
| FunctionType& new_type = FunctionType::Handle( | |
| zone, FunctionType::New( | |
| NumParentTypeArguments() + num_parent_type_args_adjustment, | |
| nullability(), space)); | |
| AbstractType& type = AbstractType::Handle(zone); | |
| FunctionTypeMapping scope(zone, &function_type_mapping, *this, new_type); | |
| const TypeParameters& type_params = | |
| TypeParameters::Handle(zone, type_parameters()); | |
| if (!type_params.IsNull()) { | |
| const TypeParameters& new_type_params = | |
| TypeParameters::Handle(zone, TypeParameters::New()); | |
| // No need to set names that are ignored in a signature, however, the | |
| // length of the names array defines the number of type parameters. | |
| new_type_params.set_names(Array::Handle(zone, type_params.names())); | |
| new_type_params.set_flags(Array::Handle(zone, type_params.flags())); | |
| TypeArguments& type_args = TypeArguments::Handle(zone); | |
| type_args = type_params.bounds(); | |
| if (!type_args.IsNull()) { | |
| type_args = type_args.UpdateFunctionTypes(num_parent_type_args_adjustment, | |
| num_free_fun_type_params, space, | |
| function_type_mapping); | |
| } | |
| new_type_params.set_bounds(type_args); | |
| type_args = type_params.defaults(); | |
| if (!type_args.IsNull()) { | |
| type_args = type_args.UpdateFunctionTypes(num_parent_type_args_adjustment, | |
| num_free_fun_type_params, space, | |
| function_type_mapping); | |
| } | |
| new_type_params.set_defaults(type_args); | |
| new_type.SetTypeParameters(new_type_params); | |
| } | |
| type = result_type(); | |
| type = type.UpdateFunctionTypes(num_parent_type_args_adjustment, | |
| num_free_fun_type_params, space, | |
| function_type_mapping); | |
| new_type.set_result_type(type); | |
| const intptr_t num_params = NumParameters(); | |
| new_type.set_num_implicit_parameters(num_implicit_parameters()); | |
| new_type.set_num_fixed_parameters(num_fixed_parameters()); | |
| new_type.SetNumOptionalParameters(NumOptionalParameters(), | |
| HasOptionalPositionalParameters()); | |
| new_type.set_parameter_types(Array::Handle(Array::New(num_params, space))); | |
| for (intptr_t i = 0; i < num_params; i++) { | |
| type = ParameterTypeAt(i); | |
| type = type.UpdateFunctionTypes(num_parent_type_args_adjustment, | |
| num_free_fun_type_params, space, | |
| function_type_mapping); | |
| new_type.SetParameterTypeAt(i, type); | |
| } | |
| new_type.set_named_parameter_names( | |
| Array::Handle(zone, named_parameter_names())); | |
| new_type.SetIsFinalized(); | |
| return new_type.ptr(); | |
| } | |
| // Checks if the type of the specified parameter of this signature is a | |
| // supertype of the type of the specified parameter of the other signature | |
| // (i.e. check parameter contravariance). | |
| // Note that types marked as covariant are already dealt with in the front-end. | |
| bool FunctionType::IsContravariantParameter( | |
| intptr_t parameter_position, | |
| const FunctionType& other, | |
| intptr_t other_parameter_position, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_equivalence) const { | |
| const AbstractType& param_type = | |
| AbstractType::Handle(ParameterTypeAt(parameter_position)); | |
| if (param_type.IsTopType()) { | |
| return true; | |
| } | |
| const AbstractType& other_param_type = | |
| AbstractType::Handle(other.ParameterTypeAt(other_parameter_position)); | |
| return other_param_type.IsSubtypeOf(param_type, space, | |
| function_type_equivalence); | |
| } | |
| bool FunctionType::HasSameTypeParametersAndBounds( | |
| const FunctionType& other, | |
| TypeEquality kind, | |
| FunctionTypeMapping* function_type_equivalence) const { | |
| Zone* const zone = Thread::Current()->zone(); | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " FunctionType::HasSameTypeParametersAndBounds(%s, %s)\n", ToCString(), | |
| other.ToCString()); | |
| const intptr_t num_type_params = NumTypeParameters(); | |
| if (num_type_params != other.NumTypeParameters()) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (number of type parameters)\n"); | |
| return false; | |
| } | |
| if (num_type_params > 0) { | |
| const TypeParameters& type_params = | |
| TypeParameters::Handle(zone, type_parameters()); | |
| ASSERT(!type_params.IsNull()); | |
| const TypeParameters& other_type_params = | |
| TypeParameters::Handle(zone, other.type_parameters()); | |
| ASSERT(!other_type_params.IsNull()); | |
| if (kind == TypeEquality::kInSubtypeTest) { | |
| if (!type_params.AllDynamicBounds() || | |
| !other_type_params.AllDynamicBounds()) { | |
| AbstractType& bound = AbstractType::Handle(zone); | |
| AbstractType& other_bound = AbstractType::Handle(zone); | |
| for (intptr_t i = 0; i < num_type_params; i++) { | |
| bound = type_params.BoundAt(i); | |
| other_bound = other_type_params.BoundAt(i); | |
| // Bounds that are mutual subtypes are considered equal. | |
| if (!bound.IsSubtypeOf(other_bound, Heap::kOld, | |
| function_type_equivalence) || | |
| !other_bound.IsSubtypeOf(bound, Heap::kOld, | |
| function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (bounds are not mutual subtypes)\n"); | |
| return false; | |
| } | |
| } | |
| } | |
| } else { | |
| if (NumParentTypeArguments() != other.NumParentTypeArguments()) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (mismatch in number of type arguments)\n"); | |
| return false; | |
| } | |
| const TypeArguments& bounds = | |
| TypeArguments::Handle(zone, type_params.bounds()); | |
| const TypeArguments& other_bounds = | |
| TypeArguments::Handle(zone, other_type_params.bounds()); | |
| if (!bounds.IsEquivalent(other_bounds, kind, function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (bounds are not equivalent)\n"); | |
| return false; | |
| } | |
| if (kind == TypeEquality::kCanonical) { | |
| // Compare default arguments. | |
| const TypeArguments& defaults = | |
| TypeArguments::Handle(zone, type_params.defaults()); | |
| const TypeArguments& other_defaults = | |
| TypeArguments::Handle(zone, other_type_params.defaults()); | |
| if (!defaults.IsEquivalent(other_defaults, kind, | |
| function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (default types are not equivalent," | |
| " %s - %s)\n", | |
| defaults.ToCString(), other_defaults.ToCString()); | |
| return false; | |
| } | |
| } | |
| } | |
| if (kind != TypeEquality::kInSubtypeTest) { | |
| // Compare flags (IsGenericCovariantImpl). | |
| if (!Array::Equals(type_params.flags(), other_type_params.flags())) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: false (flags are not equal)\n"); | |
| return false; | |
| } | |
| } | |
| } | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: true\n"); | |
| return true; | |
| } | |
| bool FunctionType::IsSubtypeOf( | |
| const FunctionType& other, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_equivalence) const { | |
| TRACE_TYPE_CHECKS_VERBOSE(" FunctionType::IsSubtypeOf(%s, %s)\n", | |
| ToCString(), other.ToCString()); | |
| const intptr_t num_fixed_params = num_fixed_parameters(); | |
| const intptr_t num_opt_pos_params = NumOptionalPositionalParameters(); | |
| const intptr_t num_opt_named_params = NumOptionalNamedParameters(); | |
| const intptr_t other_num_fixed_params = other.num_fixed_parameters(); | |
| const intptr_t other_num_opt_pos_params = | |
| other.NumOptionalPositionalParameters(); | |
| const intptr_t other_num_opt_named_params = | |
| other.NumOptionalNamedParameters(); | |
| // This signature requires the same arguments or less and accepts the same | |
| // arguments or more. We can ignore implicit parameters. | |
| const intptr_t num_ignored_params = num_implicit_parameters(); | |
| const intptr_t other_num_ignored_params = other.num_implicit_parameters(); | |
| if (((num_fixed_params - num_ignored_params) > | |
| (other_num_fixed_params - other_num_ignored_params)) || | |
| ((num_fixed_params - num_ignored_params + num_opt_pos_params) < | |
| (other_num_fixed_params - other_num_ignored_params + | |
| other_num_opt_pos_params)) || | |
| (num_opt_named_params < other_num_opt_named_params)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (mismatch in number of parameters)\n"); | |
| return false; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| FunctionTypeMapping scope(zone, &function_type_equivalence, *this, other); | |
| // Check the type parameters and bounds of generic functions. | |
| if (!HasSameTypeParametersAndBounds(other, TypeEquality::kInSubtypeTest, | |
| function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (mismatch in type parameters)\n"); | |
| return false; | |
| } | |
| // Check the result type. | |
| const AbstractType& other_res_type = | |
| AbstractType::Handle(zone, other.result_type()); | |
| // 'void Function()' is a subtype of 'Object Function()'. | |
| if (!other_res_type.IsTopType()) { | |
| const AbstractType& res_type = AbstractType::Handle(zone, result_type()); | |
| if (!res_type.IsSubtypeOf(other_res_type, space, | |
| function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: false (result type)\n"); | |
| return false; | |
| } | |
| } | |
| // Check the types of fixed and optional positional parameters. | |
| for (intptr_t i = 0; i < (other_num_fixed_params - other_num_ignored_params + | |
| other_num_opt_pos_params); | |
| i++) { | |
| if (!IsContravariantParameter(i + num_ignored_params, other, | |
| i + other_num_ignored_params, space, | |
| function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: false (parameter type)\n"); | |
| return false; | |
| } | |
| } | |
| // Check that for each optional named parameter of type T of the other | |
| // function type, there exists an optional named parameter of this function | |
| // type with an identical name and with a type S that is a supertype of T. | |
| // Note that SetParameterNameAt() guarantees that names are symbols, so we | |
| // can compare their raw pointers. | |
| const int num_params = num_fixed_params + num_opt_named_params; | |
| const int other_num_params = | |
| other_num_fixed_params + other_num_opt_named_params; | |
| bool found_param_name; | |
| String& other_param_name = String::Handle(zone); | |
| for (intptr_t i = other_num_fixed_params; i < other_num_params; i++) { | |
| other_param_name = other.ParameterNameAt(i); | |
| ASSERT(other_param_name.IsSymbol()); | |
| found_param_name = false; | |
| for (intptr_t j = num_fixed_params; j < num_params; j++) { | |
| ASSERT(String::Handle(zone, ParameterNameAt(j)).IsSymbol()); | |
| if (ParameterNameAt(j) == other_param_name.ptr()) { | |
| found_param_name = true; | |
| if (!IsContravariantParameter(j, other, i, space, | |
| function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (optional parameter type)\n"); | |
| return false; | |
| } | |
| break; | |
| } | |
| } | |
| if (!found_param_name) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (named parameter not found)\n"); | |
| return false; | |
| } | |
| } | |
| // Check that for each required named parameter in this function, there's a | |
| // corresponding required named parameter in the other function. | |
| String& param_name = other_param_name; | |
| for (intptr_t j = num_params - num_opt_named_params; j < num_params; j++) { | |
| if (IsRequiredAt(j)) { | |
| param_name = ParameterNameAt(j); | |
| ASSERT(param_name.IsSymbol()); | |
| bool found = false; | |
| for (intptr_t i = other_num_fixed_params; i < other_num_params; i++) { | |
| ASSERT(String::Handle(zone, other.ParameterNameAt(i)).IsSymbol()); | |
| if (other.ParameterNameAt(i) == param_name.ptr()) { | |
| found = true; | |
| if (!other.IsRequiredAt(i)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (mismatch in required named " | |
| "parameters)\n"); | |
| return false; | |
| } | |
| } | |
| } | |
| if (!found) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (required named parameter not found)\n"); | |
| return false; | |
| } | |
| } | |
| } | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: true\n"); | |
| return true; | |
| } | |
| // The compiler generates an implicit constructor if a class definition | |
| // does not contain an explicit constructor or factory. The implicit | |
| // constructor has the same token position as the owner class. | |
| bool Function::IsImplicitConstructor() const { | |
| return IsGenerativeConstructor() && (token_pos() == end_token_pos()); | |
| } | |
| bool Function::IsImplicitStaticClosureFunction(FunctionPtr func) { | |
| NoSafepointScope no_safepoint; | |
| uint32_t kind_tag = func->untag()->kind_tag_.load(std::memory_order_relaxed); | |
| return (KindBits::decode(kind_tag) == | |
| UntaggedFunction::kImplicitClosureFunction) && | |
| StaticBit::decode(kind_tag); | |
| } | |
| bool Function::IsImplicitInstanceClosureFunction(FunctionPtr func) { | |
| NoSafepointScope no_safepoint; | |
| uint32_t kind_tag = func->untag()->kind_tag_.load(std::memory_order_relaxed); | |
| return (KindBits::decode(kind_tag) == | |
| UntaggedFunction::kImplicitClosureFunction) && | |
| !StaticBit::decode(kind_tag); | |
| } | |
| FunctionPtr Function::New(Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kFunctionCid) != | |
| Class::null()); | |
| return Object::Allocate<Function>(space); | |
| } | |
| FunctionPtr Function::New(const FunctionType& signature, | |
| const String& name, | |
| UntaggedFunction::Kind kind, | |
| bool is_static, | |
| bool is_const, | |
| bool is_abstract, | |
| bool is_external, | |
| bool is_native, | |
| const Object& owner, | |
| TokenPosition token_pos, | |
| Heap::Space space) { | |
| ASSERT(!owner.IsNull()); | |
| ASSERT(!signature.IsNull()); | |
| const Function& result = Function::Handle(Function::New(space)); | |
| result.set_kind_tag(0); | |
| result.set_name(name); | |
| result.set_kind_tag(0); // Ensure determinism of uninitialized bits. | |
| result.set_kind(kind); | |
| result.set_recognized_kind(MethodRecognizer::kUnknown); | |
| result.set_modifier(UntaggedFunction::kNoModifier); | |
| result.set_is_static(is_static); | |
| result.set_is_const(is_const); | |
| result.set_is_abstract(is_abstract); | |
| result.set_is_external(is_external); | |
| result.set_is_native(is_native); | |
| result.set_is_reflectable(true); // Will be computed later. | |
| result.set_is_visible(true); // Will be computed later. | |
| result.set_is_debuggable(true); // Will be computed later. | |
| result.set_is_intrinsic(false); | |
| result.set_has_pragma(false); | |
| result.set_is_polymorphic_target(false); | |
| result.set_is_synthetic(false); | |
| NOT_IN_PRECOMPILED(result.set_state_bits(0)); | |
| result.set_owner(owner); | |
| NOT_IN_PRECOMPILED(result.set_token_pos(token_pos)); | |
| NOT_IN_PRECOMPILED(result.set_end_token_pos(token_pos)); | |
| NOT_IN_PRECOMPILED(result.set_usage_counter(0)); | |
| NOT_IN_PRECOMPILED(result.set_deoptimization_counter(0)); | |
| NOT_IN_PRECOMPILED(result.set_optimized_instruction_count(0)); | |
| NOT_IN_PRECOMPILED(result.set_optimized_call_site_count(0)); | |
| NOT_IN_PRECOMPILED(result.set_inlining_depth(0)); | |
| NOT_IN_PRECOMPILED(result.set_kernel_offset(0)); | |
| NOT_IN_PRECOMPILED(result.set_is_optimizable(is_native ? false : true)); | |
| result.set_is_inlinable(true); | |
| result.reset_unboxed_parameters_and_return(); | |
| result.SetInstructionsSafe(StubCode::LazyCompile()); | |
| // See Function::set_data() for more information. | |
| if (kind == UntaggedFunction::kClosureFunction || | |
| kind == UntaggedFunction::kImplicitClosureFunction) { | |
| ASSERT(space == Heap::kOld); | |
| const ClosureData& data = ClosureData::Handle(ClosureData::New()); | |
| data.set_awaiter_link({}); | |
| result.set_data(data); | |
| } else if (kind == UntaggedFunction::kFfiTrampoline) { | |
| const FfiTrampolineData& data = | |
| FfiTrampolineData::Handle(FfiTrampolineData::New()); | |
| result.set_data(data); | |
| } else if (result.is_old_native()) { | |
| const auto& data = | |
| Array::Handle(Array::New(NativeFunctionData::kLength, Heap::kOld)); | |
| result.set_data(data); | |
| } else { | |
| // Functions other than signature functions have no reason to be allocated | |
| // in new space. | |
| ASSERT(space == Heap::kOld); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| // Force-optimized functions are not debuggable because they cannot | |
| // deoptimize. | |
| if (result.ForceOptimize()) { | |
| result.set_is_debuggable(false); | |
| } | |
| #endif | |
| signature.set_num_implicit_parameters(result.NumImplicitParameters()); | |
| result.SetSignature(signature); | |
| NOT_IN_PRECOMPILED( | |
| result.set_positional_parameter_names(Object::empty_array())); | |
| return result.ptr(); | |
| } | |
| FunctionPtr Function::NewClosureFunctionWithKind(UntaggedFunction::Kind kind, | |
| const String& name, | |
| const Function& parent, | |
| bool is_static, | |
| TokenPosition token_pos, | |
| const Object& owner) { | |
| ASSERT((kind == UntaggedFunction::kClosureFunction) || | |
| (kind == UntaggedFunction::kImplicitClosureFunction)); | |
| ASSERT(!parent.IsNull()); | |
| ASSERT(!owner.IsNull()); | |
| const FunctionType& signature = FunctionType::Handle(FunctionType::New( | |
| kind == UntaggedFunction::kClosureFunction ? parent.NumTypeArguments() | |
| : 0)); | |
| const Function& result = Function::Handle( | |
| Function::New(signature, name, kind, | |
| /* is_static = */ is_static, | |
| /* is_const = */ false, | |
| /* is_abstract = */ false, | |
| /* is_external = */ false, | |
| /* is_native = */ false, owner, token_pos)); | |
| result.set_parent_function(parent); | |
| return result.ptr(); | |
| } | |
| FunctionPtr Function::NewClosureFunction(const String& name, | |
| const Function& parent, | |
| TokenPosition token_pos) { | |
| // Use the owner defining the parent function and not the class containing it. | |
| const Object& parent_owner = Object::Handle(parent.RawOwner()); | |
| return NewClosureFunctionWithKind(UntaggedFunction::kClosureFunction, name, | |
| parent, parent.is_static(), token_pos, | |
| parent_owner); | |
| } | |
| FunctionPtr Function::NewImplicitClosureFunction(const String& name, | |
| const Function& parent, | |
| TokenPosition token_pos) { | |
| // Use the owner defining the parent function and not the class containing it. | |
| const Object& parent_owner = Object::Handle(parent.RawOwner()); | |
| return NewClosureFunctionWithKind( | |
| UntaggedFunction::kImplicitClosureFunction, name, parent, | |
| parent.is_static() || parent.IsConstructor(), token_pos, parent_owner); | |
| } | |
| bool Function::SafeToClosurize() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| return HasImplicitClosureFunction(); | |
| #else | |
| return true; | |
| #endif | |
| } | |
| bool Function::IsDynamicClosureCallDispatcher() const { | |
| if (!IsInvokeFieldDispatcher()) return false; | |
| if (!Class::IsClosureClass(Owner())) return false; | |
| return name() == Symbols::DynamicCall().ptr(); | |
| } | |
| bool Function::IsClosureCallDispatcher() const { | |
| if (!IsInvokeFieldDispatcher()) return false; | |
| if (!Class::IsClosureClass(Owner())) return false; | |
| return name() == Symbols::call().ptr(); | |
| } | |
| FunctionPtr Function::ImplicitClosureFunction() const { | |
| // Return the existing implicit closure function if any. | |
| if (implicit_closure_function() != Function::null()) { | |
| return implicit_closure_function(); | |
| } | |
| #if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| // In AOT mode all implicit closures are pre-created. | |
| FATAL("Cannot create implicit closure in AOT!"); | |
| return Function::null(); | |
| #else | |
| ASSERT(!IsClosureFunction()); | |
| Thread* thread = Thread::Current(); | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| if (implicit_closure_function() != Function::null()) { | |
| return implicit_closure_function(); | |
| } | |
| // Create closure function. | |
| Zone* zone = thread->zone(); | |
| const String& closure_name = String::Handle(zone, name()); | |
| const Function& closure_function = Function::Handle( | |
| zone, NewImplicitClosureFunction(closure_name, *this, token_pos())); | |
| // Set closure function's context scope. | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| if (is_static() || IsConstructor()) { | |
| closure_function.set_context_scope(Object::empty_context_scope()); | |
| } else { | |
| const ContextScope& context_scope = ContextScope::Handle( | |
| zone, LocalScope::CreateImplicitClosureScope(*this)); | |
| closure_function.set_context_scope(context_scope); | |
| } | |
| #endif | |
| FunctionType& closure_signature = | |
| FunctionType::Handle(zone, closure_function.signature()); | |
| const auto& cls = Class::Handle(zone, Owner()); | |
| if (!is_static() && !IsConstructor() && | |
| StackTraceUtils::IsPossibleAwaiterLink(cls)) { | |
| closure_function.set_awaiter_link({0, 0}); | |
| } | |
| const intptr_t num_type_params = | |
| IsGenerativeConstructor() ? cls.NumTypeParameters() : NumTypeParameters(); | |
| TypeArguments& instantiator_type_arguments = TypeArguments::Handle(zone); | |
| TypeArguments& function_type_arguments = TypeArguments::Handle(zone); | |
| FunctionTypeMapping* function_type_mapping = nullptr; | |
| FunctionTypeMapping scope(zone, &function_type_mapping, | |
| FunctionType::Handle(zone, signature()), | |
| closure_signature); | |
| auto transform_type = [&](AbstractType& type) { | |
| if (num_type_params > 0) { | |
| if (IsGenerativeConstructor()) { | |
| type = type.UpdateFunctionTypes(num_type_params, kAllFree, Heap::kOld, | |
| nullptr); | |
| if (!type.IsInstantiated(kCurrentClass)) { | |
| type = type.InstantiateFrom( | |
| instantiator_type_arguments, function_type_arguments, | |
| kNoneFree /* avoid truncating parent type args */, Heap::kOld); | |
| } | |
| } else { | |
| type = type.UpdateFunctionTypes(0, kNoneFree, Heap::kOld, | |
| function_type_mapping); | |
| } | |
| } | |
| }; | |
| auto transform_type_args = [&](TypeArguments& type_args) { | |
| ASSERT(num_type_params > 0); | |
| if (!type_args.IsNull()) { | |
| if (IsGenerativeConstructor()) { | |
| type_args = type_args.UpdateFunctionTypes(num_type_params, kAllFree, | |
| Heap::kOld, nullptr); | |
| if (!type_args.IsInstantiated(kCurrentClass)) { | |
| type_args = type_args.InstantiateFrom( | |
| instantiator_type_arguments, function_type_arguments, | |
| kNoneFree /* avoid truncating parent type args */, Heap::kOld); | |
| } | |
| } else { | |
| type_args = type_args.UpdateFunctionTypes(0, kNoneFree, Heap::kOld, | |
| function_type_mapping); | |
| } | |
| } | |
| }; | |
| // Set closure function's type parameters. | |
| if (num_type_params > 0) { | |
| const TypeParameters& old_type_params = TypeParameters::Handle( | |
| zone, | |
| IsGenerativeConstructor() ? cls.type_parameters() : type_parameters()); | |
| const TypeParameters& new_type_params = | |
| TypeParameters::Handle(zone, TypeParameters::New()); | |
| // No need to set names that are ignored in a signature, however, the | |
| // length of the names array defines the number of type parameters. | |
| new_type_params.set_names(Array::Handle(zone, old_type_params.names())); | |
| new_type_params.set_flags(Array::Handle(zone, old_type_params.flags())); | |
| closure_signature.SetTypeParameters(new_type_params); | |
| ASSERT(closure_signature.NumTypeParameters() == num_type_params); | |
| TypeArguments& type_args = TypeArguments::Handle(zone); | |
| type_args = TypeArguments::New(num_type_params); | |
| TypeParameter& type_param = TypeParameter::Handle(zone); | |
| for (intptr_t i = 0; i < num_type_params; i++) { | |
| type_param = closure_signature.TypeParameterAt(i); | |
| type_args.SetTypeAt(i, type_param); | |
| } | |
| if (IsGenerativeConstructor()) { | |
| instantiator_type_arguments = | |
| type_args.ToInstantiatorTypeArguments(thread, cls); | |
| } else { | |
| ASSERT(NumTypeArguments() == type_args.Length()); | |
| function_type_arguments = type_args.ptr(); | |
| } | |
| type_args = old_type_params.bounds(); | |
| transform_type_args(type_args); | |
| new_type_params.set_bounds(type_args); | |
| type_args = old_type_params.defaults(); | |
| transform_type_args(type_args); | |
| new_type_params.set_defaults(type_args); | |
| } | |
| // Set closure function's result type. | |
| AbstractType& result_type = AbstractType::Handle(zone); | |
| if (IsGenerativeConstructor()) { | |
| result_type = cls.DeclarationType(); | |
| } else { | |
| result_type = this->result_type(); | |
| } | |
| transform_type(result_type); | |
| closure_signature.set_result_type(result_type); | |
| // Set closure function's end token to this end token. | |
| NOT_IN_PRECOMPILED(closure_function.set_end_token_pos(end_token_pos())); | |
| // The closurized method stub just calls into the original method and should | |
| // therefore be skipped by the debugger and in stack traces. | |
| closure_function.set_is_debuggable(false); | |
| closure_function.set_is_visible(false); | |
| // Set closure function's formal parameters to this formal parameters, | |
| // removing the receiver if this is an instance method and adding the closure | |
| // object as first parameter. | |
| const int kClosure = 1; | |
| const int num_implicit_params = NumImplicitParameters(); | |
| const int num_fixed_params = | |
| kClosure - num_implicit_params + num_fixed_parameters(); | |
| const int num_opt_params = NumOptionalParameters(); | |
| const bool has_opt_pos_params = HasOptionalPositionalParameters(); | |
| const int num_params = num_fixed_params + num_opt_params; | |
| const int num_pos_params = has_opt_pos_params ? num_params : num_fixed_params; | |
| closure_signature.set_num_fixed_parameters(num_fixed_params); | |
| closure_signature.SetNumOptionalParameters(num_opt_params, | |
| has_opt_pos_params); | |
| closure_signature.set_parameter_types( | |
| Array::Handle(zone, Array::New(num_params, Heap::kOld))); | |
| NOT_IN_PRECOMPILED(closure_function.CreateNameArray()); | |
| closure_signature.CreateNameArrayIncludingFlags(); | |
| AbstractType& param_type = AbstractType::Handle(zone); | |
| String& param_name = String::Handle(zone); | |
| // Add implicit closure object parameter. | |
| param_type = Type::DynamicType(); | |
| closure_signature.SetParameterTypeAt(0, param_type); | |
| NOT_IN_PRECOMPILED( | |
| closure_function.SetParameterNameAt(0, Symbols::ClosureParameter())); | |
| for (int i = kClosure; i < num_pos_params; i++) { | |
| param_type = ParameterTypeAt(num_implicit_params - kClosure + i); | |
| transform_type(param_type); | |
| closure_signature.SetParameterTypeAt(i, param_type); | |
| param_name = ParameterNameAt(num_implicit_params - kClosure + i); | |
| // Set the name in the function for positional parameters. | |
| NOT_IN_PRECOMPILED(closure_function.SetParameterNameAt(i, param_name)); | |
| } | |
| for (int i = num_pos_params; i < num_params; i++) { | |
| param_type = ParameterTypeAt(num_implicit_params - kClosure + i); | |
| transform_type(param_type); | |
| closure_signature.SetParameterTypeAt(i, param_type); | |
| param_name = ParameterNameAt(num_implicit_params - kClosure + i); | |
| // Set the name in the signature for named parameters. | |
| closure_signature.SetParameterNameAt(i, param_name); | |
| if (IsRequiredAt(num_implicit_params - kClosure + i)) { | |
| closure_signature.SetIsRequiredAt(i); | |
| } | |
| } | |
| closure_signature.FinalizeNameArray(); | |
| closure_function.InheritKernelOffsetFrom(*this); | |
| if (!is_static() && !IsConstructor()) { | |
| // Change covariant parameter types to Object?. | |
| BitVector is_covariant(zone, NumParameters()); | |
| BitVector is_generic_covariant_impl(zone, NumParameters()); | |
| ReadParameterCovariance(&is_covariant, &is_generic_covariant_impl); | |
| ObjectStore* object_store = IsolateGroup::Current()->object_store(); | |
| const auto& object_type = | |
| Type::Handle(zone, object_store->nullable_object_type()); | |
| ASSERT(object_type.IsCanonical()); | |
| for (intptr_t i = kClosure; i < num_params; ++i) { | |
| const intptr_t original_param_index = num_implicit_params - kClosure + i; | |
| if (is_covariant.Contains(original_param_index) || | |
| is_generic_covariant_impl.Contains(original_param_index)) { | |
| closure_signature.SetParameterTypeAt(i, object_type); | |
| } | |
| } | |
| } | |
| #if defined(DART_DYNAMIC_MODULES) | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| const bool attach_bytecode = true; | |
| #else | |
| const bool attach_bytecode = is_declared_in_bytecode(); | |
| #endif | |
| if (attach_bytecode) { | |
| if (is_static()) { | |
| closure_function.AttachBytecode( | |
| Object::implicit_static_closure_bytecode()); | |
| } else if (IsConstructor()) { | |
| closure_function.AttachBytecode( | |
| Object::implicit_constructor_closure_bytecode()); | |
| } else { | |
| closure_function.AttachBytecode( | |
| Object::implicit_instance_closure_bytecode()); | |
| } | |
| } | |
| #endif | |
| ASSERT(!closure_signature.IsFinalized()); | |
| closure_signature ^= ClassFinalizer::FinalizeType(closure_signature); | |
| closure_function.SetSignature(closure_signature); | |
| set_implicit_closure_function(closure_function); | |
| ASSERT(closure_function.IsImplicitClosureFunction()); | |
| ASSERT(HasImplicitClosureFunction()); | |
| return closure_function.ptr(); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| void Function::DropUncompiledImplicitClosureFunction() const { | |
| if (implicit_closure_function() != Function::null()) { | |
| const Function& func = Function::Handle(implicit_closure_function()); | |
| if (!func.HasCode()) { | |
| set_implicit_closure_function(Function::Handle()); | |
| } | |
| } | |
| } | |
| StringPtr Function::InternalSignature() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (signature() == FunctionType::null()) { | |
| return String::null(); | |
| } | |
| #endif | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| const FunctionType& sig = FunctionType::Handle(signature()); | |
| sig.Print(kInternalName, &printer); | |
| return Symbols::New(thread, printer.buffer()); | |
| } | |
| StringPtr Function::UserVisibleSignature() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (signature() == FunctionType::null()) { | |
| return String::null(); | |
| } | |
| #endif | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| const FunctionType& sig = FunctionType::Handle(signature()); | |
| sig.Print(kUserVisibleName, &printer); | |
| return Symbols::New(thread, printer.buffer()); | |
| } | |
| void FunctionType::PrintParameters(Thread* thread, | |
| Zone* zone, | |
| NameVisibility name_visibility, | |
| BaseTextBuffer* printer) const { | |
| AbstractType& param_type = AbstractType::Handle(zone); | |
| const intptr_t num_params = NumParameters(); | |
| const intptr_t num_fixed_params = num_fixed_parameters(); | |
| const intptr_t num_opt_pos_params = NumOptionalPositionalParameters(); | |
| const intptr_t num_opt_named_params = NumOptionalNamedParameters(); | |
| const intptr_t num_opt_params = num_opt_pos_params + num_opt_named_params; | |
| ASSERT((num_fixed_params + num_opt_params) == num_params); | |
| intptr_t i = 0; | |
| if (name_visibility == kUserVisibleName) { | |
| // Hide implicit parameters. | |
| i = num_implicit_parameters(); | |
| } | |
| String& name = String::Handle(zone); | |
| while (i < num_fixed_params) { | |
| param_type = ParameterTypeAt(i); | |
| ASSERT(!param_type.IsNull()); | |
| param_type.PrintName(name_visibility, printer); | |
| if (i != (num_params - 1)) { | |
| printer->AddString(", "); | |
| } | |
| i++; | |
| } | |
| if (num_opt_params > 0) { | |
| if (num_opt_pos_params > 0) { | |
| printer->AddString("["); | |
| } else { | |
| printer->AddString("{"); | |
| } | |
| for (intptr_t i = num_fixed_params; i < num_params; i++) { | |
| if (num_opt_named_params > 0 && IsRequiredAt(i)) { | |
| printer->AddString("required "); | |
| } | |
| param_type = ParameterTypeAt(i); | |
| ASSERT(!param_type.IsNull()); | |
| param_type.PrintName(name_visibility, printer); | |
| // The parameter name of an optional positional parameter does not need | |
| // to be part of the signature, since it is not used. | |
| if (num_opt_named_params > 0) { | |
| name = ParameterNameAt(i); | |
| printer->AddString(" "); | |
| printer->AddString(name.ToCString()); | |
| } | |
| if (i != (num_params - 1)) { | |
| printer->AddString(", "); | |
| } | |
| } | |
| if (num_opt_pos_params > 0) { | |
| printer->AddString("]"); | |
| } else { | |
| printer->AddString("}"); | |
| } | |
| } | |
| } | |
| ClosurePtr Function::ImplicitStaticClosure() const { | |
| ASSERT(IsImplicitStaticClosureFunction()); | |
| if (implicit_static_closure() != Closure::null()) { | |
| return implicit_static_closure(); | |
| } | |
| auto thread = Thread::Current(); | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| if (implicit_static_closure() != Closure::null()) { | |
| return implicit_static_closure(); | |
| } | |
| Zone* zone = thread->zone(); | |
| const auto& closure = | |
| Closure::Handle(zone, Closure::New(Object::null_type_arguments(), | |
| Object::null_type_arguments(), *this, | |
| Object::null_object(), Heap::kOld)); | |
| set_implicit_static_closure(closure); | |
| return implicit_static_closure(); | |
| } | |
| ClosurePtr Function::ImplicitInstanceClosure(const Instance& receiver) const { | |
| ASSERT(IsImplicitClosureFunction()); | |
| Zone* zone = Thread::Current()->zone(); | |
| TypeArguments& instantiator_type_arguments = TypeArguments::Handle(zone); | |
| if (!HasInstantiatedSignature(kCurrentClass)) { | |
| instantiator_type_arguments = receiver.GetTypeArguments(); | |
| } | |
| ASSERT(!HasGenericParent()); // No generic parent function. | |
| return Closure::New(instantiator_type_arguments, | |
| Object::null_type_arguments(), *this, receiver); | |
| } | |
| FunctionPtr Function::ImplicitClosureTarget(Zone* zone) const { | |
| const auto& parent = Function::Handle(zone, parent_function()); | |
| const auto& func_name = String::Handle(zone, parent.name()); | |
| const auto& owner = Class::Handle(zone, parent.Owner()); | |
| Thread* thread = Thread::Current(); | |
| const auto& error = owner.EnsureIsFinalized(thread); | |
| ASSERT(error == Error::null()); | |
| auto& target = | |
| Function::Handle(zone, Resolver::ResolveFunction(zone, owner, func_name)); | |
| if (!target.IsNull() && (target.ptr() != parent.ptr())) { | |
| DEBUG_ASSERT(IsolateGroup::Current()->HasAttemptedReload()); | |
| if ((target.is_static() != parent.is_static()) || | |
| (target.kind() != parent.kind())) { | |
| target = Function::null(); | |
| } | |
| } | |
| return target.ptr(); | |
| } | |
| void FunctionType::Print(NameVisibility name_visibility, | |
| BaseTextBuffer* printer) const { | |
| if (IsNull()) { | |
| printer->AddString("null"); // Signature optimized out in precompiler. | |
| return; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const TypeParameters& type_params = | |
| TypeParameters::Handle(zone, type_parameters()); | |
| if (!type_params.IsNull()) { | |
| printer->AddString("<"); | |
| const intptr_t base = NumParentTypeArguments(); | |
| const bool kIsClassTypeParameter = false; | |
| // Type parameter names are meaningless after canonicalization. | |
| type_params.Print(thread, zone, kIsClassTypeParameter, base, | |
| name_visibility, printer); | |
| printer->AddString(">"); | |
| } | |
| printer->AddString("("); | |
| PrintParameters(thread, zone, name_visibility, printer); | |
| printer->AddString(") => "); | |
| const AbstractType& res_type = AbstractType::Handle(zone, result_type()); | |
| if (!res_type.IsNull()) { | |
| res_type.PrintName(name_visibility, printer); | |
| } else { | |
| printer->AddString("null"); | |
| } | |
| } | |
| bool Function::HasInstantiatedSignature( | |
| Genericity genericity, | |
| intptr_t num_free_fun_type_params) const { | |
| return FunctionType::Handle(signature()) | |
| .IsInstantiated(genericity, num_free_fun_type_params); | |
| } | |
| bool FunctionType::IsInstantiated(Genericity genericity, | |
| intptr_t num_free_fun_type_params) const { | |
| if (num_free_fun_type_params == kCurrentAndEnclosingFree) { | |
| num_free_fun_type_params = kAllFree; | |
| } else if (genericity != kCurrentClass) { | |
| const intptr_t num_parent_type_args = NumParentTypeArguments(); | |
| if (num_parent_type_args > 0 && num_free_fun_type_params > 0) { | |
| // The number of parent type arguments is cached in the FunctionType, so | |
| // we can't consider any FunctionType with free parent type arguments as | |
| // fully instantiated. Instead, the FunctionType must be instantiated to | |
| // reduce the number of parent type arguments, even if they're unused in | |
| // its component types. | |
| return false; | |
| } | |
| // Don't consider local function type parameters as free. | |
| if (num_free_fun_type_params > num_parent_type_args) { | |
| num_free_fun_type_params = num_parent_type_args; | |
| } | |
| } | |
| AbstractType& type = AbstractType::Handle(result_type()); | |
| if (!type.IsInstantiated(genericity, num_free_fun_type_params)) { | |
| return false; | |
| } | |
| const intptr_t num_parameters = NumParameters(); | |
| for (intptr_t i = 0; i < num_parameters; i++) { | |
| type = ParameterTypeAt(i); | |
| if (!type.IsInstantiated(genericity, num_free_fun_type_params)) { | |
| return false; | |
| } | |
| } | |
| const intptr_t num_type_params = NumTypeParameters(); | |
| if (num_type_params > 0) { | |
| TypeParameters& type_params = TypeParameters::Handle(type_parameters()); | |
| if (!type_params.AllDynamicBounds()) { | |
| for (intptr_t i = 0; i < type_params.Length(); ++i) { | |
| type = type_params.BoundAt(i); | |
| if (!type.IsInstantiated(genericity, num_free_fun_type_params)) { | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool Function::IsPrivate() const { | |
| return Library::IsPrivate(String::Handle(name())); | |
| } | |
| ClassPtr Function::Owner(FunctionPtr function) { | |
| ObjectPtr owner = function->untag()->owner(); | |
| ASSERT(owner != Object::null()); | |
| if (owner->IsClass()) { | |
| return Class::RawCast(owner); | |
| } | |
| ASSERT(owner->IsPatchClass()); | |
| return PatchClass::RawCast(owner)->untag()->wrapped_class(); | |
| } | |
| #if defined(DART_DYNAMIC_MODULES) | |
| bool Function::is_declared_in_bytecode() const { | |
| return Class::Handle(Owner()).is_declared_in_bytecode(); | |
| } | |
| #endif | |
| void Function::InheritKernelOffsetFrom(const Function& src) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| #if !defined(DART_DYNAMIC_MODULES) | |
| UNREACHABLE(); | |
| #endif | |
| #else | |
| StoreNonPointer(&untag()->kernel_offset_, src.untag()->kernel_offset_); | |
| #endif | |
| } | |
| void Function::InheritKernelOffsetFrom(const Field& src) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| #if !defined(DART_DYNAMIC_MODULES) | |
| UNREACHABLE(); | |
| #endif | |
| #else | |
| set_kernel_offset(src.kernel_offset()); | |
| #endif | |
| } | |
| void Function::SetKernelLibraryAndEvalScript( | |
| const Script& script, | |
| const class KernelProgramInfo& kernel_program_info, | |
| intptr_t index) const { | |
| Array& data_field = Array::Handle( | |
| Array::New(static_cast<intptr_t>(EvalFunctionData::kLength))); | |
| data_field.SetAt(static_cast<intptr_t>(EvalFunctionData::kScript), script); | |
| data_field.SetAt(static_cast<intptr_t>(EvalFunctionData::kKernelProgramInfo), | |
| kernel_program_info); | |
| data_field.SetAt(static_cast<intptr_t>(EvalFunctionData::kKernelLibraryIndex), | |
| Smi::Handle(Smi::New(index))); | |
| set_data(data_field); | |
| } | |
| ScriptPtr Function::script(FunctionPtr function) { | |
| // NOTE(turnidge): If you update this function, you probably want to | |
| // update Class::PatchFieldsAndFunctions() at the same time. | |
| if (function == Function::null()) { | |
| return Script::null(); | |
| } | |
| const UntaggedFunction::Kind kind = KindOf(function); | |
| const ObjectPtr data = function->untag()->data<std::memory_order_acquire>(); | |
| if (kind == UntaggedFunction::kDynamicInvocationForwarder) { | |
| const FunctionPtr target = | |
| Function::RawCast(WeakSerializationReference::Unwrap(data)); | |
| return script(target); | |
| } | |
| if (kind == UntaggedFunction::kImplicitGetter || | |
| kind == UntaggedFunction::kImplicitSetter || | |
| kind == UntaggedFunction::kImplicitStaticGetter) { | |
| const FieldPtr field = Field::RawCast(data); | |
| return Field::Script(field); | |
| } | |
| if (data->IsArray() && Array::LengthOf(Array::RawCast(data)) == | |
| static_cast<intptr_t>(EvalFunctionData::kLength)) { | |
| const Array& fdata = Array::Handle(Array::RawCast(data)); | |
| return Script::RawCast( | |
| fdata.At(static_cast<intptr_t>(EvalFunctionData::kScript))); | |
| } | |
| const ObjectPtr obj = function->untag()->owner(); | |
| if (obj->IsPatchClass()) { | |
| return static_cast<PatchClassPtr>(obj)->untag()->script(); | |
| } | |
| if (kind == UntaggedFunction::kClosureFunction) { | |
| const ClosureDataPtr closure_data = static_cast<ClosureDataPtr>(data); | |
| if (closure_data == ClosureData::null()) { | |
| return Script::null(); | |
| } | |
| const FunctionPtr parent = | |
| Function::RawCast(WeakSerializationReference::Unwrap( | |
| closure_data->untag()->parent_function())); | |
| return script(parent); | |
| } | |
| ASSERT(obj->IsClass()); | |
| return static_cast<ClassPtr>(obj)->untag()->script(); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| KernelProgramInfoPtr Function::KernelProgramInfo() const { | |
| if (is_eval_function()) { | |
| const auto& fdata = Array::Handle(Array::RawCast(data())); | |
| return KernelProgramInfo::RawCast( | |
| fdata.At(static_cast<intptr_t>(EvalFunctionData::kKernelProgramInfo))); | |
| } | |
| if (IsClosureFunction()) { | |
| const auto& parent = Function::Handle(parent_function()); | |
| return parent.KernelProgramInfo(); | |
| } | |
| const auto& owner = Object::Handle(RawOwner()); | |
| if (owner.IsClass()) { | |
| return Class::Cast(owner).KernelProgramInfo(); | |
| } | |
| return PatchClass::Cast(owner).kernel_program_info(); | |
| } | |
| TypedDataViewPtr Function::KernelLibrary() const { | |
| const intptr_t kernel_library_index = KernelLibraryIndex(); | |
| if (kernel_library_index == -1) return TypedDataView::null(); | |
| const auto& info = KernelProgramInfo::Handle(KernelProgramInfo()); | |
| return info.KernelLibrary(kernel_library_index); | |
| } | |
| intptr_t Function::KernelLibraryOffset() const { | |
| const intptr_t kernel_library_index = KernelLibraryIndex(); | |
| if (kernel_library_index == -1) return 0; | |
| const auto& info = KernelProgramInfo::Handle(KernelProgramInfo()); | |
| return info.KernelLibraryStartOffset(kernel_library_index); | |
| } | |
| intptr_t Function::KernelLibraryIndex() const { | |
| ASSERT(!is_declared_in_bytecode()); | |
| if (IsNoSuchMethodDispatcher() || IsInvokeFieldDispatcher() || | |
| IsFfiCallbackTrampoline()) { | |
| return -1; | |
| } | |
| if (is_eval_function()) { | |
| const auto& fdata = Array::Handle(Array::RawCast(data())); | |
| return Smi::Value(static_cast<SmiPtr>(fdata.At( | |
| static_cast<intptr_t>(EvalFunctionData::kKernelLibraryIndex)))); | |
| } | |
| if (IsClosureFunction()) { | |
| const auto& parent = Function::Handle(parent_function()); | |
| ASSERT(!parent.IsNull()); | |
| return parent.KernelLibraryIndex(); | |
| } | |
| const auto& obj = Object::Handle(untag()->owner()); | |
| if (obj.IsClass()) { | |
| const auto& lib = Library::Handle(Class::Cast(obj).library()); | |
| return lib.kernel_library_index(); | |
| } | |
| ASSERT(obj.IsPatchClass()); | |
| return PatchClass::Cast(obj).kernel_library_index(); | |
| } | |
| #endif | |
| bool Function::HasOptimizedCode() const { | |
| return HasCode() && Code::Handle(CurrentCode()).is_optimized(); | |
| } | |
| const char* Function::NameCString(NameVisibility name_visibility) const { | |
| switch (name_visibility) { | |
| case kInternalName: | |
| return String::Handle(name()).ToCString(); | |
| case kScrubbedName: | |
| case kUserVisibleName: | |
| return UserVisibleNameCString(); | |
| } | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| const char* Function::UserVisibleNameCString() const { | |
| if (FLAG_show_internal_names) { | |
| return String::Handle(name()).ToCString(); | |
| } | |
| return String::ScrubName(String::Handle(name()), | |
| is_extension_member() || is_extension_type_member()); | |
| } | |
| StringPtr Function::UserVisibleName() const { | |
| if (FLAG_show_internal_names) { | |
| return name(); | |
| } | |
| return Symbols::New( | |
| Thread::Current(), | |
| String::ScrubName(String::Handle(name()), | |
| is_extension_member() || is_extension_type_member())); | |
| } | |
| StringPtr Function::QualifiedScrubbedName() const { | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| PrintName(NameFormattingParams(kScrubbedName), &printer); | |
| return Symbols::New(thread, printer.buffer()); | |
| } | |
| const char* Function::QualifiedScrubbedNameCString() const { | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| PrintName(NameFormattingParams(kScrubbedName), &printer); | |
| return printer.buffer(); | |
| } | |
| StringPtr Function::QualifiedUserVisibleName() const { | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| PrintName(NameFormattingParams(kUserVisibleName), &printer); | |
| return Symbols::New(thread, printer.buffer()); | |
| } | |
| const char* Function::QualifiedUserVisibleNameCString() const { | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| PrintName(NameFormattingParams(kUserVisibleName), &printer); | |
| return printer.buffer(); | |
| } | |
| static void FunctionPrintNameHelper(const Function& fun, | |
| const NameFormattingParams& params, | |
| BaseTextBuffer* printer) { | |
| if (fun.IsNonImplicitClosureFunction()) { | |
| if (params.include_parent_name) { | |
| const auto& parent = Function::Handle(fun.parent_function()); | |
| if (parent.IsNull()) { | |
| printer->AddString(Symbols::OptimizedOut().ToCString()); | |
| } else { | |
| parent.PrintName(params, printer); | |
| } | |
| // A function's scrubbed name and its user visible name are identical. | |
| printer->AddString("."); | |
| } | |
| if (params.disambiguate_names && | |
| fun.name() == Symbols::AnonymousClosure().ptr()) { | |
| if (fun.token_pos().IsReal()) { | |
| printer->Printf("<anonymous closure @%" Pd ">", fun.token_pos().Pos()); | |
| } else { | |
| printer->Printf("<anonymous closure @no position>"); | |
| } | |
| } else { | |
| printer->AddString(fun.NameCString(params.name_visibility)); | |
| if (params.disambiguate_names) { | |
| if (fun.token_pos().IsReal()) { | |
| printer->Printf("@<%" Pd ">", fun.token_pos().Pos()); | |
| } else { | |
| printer->Printf("@<no position>"); | |
| } | |
| } | |
| } | |
| return; | |
| } | |
| if (params.disambiguate_names) { | |
| if (fun.IsInvokeFieldDispatcher()) { | |
| printer->AddString("[invoke-field] "); | |
| } | |
| if (fun.IsNoSuchMethodDispatcher()) { | |
| printer->AddString("[no-such-method] "); | |
| } | |
| if (fun.IsImplicitClosureFunction()) { | |
| printer->AddString("[tear-off] "); | |
| } | |
| if (fun.IsMethodExtractor()) { | |
| printer->AddString("[tear-off-extractor] "); | |
| } | |
| } | |
| if (fun.kind() == UntaggedFunction::kConstructor) { | |
| printer->AddString("new "); | |
| } else if (params.include_class_name) { | |
| const Class& cls = Class::Handle(fun.Owner()); | |
| if (!cls.IsTopLevel()) { | |
| const Class& mixin = Class::Handle(cls.Mixin()); | |
| printer->AddString(params.name_visibility == Object::kUserVisibleName | |
| ? mixin.UserVisibleNameCString() | |
| : cls.NameCString(params.name_visibility)); | |
| printer->AddString("."); | |
| } | |
| } | |
| printer->AddString(fun.NameCString(params.name_visibility)); | |
| // Dispatchers that are created with an arguments descriptor need both the | |
| // name and the saved arguments descriptor to disambiguate. | |
| if (params.disambiguate_names && fun.HasSavedArgumentsDescriptor()) { | |
| const auto& args_desc_array = Array::Handle(fun.saved_args_desc()); | |
| const ArgumentsDescriptor args_desc(args_desc_array); | |
| args_desc.PrintTo(printer); | |
| } | |
| } | |
| void Function::PrintName(const NameFormattingParams& params, | |
| BaseTextBuffer* printer) const { | |
| if (!IsLocalFunction()) { | |
| FunctionPrintNameHelper(*this, params, printer); | |
| return; | |
| } | |
| auto& fun = Function::Handle(ptr()); | |
| FunctionPrintNameHelper(fun, params, printer); | |
| } | |
| bool Function::NamePassesFilter(const char* name_filter) const { | |
| if (name_filter == nullptr) return true; | |
| char* save_ptr; // Needed for strtok_r. | |
| const char* scrubbed_name = QualifiedScrubbedNameCString(); | |
| const char* function_name = ToFullyQualifiedCString(); | |
| intptr_t function_name_len = strlen(function_name); | |
| intptr_t len = strlen(name_filter) + 1; // Length with \0. | |
| char* filter_buffer = new char[len]; | |
| strncpy(filter_buffer, name_filter, len); // strtok modifies arg 1. | |
| char* token = strtok_r(filter_buffer, ",", &save_ptr); | |
| bool found = false; | |
| while (token != nullptr) { | |
| if ((strstr(function_name, token) != nullptr) || | |
| (strstr(scrubbed_name, token) != nullptr)) { | |
| found = true; | |
| break; | |
| } | |
| const intptr_t token_len = strlen(token); | |
| if (token[token_len - 1] == '%') { | |
| if (function_name_len > token_len) { | |
| const char* suffix = | |
| function_name + (function_name_len - token_len + 1); | |
| if (strncmp(suffix, token, token_len - 1) == 0) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| } | |
| token = strtok_r(nullptr, ",", &save_ptr); | |
| } | |
| delete[] filter_buffer; | |
| return found; | |
| } | |
| StringPtr Function::GetSource() const { | |
| if (IsImplicitConstructor() || is_synthetic()) { | |
| // We may need to handle more cases when the restrictions on mixins are | |
| // relaxed. In particular we might start associating some source with the | |
| // forwarding constructors when it becomes possible to specify a particular | |
| // constructor from the mixin to use. | |
| return String::null(); | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| const Script& func_script = Script::Handle(zone, script()); | |
| intptr_t from_line, from_col; | |
| if (!func_script.GetTokenLocation(token_pos(), &from_line, &from_col)) { | |
| return String::null(); | |
| } | |
| intptr_t to_line, to_col; | |
| if (!func_script.GetTokenLocation(end_token_pos(), &to_line, &to_col)) { | |
| return String::null(); | |
| } | |
| intptr_t to_length = func_script.GetTokenLength(end_token_pos()); | |
| if (to_length < 0) { | |
| return String::null(); | |
| } | |
| if (to_length == 1) { | |
| // Handle special cases for end tokens of closures (where we exclude the | |
| // last token): | |
| // (1) "foo(() => null, bar);": End token is `,', but we don't print it. | |
| // (2) "foo(() => null);": End token is ')`, but we don't print it. | |
| // (3) "var foo = () => null;": End token is `;', but in this case the | |
| // token semicolon belongs to the assignment so we skip it. | |
| const String& src = String::Handle(func_script.Source()); | |
| if (src.IsNull() || src.Length() == 0) { | |
| return Symbols::OptimizedOut().ptr(); | |
| } | |
| uint16_t end_char = src.CharAt(end_token_pos().Pos()); | |
| if ((end_char == ',') || // Case 1. | |
| (end_char == ')') || // Case 2. | |
| (end_char == ';' && String::Handle(zone, name()) | |
| .Equals("<anonymous closure>"))) { // Case 3. | |
| to_length = 0; | |
| } | |
| } | |
| return func_script.GetSnippet(from_line, from_col, to_line, | |
| to_col + to_length); | |
| } | |
| // Construct fingerprint from token stream. The token stream contains also | |
| // arguments. | |
| int32_t Function::SourceFingerprint() const { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| if (is_declared_in_bytecode()) { | |
| return 0; | |
| } | |
| return kernel::KernelSourceFingerprintHelper::CalculateFunctionFingerprint( | |
| *this); | |
| #else | |
| return 0; | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| void Function::SaveICDataMap( | |
| const ZoneGrowableArray<const ICData*>& deopt_id_to_ic_data, | |
| const Array& edge_counters_array, | |
| const TypedData& coverage_array) const { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| // Already installed nothing to do. | |
| if (ic_data_array() != Array::null()) { | |
| ASSERT(coverage_array.ptr() == GetCoverageArray()); | |
| return; | |
| } | |
| // Compute number of ICData objects to save. | |
| intptr_t count = 0; | |
| for (intptr_t i = 0; i < deopt_id_to_ic_data.length(); i++) { | |
| if (deopt_id_to_ic_data[i] != nullptr) { | |
| count++; | |
| } | |
| } | |
| // Compress sparse deopt_id_to_ic_data mapping into a linear sequence of | |
| // ICData objects. | |
| const Array& array = Array::Handle( | |
| Array::New(ICDataArrayIndices::kFirstICData + count, Heap::kOld)); | |
| for (intptr_t i = 0, pos = ICDataArrayIndices::kFirstICData; | |
| i < deopt_id_to_ic_data.length(); i++) { | |
| if (deopt_id_to_ic_data[i] != nullptr) { | |
| ASSERT(i == deopt_id_to_ic_data[i]->deopt_id()); | |
| array.SetAt(pos++, *deopt_id_to_ic_data[i]); | |
| } | |
| } | |
| array.SetAt(ICDataArrayIndices::kEdgeCounters, edge_counters_array); | |
| // Preserve coverage_array which is stored early after graph construction. | |
| array.SetAt(ICDataArrayIndices::kCoverageData, coverage_array); | |
| set_ic_data_array(array); | |
| #else // DART_PRECOMPILED_RUNTIME | |
| UNREACHABLE(); | |
| #endif // DART_PRECOMPILED_RUNTIME | |
| } | |
| void Function::RestoreICDataMap( | |
| ZoneGrowableArray<const ICData*>* deopt_id_to_ic_data, | |
| bool clone_ic_data) const { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| if (FLAG_force_clone_compiler_objects) { | |
| clone_ic_data = true; | |
| } | |
| ASSERT(deopt_id_to_ic_data->is_empty()); | |
| Zone* zone = Thread::Current()->zone(); | |
| const Array& saved_ic_data = Array::Handle(zone, ic_data_array()); | |
| if (saved_ic_data.IsNull()) { | |
| // Could happen with not-yet compiled unoptimized code or force-optimized | |
| // functions. | |
| return; | |
| } | |
| const intptr_t saved_length = saved_ic_data.Length(); | |
| ASSERT(saved_length > 0); | |
| if (saved_length > ICDataArrayIndices::kFirstICData) { | |
| const intptr_t restored_length = | |
| ICData::Cast(Object::Handle(zone, saved_ic_data.At(saved_length - 1))) | |
| .deopt_id() + | |
| 1; | |
| deopt_id_to_ic_data->SetLength(restored_length); | |
| for (intptr_t i = 0; i < restored_length; i++) { | |
| (*deopt_id_to_ic_data)[i] = nullptr; | |
| } | |
| for (intptr_t i = ICDataArrayIndices::kFirstICData; i < saved_length; i++) { | |
| ICData& ic_data = ICData::ZoneHandle(zone); | |
| ic_data ^= saved_ic_data.At(i); | |
| if (clone_ic_data) { | |
| const ICData& original_ic_data = ICData::Handle(zone, ic_data.ptr()); | |
| ic_data = ICData::Clone(ic_data); | |
| ic_data.SetOriginal(original_ic_data); | |
| } | |
| ASSERT(deopt_id_to_ic_data->At(ic_data.deopt_id()) == nullptr); | |
| (*deopt_id_to_ic_data)[ic_data.deopt_id()] = &ic_data; | |
| } | |
| } | |
| #else // DART_PRECOMPILED_RUNTIME | |
| UNREACHABLE(); | |
| #endif // DART_PRECOMPILED_RUNTIME | |
| } | |
| TypedDataPtr Function::GetCoverageArray() const { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| if (HasBytecode()) { | |
| #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| const auto& bytecode = Bytecode::Handle(GetBytecode()); | |
| if (bytecode.HasRecordedCoverage()) { | |
| return bytecode.EnsureCoverageArray(Thread::Current()); | |
| } | |
| #endif | |
| return TypedData::null(); | |
| } | |
| #endif | |
| const Array& arr = Array::Handle(ic_data_array()); | |
| if (arr.IsNull()) { | |
| return TypedData::null(); | |
| } | |
| return TypedData::RawCast(arr.At(ICDataArrayIndices::kCoverageData)); | |
| } | |
| void Function::set_ic_data_array(const Array& value) const { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| ASSERT(!HasBytecode()); | |
| #endif | |
| untag()->set_ic_data_array_or_bytecode<std::memory_order_release>( | |
| value.ptr()); | |
| } | |
| ArrayPtr Function::ic_data_array() const { | |
| ObjectPtr value = | |
| untag()->ic_data_array_or_bytecode<std::memory_order_acquire>(); | |
| #if defined(DART_DYNAMIC_MODULES) | |
| if (value->IsBytecode()) { | |
| return Array::null(); | |
| } | |
| #endif | |
| return Array::RawCast(value); | |
| } | |
| void Function::ClearICDataArray() const { | |
| set_ic_data_array(Array::null_array()); | |
| } | |
| ICDataPtr Function::FindICData(intptr_t deopt_id) const { | |
| const Array& array = Array::Handle(ic_data_array()); | |
| ICData& ic_data = ICData::Handle(); | |
| for (intptr_t i = ICDataArrayIndices::kFirstICData; i < array.Length(); i++) { | |
| ic_data ^= array.At(i); | |
| if (ic_data.deopt_id() == deopt_id) { | |
| return ic_data.ptr(); | |
| } | |
| } | |
| return ICData::null(); | |
| } | |
| void Function::SetDeoptReasonForAll(intptr_t deopt_id, | |
| ICData::DeoptReasonId reason) { | |
| const Array& array = Array::Handle(ic_data_array()); | |
| ICData& ic_data = ICData::Handle(); | |
| for (intptr_t i = ICDataArrayIndices::kFirstICData; i < array.Length(); i++) { | |
| ic_data ^= array.At(i); | |
| if (ic_data.deopt_id() == deopt_id) { | |
| ic_data.AddDeoptReason(reason); | |
| } | |
| } | |
| } | |
| bool Function::CheckSourceFingerprint(int32_t fp, const char* kind) const { | |
| #if defined(DEBUG) | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| // Check that the function is marked as recognized via the vm:recognized | |
| // pragma. This is so that optimizations that change the signature will know | |
| // not to touch it. | |
| if (kind != nullptr && !MethodRecognizer::IsMarkedAsRecognized(*this, kind)) { | |
| OS::PrintErr( | |
| "Recognized method %s should be marked with: " | |
| "@pragma(\"vm:recognized\", \"%s\")\n", | |
| ToQualifiedCString(), kind); | |
| return false; | |
| } | |
| #endif | |
| if (IsolateGroup::Current()->obfuscate() || FLAG_precompiled_mode) { | |
| return true; // The kernel structure has been altered, skip checking. | |
| } | |
| ASSERT(!is_declared_in_bytecode()); | |
| if (SourceFingerprint() != fp) { | |
| // This output can be copied into a file, then used with sed | |
| // to replace the old values. | |
| // sed -i.bak -f /tmp/newkeys \ | |
| // runtime/vm/compiler/recognized_methods_list.h | |
| THR_Print("s/0x%08x/0x%08x/\n", fp, SourceFingerprint()); | |
| return false; | |
| } | |
| #endif // defined(DEBUG) | |
| return true; | |
| } | |
| CodePtr Function::EnsureHasCode() const { | |
| if (HasCode()) { | |
| return CurrentCode(); | |
| } | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->IsDartMutatorThread()); | |
| DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame()); | |
| Zone* zone = thread->zone(); | |
| const Object& result = Object::Handle(zone, EnsureHasCodeNoThrow()); | |
| if (result.IsError()) { | |
| if (result.ptr() == Object::out_of_memory_error().ptr()) { | |
| Exceptions::ThrowOOM(); | |
| UNREACHABLE(); | |
| } | |
| if (result.IsLanguageError()) { | |
| Exceptions::ThrowCompileTimeError(LanguageError::Cast(result)); | |
| UNREACHABLE(); | |
| } | |
| Exceptions::PropagateError(Error::Cast(result)); | |
| UNREACHABLE(); | |
| } else { | |
| return Code::Cast(result).ptr(); | |
| } | |
| } | |
| ObjectPtr Function::EnsureHasCodeNoThrow() const { | |
| if (HasCode()) { | |
| return CurrentCode(); | |
| } | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->IsDartMutatorThread()); | |
| Zone* zone = thread->zone(); | |
| const Object& result = | |
| Object::Handle(zone, Compiler::CompileFunction(thread, *this)); | |
| if (result.IsError()) { | |
| return result.ptr(); | |
| } | |
| // Compiling in unoptimized mode should never fail if there are no errors. | |
| RELEASE_ASSERT(HasCode()); | |
| ASSERT(ForceOptimize() || unoptimized_code() == result.ptr()); | |
| return CurrentCode(); | |
| } | |
| bool Function::NeedsMonomorphicCheckedEntry(Zone* zone) const { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| if (!IsDynamicFunction()) { | |
| return false; | |
| } | |
| // For functions which need an args descriptor the switchable call sites will | |
| // transition directly to calling via a stub (and therefore never call the | |
| // monomorphic entry). | |
| // | |
| // See runtime_entry.cc:DEFINE_RUNTIME_ENTRY(UnlinkedCall) | |
| if (PrologueNeedsArgumentsDescriptor()) { | |
| return false; | |
| } | |
| // All dyn:* forwarders are called via SwitchableCalls and all except the ones | |
| // with `PrologueNeedsArgumentsDescriptor()` transition into monomorphic | |
| // state. | |
| if (Function::IsDynamicInvocationForwarderName(name())) { | |
| return true; | |
| } | |
| // AOT mode uses table dispatch. | |
| // In JIT mode all instance calls use switchable calls. | |
| if (!FLAG_precompiled_mode) { | |
| return true; | |
| } | |
| // Any method from the class with a dynamically loaded subtype | |
| // can be called via switchable call (when cid range check fails | |
| // during conditional table dispatch). | |
| if (Class::Handle(zone, Owner()).has_dynamically_extendable_subtypes()) { | |
| return true; | |
| } | |
| // Only if there are dynamic callers and if we didn't create a dyn:* forwarder | |
| // for it do we need the monomorphic checked entry. | |
| return HasDynamicCallers(zone) && !NeedsDynamicInvocationForwarder(); | |
| #else | |
| UNREACHABLE(); | |
| return true; | |
| #endif | |
| } | |
| bool Function::HasDynamicCallers(Zone* zone) const { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| // Use the results of TFA to determine whether this function is ever | |
| // called dynamically, i.e. using switchable calls. | |
| kernel::ProcedureAttributesMetadata metadata; | |
| metadata = kernel::ProcedureAttributesOf(*this, zone); | |
| if (IsGetterFunction() || IsImplicitGetterFunction() || IsMethodExtractor()) { | |
| // Dynamic method call through field/getter involves dynamic call of | |
| // the field/getter. | |
| return metadata.getter_called_dynamically || | |
| metadata.method_or_setter_called_dynamically; | |
| } else { | |
| return metadata.method_or_setter_called_dynamically; | |
| } | |
| #else | |
| UNREACHABLE(); | |
| return true; | |
| #endif | |
| } | |
| bool Function::PrologueNeedsArgumentsDescriptor() const { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| // Entering interpreter needs arguments descriptor. | |
| if (is_declared_in_bytecode()) { | |
| return true; | |
| } | |
| #endif | |
| // These functions have a saved compile-time arguments descriptor that is | |
| // used in lieu of the runtime arguments descriptor in generated IL. | |
| if (HasSavedArgumentsDescriptor()) { | |
| return false; | |
| } | |
| // The prologue of those functions need to examine the arg descriptor for | |
| // various purposes. | |
| return IsGeneric() || HasOptionalParameters(); | |
| } | |
| bool Function::MayHaveUncheckedEntryPoint() const { | |
| return FLAG_enable_multiple_entrypoints && | |
| (NeedsTypeArgumentTypeChecks() || NeedsArgumentTypeChecks()); | |
| } | |
| intptr_t Function::SourceSize() const { | |
| const TokenPosition& start = token_pos(); | |
| const TokenPosition& end = end_token_pos(); | |
| if (!end.IsReal() || start.IsNoSource() || start.IsClassifying()) { | |
| // No source information, so just return 0. | |
| return 0; | |
| } | |
| if (start.IsSynthetic()) { | |
| // Try and approximate the source size using the parent's source size. | |
| const auto& parent = Function::Handle(parent_function()); | |
| ASSERT(!parent.IsNull()); | |
| const intptr_t parent_size = parent.SourceSize(); | |
| if (parent_size == 0) { | |
| return parent_size; | |
| } | |
| // Parent must have a real ending position. | |
| return parent_size - (parent.end_token_pos().Pos() - end.Pos()); | |
| } | |
| return end.Pos() - start.Pos(); | |
| } | |
| const char* Function::ToCString() const { | |
| if (IsNull()) { | |
| return "Function: null"; | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| ZoneTextBuffer buffer(zone); | |
| buffer.Printf("Function '%s':", String::Handle(zone, name()).ToCString()); | |
| if (is_static()) { | |
| buffer.AddString(" static"); | |
| } | |
| if (is_abstract()) { | |
| buffer.AddString(" abstract"); | |
| } | |
| switch (kind()) { | |
| case UntaggedFunction::kRegularFunction: | |
| case UntaggedFunction::kClosureFunction: | |
| case UntaggedFunction::kImplicitClosureFunction: | |
| case UntaggedFunction::kGetterFunction: | |
| case UntaggedFunction::kSetterFunction: | |
| break; | |
| case UntaggedFunction::kConstructor: | |
| buffer.AddString(is_static() ? " factory" : " constructor"); | |
| break; | |
| case UntaggedFunction::kImplicitGetter: | |
| buffer.AddString(" getter"); | |
| break; | |
| case UntaggedFunction::kImplicitSetter: | |
| buffer.AddString(" setter"); | |
| break; | |
| case UntaggedFunction::kImplicitStaticGetter: | |
| buffer.AddString(" static-getter"); | |
| break; | |
| case UntaggedFunction::kFieldInitializer: | |
| buffer.AddString(" field-initializer"); | |
| break; | |
| case UntaggedFunction::kMethodExtractor: | |
| buffer.AddString(" method-extractor"); | |
| break; | |
| case UntaggedFunction::kNoSuchMethodDispatcher: | |
| buffer.AddString(" no-such-method-dispatcher"); | |
| break; | |
| case UntaggedFunction::kDynamicInvocationForwarder: | |
| buffer.AddString(" dynamic-invocation-forwarder"); | |
| break; | |
| case UntaggedFunction::kInvokeFieldDispatcher: | |
| buffer.AddString(" invoke-field-dispatcher"); | |
| break; | |
| case UntaggedFunction::kFfiTrampoline: | |
| buffer.AddString(" ffi-trampoline-function"); | |
| break; | |
| case UntaggedFunction::kRecordFieldGetter: | |
| buffer.AddString(" record-field-getter"); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| if (HasSavedArgumentsDescriptor()) { | |
| const auto& args_desc_array = Array::Handle(zone, saved_args_desc()); | |
| const ArgumentsDescriptor args_desc(args_desc_array); | |
| buffer.AddChar('['); | |
| args_desc.PrintTo(&buffer); | |
| buffer.AddChar(']'); | |
| } | |
| if (is_const()) { | |
| buffer.AddString(" const"); | |
| } | |
| buffer.AddChar('.'); | |
| return buffer.buffer(); | |
| } | |
| void FunctionType::set_packed_parameter_counts( | |
| uint32_t packed_parameter_counts) const { | |
| untag()->packed_parameter_counts_ = packed_parameter_counts; | |
| } | |
| void FunctionType::set_packed_type_parameter_counts( | |
| uint16_t packed_type_parameter_counts) const { | |
| untag()->packed_type_parameter_counts_ = packed_type_parameter_counts; | |
| } | |
| void FunctionType::set_num_implicit_parameters(intptr_t value) const { | |
| ASSERT(value >= 0); | |
| untag()->packed_parameter_counts_.Update<PackedNumImplicitParameters>(value); | |
| } | |
| void ClosureData::set_default_type_arguments_instantiation_mode( | |
| InstantiationMode value) const { | |
| untag()->packed_fields_.Update<PackedInstantiationMode>(value); | |
| } | |
| #if defined(DART_DYNAMIC_MODULES) && !defined(PRODUCT) && \ | |
| !defined(DART_PRECOMPILED_RUNTIME) | |
| void ClosureData::set_reloaded_implicit_closure(bool value) const { | |
| untag()->packed_fields_.Update<UntaggedClosureData::ReloadedImplicitClosure>( | |
| value); | |
| } | |
| #endif | |
| Function::AwaiterLink ClosureData::awaiter_link() const { | |
| const uint8_t depth = | |
| untag() | |
| ->packed_fields_.Read<UntaggedClosureData::PackedAwaiterLinkDepth>(); | |
| const uint8_t index = | |
| untag() | |
| ->packed_fields_.Read<UntaggedClosureData::PackedAwaiterLinkIndex>(); | |
| return {depth, index}; | |
| } | |
| void ClosureData::set_awaiter_link(Function::AwaiterLink link) const { | |
| untag()->packed_fields_.Update<UntaggedClosureData::PackedAwaiterLinkDepth>( | |
| link.depth); | |
| untag()->packed_fields_.Update<UntaggedClosureData::PackedAwaiterLinkIndex>( | |
| link.index); | |
| } | |
| bool ClosureData::captures_only_final_not_late_vars() const { | |
| return untag() | |
| ->packed_fields_.Read<UntaggedClosureData::CapturesOnlySharedFields>(); | |
| } | |
| void ClosureData::set_captures_only_final_not_late_vars(bool value) const { | |
| untag()->packed_fields_.Update<UntaggedClosureData::CapturesOnlySharedFields>( | |
| value); | |
| } | |
| ClosureDataPtr ClosureData::New() { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kClosureDataCid) != | |
| Class::null()); | |
| return Object::Allocate<ClosureData>(Heap::kOld); | |
| } | |
| const char* ClosureData::ToCString() const { | |
| if (IsNull()) { | |
| return "ClosureData: null"; | |
| } | |
| auto const zone = Thread::Current()->zone(); | |
| ZoneTextBuffer buffer(zone); | |
| buffer.Printf("ClosureData: context_scope: 0x%" Px "", | |
| static_cast<uword>(context_scope())); | |
| buffer.AddString(" parent_function: "); | |
| if (parent_function() == Object::null()) { | |
| buffer.AddString("null"); | |
| } else { | |
| buffer.AddString(Object::Handle(parent_function()).ToCString()); | |
| } | |
| buffer.Printf(" implicit_static_closure: 0x%" Px "", | |
| static_cast<uword>(implicit_static_closure())); | |
| return buffer.buffer(); | |
| } | |
| void FunctionType::set_num_fixed_parameters(intptr_t value) const { | |
| ASSERT(value >= 0); | |
| untag()->packed_parameter_counts_.Update<PackedNumFixedParameters>(value); | |
| } | |
| void FfiTrampolineData::set_callback_target(const Function& value) const { | |
| untag()->set_callback_target(value.ptr()); | |
| } | |
| void FunctionType::SetNumOptionalParameters( | |
| intptr_t value, | |
| bool are_optional_positional) const { | |
| // HasOptionalNamedParameters only checks this bit, so only set it if there | |
| // are actual named parameters. | |
| untag()->packed_parameter_counts_.Update<PackedHasNamedOptionalParameters>( | |
| (value > 0) && !are_optional_positional); | |
| untag()->packed_parameter_counts_.Update<PackedNumOptionalParameters>(value); | |
| } | |
| FunctionTypePtr FunctionType::New(Heap::Space space) { | |
| return Object::Allocate<FunctionType>(space); | |
| } | |
| FunctionTypePtr FunctionType::New(intptr_t num_parent_type_arguments, | |
| Nullability nullability, | |
| Heap::Space space) { | |
| Zone* Z = Thread::Current()->zone(); | |
| const FunctionType& result = | |
| FunctionType::Handle(Z, FunctionType::New(space)); | |
| result.set_packed_parameter_counts(0); | |
| result.set_packed_type_parameter_counts(0); | |
| result.set_named_parameter_names(Object::empty_array()); | |
| result.SetNumParentTypeArguments(num_parent_type_arguments); | |
| result.SetHash(0); | |
| result.set_flags(0); | |
| result.set_nullability(nullability); | |
| result.set_type_state(UntaggedAbstractType::kAllocated); | |
| result.InitializeTypeTestingStubNonAtomic( | |
| Code::Handle(Z, TypeTestingStubGenerator::DefaultCodeForType(result))); | |
| return result.ptr(); | |
| } | |
| FunctionTypePtr FunctionType::Clone(const FunctionType& orig, | |
| Heap::Space space) { | |
| if (orig.IsGeneric()) { | |
| // Need a deep clone in order to update owners of type parameters. | |
| return FunctionType::RawCast( | |
| orig.UpdateFunctionTypes(0, kAllFree, space, nullptr)); | |
| } else { | |
| return FunctionType::RawCast(Object::Clone(orig, space)); | |
| } | |
| } | |
| const char* FunctionType::ToUserVisibleCString() const { | |
| Zone* zone = Thread::Current()->zone(); | |
| ZoneTextBuffer printer(zone); | |
| Print(kUserVisibleName, &printer); | |
| return printer.buffer(); | |
| } | |
| StringPtr FunctionType::ToUserVisibleString() const { | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| Print(kUserVisibleName, &printer); | |
| return Symbols::New(thread, printer.buffer()); | |
| } | |
| const char* FunctionType::ToCString() const { | |
| if (IsNull()) { | |
| return "FunctionType: null"; | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| ZoneTextBuffer printer(zone); | |
| const char* suffix = NullabilitySuffix(kInternalName); | |
| if (suffix[0] != '\0') { | |
| printer.AddString("("); | |
| } | |
| Print(kInternalName, &printer); | |
| if (suffix[0] != '\0') { | |
| printer.AddString(")"); | |
| printer.AddString(suffix); | |
| } | |
| return printer.buffer(); | |
| } | |
| void ClosureData::set_context_scope(const ContextScope& value) const { | |
| untag()->set_context_scope<std::memory_order_release>(value.ptr()); | |
| } | |
| void ClosureData::set_implicit_static_closure(const Closure& closure) const { | |
| ASSERT(!closure.IsNull()); | |
| ASSERT(untag()->closure() == Closure::null()); | |
| untag()->set_closure<std::memory_order_release>(closure.ptr()); | |
| } | |
| void FfiTrampolineData::set_c_signature(const FunctionType& value) const { | |
| untag()->set_c_signature(value.ptr()); | |
| } | |
| void FfiTrampolineData::set_callback_id(int32_t callback_id) const { | |
| StoreNonPointer(&untag()->callback_id_, callback_id); | |
| } | |
| void FfiTrampolineData::set_callback_exceptional_return( | |
| const Instance& value) const { | |
| untag()->set_callback_exceptional_return(value.ptr()); | |
| } | |
| void FfiTrampolineData::set_ffi_function_kind(FfiCallbackKind kind) const { | |
| StoreNonPointer(&untag()->ffi_function_kind_, static_cast<uint8_t>(kind)); | |
| } | |
| FfiTrampolineDataPtr FfiTrampolineData::New() { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kFfiTrampolineDataCid) != | |
| Class::null()); | |
| const auto& data = FfiTrampolineData::Handle( | |
| Object::Allocate<FfiTrampolineData>(Heap::kOld)); | |
| data.set_callback_id(-1); | |
| return data.ptr(); | |
| } | |
| const char* FfiTrampolineData::ToCString() const { | |
| const FunctionType& c_sig = FunctionType::Handle(c_signature()); | |
| return OS::SCreate(Thread::Current()->zone(), | |
| "TrampolineData: c_signature=%s", | |
| c_sig.ToUserVisibleCString()); | |
| } | |
| FieldPtr Field::CloneFromOriginal() const { | |
| return this->Clone(*this); | |
| } | |
| FieldPtr Field::Original() const { | |
| if (IsNull()) { | |
| return Field::null(); | |
| } | |
| if (untag()->owner()->IsField()) { | |
| return static_cast<FieldPtr>(untag()->owner()); | |
| } | |
| return this->ptr(); | |
| } | |
| intptr_t Field::guarded_cid() const { | |
| #if defined(DEBUG) | |
| // This assertion ensures that the cid seen by the background compiler is | |
| // consistent. So the assertion passes if the field is a clone. It also | |
| // passes if the field is static, because we don't use field guards on | |
| // static fields. It also passes if we're compiling unoptimized | |
| // code (in which case the caller might get different answers if it obtains | |
| // the guarded cid multiple times). | |
| Thread* thread = Thread::Current(); | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| ASSERT(!thread->IsInsideCompiler() || is_static()); | |
| #else | |
| ASSERT(!thread->IsInsideCompiler() || | |
| ((CompilerState::Current().should_clone_fields() == !IsOriginal())) || | |
| is_static()); | |
| #endif | |
| #endif | |
| return LoadNonPointer<ClassIdTagType, std::memory_order_relaxed>( | |
| &untag()->guarded_cid_); | |
| } | |
| bool Field::is_nullable() const { | |
| #if defined(DEBUG) | |
| // Same assert as guarded_cid(), because is_nullable() also needs to be | |
| // consistent for the background compiler. | |
| Thread* thread = Thread::Current(); | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| ASSERT(!thread->IsInsideCompiler() || is_static()); | |
| #else | |
| ASSERT(!thread->IsInsideCompiler() || | |
| ((CompilerState::Current().should_clone_fields() == !IsOriginal())) || | |
| is_static()); | |
| #endif | |
| #endif | |
| return is_nullable_unsafe(); | |
| } | |
| void Field::SetOriginal(const Field& value) const { | |
| ASSERT(value.IsOriginal()); | |
| ASSERT(!value.IsNull()); | |
| untag()->set_owner(static_cast<ObjectPtr>(value.ptr())); | |
| } | |
| StringPtr Field::GetterName(const String& field_name) { | |
| return String::Concat(Symbols::GetterPrefix(), field_name); | |
| } | |
| StringPtr Field::GetterSymbol(const String& field_name) { | |
| return Symbols::FromGet(Thread::Current(), field_name); | |
| } | |
| StringPtr Field::LookupGetterSymbol(const String& field_name) { | |
| return Symbols::LookupFromGet(Thread::Current(), field_name); | |
| } | |
| StringPtr Field::SetterName(const String& field_name) { | |
| return String::Concat(Symbols::SetterPrefix(), field_name); | |
| } | |
| StringPtr Field::SetterSymbol(const String& field_name) { | |
| return Symbols::FromSet(Thread::Current(), field_name); | |
| } | |
| StringPtr Field::LookupSetterSymbol(const String& field_name) { | |
| return Symbols::LookupFromSet(Thread::Current(), field_name); | |
| } | |
| StringPtr Field::NameFromGetter(const String& getter_name) { | |
| return Symbols::New(Thread::Current(), getter_name, kGetterPrefixLength, | |
| getter_name.Length() - kGetterPrefixLength); | |
| } | |
| StringPtr Field::NameFromSetter(const String& setter_name) { | |
| return Symbols::New(Thread::Current(), setter_name, kSetterPrefixLength, | |
| setter_name.Length() - kSetterPrefixLength); | |
| } | |
| StringPtr Field::NameFromInit(const String& init_name) { | |
| return Symbols::New(Thread::Current(), init_name, kInitPrefixLength, | |
| init_name.Length() - kInitPrefixLength); | |
| } | |
| bool Field::IsGetterName(const String& function_name) { | |
| return function_name.StartsWith(Symbols::GetterPrefix()); | |
| } | |
| bool Field::IsSetterName(const String& function_name) { | |
| return function_name.StartsWith(Symbols::SetterPrefix()); | |
| } | |
| bool Field::IsInitName(const String& function_name) { | |
| return function_name.StartsWith(Symbols::InitPrefix()); | |
| } | |
| void Field::set_name(const String& value) const { | |
| ASSERT(value.IsSymbol()); | |
| ASSERT(IsOriginal()); | |
| untag()->set_name(value.ptr()); | |
| } | |
| ObjectPtr Field::RawOwner() const { | |
| if (IsOriginal()) { | |
| return untag()->owner(); | |
| } else { | |
| const Field& field = Field::Handle(Original()); | |
| ASSERT(field.IsOriginal()); | |
| ASSERT(!Object::Handle(field.untag()->owner()).IsField()); | |
| return field.untag()->owner(); | |
| } | |
| } | |
| ClassPtr Field::Owner() const { | |
| const Field& field = Field::Handle(Original()); | |
| ASSERT(field.IsOriginal()); | |
| const Object& obj = Object::Handle(field.untag()->owner()); | |
| if (obj.IsClass()) { | |
| return Class::Cast(obj).ptr(); | |
| } | |
| ASSERT(obj.IsPatchClass()); | |
| return PatchClass::Cast(obj).wrapped_class(); | |
| } | |
| ScriptPtr Field::Script(FieldPtr field) { | |
| // NOTE(turnidge): If you update this function, you probably want to | |
| // update Class::PatchFieldsAndFunctions() at the same time. | |
| if (field == Field::null()) { | |
| return Script::null(); | |
| } | |
| FieldPtr original_field = field; | |
| if (field->untag()->owner()->IsField()) { | |
| original_field = static_cast<FieldPtr>(field->untag()->owner()); | |
| } | |
| const ObjectPtr obj = original_field->untag()->owner(); | |
| if (obj->IsClass()) { | |
| return static_cast<ClassPtr>(obj)->untag()->script(); | |
| } | |
| ASSERT(obj->IsPatchClass()); | |
| return static_cast<PatchClassPtr>(obj)->untag()->script(); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| KernelProgramInfoPtr Field::KernelProgramInfo() const { | |
| const auto& owner = Object::Handle(RawOwner()); | |
| if (owner.IsClass()) { | |
| return Class::Cast(owner).KernelProgramInfo(); | |
| } | |
| return PatchClass::Cast(owner).kernel_program_info(); | |
| } | |
| #endif | |
| uint32_t Field::Hash() const { | |
| return String::HashRawSymbol(name()); | |
| } | |
| #if defined(DART_DYNAMIC_MODULES) | |
| bool Field::is_declared_in_bytecode() const { | |
| return Class::Handle(Owner()).is_declared_in_bytecode(); | |
| } | |
| #endif | |
| void Field::InheritKernelOffsetFrom(const Field& src) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| StoreNonPointer(&untag()->kernel_offset_, src.untag()->kernel_offset_); | |
| #endif | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| TypedDataViewPtr Field::KernelLibrary() const { | |
| const intptr_t kernel_library_index = KernelLibraryIndex(); | |
| if (kernel_library_index == -1) return TypedDataView::null(); | |
| const auto& info = KernelProgramInfo::Handle(KernelProgramInfo()); | |
| return info.KernelLibrary(kernel_library_index); | |
| } | |
| intptr_t Field::KernelLibraryOffset() const { | |
| const intptr_t kernel_library_index = KernelLibraryIndex(); | |
| if (kernel_library_index == -1) return 0; | |
| const auto& info = KernelProgramInfo::Handle(KernelProgramInfo()); | |
| return info.KernelLibraryStartOffset(kernel_library_index); | |
| } | |
| intptr_t Field::KernelLibraryIndex() const { | |
| const Object& obj = Object::Handle(untag()->owner()); | |
| // During background JIT compilation field objects are copied | |
| // and copy points to the original field via the owner field. | |
| if (obj.IsField()) { | |
| return Field::Cast(obj).KernelLibraryIndex(); | |
| } else if (obj.IsClass()) { | |
| const auto& lib = Library::Handle(Class::Cast(obj).library()); | |
| return lib.kernel_library_index(); | |
| } | |
| ASSERT(obj.IsPatchClass()); | |
| return PatchClass::Cast(obj).kernel_library_index(); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| void Field::SetFieldTypeSafe(const AbstractType& value) const { | |
| ASSERT(IsOriginal()); | |
| ASSERT(!value.IsNull()); | |
| if (value.ptr() != type()) { | |
| untag()->set_type(value.ptr()); | |
| } | |
| } | |
| // Called at finalization time | |
| void Field::SetFieldType(const AbstractType& value) const { | |
| DEBUG_ASSERT( | |
| IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| SetFieldTypeSafe(value); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| void Field::set_inferred_type(const AbstractType& value) const { | |
| DEBUG_ASSERT( | |
| IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(IsOriginal()); | |
| untag()->set_inferred_type(value.ptr()); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| FieldPtr Field::New() { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kFieldCid) != | |
| Class::null()); | |
| return Object::Allocate<Field>(Heap::kOld); | |
| } | |
| void Field::InitializeNew(const Field& result, | |
| const String& name, | |
| bool is_static, | |
| bool is_final, | |
| bool is_const, | |
| bool is_reflectable, | |
| bool is_late, | |
| const Object& owner, | |
| TokenPosition token_pos, | |
| TokenPosition end_token_pos) { | |
| result.set_name(name); | |
| result.set_is_static(is_static); | |
| if (is_static) { | |
| result.set_field_id_unsafe(-1); | |
| } else { | |
| result.SetOffset(0, 0); | |
| } | |
| result.set_is_final(is_final); | |
| result.set_is_const(is_const); | |
| result.set_is_reflectable(is_reflectable); | |
| result.set_is_late(is_late); | |
| result.set_owner(owner); | |
| result.set_token_pos(token_pos); | |
| result.set_end_token_pos(end_token_pos); | |
| result.set_has_nontrivial_initializer_unsafe(false); | |
| result.set_has_initializer_unsafe(false); | |
| // We will make unboxing decision once we read static type or | |
| // in KernelLoader::ReadInferredType. | |
| result.set_is_unboxed_unsafe(false); | |
| result.set_initializer_changed_after_initialization(false); | |
| NOT_IN_PRECOMPILED(result.set_kernel_offset(0)); | |
| NOT_IN_PRECOMPILED(result.set_is_inferred_type_exact(false)); | |
| result.set_has_pragma(false); | |
| result.set_static_type_exactness_state_unsafe( | |
| StaticTypeExactnessState::NotTracking()); | |
| auto isolate_group = IsolateGroup::Current(); | |
| // Use field guards if they are enabled and the isolate has never reloaded. | |
| // TODO(johnmccutchan): The reload case assumes the worst case (everything is | |
| // dynamic and possibly null). Attempt to relax this later. | |
| // | |
| // Do not use field guards for late fields as late field initialization | |
| // doesn't update guarded cid and length. | |
| #if defined(PRODUCT) | |
| const bool use_guarded_cid = | |
| FLAG_precompiled_mode || (isolate_group->use_field_guards() && !is_late); | |
| #else | |
| const bool use_guarded_cid = | |
| FLAG_precompiled_mode || | |
| (isolate_group->use_field_guards() && | |
| !isolate_group->HasAttemptedReload() && !is_late); | |
| #endif // !defined(PRODUCT) | |
| result.set_guarded_cid_unsafe(use_guarded_cid ? kIllegalCid : kDynamicCid); | |
| result.set_is_nullable_unsafe(use_guarded_cid ? false : true); | |
| result.set_guarded_list_length_in_object_offset_unsafe( | |
| Field::kUnknownLengthOffset); | |
| // Presently, we only attempt to remember the list length for final fields. | |
| if (is_final && use_guarded_cid) { | |
| result.set_guarded_list_length_unsafe(Field::kUnknownFixedLength); | |
| } else { | |
| result.set_guarded_list_length_unsafe(Field::kNoFixedLength); | |
| } | |
| } | |
| FieldPtr Field::New(const String& name, | |
| bool is_static, | |
| bool is_final, | |
| bool is_const, | |
| bool is_reflectable, | |
| bool is_late, | |
| const Object& owner, | |
| const AbstractType& type, | |
| TokenPosition token_pos, | |
| TokenPosition end_token_pos) { | |
| ASSERT(!owner.IsNull()); | |
| const Field& result = Field::Handle(Field::New()); | |
| InitializeNew(result, name, is_static, is_final, is_const, is_reflectable, | |
| is_late, owner, token_pos, end_token_pos); | |
| result.SetFieldTypeSafe(type); | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| compiler::target::UnboxFieldIfSupported(result, type); | |
| #endif | |
| return result.ptr(); | |
| } | |
| FieldPtr Field::NewTopLevel(const String& name, | |
| bool is_final, | |
| bool is_const, | |
| bool is_late, | |
| const Object& owner, | |
| TokenPosition token_pos, | |
| TokenPosition end_token_pos) { | |
| ASSERT(!owner.IsNull()); | |
| const Field& result = Field::Handle(Field::New()); | |
| InitializeNew(result, name, true, /* is_static */ | |
| is_final, is_const, true, /* is_reflectable */ | |
| is_late, owner, token_pos, end_token_pos); | |
| return result.ptr(); | |
| } | |
| FieldPtr Field::Clone(const Field& original) const { | |
| if (original.IsNull()) { | |
| return Field::null(); | |
| } | |
| ASSERT(original.IsOriginal()); | |
| Field& clone = Field::Handle(); | |
| // Using relaxed loading is fine because concurrent fields changes are all | |
| // guarded, will be reconciled during optimized code installation. | |
| clone ^= Object::Clone(*this, Heap::kOld, /*load_with_relaxed_atomics=*/true); | |
| clone.SetOriginal(original); | |
| clone.InheritKernelOffsetFrom(original); | |
| return clone.ptr(); | |
| } | |
| int32_t Field::SourceFingerprint() const { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| if (is_declared_in_bytecode()) { | |
| return 0; | |
| } | |
| return kernel::KernelSourceFingerprintHelper::CalculateFieldFingerprint( | |
| *this); | |
| #else | |
| return 0; | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| const char* Field::UserVisibleNameCString() const { | |
| NoSafepointScope no_safepoint; | |
| if (FLAG_show_internal_names) { | |
| return String::Handle(name()).ToCString(); | |
| } | |
| return String::ScrubName(String::Handle(name()), | |
| is_extension_member() || is_extension_type_member()); | |
| } | |
| StringPtr Field::UserVisibleName() const { | |
| if (FLAG_show_internal_names) { | |
| return name(); | |
| } | |
| return Symbols::New( | |
| Thread::Current(), | |
| String::ScrubName(String::Handle(name()), | |
| is_extension_member() || is_extension_type_member())); | |
| } | |
| intptr_t Field::guarded_list_length() const { | |
| return Smi::Value(untag()->guarded_list_length()); | |
| } | |
| void Field::set_guarded_list_length_unsafe(intptr_t list_length) const { | |
| ASSERT(IsOriginal()); | |
| untag()->set_guarded_list_length(Smi::New(list_length)); | |
| } | |
| intptr_t Field::guarded_list_length_in_object_offset() const { | |
| return untag()->guarded_list_length_in_object_offset_ + kHeapObjectTag; | |
| } | |
| void Field::set_guarded_list_length_in_object_offset_unsafe( | |
| intptr_t list_length_offset) const { | |
| ASSERT(IsOriginal()); | |
| StoreNonPointer<int8_t, int8_t, std::memory_order_relaxed>( | |
| &untag()->guarded_list_length_in_object_offset_, | |
| static_cast<int8_t>(list_length_offset - kHeapObjectTag)); | |
| ASSERT(guarded_list_length_in_object_offset() == list_length_offset); | |
| } | |
| bool Field::NeedsSetter() const { | |
| // According to the Dart language specification, final fields don't have | |
| // a setter, except late final fields without initializer. | |
| if (is_final()) { | |
| // Late final fields without initializer always need a setter to check | |
| // if they are already initialized. | |
| if (is_late() && !has_initializer()) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| // Instance non-final fields always need a setter. | |
| if (!is_static()) { | |
| return true; | |
| } | |
| // Otherwise, setters for static fields can be omitted | |
| // and fields can be accessed directly. | |
| return false; | |
| } | |
| bool Field::NeedsGetter() const { | |
| // All instance fields need a getter. | |
| if (!is_static()) return true; | |
| // Static fields also need a getter if they have a non-trivial initializer, | |
| // because it needs to be initialized lazily. | |
| if (has_nontrivial_initializer()) return true; | |
| // Static late fields with no initializer also need a getter, to check if it's | |
| // been initialized. | |
| return is_late() && !has_initializer(); | |
| } | |
| const char* Field::ToCString() const { | |
| NoSafepointScope no_safepoint; | |
| if (IsNull()) { | |
| return "Field: null"; | |
| } | |
| const char* kF0 = is_static() ? " static" : ""; | |
| const char* kF1 = is_late() ? " late" : ""; | |
| const char* kF2 = is_final() ? " final" : ""; | |
| const char* kF3 = is_const() ? " const" : ""; | |
| const char* kF4 = is_shared() ? " shared" : ""; | |
| const char* field_name = String::Handle(name()).ToCString(); | |
| const Class& cls = Class::Handle(Owner()); | |
| const char* cls_name = String::Handle(cls.Name()).ToCString(); | |
| return OS::SCreate(Thread::Current()->zone(), "Field <%s.%s>:%s%s%s%s%s", | |
| cls_name, field_name, kF0, kF1, kF2, kF3, kF4); | |
| } | |
| WeakArrayPtr Field::dependent_code() const { | |
| DEBUG_ASSERT( | |
| IsolateGroup::Current()->program_lock()->IsCurrentThreadReader()); | |
| return untag()->dependent_code(); | |
| } | |
| void Field::set_dependent_code(const WeakArray& array) const { | |
| ASSERT(IsOriginal()); | |
| DEBUG_ASSERT( | |
| IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| // relaxed: races with Field::Clone. | |
| untag()->set_dependent_code<std::memory_order_relaxed>(array.ptr()); | |
| } | |
| class FieldDependentArray : public WeakCodeReferences { | |
| public: | |
| explicit FieldDependentArray(const Field& field) | |
| : WeakCodeReferences(WeakArray::Handle(field.dependent_code())), | |
| field_(field) {} | |
| virtual void UpdateArrayTo(const WeakArray& value) { | |
| field_.set_dependent_code(value); | |
| } | |
| virtual void ReportDeoptimization(const Code& code) { | |
| if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) { | |
| Function& function = Function::Handle(code.function()); | |
| THR_Print("Deoptimizing %s because guard on field %s failed.\n", | |
| function.ToFullyQualifiedCString(), field_.ToCString()); | |
| } | |
| } | |
| virtual void ReportSwitchingCode(const Code& code) { | |
| if (FLAG_trace_deoptimization || FLAG_trace_deoptimization_verbose) { | |
| Function& function = Function::Handle(code.function()); | |
| THR_Print( | |
| "Switching '%s' to unoptimized code because guard" | |
| " on field '%s' was violated.\n", | |
| function.ToFullyQualifiedCString(), field_.ToCString()); | |
| } | |
| } | |
| private: | |
| const Field& field_; | |
| DISALLOW_COPY_AND_ASSIGN(FieldDependentArray); | |
| }; | |
| void Field::RegisterDependentCode(const Code& code) const { | |
| ASSERT(IsOriginal()); | |
| DEBUG_ASSERT(IsMutatorOrAtDeoptSafepoint()); | |
| ASSERT(code.is_optimized()); | |
| FieldDependentArray a(*this); | |
| a.Register(code); | |
| } | |
| void Field::DeoptimizeDependentCode(bool are_mutators_stopped) const { | |
| DEBUG_ASSERT( | |
| IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(IsOriginal()); | |
| FieldDependentArray a(*this); | |
| if (FLAG_trace_deoptimization && a.HasCodes()) { | |
| THR_Print("Deopt for field guard (field %s)\n", ToCString()); | |
| } | |
| a.DisableCode(are_mutators_stopped); | |
| } | |
| bool Field::IsConsistentWith(const Field& other) const { | |
| return (untag()->guarded_cid_ == other.untag()->guarded_cid_) && | |
| (untag()->is_nullable_ == other.untag()->is_nullable_) && | |
| (untag()->guarded_list_length() == | |
| other.untag()->guarded_list_length()) && | |
| (is_unboxed() == other.is_unboxed()) && | |
| (static_type_exactness_state().Encode() == | |
| other.static_type_exactness_state().Encode()); | |
| } | |
| bool Field::IsUninitialized() const { | |
| Thread* thread = Thread::Current(); | |
| const FieldTable* field_table = thread->isolate()->field_table(); | |
| const ObjectPtr raw_value = field_table->At(field_id()); | |
| return raw_value == Object::sentinel().ptr(); | |
| } | |
| FunctionPtr Field::EnsureInitializerFunction() const { | |
| ASSERT(has_nontrivial_initializer()); | |
| ASSERT(IsOriginal()); | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| Function& initializer = Function::Handle(zone, InitializerFunction()); | |
| if (initializer.IsNull()) { | |
| #if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| UNREACHABLE(); | |
| #else | |
| SafepointMutexLocker ml( | |
| thread->isolate_group()->initializer_functions_mutex()); | |
| // Double check after grabbing the lock. | |
| initializer = InitializerFunction(); | |
| if (initializer.IsNull()) { | |
| initializer = CreateFieldInitializerFunction(thread); | |
| } | |
| #endif | |
| } | |
| return initializer.ptr(); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) || defined(DART_DYNAMIC_MODULES) | |
| FunctionPtr Field::CreateFieldInitializerFunction(Thread* thread) const { | |
| Zone* zone = thread->zone(); | |
| ASSERT(InitializerFunction() == Function::null()); | |
| String& init_name = String::Handle(zone, name()); | |
| init_name = Symbols::FromConcat(thread, Symbols::InitPrefix(), init_name); | |
| const auto& field_owner = Class::Handle(zone, Owner()); | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| const auto& initializer_owner = Class::Handle(zone, field_owner.ptr()); | |
| #else | |
| // Static field initializers are not added as members of their owning class, | |
| // so they must be preemptively given a patch class to avoid the meaning of | |
| // their kernel/token position changing during a reload. Compare | |
| // Class::PatchFieldsAndFunctions(). | |
| // This might also be necessary for lazy computation of local var descriptors. | |
| // Compare https://codereview.chromium.org//1317753004 | |
| const auto& script = Script::Handle(zone, Script()); | |
| const auto& kernel_program_info = | |
| KernelProgramInfo::Handle(zone, KernelProgramInfo()); | |
| const auto& initializer_owner = PatchClass::Handle( | |
| zone, PatchClass::New(field_owner, kernel_program_info, script)); | |
| if (!is_declared_in_bytecode()) { | |
| const Library& lib = Library::Handle(zone, field_owner.library()); | |
| initializer_owner.set_kernel_library_index(lib.kernel_library_index()); | |
| } | |
| #endif | |
| // Create a static initializer. | |
| FunctionType& signature = FunctionType::Handle(zone, FunctionType::New()); | |
| const Function& initializer_fun = Function::Handle( | |
| zone, | |
| Function::New(signature, init_name, UntaggedFunction::kFieldInitializer, | |
| is_static(), // is_static | |
| false, // is_const | |
| false, // is_abstract | |
| false, // is_external | |
| false, // is_native | |
| initializer_owner, TokenPosition::kNoSource)); | |
| if (!is_static()) { | |
| signature.set_num_fixed_parameters(1); | |
| signature.set_parameter_types( | |
| Array::Handle(zone, Array::New(1, Heap::kOld))); | |
| signature.SetParameterTypeAt( | |
| 0, AbstractType::Handle(zone, field_owner.DeclarationType())); | |
| NOT_IN_PRECOMPILED(initializer_fun.CreateNameArray()); | |
| NOT_IN_PRECOMPILED(initializer_fun.SetParameterNameAt(0, Symbols::This())); | |
| } | |
| signature.set_result_type(AbstractType::Handle(zone, type())); | |
| initializer_fun.set_is_reflectable(false); | |
| initializer_fun.set_is_inlinable(false); | |
| NOT_IN_PRECOMPILED(initializer_fun.set_token_pos(token_pos())); | |
| NOT_IN_PRECOMPILED(initializer_fun.set_end_token_pos(end_token_pos())); | |
| initializer_fun.set_accessor_field(*this); | |
| initializer_fun.InheritKernelOffsetFrom(*this); | |
| initializer_fun.set_is_extension_member(is_extension_member()); | |
| initializer_fun.set_is_extension_type_member(is_extension_type_member()); | |
| signature ^= ClassFinalizer::FinalizeType(signature); | |
| initializer_fun.SetSignature(signature); | |
| SetInitializerFunction(initializer_fun); | |
| return initializer_fun.ptr(); | |
| } | |
| void Field::SetInitializerFunction(const Function& initializer) const { | |
| ASSERT(IsOriginal()); | |
| ASSERT(IsolateGroup::Current() | |
| ->initializer_functions_mutex() | |
| ->IsOwnedByCurrentThread()); | |
| // We have to ensure that all stores into the initializer function object | |
| // happen before releasing the pointer to the initializer as it may be | |
| // accessed without grabbing the lock. | |
| untag()->set_initializer_function<std::memory_order_release>( | |
| initializer.ptr()); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) || defined(DART_DYNAMIC_MODULES) | |
| bool Field::HasInitializerFunction() const { | |
| return untag()->initializer_function() != Function::null(); | |
| } | |
| ErrorPtr Field::InitializeInstance(const Instance& instance) const { | |
| ASSERT(IsOriginal()); | |
| ASSERT(is_instance()); | |
| ASSERT(instance.GetField(*this) == Object::sentinel().ptr()); | |
| Object& value = Object::Handle(); | |
| if (has_nontrivial_initializer()) { | |
| const Function& initializer = Function::Handle(EnsureInitializerFunction()); | |
| const Array& args = Array::Handle(Array::New(1)); | |
| args.SetAt(0, instance); | |
| value = DartEntry::InvokeFunction(initializer, args); | |
| if (!value.IsNull() && value.IsError()) { | |
| return Error::Cast(value).ptr(); | |
| } | |
| } else { | |
| if (is_late() && !has_initializer()) { | |
| Exceptions::ThrowLateFieldNotInitialized(String::Handle(name())); | |
| UNREACHABLE(); | |
| } | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| // Our trivial initializer is `null`. Any non-`null` initializer is | |
| // non-trivial (see `KernelLoader::CheckForInitializer()`). | |
| value = Object::null(); | |
| #endif | |
| } | |
| ASSERT(value.IsNull() || value.IsInstance()); | |
| if (is_late() && is_final() && | |
| (instance.GetField(*this) != Object::sentinel().ptr())) { | |
| Exceptions::ThrowLateFieldAssignedDuringInitialization( | |
| String::Handle(name())); | |
| UNREACHABLE(); | |
| } | |
| instance.SetField(*this, value); | |
| return Error::null(); | |
| } | |
| ErrorPtr Field::InitializeStatic() const { | |
| ASSERT(IsOriginal()); | |
| ASSERT(is_static()); | |
| if (StaticValue() == Object::sentinel().ptr()) { | |
| ASSERT(is_late()); | |
| auto& value = Object::Handle(); | |
| if (!has_initializer()) { | |
| Exceptions::ThrowLateFieldNotInitialized(String::Handle(name())); | |
| UNREACHABLE(); | |
| } | |
| value = EvaluateInitializer(); | |
| if (value.IsError()) { | |
| return Error::Cast(value).ptr(); | |
| } | |
| if (is_final() && (StaticValue() != Object::sentinel().ptr())) { | |
| Exceptions::ThrowLateFieldAssignedDuringInitialization( | |
| String::Handle(name())); | |
| UNREACHABLE(); | |
| } | |
| ASSERT(value.IsNull() || value.IsInstance()); | |
| SetStaticValue(value.IsNull() ? Instance::null_instance() | |
| : Instance::Cast(value)); | |
| return Error::null(); | |
| } | |
| return Error::null(); | |
| } | |
| ObjectPtr Field::StaticConstFieldValue() const { | |
| ASSERT(is_static() && | |
| (is_const() || (is_final() && has_trivial_initializer()))); | |
| auto thread = Thread::Current(); | |
| auto zone = thread->zone(); | |
| auto initial_field_table = | |
| is_shared() ? thread->isolate_group()->shared_initial_field_table() | |
| : thread->isolate_group()->initial_field_table(); | |
| // We can safely cache the value of the static const field in the initial | |
| // field table. | |
| auto& value = Object::Handle( | |
| zone, initial_field_table->At(field_id(), /*concurrent_use=*/true)); | |
| if (value.ptr() == Object::sentinel().ptr()) { | |
| // Fields with trivial initializers get their initial value | |
| // eagerly when they are registered. | |
| ASSERT(is_const()); | |
| ASSERT(has_initializer()); | |
| ASSERT(has_nontrivial_initializer()); | |
| value = EvaluateInitializer(); | |
| if (!value.IsError()) { | |
| ASSERT(value.IsNull() || value.IsInstance()); | |
| SetStaticConstFieldValue(value.IsNull() ? Instance::null_instance() | |
| : Instance::Cast(value)); | |
| } | |
| } | |
| return value.ptr(); | |
| } | |
| void Field::SetStaticConstFieldValue(const Instance& value, | |
| bool assert_initializing_store) const { | |
| ASSERT(is_static()); | |
| auto thread = Thread::Current(); | |
| auto initial_field_table = | |
| is_shared() ? thread->isolate_group()->shared_initial_field_table() | |
| : thread->isolate_group()->initial_field_table(); | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| ASSERT(initial_field_table->At(field_id()) == Object::sentinel().ptr() || | |
| initial_field_table->At(field_id()) == value.ptr() || | |
| !assert_initializing_store); | |
| initial_field_table->SetAt(field_id(), | |
| value.IsNull() ? Instance::null_instance().ptr() | |
| : Instance::Cast(value).ptr(), | |
| /*concurrent_use=*/true); | |
| } | |
| ObjectPtr Field::EvaluateInitializer() const { | |
| ASSERT(Thread::Current()->IsDartMutatorThread()); | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| if (is_static() && is_const()) { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| if (is_declared_in_bytecode()) { | |
| const auto& initializer = Function::Handle(InitializerFunction()); | |
| ASSERT(!initializer.IsNull()); | |
| const auto& bytecode = Bytecode::Handle(initializer.GetBytecode()); | |
| ASSERT(!bytecode.IsNull()); | |
| const auto& pool = ObjectPool::Handle(bytecode.object_pool()); | |
| ASSERT(!pool.IsNull()); | |
| ASSERT(pool.Length() == 1); | |
| return pool.ObjectAt(0); | |
| } | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| return kernel::EvaluateStaticConstFieldInitializer(*this); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| const Function& initializer = Function::Handle(EnsureInitializerFunction()); | |
| return DartEntry::InvokeFunction(initializer, Object::empty_array()); | |
| } | |
| static intptr_t GetListLength(const Object& value) { | |
| if (value.IsTypedDataBase()) { | |
| return TypedDataBase::Cast(value).Length(); | |
| } else if (value.IsArray()) { | |
| return Array::Cast(value).Length(); | |
| } else if (value.IsGrowableObjectArray()) { | |
| // List length is variable. | |
| return Field::kNoFixedLength; | |
| } | |
| return Field::kNoFixedLength; | |
| } | |
| static intptr_t GetListLengthOffset(intptr_t cid) { | |
| if (IsTypedDataClassId(cid) || IsTypedDataViewClassId(cid) || | |
| IsUnmodifiableTypedDataViewClassId(cid) || | |
| IsExternalTypedDataClassId(cid)) { | |
| return TypedData::length_offset(); | |
| } else if (cid == kArrayCid || cid == kImmutableArrayCid) { | |
| return Array::length_offset(); | |
| } else if (cid == kGrowableObjectArrayCid) { | |
| // List length is variable. | |
| return Field::kUnknownLengthOffset; | |
| } | |
| return Field::kUnknownLengthOffset; | |
| } | |
| const char* Field::GuardedPropertiesAsCString() const { | |
| if (guarded_cid() == kIllegalCid) { | |
| return "<?>"; | |
| } else if (guarded_cid() == kDynamicCid) { | |
| ASSERT(!static_type_exactness_state().IsExactOrUninitialized()); | |
| return "<*>"; | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| const char* exactness = ""; | |
| if (static_type_exactness_state().IsTracking()) { | |
| exactness = | |
| zone->PrintToString(" {%s}", static_type_exactness_state().ToCString()); | |
| } | |
| const Class& cls = | |
| Class::Handle(IsolateGroup::Current()->class_table()->At(guarded_cid())); | |
| const char* class_name = String::Handle(cls.Name()).ToCString(); | |
| if (IsBuiltinListClassId(guarded_cid()) && !is_nullable() && is_final()) { | |
| ASSERT(guarded_list_length() != kUnknownFixedLength); | |
| if (guarded_list_length() == kNoFixedLength) { | |
| return zone->PrintToString("<%s [*]%s>", class_name, exactness); | |
| } else { | |
| return zone->PrintToString( | |
| "<%s [%" Pd " @%" Pd "]%s>", class_name, guarded_list_length(), | |
| guarded_list_length_in_object_offset(), exactness); | |
| } | |
| } | |
| return zone->PrintToString("<%s %s%s>", | |
| is_nullable() ? "nullable" : "not-nullable", | |
| class_name, exactness); | |
| } | |
| void Field::InitializeGuardedListLengthInObjectOffset(bool unsafe) const { | |
| auto setter = unsafe ? &Field::set_guarded_list_length_in_object_offset_unsafe | |
| : &Field::set_guarded_list_length_in_object_offset; | |
| ASSERT(IsOriginal()); | |
| if (needs_length_check() && | |
| (guarded_list_length() != Field::kUnknownFixedLength)) { | |
| const intptr_t offset = GetListLengthOffset(guarded_cid()); | |
| (this->*setter)(offset); | |
| ASSERT(offset != Field::kUnknownLengthOffset); | |
| } else { | |
| (this->*setter)(Field::kUnknownLengthOffset); | |
| } | |
| } | |
| class FieldGuardUpdater { | |
| public: | |
| FieldGuardUpdater(const Field* field, const Object& value); | |
| bool IsUpdateNeeded() { | |
| return does_guarded_cid_need_update_ || does_is_nullable_need_update_ || | |
| does_list_length_and_offset_need_update_ || | |
| does_static_type_exactness_state_need_update_; | |
| } | |
| void DoUpdate(); | |
| private: | |
| void ReviewExactnessState(); | |
| void ReviewGuards(); | |
| intptr_t guarded_cid() { return guarded_cid_; } | |
| void set_guarded_cid(intptr_t guarded_cid) { | |
| guarded_cid_ = guarded_cid; | |
| does_guarded_cid_need_update_ = true; | |
| } | |
| bool is_nullable() { return is_nullable_; } | |
| void set_is_nullable(bool is_nullable) { | |
| is_nullable_ = is_nullable; | |
| does_is_nullable_need_update_ = true; | |
| } | |
| intptr_t guarded_list_length() { return list_length_; } | |
| void set_guarded_list_length_and_offset( | |
| intptr_t list_length, | |
| intptr_t list_length_in_object_offset) { | |
| list_length_ = list_length; | |
| list_length_in_object_offset_ = list_length_in_object_offset; | |
| does_list_length_and_offset_need_update_ = true; | |
| } | |
| StaticTypeExactnessState static_type_exactness_state() { | |
| return static_type_exactness_state_; | |
| } | |
| void set_static_type_exactness_state(StaticTypeExactnessState state) { | |
| static_type_exactness_state_ = state; | |
| does_static_type_exactness_state_need_update_ = true; | |
| } | |
| const Field* field_; | |
| const Object& value_; | |
| intptr_t guarded_cid_; | |
| bool is_nullable_; | |
| intptr_t list_length_; | |
| intptr_t list_length_in_object_offset_; | |
| StaticTypeExactnessState static_type_exactness_state_; | |
| bool does_guarded_cid_need_update_ = false; | |
| bool does_is_nullable_need_update_ = false; | |
| bool does_list_length_and_offset_need_update_ = false; | |
| bool does_static_type_exactness_state_need_update_ = false; | |
| }; | |
| void FieldGuardUpdater::ReviewGuards() { | |
| ASSERT(field_->IsOriginal()); | |
| const intptr_t cid = value_.GetClassId(); | |
| if (guarded_cid() == kIllegalCid) { | |
| set_guarded_cid(cid); | |
| set_is_nullable(cid == kNullCid); | |
| // Start tracking length if needed. | |
| ASSERT((guarded_list_length() == Field::kUnknownFixedLength) || | |
| (guarded_list_length() == Field::kNoFixedLength)); | |
| if (field_->needs_length_check()) { | |
| ASSERT(guarded_list_length() == Field::kUnknownFixedLength); | |
| set_guarded_list_length_and_offset(GetListLength(value_), | |
| GetListLengthOffset(cid)); | |
| } | |
| if (FLAG_trace_field_guards) { | |
| THR_Print(" => %s\n", field_->GuardedPropertiesAsCString()); | |
| } | |
| return; | |
| } | |
| if ((cid == guarded_cid()) || ((cid == kNullCid) && is_nullable())) { | |
| // Class id of the assigned value matches expected class id and nullability. | |
| // If we are tracking length check if it has matches. | |
| if (field_->needs_length_check() && | |
| (guarded_list_length() != GetListLength(value_))) { | |
| ASSERT(guarded_list_length() != Field::kUnknownFixedLength); | |
| set_guarded_list_length_and_offset(Field::kNoFixedLength, | |
| Field::kUnknownLengthOffset); | |
| return; | |
| } | |
| // Everything matches. | |
| return; | |
| } | |
| if ((cid == kNullCid) && !is_nullable()) { | |
| // Assigning null value to a non-nullable field makes it nullable. | |
| set_is_nullable(true); | |
| } else if ((cid != kNullCid) && (guarded_cid() == kNullCid)) { | |
| // Assigning non-null value to a field that previously contained only null | |
| // turns it into a nullable field with the given class id. | |
| ASSERT(is_nullable()); | |
| set_guarded_cid(cid); | |
| } else { | |
| // Give up on tracking class id of values contained in this field. | |
| ASSERT(guarded_cid() != cid); | |
| set_guarded_cid(kDynamicCid); | |
| set_is_nullable(true); | |
| } | |
| // If we were tracking length drop collected feedback. | |
| if (field_->needs_length_check()) { | |
| ASSERT(guarded_list_length() != Field::kUnknownFixedLength); | |
| set_guarded_list_length_and_offset(Field::kNoFixedLength, | |
| Field::kUnknownLengthOffset); | |
| } | |
| } | |
| bool Class::FindInstantiationOf(Zone* zone, | |
| const Class& cls, | |
| GrowableArray<const Type*>* path, | |
| bool consider_only_super_classes) const { | |
| ASSERT(cls.is_type_finalized()); | |
| if (cls.ptr() == ptr()) { | |
| return true; // Found instantiation. | |
| } | |
| Class& cls2 = Class::Handle(zone); | |
| Type& super = Type::Handle(zone, super_type()); | |
| if (!super.IsNull() && !super.IsObjectType()) { | |
| cls2 = super.type_class(); | |
| if (path != nullptr) { | |
| path->Add(&super); | |
| } | |
| if (cls2.FindInstantiationOf(zone, cls, path, | |
| consider_only_super_classes)) { | |
| return true; // Found instantiation. | |
| } | |
| if (path != nullptr) { | |
| path->RemoveLast(); | |
| } | |
| } | |
| if (!consider_only_super_classes) { | |
| Array& super_interfaces = Array::Handle(zone, interfaces()); | |
| for (intptr_t i = 0; i < super_interfaces.Length(); i++) { | |
| super ^= super_interfaces.At(i); | |
| cls2 = super.type_class(); | |
| if (path != nullptr) { | |
| path->Add(&super); | |
| } | |
| if (cls2.FindInstantiationOf(zone, cls, path)) { | |
| return true; // Found instantiation. | |
| } | |
| if (path != nullptr) { | |
| path->RemoveLast(); | |
| } | |
| } | |
| } | |
| return false; // Not found. | |
| } | |
| bool Class::FindInstantiationOf(Zone* zone, | |
| const Type& type, | |
| GrowableArray<const Type*>* path, | |
| bool consider_only_super_classes) const { | |
| return FindInstantiationOf(zone, Class::Handle(zone, type.type_class()), path, | |
| consider_only_super_classes); | |
| } | |
| TypePtr Class::GetInstantiationOf(Zone* zone, const Class& cls) const { | |
| if (ptr() == cls.ptr()) { | |
| return DeclarationType(); | |
| } | |
| if (FindInstantiationOf(zone, cls, /*consider_only_super_classes=*/true)) { | |
| // Since [cls] is a superclass of [this], use [cls]'s declaration type. | |
| return cls.DeclarationType(); | |
| } | |
| const auto& decl_type = Type::Handle(zone, DeclarationType()); | |
| GrowableArray<const Type*> path(zone, 0); | |
| if (!FindInstantiationOf(zone, cls, &path)) { | |
| return Type::null(); | |
| } | |
| Thread* thread = Thread::Current(); | |
| ASSERT(!path.is_empty()); | |
| auto& calculated_type = Type::Handle(zone, decl_type.ptr()); | |
| auto& calculated_type_class = | |
| Class::Handle(zone, calculated_type.type_class()); | |
| auto& calculated_type_args = | |
| TypeArguments::Handle(zone, calculated_type.arguments()); | |
| calculated_type_args = calculated_type_args.ToInstantiatorTypeArguments( | |
| thread, calculated_type_class); | |
| for (auto* const type : path) { | |
| calculated_type ^= type->ptr(); | |
| if (!calculated_type.IsInstantiated()) { | |
| calculated_type ^= calculated_type.InstantiateFrom( | |
| calculated_type_args, Object::null_type_arguments(), kAllFree, | |
| Heap::kNew); | |
| } | |
| calculated_type_class = calculated_type.type_class(); | |
| calculated_type_args = calculated_type.arguments(); | |
| calculated_type_args = calculated_type_args.ToInstantiatorTypeArguments( | |
| thread, calculated_type_class); | |
| } | |
| ASSERT_EQUAL(calculated_type.type_class_id(), cls.id()); | |
| return calculated_type.ptr(); | |
| } | |
| TypePtr Class::GetInstantiationOf(Zone* zone, const Type& type) const { | |
| return GetInstantiationOf(zone, Class::Handle(zone, type.type_class())); | |
| } | |
| void Field::SetStaticValue(const Object& value) const { | |
| auto thread = Thread::Current(); | |
| ASSERT(thread->IsDartMutatorThread()); | |
| ASSERT(value.IsNull() || value.IsSentinel() || value.IsInstance() || | |
| value.IsSmi()); | |
| ASSERT(is_static()); // Valid only for static dart fields. | |
| const intptr_t id = field_id(); | |
| ASSERT(id >= 0); | |
| if (FLAG_experimental_shared_data && is_shared()) { | |
| value.EnsureDeeplyImmutable(thread->zone()); | |
| } | |
| SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| if (is_shared()) { | |
| thread->isolate_group()->shared_field_table()->SetAt( | |
| id, value.ptr(), | |
| /*concurrent_use=*/true); | |
| } else { | |
| thread->isolate()->field_table()->SetAt(id, value.ptr()); | |
| } | |
| } | |
| static StaticTypeExactnessState TrivialTypeExactnessFor(const Class& cls) { | |
| const intptr_t type_arguments_offset = cls.host_type_arguments_field_offset(); | |
| ASSERT(type_arguments_offset != Class::kNoTypeArguments); | |
| if (StaticTypeExactnessState::CanRepresentAsTriviallyExact( | |
| type_arguments_offset / kCompressedWordSize)) { | |
| return StaticTypeExactnessState::TriviallyExact(type_arguments_offset / | |
| kCompressedWordSize); | |
| } else { | |
| return StaticTypeExactnessState::NotExact(); | |
| } | |
| } | |
| static const char* SafeTypeArgumentsToCString(const TypeArguments& args) { | |
| return (args.ptr() == TypeArguments::null()) ? "<null>" : args.ToCString(); | |
| } | |
| StaticTypeExactnessState StaticTypeExactnessState::Compute( | |
| const Type& static_type, | |
| const Instance& value, | |
| bool print_trace /* = false */) { | |
| ASSERT(!value.IsNull()); // Should be handled by the caller. | |
| ASSERT(value.ptr() != Object::sentinel().ptr()); | |
| Thread* thread = Thread::Current(); | |
| Zone* const zone = thread->zone(); | |
| const TypeArguments& static_type_args = | |
| TypeArguments::Handle(zone, static_type.GetInstanceTypeArguments(thread)); | |
| TypeArguments& args = TypeArguments::Handle(zone); | |
| ASSERT(static_type.IsFinalized()); | |
| const Class& cls = Class::Handle(zone, value.clazz()); | |
| GrowableArray<const Type*> path(10); | |
| bool is_super_class = true; | |
| if (!cls.FindInstantiationOf(zone, static_type, &path, | |
| /*consider_only_super_classes=*/true)) { | |
| is_super_class = false; | |
| bool found_super_interface = | |
| cls.FindInstantiationOf(zone, static_type, &path); | |
| ASSERT(found_super_interface); | |
| } | |
| // Trivial case: field has type G<T0, ..., Tn> and value has type | |
| // G<U0, ..., Un>. Check if type arguments match. | |
| if (path.is_empty()) { | |
| ASSERT(cls.ptr() == static_type.type_class()); | |
| args = value.GetTypeArguments(); | |
| // TODO(dartbug.com/34170) Evaluate if comparing relevant subvectors (that | |
| // disregards superclass own arguments) improves precision of the | |
| // tracking. | |
| if (args.ptr() == static_type_args.ptr()) { | |
| return TrivialTypeExactnessFor(cls); | |
| } | |
| if (print_trace) { | |
| THR_Print(" expected %s got %s type arguments\n", | |
| SafeTypeArgumentsToCString(static_type_args), | |
| SafeTypeArgumentsToCString(args)); | |
| } | |
| return StaticTypeExactnessState::NotExact(); | |
| } | |
| // Value has type C<U0, ..., Un> and field has type G<T0, ..., Tn> and G != C. | |
| // Compute C<X0, ..., Xn> at G (Xi are free type arguments). | |
| // Path array contains a chain of immediate supertypes S0 <: S1 <: ... Sn, | |
| // such that S0 is an immediate supertype of C and Sn is G<...>. | |
| // Each Si might depend on type parameters of the previous supertype S{i-1}. | |
| // To compute C<X0, ..., Xn> at G we walk the chain backwards and | |
| // instantiate Si using type parameters of S{i-1} which gives us a type | |
| // depending on type parameters of S{i-2}. | |
| Type& type = Type::Handle(zone, path.Last()->ptr()); | |
| for (intptr_t i = path.length() - 2; (i >= 0) && !type.IsInstantiated(); | |
| i--) { | |
| args = path[i]->GetInstanceTypeArguments(thread, /*canonicalize=*/false); | |
| type ^= type.InstantiateFrom(args, TypeArguments::null_type_arguments(), | |
| kAllFree, Heap::kNew); | |
| } | |
| if (type.IsInstantiated()) { | |
| // C<X0, ..., Xn> at G is fully instantiated and does not depend on | |
| // Xi. In this case just check if type arguments match. | |
| args = type.GetInstanceTypeArguments(thread, /*canonicalize=*/false); | |
| if (args.Equals(static_type_args)) { | |
| return is_super_class ? StaticTypeExactnessState::HasExactSuperClass() | |
| : StaticTypeExactnessState::HasExactSuperType(); | |
| } | |
| if (print_trace) { | |
| THR_Print(" expected %s got %s type arguments\n", | |
| SafeTypeArgumentsToCString(static_type_args), | |
| SafeTypeArgumentsToCString(args)); | |
| } | |
| return StaticTypeExactnessState::NotExact(); | |
| } | |
| // The most complicated case: C<X0, ..., Xn> at G depends on | |
| // Xi values. To compare type arguments we would need to instantiate | |
| // it fully from value's type arguments and compare with <U0, ..., Un>. | |
| // However this would complicate fast path in the native code. To avoid this | |
| // complication we would optimize for the trivial case: we check if | |
| // C<X0, ..., Xn> at G is exactly G<X0, ..., Xn> which means we can simply | |
| // compare values type arguments (<T0, ..., Tn>) to fields type arguments | |
| // (<U0, ..., Un>) to establish if field type is exact. | |
| ASSERT(cls.IsGeneric()); | |
| const intptr_t num_type_params = cls.NumTypeParameters(); | |
| bool trivial_case = | |
| (num_type_params == | |
| Class::Handle(zone, static_type.type_class()).NumTypeParameters()) && | |
| (value.GetTypeArguments() == static_type_args.ptr()); | |
| if (!trivial_case && FLAG_trace_field_guards) { | |
| THR_Print("Not a simple case: %" Pd " vs %" Pd | |
| " type parameters, %s vs %s type arguments\n", | |
| num_type_params, | |
| Class::Handle(zone, static_type.type_class()).NumTypeParameters(), | |
| SafeTypeArgumentsToCString( | |
| TypeArguments::Handle(zone, value.GetTypeArguments())), | |
| SafeTypeArgumentsToCString(static_type_args)); | |
| } | |
| AbstractType& type_arg = AbstractType::Handle(zone); | |
| args = type.GetInstanceTypeArguments(thread, /*canonicalize=*/false); | |
| for (intptr_t i = 0; (i < num_type_params) && trivial_case; i++) { | |
| type_arg = args.TypeAt(i); | |
| if (!type_arg.IsTypeParameter() || | |
| (TypeParameter::Cast(type_arg).index() != i)) { | |
| if (FLAG_trace_field_guards) { | |
| THR_Print(" => encountered %s at index % " Pd "\n", | |
| type_arg.ToCString(), i); | |
| } | |
| trivial_case = false; | |
| } | |
| } | |
| return trivial_case ? TrivialTypeExactnessFor(cls) | |
| : StaticTypeExactnessState::NotExact(); | |
| } | |
| const char* StaticTypeExactnessState::ToCString() const { | |
| if (!IsTracking()) { | |
| return "not-tracking"; | |
| } else if (!IsExactOrUninitialized()) { | |
| return "not-exact"; | |
| } else if (IsTriviallyExact()) { | |
| return Thread::Current()->zone()->PrintToString( | |
| "trivially-exact(%hhu)", GetTypeArgumentsOffsetInWords()); | |
| } else if (IsHasExactSuperType()) { | |
| return "has-exact-super-type"; | |
| } else if (IsHasExactSuperClass()) { | |
| return "has-exact-super-class"; | |
| } else { | |
| ASSERT(IsUninitialized()); | |
| return "uninitialized-exactness"; | |
| } | |
| } | |
| void FieldGuardUpdater::ReviewExactnessState() { | |
| if (!static_type_exactness_state().IsExactOrUninitialized()) { | |
| // Nothing to update. | |
| return; | |
| } | |
| if (guarded_cid() == kDynamicCid) { | |
| if (FLAG_trace_field_guards) { | |
| THR_Print( | |
| " => switching off exactness tracking because guarded cid is " | |
| "dynamic\n"); | |
| } | |
| set_static_type_exactness_state(StaticTypeExactnessState::NotExact()); | |
| return; | |
| } | |
| // If we are storing null into a field or we have an exact super type | |
| // then there is nothing to do. | |
| if (value_.IsNull() || static_type_exactness_state().IsHasExactSuperType() || | |
| static_type_exactness_state().IsHasExactSuperClass()) { | |
| return; | |
| } | |
| // If we are storing a non-null value into a field that is considered | |
| // to be trivially exact then we need to check if value has an appropriate | |
| // type. | |
| ASSERT(guarded_cid() != kNullCid); | |
| const Type& field_type = Type::Cast(AbstractType::Handle(field_->type())); | |
| const Instance& instance = Instance::Cast(value_); | |
| if (static_type_exactness_state().IsTriviallyExact()) { | |
| const TypeArguments& args = | |
| TypeArguments::Handle(instance.GetTypeArguments()); | |
| const TypeArguments& field_type_args = TypeArguments::Handle( | |
| field_type.GetInstanceTypeArguments(Thread::Current())); | |
| if (args.ptr() == field_type_args.ptr()) { | |
| return; | |
| } | |
| if (FLAG_trace_field_guards) { | |
| THR_Print(" expected %s got %s type arguments\n", | |
| field_type_args.ToCString(), args.ToCString()); | |
| } | |
| set_static_type_exactness_state(StaticTypeExactnessState::NotExact()); | |
| return; | |
| } | |
| ASSERT(static_type_exactness_state().IsUninitialized()); | |
| set_static_type_exactness_state(StaticTypeExactnessState::Compute( | |
| field_type, instance, FLAG_trace_field_guards)); | |
| return; | |
| } | |
| FieldGuardUpdater::FieldGuardUpdater(const Field* field, const Object& value) | |
| : field_(field), | |
| value_(value), | |
| guarded_cid_(field->guarded_cid()), | |
| is_nullable_(field->is_nullable()), | |
| list_length_(field->guarded_list_length()), | |
| list_length_in_object_offset_( | |
| field->guarded_list_length_in_object_offset()), | |
| static_type_exactness_state_(field->static_type_exactness_state()) { | |
| ReviewGuards(); | |
| ReviewExactnessState(); | |
| } | |
| void FieldGuardUpdater::DoUpdate() { | |
| if (does_guarded_cid_need_update_) { | |
| field_->set_guarded_cid(guarded_cid_); | |
| } | |
| if (does_is_nullable_need_update_) { | |
| field_->set_is_nullable(is_nullable_); | |
| } | |
| if (does_list_length_and_offset_need_update_) { | |
| field_->set_guarded_list_length(list_length_); | |
| field_->set_guarded_list_length_in_object_offset( | |
| list_length_in_object_offset_); | |
| } | |
| if (does_static_type_exactness_state_need_update_) { | |
| field_->set_static_type_exactness_state(static_type_exactness_state_); | |
| } | |
| } | |
| void Field::RecordStore(const Object& value) const { | |
| ASSERT(IsOriginal()); | |
| Thread* const thread = Thread::Current(); | |
| if (!thread->isolate_group()->use_field_guards()) { | |
| return; | |
| } | |
| // We should never try to record a sentinel. | |
| ASSERT(value.ptr() != Object::sentinel().ptr()); | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| if ((guarded_cid() == kDynamicCid) || | |
| (is_nullable() && value.ptr() == Object::null())) { | |
| // Nothing to do: the field is not guarded or we are storing null into | |
| // a nullable field. | |
| return; | |
| } | |
| if (FLAG_trace_field_guards) { | |
| THR_Print("Store %s %s <- %s\n", ToCString(), GuardedPropertiesAsCString(), | |
| value.ToCString()); | |
| } | |
| FieldGuardUpdater updater(this, value); | |
| if (updater.IsUpdateNeeded()) { | |
| if (FLAG_trace_field_guards) { | |
| THR_Print(" => %s\n", GuardedPropertiesAsCString()); | |
| } | |
| // Nobody else could have updated guard state since we are holding write | |
| // program lock. But we need to ensure we stop mutators as we update | |
| // guard state as we can't have optimized code running with updated fields. | |
| auto isolate_group = IsolateGroup::Current(); | |
| isolate_group->RunWithStoppedMutators([&]() { | |
| updater.DoUpdate(); | |
| DeoptimizeDependentCode(/*are_mutators_stopped=*/true); | |
| }); | |
| } | |
| } | |
| void Field::ForceDynamicGuardedCidAndLength() const { | |
| if (!is_unboxed()) { | |
| set_guarded_cid(kDynamicCid); | |
| set_is_nullable(true); | |
| } | |
| set_guarded_list_length(Field::kNoFixedLength); | |
| set_guarded_list_length_in_object_offset(Field::kUnknownLengthOffset); | |
| if (static_type_exactness_state().IsTracking()) { | |
| set_static_type_exactness_state(StaticTypeExactnessState::NotExact()); | |
| } | |
| // Drop any code that relied on the above assumptions. | |
| DeoptimizeDependentCode(); | |
| } | |
| StringPtr Script::resolved_url() const { | |
| #if defined(DART_PRECOMPILER) | |
| return String::RawCast( | |
| WeakSerializationReference::Unwrap(untag()->resolved_url())); | |
| #else | |
| return untag()->resolved_url(); | |
| #endif | |
| } | |
| bool Script::HasSource() const { | |
| return untag()->source() != String::null(); | |
| } | |
| StringPtr Script::Source() const { | |
| return untag()->source(); | |
| } | |
| bool Script::IsPartOfDartColonLibrary() const { | |
| const String& script_url = String::Handle(url()); | |
| return script_url.StartsWith(Symbols::DartScheme()); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| void Script::LoadSourceFromKernel(const uint8_t* kernel_buffer, | |
| intptr_t kernel_buffer_len) const { | |
| String& uri = String::Handle(resolved_url()); | |
| String& source = String::Handle(kernel::KernelLoader::FindSourceForScript( | |
| kernel_buffer, kernel_buffer_len, uri)); | |
| set_source(source); | |
| } | |
| void Script::InitializeFromKernel( | |
| const KernelProgramInfo& info, | |
| intptr_t script_index, | |
| const TypedData& line_starts, | |
| const TypedDataView& constant_coverage) const { | |
| StoreNonPointer(&untag()->kernel_script_index_, script_index); | |
| untag()->set_kernel_program_info(info.ptr()); | |
| untag()->set_line_starts(line_starts.ptr()); | |
| untag()->set_debug_positions(Array::null_array().ptr()); | |
| NOT_IN_PRODUCT(untag()->set_constant_coverage(constant_coverage.ptr())); | |
| } | |
| #endif | |
| GrowableObjectArrayPtr Script::GenerateLineNumberArray() const { | |
| Zone* zone = Thread::Current()->zone(); | |
| const GrowableObjectArray& info = | |
| GrowableObjectArray::Handle(zone, GrowableObjectArray::New()); | |
| const Object& line_separator = Object::Handle(zone); | |
| if (line_starts() == TypedData::null()) { | |
| // Scripts in the AOT snapshot do not have a line starts array. | |
| // A well-formed line number array has a leading null. | |
| info.Add(line_separator); // New line. | |
| return info.ptr(); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| Smi& value = Smi::Handle(zone); | |
| const TypedData& line_starts_data = TypedData::Handle(zone, line_starts()); | |
| intptr_t line_count = line_starts_data.Length(); | |
| const Array& debug_positions_array = Array::Handle(debug_positions()); | |
| ASSERT(!debug_positions_array.IsNull()); | |
| intptr_t token_count = debug_positions_array.Length(); | |
| int token_index = 0; | |
| LineStartsReader line_starts_reader(line_starts_data); | |
| for (int line_index = 0; line_index < line_count; ++line_index) { | |
| intptr_t start = line_starts_reader.At(line_index); | |
| // Output the rest of the tokens if we have no next line. | |
| intptr_t end = TokenPosition::kMaxSourcePos; | |
| if (line_index + 1 < line_count) { | |
| end = line_starts_reader.At(line_index + 1); | |
| } | |
| bool first = true; | |
| while (token_index < token_count) { | |
| value ^= debug_positions_array.At(token_index); | |
| intptr_t debug_position = value.Value(); | |
| if (debug_position >= end) break; | |
| if (first) { | |
| info.Add(line_separator); // New line. | |
| value = Smi::New(line_index + 1); // Line number. | |
| info.Add(value); | |
| first = false; | |
| } | |
| value ^= debug_positions_array.At(token_index); | |
| info.Add(value); // Token position. | |
| value = Smi::New(debug_position - start + 1); // Column. | |
| info.Add(value); | |
| ++token_index; | |
| } | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| return info.ptr(); | |
| } | |
| TokenPosition Script::MaxPosition() const { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| if (HasCachedMaxPosition()) { | |
| return TokenPosition::Deserialize( | |
| UntaggedScript::CachedMaxPositionBitField::decode( | |
| untag()->flags_and_max_position_)); | |
| } | |
| if (!HasCachedMaxPosition() && line_starts() != TypedData::null()) { | |
| auto const zone = Thread::Current()->zone(); | |
| const auto& starts = TypedData::Handle(zone, line_starts()); | |
| LineStartsReader reader(starts); | |
| const intptr_t max_position = reader.MaxPosition(); | |
| SetCachedMaxPosition(max_position); | |
| SetHasCachedMaxPosition(true); | |
| return TokenPosition::Deserialize(max_position); | |
| } | |
| #endif | |
| return TokenPosition::kNoSource; | |
| } | |
| void Script::set_url(const String& value) const { | |
| untag()->set_url(value.ptr()); | |
| } | |
| void Script::set_resolved_url(const String& value) const { | |
| untag()->set_resolved_url(value.ptr()); | |
| } | |
| void Script::set_source(const String& value) const { | |
| untag()->set_source(value.ptr()); | |
| } | |
| #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| TypedDataViewPtr Script::kernel_constant_coverage() const { | |
| return TypedDataView::RawCast(untag()->constant_coverage()); | |
| } | |
| #if defined(DART_DYNAMIC_MODULES) | |
| ArrayPtr Script::collected_constant_coverage() const { | |
| return Array::RawCast(untag()->constant_coverage()); | |
| } | |
| void Script::set_collected_constant_coverage(const Array& value) const { | |
| ASSERT(!value.IsNull()); | |
| // Collected constant coverage should only be initialized once, | |
| // whether empty or non-empty. | |
| ASSERT(untag()->constant_coverage() == Object::null()); | |
| untag()->set_constant_coverage(value.ptr()); | |
| } | |
| bool Script::HasCollectedConstantCoverage() const { | |
| // Constant coverage should always be initialized to a non-null value. | |
| ASSERT(untag()->constant_coverage() != Object::null()); | |
| return untag()->constant_coverage()->IsArray() || | |
| untag()->constant_coverage()->IsImmutableArray(); | |
| } | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| #endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| TypedDataPtr Script::line_starts() const { | |
| return untag()->line_starts(); | |
| } | |
| void Script::set_line_starts(const TypedData& value) const { | |
| untag()->set_line_starts(value.ptr()); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| static int LowestFirst(const intptr_t* a, const intptr_t* b) { | |
| return *a - *b; | |
| } | |
| static ArrayPtr SortAndDeduplicate(GrowableArray<intptr_t>* source) { | |
| intptr_t size = source->length(); | |
| if (size == 0) { | |
| return Object::empty_array().ptr(); | |
| } | |
| source->Sort(LowestFirst); | |
| intptr_t last = 0; | |
| for (intptr_t current = 1; current < size; ++current) { | |
| if (source->At(last) != source->At(current)) { | |
| (*source)[++last] = source->At(current); | |
| } | |
| } | |
| Array& array_object = Array::Handle(); | |
| array_object = Array::New(last + 1, Heap::kOld); | |
| Smi& smi_value = Smi::Handle(); | |
| for (intptr_t i = 0; i <= last; ++i) { | |
| smi_value = Smi::New(source->At(i)); | |
| array_object.SetAt(i, smi_value); | |
| } | |
| return array_object.ptr(); | |
| } | |
| void Script::CollectDebugTokenPositions() const { | |
| GrowableArray<intptr_t> token_positions(10); | |
| if (kernel_program_info() != Object::null()) { | |
| kernel::CollectScriptTokenPositionsFromKernel(*this, &token_positions); | |
| } else { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| bytecode::BytecodeReader::CollectScriptTokenPositionsFromBytecode( | |
| *this, &token_positions); | |
| #else | |
| UNREACHABLE(); | |
| #endif | |
| } | |
| const auto& debug_positions = | |
| Array::Handle(SortAndDeduplicate(&token_positions)); | |
| set_debug_positions(debug_positions); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| ArrayPtr Script::CollectConstConstructorCoverageFrom() const { | |
| #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| #if defined(DART_DYNAMIC_MODULES) | |
| if (HasCollectedConstantCoverage()) { | |
| return Array::RawCast(untag()->constant_coverage()); | |
| } | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| return CollectConstConstructorCoverageFromKernel(); | |
| #else | |
| return Object::empty_array().ptr(); | |
| #endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| ArrayPtr Script::debug_positions() const { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| Array& debug_positions_array = Array::Handle(untag()->debug_positions()); | |
| if (debug_positions_array.IsNull()) { | |
| // This is created lazily. Now we need it. | |
| CollectDebugTokenPositions(); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| return untag()->debug_positions(); | |
| } | |
| void Script::set_debug_positions(const Array& value) const { | |
| untag()->set_debug_positions(value.ptr()); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| bool Script::HasCachedMaxPosition() const { | |
| return UntaggedScript::HasCachedMaxPositionBit::decode( | |
| untag()->flags_and_max_position_); | |
| } | |
| void Script::SetHasCachedMaxPosition(bool value) const { | |
| StoreNonPointer(&untag()->flags_and_max_position_, | |
| UntaggedScript::HasCachedMaxPositionBit::update( | |
| value, untag()->flags_and_max_position_)); | |
| } | |
| void Script::SetCachedMaxPosition(intptr_t value) const { | |
| StoreNonPointer(&untag()->flags_and_max_position_, | |
| UntaggedScript::CachedMaxPositionBitField::update( | |
| value, untag()->flags_and_max_position_)); | |
| } | |
| #endif | |
| void Script::set_load_timestamp(int64_t value) const { | |
| StoreNonPointer(&untag()->load_timestamp_, value); | |
| } | |
| bool Script::IsValidTokenPosition(TokenPosition token_pos) const { | |
| const TokenPosition& max_position = MaxPosition(); | |
| // We may end up with scripts that have the empty string as a source file | |
| // in testing and the like, so allow any token position when the max position | |
| // is 0 as well as when it is kNoSource. | |
| return !max_position.IsReal() || !token_pos.IsReal() || | |
| max_position.Pos() == 0 || token_pos <= max_position; | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| static bool IsLetter(int32_t c) { | |
| return (('A' <= c) && (c <= 'Z')) || (('a' <= c) && (c <= 'z')); | |
| } | |
| static bool IsDecimalDigit(int32_t c) { | |
| return '0' <= c && c <= '9'; | |
| } | |
| static bool IsIdentStartChar(int32_t c) { | |
| return IsLetter(c) || (c == '_') || (c == '$'); | |
| } | |
| static bool IsIdentChar(int32_t c) { | |
| return IsLetter(c) || IsDecimalDigit(c) || (c == '_') || (c == '$'); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| bool Script::GetTokenLocation(const TokenPosition& token_pos, | |
| intptr_t* line, | |
| intptr_t* column) const { | |
| ASSERT(line != nullptr); | |
| #if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| // Scripts in the AOT snapshot do not have a line starts array. | |
| return false; | |
| #else | |
| if (!token_pos.IsReal()) return false; | |
| auto const zone = Thread::Current()->zone(); | |
| const TypedData& line_starts_data = TypedData::Handle(zone, line_starts()); | |
| if (line_starts_data.IsNull()) return false; | |
| LineStartsReader line_starts_reader(line_starts_data); | |
| return line_starts_reader.LocationForPosition(token_pos.Pos(), line, column); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| } | |
| intptr_t Script::GetTokenLength(const TokenPosition& token_pos) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| // Scripts in the AOT snapshot do not have their source. | |
| return -1; | |
| #else | |
| if (!HasSource() || !token_pos.IsReal()) return -1; | |
| auto const zone = Thread::Current()->zone(); | |
| // We don't explicitly save this data: Load the source and find it from there. | |
| const String& source = String::Handle(zone, Source()); | |
| const intptr_t start = token_pos.Pos(); | |
| if (start >= source.Length()) return -1; // Can't determine token_len. | |
| intptr_t end = start; | |
| if (IsIdentStartChar(source.CharAt(end++))) { | |
| for (; end < source.Length(); ++end) { | |
| if (!IsIdentChar(source.CharAt(end))) break; | |
| } | |
| } | |
| return end - start; | |
| #endif | |
| } | |
| bool Script::TokenRangeAtLine(intptr_t line_number, | |
| TokenPosition* first_token_index, | |
| TokenPosition* last_token_index) const { | |
| ASSERT(first_token_index != nullptr && last_token_index != nullptr); | |
| #if defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| // Scripts in the AOT snapshot do not have a line starts array. | |
| return false; | |
| #else | |
| // Line numbers are 1-indexed. | |
| if (line_number <= 0) return false; | |
| Zone* zone = Thread::Current()->zone(); | |
| const TypedData& line_starts_data = TypedData::Handle(zone, line_starts()); | |
| if (line_starts_data.IsNull()) return false; | |
| LineStartsReader line_starts_reader(line_starts_data); | |
| if (!line_starts_reader.TokenRangeAtLine(line_number, first_token_index, | |
| last_token_index)) { | |
| return false; | |
| } | |
| #if defined(DEBUG) | |
| intptr_t source_length; | |
| if (!HasSource()) { | |
| Smi& value = Smi::Handle(zone); | |
| const Array& debug_positions_array = Array::Handle(zone, debug_positions()); | |
| ASSERT(!debug_positions_array.IsNull()); | |
| value ^= debug_positions_array.At(debug_positions_array.Length() - 1); | |
| source_length = value.Value(); | |
| } else { | |
| const String& source = String::Handle(zone, Source()); | |
| source_length = source.Length(); | |
| } | |
| ASSERT(last_token_index->Serialize() <= source_length); | |
| #endif | |
| return true; | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) && !defined(DART_DYNAMIC_MODULES) | |
| } | |
| // Returns the index in the given source string for the given (1-based) absolute | |
| // line and column numbers. The line and column offsets are used to calculate | |
| // the absolute line and column number for the starting index in the source. | |
| // | |
| // If the given line number is outside the range of lines represented by the | |
| // source, the given column number invalid for the given line, or a negative | |
| // starting index is given, a negative value is returned to indicate failure. | |
| static intptr_t GetRelativeSourceIndex(const String& src, | |
| intptr_t line, | |
| intptr_t line_offset = 0, | |
| intptr_t column = 1, | |
| intptr_t column_offset = 0, | |
| intptr_t starting_index = 0) { | |
| if (starting_index < 0 || line < 1 || column < 1 || line <= line_offset || | |
| (line == line_offset + 1 && column <= column_offset)) { | |
| return -1; | |
| } | |
| intptr_t len = src.Length(); | |
| intptr_t current_line = line_offset + 1; | |
| intptr_t current_index = starting_index; | |
| for (; current_index < len; current_index++) { | |
| if (current_line == line) { | |
| break; | |
| } | |
| const uint16_t c = src.CharAt(current_index); | |
| if (c == '\n' || c == '\r') { | |
| current_line++; | |
| } | |
| if (c == '\r' && current_index + 1 < len && | |
| src.CharAt(current_index + 1) == '\n') { | |
| // \r\n is treated as a single line terminator. | |
| current_index++; | |
| } | |
| } | |
| if (current_line != line) { | |
| return -1; | |
| } | |
| // Only adjust with column offset when still on the first line. | |
| intptr_t current_column = 1 + (line == line_offset + 1 ? column_offset : 0); | |
| for (; current_index < len; current_index++, current_column++) { | |
| if (current_column == column) { | |
| return current_index; | |
| } | |
| const uint16_t c = src.CharAt(current_index); | |
| if (c == '\n' || c == '\r') { | |
| break; | |
| } | |
| } | |
| // Check for a column value representing the source's end. | |
| if (current_column == column) { | |
| return current_index; | |
| } | |
| return -1; | |
| } | |
| StringPtr Script::GetLine(intptr_t line_number, Heap::Space space) const { | |
| if (!HasSource()) { | |
| return Symbols::OptimizedOut().ptr(); | |
| } | |
| const String& src = String::Handle(Source()); | |
| const intptr_t start = | |
| GetRelativeSourceIndex(src, line_number, line_offset()); | |
| if (start < 0) { | |
| return Symbols::Empty().ptr(); | |
| } | |
| intptr_t end = start; | |
| for (; end < src.Length(); end++) { | |
| const uint16_t c = src.CharAt(end); | |
| if (c == '\n' || c == '\r') { | |
| break; | |
| } | |
| } | |
| return String::SubString(src, start, end - start, space); | |
| } | |
| StringPtr Script::GetSnippet(intptr_t from_line, | |
| intptr_t from_column, | |
| intptr_t to_line, | |
| intptr_t to_column) const { | |
| if (!HasSource()) { | |
| return Symbols::OptimizedOut().ptr(); | |
| } | |
| const String& src = String::Handle(Source()); | |
| const intptr_t start = GetRelativeSourceIndex(src, from_line, line_offset(), | |
| from_column, col_offset()); | |
| // Lines and columns are 1-based, so need to subtract one to get offsets. | |
| const intptr_t end = GetRelativeSourceIndex( | |
| src, to_line, from_line - 1, to_column, from_column - 1, start); | |
| // Only need to check end, because a negative start results in a negative end. | |
| if (end < 0) { | |
| return String::null(); | |
| } | |
| return String::SubString(src, start, end - start); | |
| } | |
| ScriptPtr Script::New(const String& url, const String& source) { | |
| return Script::New(url, url, source); | |
| } | |
| ScriptPtr Script::New(const String& url, | |
| const String& resolved_url, | |
| const String& source) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kScriptCid) != | |
| Class::null()); | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const Script& result = | |
| Script::Handle(zone, Object::Allocate<Script>(Heap::kOld)); | |
| result.set_url(String::Handle(zone, Symbols::New(thread, url))); | |
| result.set_resolved_url( | |
| String::Handle(zone, Symbols::New(thread, resolved_url))); | |
| result.set_source(source); | |
| NOT_IN_PRECOMPILED(ASSERT_EQUAL(result.HasCachedMaxPosition(), false)); | |
| ASSERT_EQUAL(result.kernel_script_index(), 0); | |
| if (FLAG_remove_script_timestamps_for_test) { | |
| ASSERT_EQUAL(result.load_timestamp(), 0); | |
| } else { | |
| result.set_load_timestamp(OS::GetCurrentTimeMillis()); | |
| } | |
| return result.ptr(); | |
| } | |
| const char* Script::ToCString() const { | |
| const String& name = String::Handle(url()); | |
| return OS::SCreate(Thread::Current()->zone(), "Script(%s)", name.ToCString()); | |
| } | |
| LibraryPtr Script::FindLibrary() const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| auto isolate_group = thread->isolate_group(); | |
| const GrowableObjectArray& libs = GrowableObjectArray::Handle( | |
| zone, isolate_group->object_store()->libraries()); | |
| Library& lib = Library::Handle(zone); | |
| Array& scripts = Array::Handle(zone); | |
| for (intptr_t i = 0; i < libs.Length(); i++) { | |
| lib ^= libs.At(i); | |
| scripts = lib.LoadedScripts(); | |
| for (intptr_t j = 0; j < scripts.Length(); j++) { | |
| if (scripts.At(j) == ptr()) { | |
| return lib.ptr(); | |
| } | |
| } | |
| } | |
| return Library::null(); | |
| } | |
| DictionaryIterator::DictionaryIterator(const Library& library) | |
| : array_(Array::Handle(library.dictionary())), | |
| // Last element in array is a Smi indicating the number of entries used. | |
| size_(Array::Handle(library.dictionary()).Length() - 1), | |
| next_ix_(0) { | |
| MoveToNextObject(); | |
| } | |
| ObjectPtr DictionaryIterator::GetNext() { | |
| ASSERT(HasNext()); | |
| int ix = next_ix_++; | |
| MoveToNextObject(); | |
| ASSERT(array_.At(ix) != Object::null()); | |
| return array_.At(ix); | |
| } | |
| void DictionaryIterator::MoveToNextObject() { | |
| Object& obj = Object::Handle(array_.At(next_ix_)); | |
| while (obj.IsNull() && HasNext()) { | |
| next_ix_++; | |
| obj = array_.At(next_ix_); | |
| } | |
| } | |
| ClassDictionaryIterator::ClassDictionaryIterator(const Library& library, | |
| IterationKind kind) | |
| : DictionaryIterator(library), | |
| toplevel_class_(Class::Handle((kind == kIteratePrivate) | |
| ? library.toplevel_class() | |
| : Class::null())) { | |
| MoveToNextClass(); | |
| } | |
| ClassPtr ClassDictionaryIterator::GetNextClass() { | |
| ASSERT(HasNext()); | |
| Class& cls = Class::Handle(); | |
| if (next_ix_ < size_) { | |
| int ix = next_ix_++; | |
| cls ^= array_.At(ix); | |
| MoveToNextClass(); | |
| return cls.ptr(); | |
| } | |
| ASSERT(!toplevel_class_.IsNull()); | |
| cls = toplevel_class_.ptr(); | |
| toplevel_class_ = Class::null(); | |
| return cls.ptr(); | |
| } | |
| void ClassDictionaryIterator::MoveToNextClass() { | |
| Object& obj = Object::Handle(); | |
| while (next_ix_ < size_) { | |
| obj = array_.At(next_ix_); | |
| if (obj.IsClass()) { | |
| return; | |
| } | |
| next_ix_++; | |
| } | |
| } | |
| static void ReportTooManyImports(const Library& lib) { | |
| const String& url = String::Handle(lib.url()); | |
| Report::MessageF(Report::kError, Script::Handle(lib.LookupScript(url)), | |
| TokenPosition::kNoSource, Report::AtLocation, | |
| "too many imports in library '%s'", url.ToCString()); | |
| UNREACHABLE(); | |
| } | |
| void Library::set_num_imports(intptr_t value) const { | |
| if (!Utils::IsUint(16, value)) { | |
| ReportTooManyImports(*this); | |
| } | |
| StoreNonPointer(&untag()->num_imports_, value); | |
| } | |
| void Library::set_name(const String& name) const { | |
| ASSERT(name.IsSymbol()); | |
| untag()->set_name(name.ptr()); | |
| } | |
| void Library::set_url(const String& url) const { | |
| untag()->set_url(url.ptr()); | |
| } | |
| void Library::set_private_key(const String& key) const { | |
| untag()->set_private_key(key.ptr()); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| void Library::set_kernel_program_info(const KernelProgramInfo& info) const { | |
| untag()->set_kernel_program_info(info.ptr()); | |
| } | |
| TypedDataViewPtr Library::KernelLibrary() const { | |
| const auto& info = KernelProgramInfo::Handle(kernel_program_info()); | |
| return info.KernelLibrary(kernel_library_index()); | |
| } | |
| intptr_t Library::KernelLibraryOffset() const { | |
| const auto& info = KernelProgramInfo::Handle(kernel_program_info()); | |
| return info.KernelLibraryStartOffset(kernel_library_index()); | |
| } | |
| #endif | |
| void Library::set_loading_unit(const LoadingUnit& value) const { | |
| untag()->set_loading_unit(value.ptr()); | |
| } | |
| void Library::SetName(const String& name) const { | |
| // Only set name once. | |
| ASSERT(!Loaded()); | |
| set_name(name); | |
| } | |
| void Library::SetLoadInProgress() const { | |
| // Must not already be in the process of being loaded. | |
| ASSERT(untag()->load_state_ <= UntaggedLibrary::kLoadRequested); | |
| StoreNonPointer(&untag()->load_state_, UntaggedLibrary::kLoadInProgress); | |
| } | |
| void Library::SetLoadRequested() const { | |
| // Must not be already loaded. | |
| ASSERT(untag()->load_state_ == UntaggedLibrary::kAllocated); | |
| StoreNonPointer(&untag()->load_state_, UntaggedLibrary::kLoadRequested); | |
| } | |
| void Library::SetLoaded() const { | |
| // Should not be already loaded or just allocated. | |
| ASSERT(LoadInProgress() || LoadRequested()); | |
| StoreNonPointer(&untag()->load_state_, UntaggedLibrary::kLoaded); | |
| } | |
| void Library::AddMetadata(const Object& declaration, | |
| intptr_t kernel_offset) const { | |
| AddMetadata(declaration, Smi::Handle(Smi::New(kernel_offset))); | |
| } | |
| void Library::AddMetadata(const Object& declaration, | |
| const Object& metadata_value) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); | |
| MetadataMap map(metadata()); | |
| map.UpdateOrInsert(declaration, metadata_value); | |
| set_metadata(map.Release()); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| ObjectPtr Library::GetMetadata(const Object& declaration) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| return Object::empty_array().ptr(); | |
| #else | |
| RELEASE_ASSERT(declaration.IsClass() || declaration.IsField() || | |
| declaration.IsFunction() || declaration.IsLibrary() || | |
| declaration.IsTypeParameter() || declaration.IsNamespace()); | |
| auto thread = Thread::Current(); | |
| auto zone = thread->zone(); | |
| if (declaration.IsLibrary()) { | |
| // Ensure top-level class is loaded as it may contain annotations of | |
| // a library. | |
| const auto& cls = Class::Handle(zone, toplevel_class()); | |
| if (!cls.IsNull()) { | |
| cls.EnsureDeclarationLoaded(); | |
| } | |
| } | |
| Object& value = Object::Handle(zone); | |
| { | |
| SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| MetadataMap map(metadata()); | |
| value = map.GetOrNull(declaration); | |
| set_metadata(map.Release()); | |
| } | |
| if (value.IsNull()) { | |
| // There is no metadata for this object. | |
| return Object::empty_array().ptr(); | |
| } | |
| if (!value.IsSmi()) { | |
| // Metadata is already evaluated. | |
| ASSERT(value.IsArray() || value.IsLanguageError()); | |
| return value.ptr(); | |
| } | |
| const auto& smi_value = Smi::Cast(value); | |
| intptr_t kernel_offset = smi_value.Value(); | |
| ASSERT(kernel_offset > 0); | |
| const auto& evaluated_value = Object::Handle( | |
| zone, kernel::EvaluateMetadata( | |
| *this, kernel_offset, | |
| /* is_annotations_offset = */ declaration.IsLibrary() || | |
| declaration.IsNamespace())); | |
| if (evaluated_value.IsArray() || evaluated_value.IsNull() || | |
| evaluated_value.IsLanguageError()) { | |
| ASSERT(evaluated_value.ptr() != Object::empty_array().ptr()); | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| MetadataMap map(metadata()); | |
| if (map.GetOrNull(declaration) == smi_value.ptr()) { | |
| map.UpdateOrInsert(declaration, evaluated_value); | |
| } else { | |
| ASSERT(map.GetOrNull(declaration) == evaluated_value.ptr()); | |
| } | |
| set_metadata(map.Release()); | |
| } | |
| return evaluated_value.ptr(); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| static bool HasPragma(const Object& declaration) { | |
| return (declaration.IsClass() && Class::Cast(declaration).has_pragma()) || | |
| (declaration.IsFunction() && | |
| Function::Cast(declaration).has_pragma()) || | |
| (declaration.IsField() && Field::Cast(declaration).has_pragma()); | |
| } | |
| void Library::EvaluatePragmas() { | |
| Object& declaration = Object::Handle(); | |
| const GrowableObjectArray& declarations = | |
| GrowableObjectArray::Handle(GrowableObjectArray::New()); | |
| { | |
| auto thread = Thread::Current(); | |
| SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| MetadataMap map(metadata()); | |
| MetadataMap::Iterator it(&map); | |
| while (it.MoveNext()) { | |
| const intptr_t entry = it.Current(); | |
| ASSERT(entry != -1); | |
| declaration = map.GetKey(entry); | |
| if (HasPragma(declaration)) { | |
| declarations.Add(declaration); | |
| } | |
| } | |
| set_metadata(map.Release()); | |
| } | |
| for (intptr_t i = 0; i < declarations.Length(); ++i) { | |
| declaration = declarations.At(i); | |
| GetMetadata(declaration); | |
| } | |
| } | |
| void Library::CopyPragmas(const Library& old_lib) { | |
| auto thread = Thread::Current(); | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| MetadataMap new_map(metadata()); | |
| MetadataMap old_map(old_lib.metadata()); | |
| Object& declaration = Object::Handle(); | |
| Object& value = Object::Handle(); | |
| MetadataMap::Iterator it(&old_map); | |
| while (it.MoveNext()) { | |
| const intptr_t entry = it.Current(); | |
| ASSERT(entry != -1); | |
| declaration = old_map.GetKey(entry); | |
| if (HasPragma(declaration)) { | |
| value = old_map.GetPayload(entry, 0); | |
| ASSERT(!value.IsNull()); | |
| // Pragmas should be evaluated during hot reload phase 1 | |
| // (when checkpointing libraries). | |
| ASSERT(!value.IsSmi()); | |
| new_map.UpdateOrInsert(declaration, value); | |
| } | |
| } | |
| old_lib.set_metadata(old_map.Release()); | |
| set_metadata(new_map.Release()); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| static bool ShouldBePrivate(const String& name) { | |
| // _foo | |
| if (name.Length() >= 1 && name.CharAt(0) == '_') { | |
| return true; | |
| } | |
| // get:_foo, set:_foo or dyn:_foo | |
| if (name.Length() >= 5 && name.CharAt(3) == ':' && name.CharAt(4) == '_') { | |
| if ((name.CharAt(0) == 'g' || name.CharAt(0) == 's') && | |
| name.CharAt(1) == 'e' && name.CharAt(2) == 't') { | |
| return true; | |
| } | |
| if (name.CharAt(0) == 'd' && name.CharAt(1) == 'y' && | |
| name.CharAt(2) == 'n') { | |
| return true; | |
| } | |
| } | |
| // dyn:get:_foo, dyn:set:_foo | |
| if (name.Length() >= 9 && name.CharAt(0) == 'd' && name.CharAt(1) == 'y' && | |
| name.CharAt(2) == 'n' && name.CharAt(3) == ':' && | |
| (name.CharAt(4) == 'g' || name.CharAt(4) == 's') && | |
| name.CharAt(5) == 'e' && name.CharAt(6) == 't' && name.CharAt(7) == ':' && | |
| name.CharAt(8) == '_') { | |
| return true; | |
| } | |
| return false; | |
| } | |
| void Library::RehashDictionary(const Array& old_dict, | |
| intptr_t new_dict_size) const { | |
| intptr_t old_dict_size = old_dict.Length() - 1; | |
| const Array& new_dict = | |
| Array::Handle(Array::New(new_dict_size + 1, Heap::kOld)); | |
| // Rehash all elements from the original dictionary | |
| // to the newly allocated array. | |
| Object& entry = Class::Handle(); | |
| String& entry_name = String::Handle(); | |
| Object& new_entry = Object::Handle(); | |
| intptr_t used = 0; | |
| for (intptr_t i = 0; i < old_dict_size; i++) { | |
| entry = old_dict.At(i); | |
| if (!entry.IsNull()) { | |
| entry_name = entry.DictionaryName(); | |
| ASSERT(!entry_name.IsNull()); | |
| const intptr_t hash = entry_name.Hash(); | |
| intptr_t index = hash % new_dict_size; | |
| new_entry = new_dict.At(index); | |
| while (!new_entry.IsNull()) { | |
| index = (index + 1) % new_dict_size; // Move to next element. | |
| new_entry = new_dict.At(index); | |
| } | |
| new_dict.SetAt(index, entry); | |
| used++; | |
| } | |
| } | |
| // Set used count. | |
| ASSERT(used < new_dict_size); // Need at least one empty slot. | |
| new_entry = Smi::New(used); | |
| new_dict.SetAt(new_dict_size, new_entry); | |
| // Remember the new dictionary now. | |
| untag()->set_dictionary(new_dict.ptr()); | |
| } | |
| void Library::AddObject(const Object& obj, const String& name) const { | |
| ASSERT(Thread::Current()->IsDartMutatorThread()); | |
| ASSERT(obj.IsClass() || obj.IsFunction() || obj.IsField() || | |
| obj.IsLibraryPrefix()); | |
| ASSERT(name.Equals(String::Handle(obj.DictionaryName()))); | |
| ASSERT(LookupLocalObject(name) == Object::null()); | |
| const Array& dict = Array::Handle(dictionary()); | |
| intptr_t dict_size = dict.Length() - 1; | |
| intptr_t index = name.Hash() % dict_size; | |
| Object& entry = Object::Handle(); | |
| entry = dict.At(index); | |
| // An empty spot will be found because we keep the hash set at most 75% full. | |
| while (!entry.IsNull()) { | |
| index = (index + 1) % dict_size; | |
| entry = dict.At(index); | |
| } | |
| // Insert the object at the empty slot. | |
| dict.SetAt(index, obj); | |
| // One more element added. | |
| intptr_t used_elements = Smi::Value(Smi::RawCast(dict.At(dict_size))) + 1; | |
| const Smi& used = Smi::Handle(Smi::New(used_elements)); | |
| dict.SetAt(dict_size, used); // Update used count. | |
| // Rehash if symbol_table is 75% full. | |
| if (used_elements > ((dict_size / 4) * 3)) { | |
| // TODO(iposva): Avoid exponential growth. | |
| RehashDictionary(dict, 2 * dict_size); | |
| } | |
| // Invalidate the cache of loaded scripts. | |
| if (loaded_scripts() != Array::null()) { | |
| untag()->set_loaded_scripts(Array::null()); | |
| } | |
| } | |
| ObjectPtr Library::LookupEntry(const String& name, intptr_t* index) const { | |
| ASSERT(!IsNull()); | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_OBJECT_HANDLESCOPE(thread); | |
| REUSABLE_STRING_HANDLESCOPE(thread); | |
| Array& dict = thread->ArrayHandle(); | |
| dict = dictionary(); | |
| intptr_t dict_size = dict.Length() - 1; | |
| *index = name.Hash() % dict_size; | |
| Object& entry = thread->ObjectHandle(); | |
| String& entry_name = thread->StringHandle(); | |
| entry = dict.At(*index); | |
| // Search the entry in the hash set. | |
| while (!entry.IsNull()) { | |
| entry_name = entry.DictionaryName(); | |
| ASSERT(!entry_name.IsNull()); | |
| if (entry_name.Equals(name)) { | |
| return entry.ptr(); | |
| } | |
| *index = (*index + 1) % dict_size; | |
| entry = dict.At(*index); | |
| } | |
| return Object::null(); | |
| } | |
| void Library::AddClass(const Class& cls) const { | |
| ASSERT(!Compiler::IsBackgroundCompilation()); | |
| const String& class_name = String::Handle(cls.Name()); | |
| AddObject(cls, class_name); | |
| // Link class to this library. | |
| cls.set_library(*this); | |
| } | |
| static void AddScriptIfUnique(const GrowableObjectArray& scripts, | |
| const Script& candidate) { | |
| if (candidate.IsNull()) { | |
| return; | |
| } | |
| Script& script_obj = Script::Handle(); | |
| for (int i = 0; i < scripts.Length(); i++) { | |
| script_obj ^= scripts.At(i); | |
| if (script_obj.ptr() == candidate.ptr()) { | |
| // We already have a reference to this script. | |
| return; | |
| } | |
| } | |
| // Add script to the list of scripts. | |
| scripts.Add(candidate); | |
| } | |
| ArrayPtr Library::LoadedScripts() const { | |
| // We compute the list of loaded scripts lazily. The result is | |
| // cached in loaded_scripts_. | |
| if (loaded_scripts() == Array::null()) { | |
| // TODO(jensj): This can be cleaned up. | |
| // It really should just return the content of `used_scripts`, and there | |
| // should be no need to do the O(n) call to `AddScriptIfUnique` per script. | |
| // Iterate over the library dictionary and collect all scripts. | |
| const GrowableObjectArray& scripts = | |
| GrowableObjectArray::Handle(GrowableObjectArray::New(8)); | |
| Object& entry = Object::Handle(); | |
| Class& cls = Class::Handle(); | |
| Script& owner_script = Script::Handle(); | |
| DictionaryIterator it(*this); | |
| while (it.HasNext()) { | |
| entry = it.GetNext(); | |
| if (entry.IsClass()) { | |
| owner_script = Class::Cast(entry).script(); | |
| } else if (entry.IsFunction()) { | |
| owner_script = Function::Cast(entry).script(); | |
| } else if (entry.IsField()) { | |
| owner_script = Field::Cast(entry).Script(); | |
| } else { | |
| continue; | |
| } | |
| AddScriptIfUnique(scripts, owner_script); | |
| } | |
| // Add all scripts from patch classes. | |
| GrowableObjectArray& patches = GrowableObjectArray::Handle(used_scripts()); | |
| for (intptr_t i = 0; i < patches.Length(); i++) { | |
| entry = patches.At(i); | |
| if (entry.IsClass()) { | |
| owner_script = Class::Cast(entry).script(); | |
| } else { | |
| ASSERT(entry.IsScript()); | |
| owner_script = Script::Cast(entry).ptr(); | |
| } | |
| AddScriptIfUnique(scripts, owner_script); | |
| } | |
| cls = toplevel_class(); | |
| if (!cls.IsNull()) { | |
| owner_script = cls.script(); | |
| AddScriptIfUnique(scripts, owner_script); | |
| // Special case: Scripts that only contain external top-level functions | |
| // are not included above, but can be referenced through a library's | |
| // anonymous classes. Example: dart-core:identical.dart. | |
| Function& func = Function::Handle(); | |
| Array& functions = Array::Handle(cls.current_functions()); | |
| for (intptr_t j = 0; j < functions.Length(); j++) { | |
| func ^= functions.At(j); | |
| if (func.is_external()) { | |
| owner_script = func.script(); | |
| AddScriptIfUnique(scripts, owner_script); | |
| } | |
| } | |
| } | |
| // Create the array of scripts and cache it in loaded_scripts_. | |
| const Array& scripts_array = Array::Handle(Array::MakeFixedLength(scripts)); | |
| untag()->set_loaded_scripts(scripts_array.ptr()); | |
| } | |
| return loaded_scripts(); | |
| } | |
| // TODO(hausner): we might want to add a script dictionary to the | |
| // library class to make this lookup faster. | |
| ScriptPtr Library::LookupScript(const String& url, | |
| bool useResolvedUri /* = false */) const { | |
| const intptr_t url_length = url.Length(); | |
| if (url_length == 0) { | |
| return Script::null(); | |
| } | |
| const Array& scripts = Array::Handle(LoadedScripts()); | |
| Script& script = Script::Handle(); | |
| String& script_url = String::Handle(); | |
| const intptr_t num_scripts = scripts.Length(); | |
| for (int i = 0; i < num_scripts; i++) { | |
| script ^= scripts.At(i); | |
| if (useResolvedUri) { | |
| // Use for urls with 'org-dartlang-sdk:' or 'file:' schemes | |
| script_url = script.resolved_url(); | |
| } else { | |
| // Use for urls with 'dart:', 'package:', or 'file:' schemes | |
| script_url = script.url(); | |
| } | |
| const intptr_t start_idx = script_url.Length() - url_length; | |
| if ((start_idx == 0) && url.Equals(script_url)) { | |
| return script.ptr(); | |
| } else if (start_idx > 0) { | |
| // If we do a suffix match, only match if the partial path | |
| // starts at or immediately after the path separator. | |
| if (((url.CharAt(0) == '/') || | |
| (script_url.CharAt(start_idx - 1) == '/')) && | |
| url.Equals(script_url, start_idx, url_length)) { | |
| return script.ptr(); | |
| } | |
| } | |
| } | |
| return Script::null(); | |
| } | |
| void Library::EnsureTopLevelClassIsFinalized() const { | |
| if (toplevel_class() == Object::null()) { | |
| return; | |
| } | |
| Thread* thread = Thread::Current(); | |
| const Class& cls = Class::Handle(thread->zone(), toplevel_class()); | |
| if (cls.is_finalized()) { | |
| return; | |
| } | |
| const Error& error = | |
| Error::Handle(thread->zone(), cls.EnsureIsFinalized(thread)); | |
| if (!error.IsNull()) { | |
| Exceptions::PropagateError(error); | |
| } | |
| } | |
| ObjectPtr Library::LookupLocalObject(const String& name) const { | |
| intptr_t index; | |
| return LookupEntry(name, &index); | |
| } | |
| FieldPtr Library::LookupFieldAllowPrivate(const String& name) const { | |
| EnsureTopLevelClassIsFinalized(); | |
| Object& obj = Object::Handle(LookupLocalObjectAllowPrivate(name)); | |
| if (obj.IsField()) { | |
| return Field::Cast(obj).ptr(); | |
| } | |
| return Field::null(); | |
| } | |
| FunctionPtr Library::LookupFunctionAllowPrivate(const String& name) const { | |
| EnsureTopLevelClassIsFinalized(); | |
| Object& obj = Object::Handle(LookupLocalObjectAllowPrivate(name)); | |
| if (obj.IsFunction()) { | |
| return Function::Cast(obj).ptr(); | |
| } | |
| return Function::null(); | |
| } | |
| ObjectPtr Library::LookupLocalObjectAllowPrivate(const String& name) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| Object& obj = Object::Handle(zone, Object::null()); | |
| obj = LookupLocalObject(name); | |
| if (obj.IsNull() && ShouldBePrivate(name)) { | |
| String& private_name = String::Handle(zone, PrivateName(name)); | |
| obj = LookupLocalObject(private_name); | |
| } | |
| return obj.ptr(); | |
| } | |
| ClassPtr Library::LookupClass(const String& name) const { | |
| Object& obj = Object::Handle(LookupLocalObject(name)); | |
| if (obj.IsClass()) { | |
| return Class::Cast(obj).ptr(); | |
| } | |
| return Class::null(); | |
| } | |
| ClassPtr Library::LookupClassAllowPrivate(const String& name) const { | |
| Object& obj = Object::Handle(LookupLocalObjectAllowPrivate(name)); | |
| if (obj.IsClass()) { | |
| return Class::Cast(obj).ptr(); | |
| } | |
| return Class::null(); | |
| } | |
| LibraryPrefixPtr Library::LookupLocalLibraryPrefix(const String& name) const { | |
| const Object& obj = Object::Handle(LookupLocalObject(name)); | |
| if (obj.IsLibraryPrefix()) { | |
| return LibraryPrefix::Cast(obj).ptr(); | |
| } | |
| return LibraryPrefix::null(); | |
| } | |
| void Library::set_toplevel_class(const Class& value) const { | |
| ASSERT(untag()->toplevel_class() == Class::null()); | |
| untag()->set_toplevel_class(value.ptr()); | |
| } | |
| void Library::set_dependencies(const Array& deps) const { | |
| untag()->set_dependencies(deps.ptr()); | |
| } | |
| void Library::set_metadata(const Array& value) const { | |
| if (untag()->metadata() != value.ptr()) { | |
| DEBUG_ASSERT( | |
| IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| untag()->set_metadata(value.ptr()); | |
| } | |
| } | |
| LibraryPtr Library::ImportLibraryAt(intptr_t index) const { | |
| Namespace& import = Namespace::Handle(ImportAt(index)); | |
| if (import.IsNull()) { | |
| return Library::null(); | |
| } | |
| return import.target(); | |
| } | |
| NamespacePtr Library::ImportAt(intptr_t index) const { | |
| if ((index < 0) || index >= num_imports()) { | |
| return Namespace::null(); | |
| } | |
| const Array& import_list = Array::Handle(imports()); | |
| return Namespace::RawCast(import_list.At(index)); | |
| } | |
| void Library::DropDependenciesAndCaches() const { | |
| untag()->set_imports(Object::empty_array().ptr()); | |
| untag()->set_exports(Object::empty_array().ptr()); | |
| StoreNonPointer(&untag()->num_imports_, 0); | |
| untag()->set_loaded_scripts(Array::null()); | |
| untag()->set_dependencies(Array::null()); | |
| #if defined(PRODUCT) | |
| // used_scripts is only used by vm-service. | |
| untag()->set_used_scripts(GrowableObjectArray::null()); | |
| #endif | |
| } | |
| void Library::AddImport(const Namespace& ns) const { | |
| Array& imports = Array::Handle(this->imports()); | |
| intptr_t capacity = imports.Length(); | |
| if (num_imports() == capacity) { | |
| capacity = capacity + kImportsCapacityIncrement + (capacity >> 2); | |
| imports = Array::Grow(imports, capacity); | |
| untag()->set_imports(imports.ptr()); | |
| } | |
| intptr_t index = num_imports(); | |
| imports.SetAt(index, ns); | |
| set_num_imports(index + 1); | |
| } | |
| // We add one namespace at a time to the exports array and don't | |
| // pre-allocate any unused capacity. The assumption is that | |
| // re-exports are quite rare. | |
| void Library::AddExport(const Namespace& ns) const { | |
| Array& exports = Array::Handle(this->exports()); | |
| intptr_t num_exports = exports.Length(); | |
| exports = Array::Grow(exports, num_exports + 1); | |
| untag()->set_exports(exports.ptr()); | |
| exports.SetAt(num_exports, ns); | |
| } | |
| static ArrayPtr NewDictionary(intptr_t initial_size) { | |
| const Array& dict = Array::Handle(Array::New(initial_size + 1, Heap::kOld)); | |
| // The last element of the dictionary specifies the number of in use slots. | |
| dict.SetAt(initial_size, Object::smi_zero()); | |
| return dict.ptr(); | |
| } | |
| void Library::InitClassDictionary() const { | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->IsDartMutatorThread()); | |
| REUSABLE_FUNCTION_HANDLESCOPE(thread); | |
| Array& dictionary = thread->ArrayHandle(); | |
| // TODO(iposva): Find reasonable initial size. | |
| const int kInitialElementCount = 16; | |
| dictionary = NewDictionary(kInitialElementCount); | |
| untag()->set_dictionary(dictionary.ptr()); | |
| } | |
| void Library::InitImportList() const { | |
| const Array& imports = | |
| Array::Handle(Array::New(kInitialImportsCapacity, Heap::kOld)); | |
| untag()->set_imports(imports.ptr()); | |
| StoreNonPointer(&untag()->num_imports_, 0); | |
| } | |
| LibraryPtr Library::New() { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kLibraryCid) != | |
| Class::null()); | |
| return Object::Allocate<Library>(Heap::kOld); | |
| } | |
| LibraryPtr Library::NewLibraryHelper(const String& url, bool import_core_lib) { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| ASSERT(thread->IsDartMutatorThread()); | |
| // Force the url to have a hash code. | |
| url.Hash(); | |
| const bool dart_scheme = url.StartsWith(Symbols::DartScheme()); | |
| const Library& result = Library::Handle(zone, Library::New()); | |
| result.untag()->set_name(Symbols::Empty().ptr()); | |
| result.untag()->set_url(url.ptr()); | |
| result.untag()->set_dictionary(Object::empty_array().ptr()); | |
| Array& array = Array::Handle(zone); | |
| array = HashTables::New<MetadataMap>(4, Heap::kOld); | |
| result.untag()->set_metadata(array.ptr()); | |
| result.untag()->set_toplevel_class(Class::null()); | |
| GrowableObjectArray& list = GrowableObjectArray::Handle(zone); | |
| list = GrowableObjectArray::New(Object::empty_array(), Heap::kOld); | |
| result.untag()->set_used_scripts(list.ptr()); | |
| result.untag()->set_imports(Object::empty_array().ptr()); | |
| result.untag()->set_exports(Object::empty_array().ptr()); | |
| NOT_IN_PRECOMPILED_RUNTIME( | |
| result.untag()->set_kernel_program_info(KernelProgramInfo::null())); | |
| result.untag()->set_loaded_scripts(Array::null()); | |
| result.set_native_entry_resolver(nullptr); | |
| result.set_native_entry_symbol_resolver(nullptr); | |
| result.set_ffi_native_resolver(nullptr); | |
| result.set_flags(0); | |
| // This logic is also in the DAP debug adapter in DDS to avoid needing | |
| // to call setLibraryDebuggable for every library for every isolate. | |
| // If these defaults change, the same should be done there in | |
| // dap/IsolateManager._getIsLibraryDebuggableByDefault. | |
| if (dart_scheme) { | |
| // Only debug dart: libraries if we have been requested to show invisible | |
| // frames. | |
| result.set_debuggable(FLAG_show_invisible_frames); | |
| } else { | |
| // Default to debuggable for all other libraries. | |
| result.set_debuggable(true); | |
| } | |
| result.set_is_dart_scheme(dart_scheme); | |
| NOT_IN_PRECOMPILED( | |
| result.StoreNonPointer(&result.untag()->kernel_library_index_, -1)); | |
| result.StoreNonPointer(&result.untag()->load_state_, | |
| UntaggedLibrary::kAllocated); | |
| result.StoreNonPointer(&result.untag()->index_, -1); | |
| result.InitClassDictionary(); | |
| result.InitImportList(); | |
| result.AllocatePrivateKey(); | |
| if (import_core_lib) { | |
| const Library& core_lib = Library::Handle(zone, Library::CoreLibrary()); | |
| ASSERT(!core_lib.IsNull()); | |
| const Namespace& ns = | |
| Namespace::Handle(zone, Namespace::New(core_lib, Object::null_array(), | |
| Object::null_array(), result)); | |
| result.AddImport(ns); | |
| } | |
| return result.ptr(); | |
| } | |
| LibraryPtr Library::New(const String& url) { | |
| return NewLibraryHelper(url, false); | |
| } | |
| void Library::set_flags(uint8_t flags) const { | |
| StoreNonPointer(&untag()->flags_, flags); | |
| } | |
| void Library::InitCoreLibrary(IsolateGroup* isolate_group) { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const String& core_lib_url = Symbols::DartCore(); | |
| const Library& core_lib = | |
| Library::Handle(zone, Library::NewLibraryHelper(core_lib_url, false)); | |
| core_lib.SetLoadRequested(); | |
| core_lib.Register(thread); | |
| isolate_group->object_store()->set_bootstrap_library(ObjectStore::kCore, | |
| core_lib); | |
| isolate_group->object_store()->set_root_library(Library::Handle()); | |
| } | |
| // Invoke the function, or noSuchMethod if it is null. | |
| static ObjectPtr InvokeInstanceFunction( | |
| Thread* thread, | |
| const Instance& receiver, | |
| const Function& function, | |
| const String& target_name, | |
| const Array& args, | |
| const Array& args_descriptor_array, | |
| bool check_is_entrypoint, | |
| EntryPointPragma pragma, | |
| bool respect_reflectable, | |
| const TypeArguments& instantiator_type_args) { | |
| // Note "args" is already the internal arguments with the receiver as the | |
| // first element. | |
| ArgumentsDescriptor args_descriptor(args_descriptor_array); | |
| if (function.IsNull() || | |
| !function.AreValidArguments(args_descriptor, nullptr) || | |
| (respect_reflectable && !function.is_reflectable())) { | |
| return DartEntry::InvokeNoSuchMethod(thread, receiver, target_name, args, | |
| args_descriptor_array); | |
| } | |
| ObjectPtr type_error = function.DoArgumentTypesMatch(args, args_descriptor, | |
| instantiator_type_args); | |
| if (type_error != Error::null()) { | |
| return type_error; | |
| } | |
| if (check_is_entrypoint) { | |
| CHECK_ERROR(function.VerifyEntryPoint(pragma)); | |
| } | |
| return DartEntry::InvokeFunction(function, args, args_descriptor_array); | |
| } | |
| static bool IsLookupOfMainFunctionInRootLibrary(const Library& lib, | |
| const String& name) { | |
| return name.Equals(Symbols::main()) && | |
| lib.ptr() == IsolateGroup::Current()->object_store()->root_library(); | |
| } | |
| ObjectPtr Library::InvokeGetter(const String& getter_name, | |
| bool check_is_entrypoint, | |
| bool respect_reflectable, | |
| bool for_invocation) const { | |
| EnsureTopLevelClassIsFinalized(); | |
| Object& obj = Object::Handle(LookupLocalObject(getter_name)); | |
| Function& getter = Function::Handle(); | |
| if (obj.IsField()) { | |
| const Field& field = Field::Cast(obj); | |
| if (!field.IsUninitialized()) { | |
| if (check_is_entrypoint) { | |
| CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kGetterOnly)); | |
| } | |
| return field.StaticValue(); | |
| } | |
| // An uninitialized field was found. Check for a getter in the field's | |
| // owner class. | |
| const Class& klass = Class::Handle(field.Owner()); | |
| const String& internal_getter_name = | |
| String::Handle(Field::GetterName(getter_name)); | |
| getter = klass.LookupStaticFunction(internal_getter_name); | |
| } else { | |
| // No field found. Check for a getter in the lib. | |
| const String& internal_getter_name = | |
| String::Handle(Field::GetterName(getter_name)); | |
| obj = LookupLocalObject(internal_getter_name); | |
| if (obj.IsFunction()) { | |
| getter = Function::Cast(obj).ptr(); | |
| } else if (!for_invocation) { | |
| // No need to re-lookup the getter name if coming from Invoke(), since | |
| // it already failed there. | |
| obj = LookupLocalObject(getter_name); | |
| if (obj.IsFunction()) { | |
| const auto& function = Function::Cast(obj); | |
| if (function.SafeToClosurize()) { | |
| // The main function of the root library always has a retained | |
| // implicit static closure. | |
| if (check_is_entrypoint && | |
| !IsLookupOfMainFunctionInRootLibrary(*this, getter_name)) { | |
| CHECK_ERROR( | |
| function.VerifyEntryPoint(EntryPointPragma::kGetterOnly)); | |
| } | |
| // Looking for a getter but found a regular method: closurize it. | |
| const auto& closure_function = | |
| Function::Handle(function.ImplicitClosureFunction()); | |
| return closure_function.ImplicitStaticClosure(); | |
| } | |
| } | |
| } | |
| } | |
| if (getter.IsNull() || (respect_reflectable && !getter.is_reflectable())) { | |
| if (for_invocation) { | |
| // Instead of throwing an NSM, indicate that no getter was found by | |
| // returning a value that cannot be returned by a getter (here, | |
| // the sentinel value). | |
| return Object::sentinel().ptr(); | |
| } | |
| return ThrowNoSuchMethod(Object::null_string(), getter_name, | |
| Object::null_array(), Object::null_array(), | |
| InvocationMirror::kTopLevel, | |
| InvocationMirror::kGetter); | |
| } | |
| if (check_is_entrypoint) { | |
| CHECK_ERROR(getter.VerifyEntryPoint(EntryPointPragma::kGetterOnly)); | |
| } | |
| // Invoke the getter and return the result. | |
| return DartEntry::InvokeFunction(getter, Object::empty_array()); | |
| } | |
| ObjectPtr Library::InvokeSetter(const String& setter_name, | |
| const Instance& value, | |
| bool check_is_entrypoint, | |
| bool respect_reflectable) const { | |
| EnsureTopLevelClassIsFinalized(); | |
| Object& obj = Object::Handle(LookupLocalObject(setter_name)); | |
| const String& internal_setter_name = | |
| String::Handle(Field::SetterName(setter_name)); | |
| AbstractType& setter_type = AbstractType::Handle(); | |
| AbstractType& argument_type = AbstractType::Handle(value.GetType(Heap::kOld)); | |
| if (obj.IsField()) { | |
| const Field& field = Field::Cast(obj); | |
| setter_type = field.type(); | |
| if (!argument_type.IsNullType() && !setter_type.IsDynamicType() && | |
| !value.IsInstanceOf(setter_type, Object::null_type_arguments(), | |
| Object::null_type_arguments())) { | |
| return ThrowTypeError(field.token_pos(), value, setter_type, setter_name); | |
| } | |
| if (field.is_final() || (respect_reflectable && !field.is_reflectable())) { | |
| const int kNumArgs = 1; | |
| const Array& args = Array::Handle(Array::New(kNumArgs)); | |
| args.SetAt(0, value); | |
| return ThrowNoSuchMethod(Object::null_string(), internal_setter_name, | |
| args, Object::null_array(), | |
| InvocationMirror::kTopLevel, | |
| InvocationMirror::kSetter); | |
| } | |
| if (check_is_entrypoint) { | |
| CHECK_ERROR(field.VerifyEntryPoint(EntryPointPragma::kSetterOnly)); | |
| } | |
| field.SetStaticValue(value); | |
| return value.ptr(); | |
| } | |
| Function& setter = Function::Handle(); | |
| obj = LookupLocalObject(internal_setter_name); | |
| if (obj.IsFunction()) { | |
| setter ^= obj.ptr(); | |
| } | |
| const int kNumArgs = 1; | |
| const Array& args = Array::Handle(Array::New(kNumArgs)); | |
| args.SetAt(0, value); | |
| if (setter.IsNull() || (respect_reflectable && !setter.is_reflectable())) { | |
| return ThrowNoSuchMethod(Object::null_string(), internal_setter_name, args, | |
| Object::null_array(), InvocationMirror::kTopLevel, | |
| InvocationMirror::kSetter); | |
| } | |
| setter_type = setter.ParameterTypeAt(0); | |
| if (!argument_type.IsNullType() && !setter_type.IsDynamicType() && | |
| !value.IsInstanceOf(setter_type, Object::null_type_arguments(), | |
| Object::null_type_arguments())) { | |
| return ThrowTypeError(setter.token_pos(), value, setter_type, setter_name); | |
| } | |
| if (check_is_entrypoint) { | |
| CHECK_ERROR(setter.VerifyEntryPoint(EntryPointPragma::kSetterOnly)); | |
| } | |
| return DartEntry::InvokeFunction(setter, args); | |
| } | |
| ObjectPtr Library::Invoke(const String& function_name, | |
| const Array& args, | |
| const Array& arg_names, | |
| bool check_is_entrypoint, | |
| bool respect_reflectable) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| EnsureTopLevelClassIsFinalized(); | |
| // 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, LookupLocalObject(function_name)); | |
| if (result.IsFunction()) { | |
| function ^= result.ptr(); | |
| } | |
| if (function.IsNull()) { | |
| // Didn't find a method: try to find a getter and invoke call on its result. | |
| const Object& getter_result = | |
| Object::Handle(zone, InvokeGetter(function_name, check_is_entrypoint, | |
| respect_reflectable, | |
| /*for_invocation=*/true)); | |
| if (getter_result.IsError()) { | |
| return getter_result.ptr(); | |
| } else if (getter_result.ptr() != Object::sentinel().ptr()) { | |
| const auto& call_args_descriptor_array = Array::Handle( | |
| zone, ArgumentsDescriptor::NewBoxed(args_descriptor.TypeArgsLen(), | |
| args_descriptor.Count() + 1, | |
| arg_names, Heap::kNew)); | |
| const auto& call_args = Array::Handle( | |
| zone, | |
| CreateCallableArgumentsFromStatic(zone, Instance::Cast(getter_result), | |
| args, arg_names, args_descriptor)); | |
| return DartEntry::InvokeClosure(thread, call_args, | |
| call_args_descriptor_array); | |
| } | |
| } | |
| if (function.IsNull() || | |
| (respect_reflectable && !function.is_reflectable())) { | |
| return ThrowNoSuchMethod(Object::null_string(), function_name, args, | |
| arg_names, InvocationMirror::kTopLevel, | |
| InvocationMirror::kMethod); | |
| } | |
| if (!function.AreValidArguments(args_descriptor, nullptr)) { | |
| return ThrowNoSuchMethod( | |
| String::Handle(function.UserVisibleSignature()), function_name, args, | |
| arg_names, InvocationMirror::kTopLevel, InvocationMirror::kMethod); | |
| } | |
| // This is a static function, so we pass an empty instantiator tav. | |
| ASSERT(function.is_static()); | |
| ObjectPtr type_error = function.DoArgumentTypesMatch( | |
| args, args_descriptor, Object::empty_type_arguments()); | |
| if (type_error != Error::null()) { | |
| return type_error; | |
| } | |
| // The main function of the root library is always callable. | |
| if (check_is_entrypoint && | |
| !IsLookupOfMainFunctionInRootLibrary(*this, function_name)) { | |
| CHECK_ERROR(function.VerifyEntryPoint(EntryPointPragma::kCallOnly)); | |
| } | |
| return DartEntry::InvokeFunction(function, args, args_descriptor_array); | |
| } | |
| void Library::InitNativeWrappersLibrary(IsolateGroup* isolate_group, | |
| bool is_kernel) { | |
| const int kNumNativeWrappersClasses = 4; | |
| COMPILE_ASSERT((kNumNativeWrappersClasses > 0) && | |
| (kNumNativeWrappersClasses < 10)); | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const String& native_flds_lib_url = Symbols::DartNativeWrappers(); | |
| const Library& native_flds_lib = Library::Handle( | |
| zone, Library::NewLibraryHelper(native_flds_lib_url, false)); | |
| const String& native_flds_lib_name = Symbols::DartNativeWrappersLibName(); | |
| native_flds_lib.SetName(native_flds_lib_name); | |
| native_flds_lib.SetLoadRequested(); | |
| native_flds_lib.Register(thread); | |
| native_flds_lib.SetLoadInProgress(); | |
| isolate_group->object_store()->set_native_wrappers_library(native_flds_lib); | |
| const char* const kNativeWrappersClass = "NativeFieldWrapperClass"; | |
| const int kNameLength = 25; | |
| ASSERT(kNameLength == (strlen(kNativeWrappersClass) + 1 + 1)); | |
| char name_buffer[kNameLength]; | |
| String& cls_name = String::Handle(zone); | |
| for (int fld_cnt = 1; fld_cnt <= kNumNativeWrappersClasses; fld_cnt++) { | |
| Utils::SNPrint(name_buffer, kNameLength, "%s%d", kNativeWrappersClass, | |
| fld_cnt); | |
| cls_name = Symbols::New(thread, name_buffer); | |
| Class::NewNativeWrapper(native_flds_lib, cls_name, fld_cnt); | |
| } | |
| // NOTE: If we bootstrap from a Kernel IR file we want to generate the | |
| // synthetic constructors for the native wrapper classes. We leave this up to | |
| // the [KernelLoader] who will take care of it later. | |
| if (!is_kernel) { | |
| native_flds_lib.SetLoaded(); | |
| } | |
| } | |
| // LibraryLookupSet maps URIs to libraries. | |
| class LibraryLookupTraits { | |
| public: | |
| static const char* Name() { return "LibraryLookupTraits"; } | |
| static bool ReportStats() { return false; } | |
| static bool IsMatch(const Object& a, const Object& b) { | |
| const String& a_str = String::Cast(a); | |
| const String& b_str = String::Cast(b); | |
| ASSERT(a_str.HasHash() && b_str.HasHash()); | |
| return a_str.Equals(b_str); | |
| } | |
| static uword Hash(const Object& key) { return String::Cast(key).Hash(); } | |
| static ObjectPtr NewKey(const String& str) { return str.ptr(); } | |
| }; | |
| typedef UnorderedHashMap<LibraryLookupTraits> LibraryLookupMap; | |
| // Returns library with given url in current isolate, or nullptr. | |
| LibraryPtr Library::LookupLibrary(Thread* thread, const String& url) { | |
| Zone* zone = thread->zone(); | |
| ObjectStore* object_store = thread->isolate_group()->object_store(); | |
| // Make sure the URL string has an associated hash code | |
| // to speed up the repeated equality checks. | |
| url.Hash(); | |
| // Use the libraries map to lookup the library by URL. | |
| Library& lib = Library::Handle(zone); | |
| SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| if (object_store->libraries_map() == Array::null()) { | |
| return Library::null(); | |
| } else { | |
| LibraryLookupMap map(object_store->libraries_map()); | |
| lib ^= map.GetOrNull(url); | |
| ASSERT(map.Release().ptr() == object_store->libraries_map()); | |
| } | |
| return lib.ptr(); | |
| } | |
| bool Library::IsPrivate(const String& name) { | |
| if (ShouldBePrivate(name)) return true; | |
| // Factory names: List._fromLiteral. | |
| for (intptr_t i = 1; i < name.Length() - 1; i++) { | |
| if (name.CharAt(i) == '.') { | |
| if (name.CharAt(i + 1) == '_') { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| // Create a private key for this library. It is based on the hash of the | |
| // library URI and the sequence number of the library to guarantee unique | |
| // private keys without having to verify. | |
| void Library::AllocatePrivateKey() const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| auto isolate_group = thread->isolate_group(); | |
| #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| if (isolate_group->IsReloading()) { | |
| // When reloading, we need to make sure we use the original private key | |
| // if this library previously existed. | |
| ProgramReloadContext* program_reload_context = | |
| isolate_group->program_reload_context(); | |
| const String& original_key = | |
| String::Handle(program_reload_context->FindLibraryPrivateKey(*this)); | |
| if (!original_key.IsNull()) { | |
| untag()->set_private_key(original_key.ptr()); | |
| return; | |
| } | |
| } | |
| #endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| // Format of the private key is: "@<sequence number><6 digits of hash> | |
| const intptr_t hash_mask = 0x7FFFF; | |
| const String& url = String::Handle(zone, this->url()); | |
| intptr_t hash_value = url.Hash() & hash_mask; | |
| const GrowableObjectArray& libs = GrowableObjectArray::Handle( | |
| zone, isolate_group->object_store()->libraries()); | |
| intptr_t sequence_value = libs.Length(); | |
| char private_key[32]; | |
| Utils::SNPrint(private_key, sizeof(private_key), "%c%" Pd "%06" Pd "", | |
| kPrivateKeySeparator, sequence_value, hash_value); | |
| const String& key = | |
| String::Handle(zone, String::New(private_key, Heap::kOld)); | |
| key.Hash(); // This string may end up in the VM isolate. | |
| untag()->set_private_key(key.ptr()); | |
| } | |
| const String& Library::PrivateCoreLibName(const String& member) { | |
| const Library& core_lib = Library::Handle(Library::CoreLibrary()); | |
| const String& private_name = String::ZoneHandle(core_lib.PrivateName(member)); | |
| return private_name; | |
| } | |
| bool Library::IsPrivateCoreLibName(const String& name, const String& member) { | |
| Zone* zone = Thread::Current()->zone(); | |
| const auto& core_lib = Library::Handle(zone, Library::CoreLibrary()); | |
| const auto& private_key = String::Handle(zone, core_lib.private_key()); | |
| ASSERT(core_lib.IsPrivate(member)); | |
| return name.EqualsConcat(member, private_key); | |
| } | |
| ClassPtr Library::LookupCoreClass(const String& class_name) { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const Library& core_lib = Library::Handle(zone, Library::CoreLibrary()); | |
| String& name = String::Handle(zone, class_name.ptr()); | |
| if (class_name.CharAt(0) == kPrivateIdentifierStart) { | |
| // Private identifiers are mangled on a per library basis. | |
| name = Symbols::FromConcat(thread, name, | |
| String::Handle(zone, core_lib.private_key())); | |
| } | |
| return core_lib.LookupClass(name); | |
| } | |
| // Cannot handle qualified names properly as it only appends private key to | |
| // the end (e.g. _Alfa.foo -> _Alfa.foo@...). | |
| StringPtr Library::PrivateName(const String& name) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| ASSERT(IsPrivate(name)); | |
| // ASSERT(strchr(name, '@') == nullptr); | |
| String& str = String::Handle(zone); | |
| str = name.ptr(); | |
| str = Symbols::FromConcat(thread, str, | |
| String::Handle(zone, this->private_key())); | |
| return str.ptr(); | |
| } | |
| LibraryPtr Library::GetLibrary(intptr_t index) { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| auto isolate_group = thread->isolate_group(); | |
| const GrowableObjectArray& libs = GrowableObjectArray::Handle( | |
| zone, isolate_group->object_store()->libraries()); | |
| ASSERT(!libs.IsNull()); | |
| if ((0 <= index) && (index < libs.Length())) { | |
| Library& lib = Library::Handle(zone); | |
| lib ^= libs.At(index); | |
| return lib.ptr(); | |
| } | |
| return Library::null(); | |
| } | |
| void Library::Register(Thread* thread) const { | |
| Zone* zone = thread->zone(); | |
| auto isolate_group = thread->isolate_group(); | |
| ObjectStore* object_store = isolate_group->object_store(); | |
| // A library is "registered" in two places: | |
| // - A growable array mapping from index to library. | |
| const String& lib_url = String::Handle(zone, url()); | |
| ASSERT(Library::LookupLibrary(thread, lib_url) == Library::null()); | |
| ASSERT(lib_url.HasHash()); | |
| SafepointWriteRwLocker ml(thread, isolate_group->program_lock()); | |
| GrowableObjectArray& libs = | |
| GrowableObjectArray::Handle(zone, object_store->libraries()); | |
| ASSERT(!libs.IsNull()); | |
| set_index(libs.Length()); | |
| libs.Add(*this); | |
| // - A map from URL string to library. | |
| if (object_store->libraries_map() == Array::null()) { | |
| LibraryLookupMap map(HashTables::New<LibraryLookupMap>(16, Heap::kOld)); | |
| object_store->set_libraries_map(map.Release()); | |
| } | |
| LibraryLookupMap map(object_store->libraries_map()); | |
| bool present = map.UpdateOrInsert(lib_url, *this); | |
| ASSERT(!present); | |
| object_store->set_libraries_map(map.Release()); | |
| } | |
| void Library::RegisterLibraries(Thread* thread, | |
| const GrowableObjectArray& libs) { | |
| Zone* zone = thread->zone(); | |
| auto isolate_group = thread->isolate_group(); | |
| Library& lib = Library::Handle(zone); | |
| String& lib_url = String::Handle(zone); | |
| LibraryLookupMap map(HashTables::New<LibraryLookupMap>(16, Heap::kOld)); | |
| intptr_t len = libs.Length(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| lib ^= libs.At(i); | |
| lib_url = lib.url(); | |
| map.InsertNewOrGetValue(lib_url, lib); | |
| } | |
| // Now remember these in the isolate's object store. | |
| isolate_group->object_store()->set_libraries(libs); | |
| isolate_group->object_store()->set_libraries_map(map.Release()); | |
| } | |
| LibraryPtr Library::AsyncLibrary() { | |
| return IsolateGroup::Current()->object_store()->async_library(); | |
| } | |
| LibraryPtr Library::ConcurrentLibrary() { | |
| return IsolateGroup::Current()->object_store()->concurrent_library(); | |
| } | |
| LibraryPtr Library::ConvertLibrary() { | |
| return IsolateGroup::Current()->object_store()->convert_library(); | |
| } | |
| LibraryPtr Library::CoreLibrary() { | |
| return IsolateGroup::Current()->object_store()->core_library(); | |
| } | |
| LibraryPtr Library::CollectionLibrary() { | |
| return IsolateGroup::Current()->object_store()->collection_library(); | |
| } | |
| LibraryPtr Library::CompactHashLibrary() { | |
| return IsolateGroup::Current()->object_store()->_compact_hash_library(); | |
| } | |
| LibraryPtr Library::DeveloperLibrary() { | |
| return IsolateGroup::Current()->object_store()->developer_library(); | |
| } | |
| LibraryPtr Library::FfiLibrary() { | |
| return IsolateGroup::Current()->object_store()->ffi_library(); | |
| } | |
| LibraryPtr Library::InternalLibrary() { | |
| return IsolateGroup::Current()->object_store()->_internal_library(); | |
| } | |
| LibraryPtr Library::VMLibrary() { | |
| return IsolateGroup::Current()->object_store()->_vm_library(); | |
| } | |
| LibraryPtr Library::IsolateLibrary() { | |
| return IsolateGroup::Current()->object_store()->isolate_library(); | |
| } | |
| LibraryPtr Library::MathLibrary() { | |
| return IsolateGroup::Current()->object_store()->math_library(); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| LibraryPtr Library::MirrorsLibrary() { | |
| return IsolateGroup::Current()->object_store()->mirrors_library(); | |
| } | |
| #endif | |
| LibraryPtr Library::NativeWrappersLibrary() { | |
| return IsolateGroup::Current()->object_store()->native_wrappers_library(); | |
| } | |
| LibraryPtr Library::TypedDataLibrary() { | |
| return IsolateGroup::Current()->object_store()->typed_data_library(); | |
| } | |
| LibraryPtr Library::VMServiceLibrary() { | |
| return IsolateGroup::Current()->object_store()->_vmservice_library(); | |
| } | |
| const char* Library::ToCString() const { | |
| NoSafepointScope no_safepoint; | |
| const String& name = String::Handle(url()); | |
| return OS::SCreate(Thread::Current()->zone(), "Library:'%s'", | |
| name.ToCString()); | |
| } | |
| LibraryPtr LibraryPrefix::GetLibrary(int index) const { | |
| if ((index >= 0) || (index < num_imports())) { | |
| const Array& imports = Array::Handle(this->imports()); | |
| Namespace& import = Namespace::Handle(); | |
| import ^= imports.At(index); | |
| return import.target(); | |
| } | |
| return Library::null(); | |
| } | |
| void LibraryPrefix::AddImport(const Namespace& import) const { | |
| intptr_t num_current_imports = num_imports(); | |
| // Prefixes with deferred libraries can only contain one library. | |
| ASSERT((num_current_imports == 0) || !is_deferred_load()); | |
| // The library needs to be added to the list. | |
| Array& imports = Array::Handle(this->imports()); | |
| const intptr_t length = (imports.IsNull()) ? 0 : imports.Length(); | |
| // Grow the list if it is full. | |
| if (num_current_imports >= length) { | |
| const intptr_t new_length = length + kIncrementSize + (length >> 2); | |
| imports = Array::Grow(imports, new_length, Heap::kOld); | |
| set_imports(imports); | |
| } | |
| imports.SetAt(num_current_imports, import); | |
| set_num_imports(num_current_imports + 1); | |
| } | |
| LibraryPrefixPtr LibraryPrefix::New() { | |
| return Object::Allocate<LibraryPrefix>(Heap::kOld); | |
| } | |
| LibraryPrefixPtr LibraryPrefix::New(const String& name, | |
| const Namespace& import, | |
| bool deferred_load, | |
| const Library& importer) { | |
| const LibraryPrefix& result = LibraryPrefix::Handle(LibraryPrefix::New()); | |
| result.set_name(name); | |
| result.set_num_imports(0); | |
| result.set_importer(importer); | |
| result.StoreNonPointer(&result.untag()->is_deferred_load_, deferred_load); | |
| result.set_imports(Array::Handle(Array::New(kInitialSize))); | |
| result.AddImport(import); | |
| return result.ptr(); | |
| } | |
| void LibraryPrefix::set_name(const String& value) const { | |
| ASSERT(value.IsSymbol()); | |
| untag()->set_name(value.ptr()); | |
| } | |
| void LibraryPrefix::set_imports(const Array& value) const { | |
| untag()->set_imports(value.ptr()); | |
| } | |
| void LibraryPrefix::set_num_imports(intptr_t value) const { | |
| if (!Utils::IsUint(16, value)) { | |
| ReportTooManyImports(Library::Handle(importer())); | |
| } | |
| StoreNonPointer(&untag()->num_imports_, value); | |
| } | |
| void LibraryPrefix::set_importer(const Library& value) const { | |
| untag()->set_importer(value.ptr()); | |
| } | |
| const char* LibraryPrefix::ToCString() const { | |
| const String& prefix = String::Handle(name()); | |
| return prefix.ToCString(); | |
| } | |
| const char* Namespace::ToCString() const { | |
| const Library& lib = Library::Handle(target()); | |
| return OS::SCreate(Thread::Current()->zone(), "Namespace for library '%s'", | |
| lib.ToCString()); | |
| } | |
| bool Namespace::HidesName(const String& name) const { | |
| // Quick check for common case with no combinators. | |
| if (hide_names() == show_names()) { | |
| ASSERT(hide_names() == Array::null()); | |
| return false; | |
| } | |
| const String* plain_name = &name; | |
| if (Field::IsGetterName(name)) { | |
| plain_name = &String::Handle(Field::NameFromGetter(name)); | |
| } else if (Field::IsSetterName(name)) { | |
| plain_name = &String::Handle(Field::NameFromSetter(name)); | |
| } | |
| // Check whether the name is in the list of explicitly hidden names. | |
| if (hide_names() != Array::null()) { | |
| const Array& names = Array::Handle(hide_names()); | |
| String& hidden = String::Handle(); | |
| intptr_t num_names = names.Length(); | |
| for (intptr_t i = 0; i < num_names; i++) { | |
| hidden ^= names.At(i); | |
| if (plain_name->Equals(hidden)) { | |
| return true; | |
| } | |
| } | |
| } | |
| // The name is not explicitly hidden. Now check whether it is in the | |
| // list of explicitly visible names, if there is one. | |
| if (show_names() != Array::null()) { | |
| const Array& names = Array::Handle(show_names()); | |
| String& shown = String::Handle(); | |
| intptr_t num_names = names.Length(); | |
| for (intptr_t i = 0; i < num_names; i++) { | |
| shown ^= names.At(i); | |
| if (plain_name->Equals(shown)) { | |
| return false; | |
| } | |
| } | |
| // There is a list of visible names. The name we're looking for is not | |
| // contained in the list, so it is hidden. | |
| return true; | |
| } | |
| // The name is not filtered out. | |
| return false; | |
| } | |
| // Look up object with given name in library and filter out hidden | |
| // names. Also look up getters and setters. | |
| ObjectPtr Namespace::Lookup(const String& name, | |
| ZoneGrowableArray<intptr_t>* trail) const { | |
| Zone* zone = Thread::Current()->zone(); | |
| const Library& lib = Library::Handle(zone, target()); | |
| if (trail != nullptr) { | |
| // Look for cycle in reexport graph. | |
| for (int i = 0; i < trail->length(); i++) { | |
| if (trail->At(i) == lib.index()) { | |
| for (int j = i + 1; j < trail->length(); j++) { | |
| (*trail)[j] = -1; | |
| } | |
| return Object::null(); | |
| } | |
| } | |
| } | |
| lib.EnsureTopLevelClassIsFinalized(); | |
| intptr_t ignore = 0; | |
| // Lookup the name in the library's symbols. | |
| Object& obj = Object::Handle(zone, lib.LookupEntry(name, &ignore)); | |
| if (!Field::IsGetterName(name) && !Field::IsSetterName(name) && | |
| (obj.IsNull() || obj.IsLibraryPrefix())) { | |
| String& accessor_name = String::Handle(zone); | |
| accessor_name = Field::LookupGetterSymbol(name); | |
| if (!accessor_name.IsNull()) { | |
| obj = lib.LookupEntry(accessor_name, &ignore); | |
| } | |
| if (obj.IsNull()) { | |
| accessor_name = Field::LookupSetterSymbol(name); | |
| if (!accessor_name.IsNull()) { | |
| obj = lib.LookupEntry(accessor_name, &ignore); | |
| } | |
| } | |
| } | |
| if (obj.IsNull() || HidesName(name) || obj.IsLibraryPrefix()) { | |
| return Object::null(); | |
| } | |
| return obj.ptr(); | |
| } | |
| NamespacePtr Namespace::New() { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kNamespaceCid) != | |
| Class::null()); | |
| return Object::Allocate<Namespace>(Heap::kOld); | |
| } | |
| NamespacePtr Namespace::New(const Library& target, | |
| const Array& show_names, | |
| const Array& hide_names, | |
| const Library& owner) { | |
| ASSERT(show_names.IsNull() || (show_names.Length() > 0)); | |
| ASSERT(hide_names.IsNull() || (hide_names.Length() > 0)); | |
| const Namespace& result = Namespace::Handle(Namespace::New()); | |
| result.untag()->set_target(target.ptr()); | |
| result.untag()->set_show_names(show_names.ptr()); | |
| result.untag()->set_hide_names(hide_names.ptr()); | |
| result.untag()->set_owner(owner.ptr()); | |
| return result.ptr(); | |
| } | |
| KernelProgramInfoPtr KernelProgramInfo::New() { | |
| return Object::Allocate<KernelProgramInfo>(Heap::kOld); | |
| } | |
| KernelProgramInfoPtr KernelProgramInfo::New( | |
| const TypedDataBase& kernel_component, | |
| const TypedDataView& string_data, | |
| const TypedDataView& metadata_payloads, | |
| const TypedDataView& metadata_mappings, | |
| const TypedDataView& constants_table, | |
| const TypedData& string_offsets, | |
| const TypedData& canonical_names, | |
| const Array& scripts, | |
| const Array& libraries_cache, | |
| const Array& classes_cache) { | |
| ASSERT(kernel_component.IsExternalOrExternalView()); | |
| ASSERT(string_data.IsExternalOrExternalView()); | |
| ASSERT(metadata_payloads.IsExternalOrExternalView()); | |
| ASSERT(metadata_mappings.IsExternalOrExternalView()); | |
| ASSERT(constants_table.IsExternalOrExternalView()); | |
| const auto& info = KernelProgramInfo::Handle(KernelProgramInfo::New()); | |
| info.untag()->set_kernel_component(kernel_component.ptr()); | |
| info.untag()->set_string_offsets(string_offsets.ptr()); | |
| info.untag()->set_string_data(string_data.ptr()); | |
| info.untag()->set_canonical_names(canonical_names.ptr()); | |
| info.untag()->set_metadata_payloads(metadata_payloads.ptr()); | |
| info.untag()->set_metadata_mappings(metadata_mappings.ptr()); | |
| info.untag()->set_scripts(scripts.ptr()); | |
| info.untag()->set_constants_table(constants_table.ptr()); | |
| info.untag()->set_libraries_cache(libraries_cache.ptr()); | |
| info.untag()->set_classes_cache(classes_cache.ptr()); | |
| return info.ptr(); | |
| } | |
| const char* KernelProgramInfo::ToCString() const { | |
| return "[KernelProgramInfo]"; | |
| } | |
| ScriptPtr KernelProgramInfo::ScriptAt(intptr_t index) const { | |
| const Array& all_scripts = Array::Handle(scripts()); | |
| ObjectPtr script = all_scripts.At(index); | |
| return Script::RawCast(script); | |
| } | |
| void KernelProgramInfo::set_scripts(const Array& scripts) const { | |
| untag()->set_scripts(scripts.ptr()); | |
| } | |
| void KernelProgramInfo::set_constants(const Array& constants) const { | |
| untag()->set_constants(constants.ptr()); | |
| } | |
| intptr_t KernelProgramInfo::KernelLibraryStartOffset( | |
| intptr_t library_index) const { | |
| const auto& blob = TypedDataBase::Handle(kernel_component()); | |
| const intptr_t library_count = | |
| Utils::BigEndianToHost32(LoadUnaligned(reinterpret_cast<uint32_t*>( | |
| blob.DataAddr(blob.LengthInBytes() - 2 * 4)))); | |
| ASSERT((library_index >= 0) && (library_index < library_count)); | |
| const intptr_t library_start = | |
| Utils::BigEndianToHost32(LoadUnaligned(reinterpret_cast<uint32_t*>( | |
| blob.DataAddr(blob.LengthInBytes() - | |
| (2 + 1 + (library_count - library_index)) * 4)))); | |
| return library_start; | |
| } | |
| TypedDataViewPtr KernelProgramInfo::KernelLibrary( | |
| intptr_t library_index) const { | |
| ASSERT(library_index >= 0); | |
| const intptr_t start_offset = KernelLibraryStartOffset(library_index); | |
| const intptr_t end_offset = KernelLibraryEndOffset(library_index); | |
| const auto& component = TypedDataBase::Handle(kernel_component()); | |
| return component.ViewFromTo(start_offset, end_offset); | |
| } | |
| intptr_t KernelProgramInfo::KernelLibraryEndOffset( | |
| intptr_t library_index) const { | |
| const auto& blob = TypedDataBase::Handle(kernel_component()); | |
| const intptr_t library_count = | |
| Utils::BigEndianToHost32(LoadUnaligned(reinterpret_cast<uint32_t*>( | |
| blob.DataAddr(blob.LengthInBytes() - 2 * 4)))); | |
| ASSERT((library_index >= 0) && (library_index < library_count)); | |
| const intptr_t library_end = Utils::BigEndianToHost32( | |
| LoadUnaligned(reinterpret_cast<uint32_t*>(blob.DataAddr( | |
| blob.LengthInBytes() - (2 + (library_count - library_index)) * 4)))); | |
| return library_end; | |
| } | |
| void KernelProgramInfo::set_constants_table(const TypedDataView& value) const { | |
| untag()->set_constants_table(value.ptr()); | |
| } | |
| void KernelProgramInfo::set_libraries_cache(const Array& cache) const { | |
| untag()->set_libraries_cache(cache.ptr()); | |
| } | |
| LibraryPtr KernelProgramInfo::LookupLibrary(Thread* thread, | |
| const Smi& name_index) const { | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_LIBRARY_HANDLESCOPE(thread); | |
| REUSABLE_OBJECT_HANDLESCOPE(thread); | |
| REUSABLE_SMI_HANDLESCOPE(thread); | |
| Array& data = thread->ArrayHandle(); | |
| Library& result = thread->LibraryHandle(); | |
| Object& key = thread->ObjectHandle(); | |
| Smi& value = thread->SmiHandle(); | |
| { | |
| SafepointMutexLocker ml( | |
| thread->isolate_group()->kernel_data_lib_cache_mutex()); | |
| data = libraries_cache(); | |
| ASSERT(!data.IsNull()); | |
| IntHashMap table(&key, &value, &data); | |
| result ^= table.GetOrNull(name_index); | |
| table.Release(); | |
| } | |
| return result.ptr(); | |
| } | |
| LibraryPtr KernelProgramInfo::InsertLibrary(Thread* thread, | |
| const Smi& name_index, | |
| const Library& lib) const { | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_LIBRARY_HANDLESCOPE(thread); | |
| REUSABLE_OBJECT_HANDLESCOPE(thread); | |
| REUSABLE_SMI_HANDLESCOPE(thread); | |
| Array& data = thread->ArrayHandle(); | |
| Library& result = thread->LibraryHandle(); | |
| Object& key = thread->ObjectHandle(); | |
| Smi& value = thread->SmiHandle(); | |
| { | |
| SafepointMutexLocker ml( | |
| thread->isolate_group()->kernel_data_lib_cache_mutex()); | |
| data = libraries_cache(); | |
| ASSERT(!data.IsNull()); | |
| IntHashMap table(&key, &value, &data); | |
| result ^= table.InsertOrGetValue(name_index, lib); | |
| set_libraries_cache(table.Release()); | |
| } | |
| return result.ptr(); | |
| } | |
| void KernelProgramInfo::set_classes_cache(const Array& cache) const { | |
| untag()->set_classes_cache(cache.ptr()); | |
| } | |
| ClassPtr KernelProgramInfo::LookupClass(Thread* thread, | |
| const Smi& name_index) const { | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_CLASS_HANDLESCOPE(thread); | |
| REUSABLE_OBJECT_HANDLESCOPE(thread); | |
| REUSABLE_SMI_HANDLESCOPE(thread); | |
| Array& data = thread->ArrayHandle(); | |
| Class& result = thread->ClassHandle(); | |
| Object& key = thread->ObjectHandle(); | |
| Smi& value = thread->SmiHandle(); | |
| { | |
| SafepointMutexLocker ml( | |
| thread->isolate_group()->kernel_data_class_cache_mutex()); | |
| data = classes_cache(); | |
| ASSERT(!data.IsNull()); | |
| IntHashMap table(&key, &value, &data); | |
| result ^= table.GetOrNull(name_index); | |
| table.Release(); | |
| } | |
| return result.ptr(); | |
| } | |
| ClassPtr KernelProgramInfo::InsertClass(Thread* thread, | |
| const Smi& name_index, | |
| const Class& klass) const { | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| REUSABLE_CLASS_HANDLESCOPE(thread); | |
| REUSABLE_OBJECT_HANDLESCOPE(thread); | |
| REUSABLE_SMI_HANDLESCOPE(thread); | |
| Array& data = thread->ArrayHandle(); | |
| Class& result = thread->ClassHandle(); | |
| Object& key = thread->ObjectHandle(); | |
| Smi& value = thread->SmiHandle(); | |
| { | |
| SafepointMutexLocker ml( | |
| thread->isolate_group()->kernel_data_class_cache_mutex()); | |
| data = classes_cache(); | |
| ASSERT(!data.IsNull()); | |
| IntHashMap table(&key, &value, &data); | |
| result ^= table.InsertOrGetValue(name_index, klass); | |
| set_classes_cache(table.Release()); | |
| } | |
| return result.ptr(); | |
| } | |
| ErrorPtr Library::CompileAll(bool ignore_error /* = false */) { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| Error& error = Error::Handle(zone); | |
| const GrowableObjectArray& libs = GrowableObjectArray::Handle( | |
| IsolateGroup::Current()->object_store()->libraries()); | |
| Library& lib = Library::Handle(zone); | |
| Class& cls = Class::Handle(zone); | |
| for (int i = 0; i < libs.Length(); i++) { | |
| lib ^= libs.At(i); | |
| ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate); | |
| while (it.HasNext()) { | |
| cls = it.GetNextClass(); | |
| error = cls.EnsureIsFinalized(thread); | |
| if (!error.IsNull()) { | |
| if (ignore_error) continue; | |
| return error.ptr(); | |
| } | |
| error = Compiler::CompileAllFunctions(cls); | |
| if (!error.IsNull()) { | |
| if (ignore_error) continue; | |
| return error.ptr(); | |
| } | |
| } | |
| } | |
| Object& result = Object::Handle(zone); | |
| ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& func) { | |
| if (!func.HasCode()) { | |
| result = Compiler::CompileFunction(thread, func); | |
| if (result.IsError()) { | |
| error = Error::Cast(result).ptr(); | |
| return false; // Stop iteration. | |
| } | |
| } | |
| return true; // Continue iteration. | |
| }); | |
| return error.ptr(); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| ErrorPtr Library::FinalizeAllClasses() { | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread->IsDartMutatorThread()); | |
| Zone* zone = thread->zone(); | |
| Error& error = Error::Handle(zone); | |
| const GrowableObjectArray& libs = GrowableObjectArray::Handle( | |
| IsolateGroup::Current()->object_store()->libraries()); | |
| Library& lib = Library::Handle(zone); | |
| Class& cls = Class::Handle(zone); | |
| for (int i = 0; i < libs.Length(); i++) { | |
| lib ^= libs.At(i); | |
| if (!lib.Loaded()) { | |
| String& uri = String::Handle(zone, lib.url()); | |
| String& msg = String::Handle( | |
| zone, | |
| String::NewFormatted("Library '%s' is not loaded. " | |
| "Did you forget to call Dart_FinalizeLoading?", | |
| uri.ToCString())); | |
| return ApiError::New(msg); | |
| } | |
| ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate); | |
| while (it.HasNext()) { | |
| cls = it.GetNextClass(); | |
| error = cls.EnsureIsFinalized(thread); | |
| if (!error.IsNull()) { | |
| return error.ptr(); | |
| } | |
| } | |
| } | |
| return Error::null(); | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| // Return Function::null() if function does not exist in libs. | |
| FunctionPtr Library::GetFunction(const Library& lib, | |
| const char* class_name, | |
| const char* function_name) { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| Function& func = Function::Handle(zone); | |
| String& class_str = String::Handle(zone); | |
| String& func_str = String::Handle(zone); | |
| Class& cls = Class::Handle(zone); | |
| if (strcmp(class_name, "::") == 0) { | |
| cls = lib.toplevel_class(); | |
| } else { | |
| class_str = String::New(class_name); | |
| cls = lib.LookupClassAllowPrivate(class_str); | |
| } | |
| if (!cls.IsNull()) { | |
| if (cls.EnsureIsFinalized(thread) == Error::null()) { | |
| func_str = String::New(function_name); | |
| if (function_name[0] == '.') { | |
| func_str = String::Concat(class_str, func_str); | |
| } | |
| func = cls.LookupFunctionAllowPrivate(func_str); | |
| } | |
| } | |
| return func.ptr(); | |
| } | |
| #if defined(DEBUG) && !defined(DART_PRECOMPILED_RUNTIME) | |
| void Library::CheckFunctionFingerprints() { | |
| Library& lib = Library::Handle(); | |
| Function& func = Function::Handle(); | |
| bool fingerprints_match = true; | |
| #define CHECK_FINGERPRINTS_INNER(library, class_name, function_name, dest, fp, \ | |
| kind) \ | |
| lib = Library::library(); \ | |
| func = Library::GetFunction(lib, #class_name, #function_name); \ | |
| if (func.IsNull()) { \ | |
| fingerprints_match = false; \ | |
| OS::PrintErr("Function not found %s.%s\n", #class_name, #function_name); \ | |
| } else { \ | |
| fingerprints_match = \ | |
| func.CheckSourceFingerprint(fp, kind) && fingerprints_match; \ | |
| } | |
| #define CHECK_FINGERPRINTS(library, class_name, function_name, dest, fp) \ | |
| CHECK_FINGERPRINTS_INNER(library, class_name, function_name, dest, fp, \ | |
| nullptr) | |
| #define CHECK_FINGERPRINTS_ASM_INTRINSIC(library, class_name, function_name, \ | |
| dest, fp) \ | |
| CHECK_FINGERPRINTS_INNER(library, class_name, function_name, dest, fp, \ | |
| "asm-intrinsic") | |
| #define CHECK_FINGERPRINTS_GRAPH_INTRINSIC(library, class_name, function_name, \ | |
| dest, fp) \ | |
| CHECK_FINGERPRINTS_INNER(library, class_name, function_name, dest, fp, \ | |
| "graph-intrinsic") | |
| #define CHECK_FINGERPRINTS_OTHER(library, class_name, function_name, dest, fp) \ | |
| CHECK_FINGERPRINTS_INNER(library, class_name, function_name, dest, fp, \ | |
| "other") | |
| POLYMORPHIC_TARGET_LIST(CHECK_FINGERPRINTS) | |
| ASM_INTRINSICS_LIST(CHECK_FINGERPRINTS_ASM_INTRINSIC) | |
| GRAPH_INTRINSICS_LIST(CHECK_FINGERPRINTS_GRAPH_INTRINSIC) | |
| OTHER_RECOGNIZED_LIST(CHECK_FINGERPRINTS_OTHER) | |
| #undef CHECK_FINGERPRINTS_INNER | |
| #undef CHECK_FINGERPRINTS | |
| #undef CHECK_FINGERPRINTS_ASM_INTRINSIC | |
| #undef CHECK_FINGERPRINTS_GRAPH_INTRINSIC | |
| #undef CHECK_FINGERPRINTS_OTHER | |
| #define CHECK_FACTORY_FINGERPRINTS(symbol, library, class_name, factory_name, \ | |
| cid, fp) \ | |
| lib = Library::library(); \ | |
| func = GetFunction(lib, #class_name, #factory_name); \ | |
| if (func.IsNull()) { \ | |
| fingerprints_match = false; \ | |
| OS::PrintErr("Function not found %s.%s.%s\n", #library, #class_name, \ | |
| #factory_name); \ | |
| } else { \ | |
| fingerprints_match = \ | |
| func.CheckSourceFingerprint(fp) && fingerprints_match; \ | |
| } | |
| RECOGNIZED_LIST_FACTORY_LIST(CHECK_FACTORY_FINGERPRINTS); | |
| #undef CHECK_FACTORY_FINGERPRINTS | |
| if (!fingerprints_match) { | |
| // Private names are mangled. Mangling depends on Library::private_key_. | |
| // If registering a new bootstrap library, add at the end. | |
| FATAL( | |
| "FP mismatch while recognizing methods. If the behavior of " | |
| "these functions has changed, then changes are also needed in " | |
| "the VM's compiler. Otherwise the fingerprint can simply be " | |
| "updated in recognized_methods_list.h\n"); | |
| } | |
| } | |
| #endif // defined(DEBUG) && !defined(DART_PRECOMPILED_RUNTIME). | |
| InstructionsPtr Instructions::New(intptr_t size, | |
| bool has_monomorphic_entry, | |
| bool should_be_aligned) { | |
| ASSERT(size >= 0); | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kInstructionsCid) != | |
| Class::null()); | |
| if (size < 0 || size > kMaxElements) { | |
| // This should be caught before we reach here. | |
| FATAL("Fatal error in Instructions::New: invalid size %" Pd "\n", size); | |
| } | |
| Instructions& result = Instructions::Handle(); | |
| { | |
| auto raw = Object::Allocate<Instructions>(Heap::kCode, size); | |
| NoSafepointScope no_safepoint; | |
| result = raw; | |
| result.SetSize(size); | |
| // Set this within the NoSafepointScope as well since it is contained in | |
| // the same bitfield as the size. | |
| result.SetHasMonomorphicEntry(has_monomorphic_entry); | |
| result.SetShouldBeAligned(should_be_aligned); | |
| } | |
| ASSERT(result.stats() == nullptr); | |
| return result.ptr(); | |
| } | |
| const char* Instructions::ToCString() const { | |
| return "Instructions"; | |
| } | |
| CodeStatistics* Instructions::stats() const { | |
| #if defined(DART_PRECOMPILER) | |
| return reinterpret_cast<CodeStatistics*>( | |
| Thread::Current()->heap()->GetPeer(ptr())); | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| void Instructions::set_stats(CodeStatistics* stats) const { | |
| #if defined(DART_PRECOMPILER) | |
| Thread::Current()->heap()->SetPeer(ptr(), stats); | |
| #endif | |
| } | |
| const char* InstructionsSection::ToCString() const { | |
| return "InstructionsSection"; | |
| } | |
| void InstructionsTable::set_length(intptr_t value) const { | |
| StoreNonPointer(&untag()->length_, value); | |
| } | |
| void InstructionsTable::set_start_pc(uword value) const { | |
| StoreNonPointer(&untag()->start_pc_, value); | |
| } | |
| void InstructionsTable::set_end_pc(uword value) const { | |
| StoreNonPointer(&untag()->end_pc_, value); | |
| } | |
| void InstructionsTable::set_code_objects(const Array& value) const { | |
| untag()->set_code_objects(value.ptr()); | |
| } | |
| void InstructionsTable::set_rodata(uword value) const { | |
| StoreNonPointer( | |
| &untag()->rodata_, | |
| reinterpret_cast<const UntaggedInstructionsTable::Data*>(value)); | |
| } | |
| InstructionsTablePtr InstructionsTable::New(intptr_t length, | |
| uword start_pc, | |
| uword end_pc, | |
| uword rodata) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kInstructionsTableCid) != | |
| Class::null()); | |
| ASSERT(length >= 0); | |
| ASSERT(start_pc <= end_pc); | |
| auto* const zone = Thread::Current()->zone(); | |
| const Array& code_objects = | |
| (length == 0) ? Object::empty_array() | |
| : Array::Handle(zone, Array::New(length, Heap::kOld)); | |
| const auto& result = InstructionsTable::Handle( | |
| zone, Object::Allocate<InstructionsTable>(Heap::kOld)); | |
| result.set_code_objects(code_objects); | |
| result.set_length(length); | |
| result.set_start_pc(start_pc); | |
| result.set_end_pc(end_pc); | |
| result.set_rodata(rodata); | |
| return result.ptr(); | |
| } | |
| void InstructionsTable::SetCodeAt(intptr_t index, CodePtr code) const { | |
| ASSERT((0 <= index) && | |
| (index < Smi::Value(code_objects()->untag()->length()))); | |
| code_objects()->untag()->set_element(index, code); | |
| } | |
| bool InstructionsTable::ContainsPc(InstructionsTablePtr table, uword pc) { | |
| return (InstructionsTable::start_pc(table) <= pc) && | |
| (pc < InstructionsTable::end_pc(table)); | |
| } | |
| uint32_t InstructionsTable::ConvertPcToOffset(InstructionsTablePtr table, | |
| uword pc) { | |
| ASSERT(InstructionsTable::ContainsPc(table, pc)); | |
| const uint32_t pc_offset = | |
| static_cast<uint32_t>(pc - InstructionsTable::start_pc(table)); | |
| ASSERT(InstructionsTable::start_pc(table) + pc_offset == pc); // No overflow. | |
| return pc_offset; | |
| } | |
| intptr_t InstructionsTable::FindEntry(InstructionsTablePtr table, | |
| uword pc, | |
| intptr_t start_index /* = 0 */) { | |
| // This can run in the middle of GC and must not allocate handles. | |
| NoSafepointScope no_safepoint; | |
| if (!InstructionsTable::ContainsPc(table, pc)) return -1; | |
| const uint32_t pc_offset = InstructionsTable::ConvertPcToOffset(table, pc); | |
| const auto rodata = table.untag()->rodata_; | |
| const auto entries = rodata->entries(); | |
| intptr_t lo = start_index; | |
| intptr_t hi = rodata->length - 1; | |
| while (lo <= hi) { | |
| intptr_t mid = (hi - lo + 1) / 2 + lo; | |
| ASSERT(mid >= lo); | |
| ASSERT(mid <= hi); | |
| if (pc_offset < entries[mid].pc_offset) { | |
| hi = mid - 1; | |
| } else if ((mid != hi) && (pc_offset >= entries[mid + 1].pc_offset)) { | |
| lo = mid + 1; | |
| } else { | |
| return mid; | |
| } | |
| } | |
| return -1; | |
| } | |
| const UntaggedCompressedStackMaps::Payload* | |
| InstructionsTable::GetCanonicalStackMap(InstructionsTablePtr table) { | |
| const auto rodata = table.untag()->rodata_; | |
| return rodata->canonical_stack_map_entries_offset != 0 | |
| ? rodata->StackMapAt(rodata->canonical_stack_map_entries_offset) | |
| : nullptr; | |
| } | |
| const UntaggedCompressedStackMaps::Payload* InstructionsTable::FindStackMap( | |
| InstructionsTablePtr table, | |
| uword pc, | |
| uword* start_pc) { | |
| // This can run in the middle of GC and must not allocate handles. | |
| NoSafepointScope no_safepoint; | |
| const intptr_t idx = FindEntry(table, pc); | |
| if (idx != -1) { | |
| const auto rodata = table.untag()->rodata_; | |
| const auto entries = rodata->entries(); | |
| *start_pc = InstructionsTable::start_pc(table) + entries[idx].pc_offset; | |
| return rodata->StackMapAt(entries[idx].stack_map_offset); | |
| } | |
| return nullptr; | |
| } | |
| CodePtr InstructionsTable::FindCode(InstructionsTablePtr table, uword pc) { | |
| // This can run in the middle of GC and must not allocate handles. | |
| NoSafepointScope no_safepoint; | |
| if (!InstructionsTable::ContainsPc(table, pc)) return Code::null(); | |
| const auto rodata = table.untag()->rodata_; | |
| const auto pc_offset = InstructionsTable::ConvertPcToOffset(table, pc); | |
| if (pc_offset <= rodata->entries()[rodata->first_entry_with_code].pc_offset) { | |
| return StubCode::UnknownDartCode().ptr(); | |
| } | |
| const auto idx = | |
| FindEntry(table, pc, table.untag()->rodata_->first_entry_with_code); | |
| if (idx != -1) { | |
| const intptr_t code_index = idx - rodata->first_entry_with_code; | |
| ASSERT(code_index >= 0); | |
| ASSERT(code_index < | |
| Smi::Value(table.untag()->code_objects()->untag()->length())); | |
| ObjectPtr result = | |
| table.untag()->code_objects()->untag()->element(code_index); | |
| ASSERT(result->IsCode()); | |
| // Note: can't use Code::RawCast(...) here because it allocates handles | |
| // in DEBUG mode. | |
| return static_cast<CodePtr>(result); | |
| } | |
| return Code::null(); | |
| } | |
| uword InstructionsTable::EntryPointAt(intptr_t code_index) const { | |
| ASSERT(0 <= code_index); | |
| ASSERT(code_index < static_cast<intptr_t>(rodata()->length)); | |
| return InstructionsTable::start_pc(this->ptr()) + | |
| rodata()->entries()[code_index].pc_offset; | |
| } | |
| const char* InstructionsTable::ToCString() const { | |
| return "InstructionsTable"; | |
| } | |
| ObjectPoolPtr ObjectPool::New(intptr_t len) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kObjectPoolCid) != | |
| Class::null()); | |
| if (len < 0 || len > kMaxElements) { | |
| // This should be caught before we reach here. | |
| FATAL("Fatal error in ObjectPool::New: invalid length %" Pd "\n", len); | |
| } | |
| // We only verify the entry bits in DEBUG, so only allocate a handle there. | |
| DEBUG_ONLY(auto& result = ObjectPool::Handle()); | |
| auto raw = Object::Allocate<ObjectPool>(Heap::kOld, len); | |
| NoSafepointScope no_safepoint; | |
| raw->untag()->length_ = len; | |
| #if defined(DEBUG) | |
| result = raw; | |
| for (intptr_t i = 0; i < len; i++) { | |
| // Verify that InitializeObject() already set the payload as expected. | |
| ASSERT_EQUAL(result.PatchableAt(i), ObjectPool::Patchability::kPatchable); | |
| ASSERT_EQUAL(result.TypeAt(i), ObjectPool::EntryType::kImmediate); | |
| ASSERT_EQUAL(result.RawValueAt(i), 0); | |
| } | |
| #endif | |
| return raw; | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| ObjectPoolPtr ObjectPool::NewFromBuilder( | |
| const compiler::ObjectPoolBuilder& builder) { | |
| const intptr_t len = builder.CurrentLength(); | |
| if (len == 0) { | |
| return Object::empty_object_pool().ptr(); | |
| } | |
| const ObjectPool& result = ObjectPool::Handle(ObjectPool::New(len)); | |
| for (intptr_t i = 0; i < len; i++) { | |
| auto entry = builder.EntryAt(i); | |
| auto type = entry.type(); | |
| auto patchable = entry.patchable(); | |
| auto snapshot_behavior = entry.snapshot_behavior(); | |
| result.SetTypeAt(i, type, patchable, snapshot_behavior); | |
| if (type == EntryType::kTaggedObject) { | |
| result.SetObjectAt(i, *entry.obj_); | |
| } else { | |
| #if defined(TARGET_ARCH_IS_32_BIT) | |
| ASSERT(type != EntryType::kImmediate64); | |
| #endif | |
| ASSERT(type != EntryType::kImmediate128); | |
| result.SetRawValueAt(i, entry.imm_); | |
| } | |
| } | |
| return result.ptr(); | |
| } | |
| void ObjectPool::CopyInto(compiler::ObjectPoolBuilder* builder) const { | |
| ASSERT(builder->CurrentLength() == 0); | |
| for (intptr_t i = 0; i < Length(); i++) { | |
| auto type = TypeAt(i); | |
| auto patchable = PatchableAt(i); | |
| auto snapshot_behavior = SnapshotBehaviorAt(i); | |
| switch (type) { | |
| case compiler::ObjectPoolBuilderEntry::kTaggedObject: { | |
| compiler::ObjectPoolBuilderEntry entry(&Object::ZoneHandle(ObjectAt(i)), | |
| patchable, snapshot_behavior); | |
| builder->AddObject(entry); | |
| break; | |
| } | |
| case compiler::ObjectPoolBuilderEntry::kImmediate: | |
| case compiler::ObjectPoolBuilderEntry::kNativeFunction: { | |
| compiler::ObjectPoolBuilderEntry entry(RawValueAt(i), type, patchable, | |
| snapshot_behavior); | |
| builder->AddObject(entry); | |
| break; | |
| } | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| ASSERT(builder->CurrentLength() == Length()); | |
| } | |
| #endif | |
| const char* ObjectPool::ToCString() const { | |
| Zone* zone = Thread::Current()->zone(); | |
| return zone->PrintToString("ObjectPool len:%" Pd, Length()); | |
| } | |
| void ObjectPool::DebugPrint() const { | |
| THR_Print("ObjectPool len:%" Pd " {\n", Length()); | |
| for (intptr_t i = 0; i < Length(); i++) { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| intptr_t offset = ObjectPool::element_offset(i); | |
| #else | |
| intptr_t offset = compiler::target::ObjectPool::element_offset(i); | |
| #endif | |
| #if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) | |
| THR_Print(" %" Pd "(pp) ", offset); // PP is untagged | |
| #elif defined(TARGET_ARCH_ARM64) | |
| THR_Print(" [pp, #%" Pd "] ", offset); // PP is untagged | |
| #elif defined(TARGET_ARCH_ARM32) | |
| THR_Print(" [pp, #%" Pd "] ", offset - kHeapObjectTag); // PP is tagged | |
| #else | |
| THR_Print(" [pp+0x%" Px "] ", offset - kHeapObjectTag); // PP is tagged | |
| #endif | |
| if (TypeAt(i) == EntryType::kTaggedObject) { | |
| const Object& obj = Object::Handle(ObjectAt(i)); | |
| THR_Print("%s (obj)\n", obj.ToCString()); | |
| } else if (TypeAt(i) == EntryType::kNativeFunction) { | |
| uword pc = RawValueAt(i); | |
| uintptr_t start = 0; | |
| const char* name = NativeSymbolResolver::LookupSymbolName(pc, &start); | |
| const char* dso_name; | |
| uword dso_base; | |
| if (name != nullptr) { | |
| THR_Print("%s (native function)\n", name); | |
| NativeSymbolResolver::FreeSymbolName(name); | |
| } else if (NativeSymbolResolver::LookupSharedObject(pc, &dso_base, | |
| &dso_name)) { | |
| uword dso_offset = pc - dso_base; | |
| THR_Print("%s+0x%" Px " (native function)\n", dso_name, dso_offset); | |
| NativeSymbolResolver::FreeSymbolName(dso_name); | |
| } else { | |
| THR_Print("0x%" Px " (native function)\n", pc); | |
| } | |
| } else { | |
| THR_Print("0x%" Px " (raw)\n", RawValueAt(i)); | |
| } | |
| } | |
| THR_Print("}\n"); | |
| } | |
| intptr_t PcDescriptors::Length() const { | |
| return untag()->length_; | |
| } | |
| void PcDescriptors::SetLength(intptr_t value) const { | |
| StoreNonPointer(&untag()->length_, value); | |
| } | |
| void PcDescriptors::CopyData(const void* bytes, intptr_t size) { | |
| NoSafepointScope no_safepoint; | |
| uint8_t* data = UnsafeMutableNonPointer(&untag()->data()[0]); | |
| // We're guaranteed these memory spaces do not overlap. | |
| memcpy(data, bytes, size); // NOLINT | |
| } | |
| PcDescriptorsPtr PcDescriptors::New(const void* delta_encoded_data, | |
| intptr_t size) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kPcDescriptorsCid) != | |
| Class::null()); | |
| Thread* thread = Thread::Current(); | |
| PcDescriptors& result = PcDescriptors::Handle(thread->zone()); | |
| { | |
| auto raw = Object::Allocate<PcDescriptors>(Heap::kOld, size); | |
| NoSafepointScope no_safepoint; | |
| result = raw; | |
| result.SetLength(size); | |
| } | |
| result.CopyData(delta_encoded_data, size); | |
| return result.ptr(); | |
| } | |
| PcDescriptorsPtr PcDescriptors::New(intptr_t length) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kPcDescriptorsCid) != | |
| Class::null()); | |
| Thread* thread = Thread::Current(); | |
| PcDescriptors& result = PcDescriptors::Handle(thread->zone()); | |
| { | |
| auto raw = Object::Allocate<PcDescriptors>(Heap::kOld, length); | |
| NoSafepointScope no_safepoint; | |
| result = raw; | |
| result.SetLength(length); | |
| } | |
| return result.ptr(); | |
| } | |
| const char* PcDescriptors::KindToCString(UntaggedPcDescriptors::Kind kind) { | |
| switch (kind) { | |
| case UntaggedPcDescriptors::kDeopt: | |
| return "deopt"; | |
| case UntaggedPcDescriptors::kIcCall: | |
| return "ic-call"; | |
| case UntaggedPcDescriptors::kUnoptStaticCall: | |
| return "unopt-call"; | |
| case UntaggedPcDescriptors::kRuntimeCall: | |
| return "runtime-call"; | |
| case UntaggedPcDescriptors::kOsrEntry: | |
| return "osr-entry"; | |
| case UntaggedPcDescriptors::kRewind: | |
| return "rewind"; | |
| case UntaggedPcDescriptors::kOther: | |
| return "other"; | |
| case UntaggedPcDescriptors::kAnyKind: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| UNREACHABLE(); | |
| return ""; | |
| } | |
| void PcDescriptors::WriteToBuffer(BaseTextBuffer* buffer, uword base) const { | |
| // 4 bits per hex digit. | |
| const int addr_width = kBitsPerWord / 4; | |
| // "*" in a printf format specifier tells it to read the field width from | |
| // the printf argument list. | |
| buffer->Printf( | |
| "%-*s kind deopt-id tok-ix try-ix yield-idx\n", | |
| addr_width, "pc"); | |
| Iterator iter(*this, UntaggedPcDescriptors::kAnyKind); | |
| while (iter.MoveNext()) { | |
| buffer->Printf( | |
| "%#-*" Px " %-13s % 8" Pd " %-10s % 8" Pd " % 8" Pd "\n", | |
| addr_width, base + iter.PcOffset(), KindToCString(iter.Kind()), | |
| iter.DeoptId(), iter.TokenPos().ToCString(), iter.TryIndex(), | |
| iter.YieldIndex()); | |
| } | |
| } | |
| const char* PcDescriptors::ToCString() const { | |
| if (Length() == 0) { | |
| return "empty PcDescriptors"; | |
| } | |
| ZoneTextBuffer buffer(Thread::Current()->zone()); | |
| WriteToBuffer(&buffer, /*base=*/0); | |
| return buffer.buffer(); | |
| } | |
| // Verify assumptions (in debug mode only). | |
| // - No two deopt descriptors have the same deoptimization id. | |
| // - No two ic-call descriptors have the same deoptimization id (type feedback). | |
| // A function without unique ids is marked as non-optimizable (e.g., because of | |
| // finally blocks). | |
| void PcDescriptors::Verify(const Function& function) const { | |
| #if defined(DEBUG) | |
| // Only check ids for unoptimized code that is optimizable. | |
| if (!function.IsOptimizable()) { | |
| return; | |
| } | |
| intptr_t max_deopt_id = 0; | |
| Iterator max_iter( | |
| *this, UntaggedPcDescriptors::kDeopt | UntaggedPcDescriptors::kIcCall); | |
| while (max_iter.MoveNext()) { | |
| if (max_iter.DeoptId() > max_deopt_id) { | |
| max_deopt_id = max_iter.DeoptId(); | |
| } | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| BitVector* deopt_ids = new (zone) BitVector(zone, max_deopt_id + 1); | |
| BitVector* iccall_ids = new (zone) BitVector(zone, max_deopt_id + 1); | |
| Iterator iter(*this, | |
| UntaggedPcDescriptors::kDeopt | UntaggedPcDescriptors::kIcCall); | |
| while (iter.MoveNext()) { | |
| // 'deopt_id' is set for kDeopt and kIcCall and must be unique for one kind. | |
| if (DeoptId::IsDeoptAfter(iter.DeoptId())) { | |
| // TODO(vegorov): some instructions contain multiple calls and have | |
| // multiple "after" targets recorded. Right now it is benign but might | |
| // lead to issues in the future. Fix that and enable verification. | |
| continue; | |
| } | |
| if (iter.Kind() == UntaggedPcDescriptors::kDeopt) { | |
| ASSERT(!deopt_ids->Contains(iter.DeoptId())); | |
| deopt_ids->Add(iter.DeoptId()); | |
| } else { | |
| ASSERT(!iccall_ids->Contains(iter.DeoptId())); | |
| iccall_ids->Add(iter.DeoptId()); | |
| } | |
| } | |
| #endif // DEBUG | |
| } | |
| void CodeSourceMap::SetLength(intptr_t value) const { | |
| StoreNonPointer(&untag()->length_, value); | |
| } | |
| CodeSourceMapPtr CodeSourceMap::New(intptr_t length) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kCodeSourceMapCid) != | |
| Class::null()); | |
| Thread* thread = Thread::Current(); | |
| CodeSourceMap& result = CodeSourceMap::Handle(thread->zone()); | |
| { | |
| auto raw = Object::Allocate<CodeSourceMap>(Heap::kOld, length); | |
| NoSafepointScope no_safepoint; | |
| result = raw; | |
| result.SetLength(length); | |
| } | |
| return result.ptr(); | |
| } | |
| const char* CodeSourceMap::ToCString() const { | |
| return "CodeSourceMap"; | |
| } | |
| uword CompressedStackMaps::Hash() const { | |
| NoSafepointScope scope; | |
| uint8_t* data = UnsafeMutableNonPointer(&untag()->payload()->data()[0]); | |
| uint8_t* end = data + payload_size(); | |
| uint32_t hash = payload_size(); | |
| for (uint8_t* cursor = data; cursor < end; cursor++) { | |
| hash = CombineHashes(hash, *cursor); | |
| } | |
| return FinalizeHash(hash, kHashBits); | |
| } | |
| void CompressedStackMaps::WriteToBuffer(BaseTextBuffer* buffer, | |
| uword base, | |
| const char* separator) const { | |
| auto it = iterator(Thread::Current()); | |
| bool first_entry = true; | |
| while (it.MoveNext()) { | |
| if (!first_entry) { | |
| buffer->AddString(separator); | |
| } | |
| buffer->Printf("0x%.8" Px ": ", base + it.pc_offset()); | |
| for (intptr_t i = 0, n = it.Length(); i < n; i++) { | |
| buffer->AddString(it.IsObject(i) ? "1" : "0"); | |
| } | |
| first_entry = false; | |
| } | |
| } | |
| CompressedStackMaps::Iterator<CompressedStackMaps> | |
| CompressedStackMaps::iterator(Thread* thread) const { | |
| return Iterator<CompressedStackMaps>( | |
| *this, CompressedStackMaps::Handle( | |
| thread->zone(), thread->isolate_group() | |
| ->object_store() | |
| ->canonicalized_stack_map_entries())); | |
| } | |
| CompressedStackMapsPtr CompressedStackMaps::New(const void* payload, | |
| intptr_t size, | |
| bool is_global_table, | |
| bool uses_global_table) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kCompressedStackMapsCid) != | |
| Class::null()); | |
| // We don't currently allow both flags to be true. | |
| ASSERT(!is_global_table || !uses_global_table); | |
| // The canonical empty instance should be used instead. | |
| ASSERT(size != 0); | |
| if (!UntaggedCompressedStackMaps::SizeField::is_valid(size)) { | |
| FATAL( | |
| "Fatal error in CompressedStackMaps::New: " | |
| "invalid payload size %" Pu "\n", | |
| size); | |
| } | |
| auto& result = CompressedStackMaps::Handle(); | |
| { | |
| // CompressedStackMaps data objects are associated with a code object, | |
| // allocate them in old generation. | |
| auto raw = Object::Allocate<CompressedStackMaps>(Heap::kOld, size); | |
| NoSafepointScope no_safepoint; | |
| result = raw; | |
| result.untag()->payload()->set_flags_and_size( | |
| UntaggedCompressedStackMaps::GlobalTableBit::encode(is_global_table) | | |
| UntaggedCompressedStackMaps::UsesTableBit::encode(uses_global_table) | | |
| UntaggedCompressedStackMaps::SizeField::encode(size)); | |
| // Perform the copy under the NoSafepointScope since it uses a raw pointer | |
| // to the payload, and so the object should not move during the copy. | |
| auto cursor = | |
| result.UnsafeMutableNonPointer(result.untag()->payload()->data()); | |
| memcpy(cursor, payload, size); // NOLINT | |
| } | |
| ASSERT(!result.IsGlobalTable() || !result.UsesGlobalTable()); | |
| return result.ptr(); | |
| } | |
| const char* CompressedStackMaps::ToCString() const { | |
| ASSERT(!IsGlobalTable()); | |
| if (payload_size() == 0) { | |
| return "CompressedStackMaps()"; | |
| } | |
| auto const t = Thread::Current(); | |
| ZoneTextBuffer buffer(t->zone(), 100); | |
| buffer.AddString("CompressedStackMaps("); | |
| WriteToBuffer(&buffer, /*base=*/0, ", "); | |
| buffer.AddString(")"); | |
| return buffer.buffer(); | |
| } | |
| StringPtr LocalVarDescriptors::GetName(intptr_t var_index) const { | |
| ASSERT(var_index < Length()); | |
| return LocalVarDescriptor::Handle(ptr()->untag()->descriptor(var_index)) | |
| .name(); | |
| } | |
| const char* LocalVarDescriptor::ToCString() const { | |
| return "LocalVarDescriptor"; | |
| } | |
| void LocalVarDescriptor::set_name(const String& value) const { | |
| untag()->set_name(value.ptr()); | |
| } | |
| void LocalVarDescriptor::set_static_type(const AbstractType& value) const { | |
| untag()->set_static_type(value.ptr()); | |
| } | |
| LocalVarDescriptorPtr LocalVarDescriptor::New(const String& name, | |
| const AbstractType& static_type, | |
| Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kLocalVarDescriptorCid) != | |
| Class::null()); | |
| LocalVarDescriptor& result = LocalVarDescriptor::Handle(); | |
| { | |
| ObjectPtr raw = Object::Allocate<LocalVarDescriptor>(space); | |
| NoSafepointScope no_safepoint; | |
| result ^= raw; | |
| } | |
| result.set_name(name); | |
| result.set_static_type(static_type); | |
| return result.ptr(); | |
| } | |
| void LocalVarDescriptors::SetVar( | |
| intptr_t var_index, | |
| const String& name, | |
| const AbstractType& static_type, | |
| UntaggedLocalVarDescriptors::VarInfo* info) const { | |
| ASSERT(var_index < Length()); | |
| const LocalVarDescriptor& desc = LocalVarDescriptor::Handle( | |
| LocalVarDescriptor::New(name, static_type, Heap::kOld)); | |
| ptr()->untag()->set_descriptor(var_index, desc.ptr()); | |
| ptr()->untag()->data()[var_index] = *info; | |
| } | |
| AbstractTypePtr LocalVarDescriptors::GetStaticType(intptr_t var_index) const { | |
| ASSERT(var_index < Length()); | |
| return LocalVarDescriptor::Handle(ptr()->untag()->descriptor(var_index)) | |
| .static_type(); | |
| } | |
| void LocalVarDescriptors::GetInfo( | |
| intptr_t var_index, | |
| UntaggedLocalVarDescriptors::VarInfo* info) const { | |
| ASSERT(var_index < Length()); | |
| *info = ptr()->untag()->data()[var_index]; | |
| } | |
| static int PrintVarInfo(char* buffer, | |
| int len, | |
| intptr_t i, | |
| const String& var_name, | |
| const UntaggedLocalVarDescriptors::VarInfo& info) { | |
| const UntaggedLocalVarDescriptors::VarInfoKind kind = info.kind(); | |
| const int32_t index = info.index(); | |
| if (kind == UntaggedLocalVarDescriptors::kContextLevel) { | |
| return Utils::SNPrint(buffer, len, | |
| "%2" Pd | |
| " %-13s level=%-3d" | |
| " begin=%-3d end=%d\n", | |
| i, LocalVarDescriptors::KindToCString(kind), index, | |
| static_cast<int>(info.begin_pos.Pos()), | |
| static_cast<int>(info.end_pos.Pos())); | |
| } else if (kind == UntaggedLocalVarDescriptors::kContextVar) { | |
| return Utils::SNPrint( | |
| buffer, len, | |
| "%2" Pd | |
| " %-13s level=%-3d index=%-3d" | |
| " begin=%-3d end=%-3d name=%s\n", | |
| i, LocalVarDescriptors::KindToCString(kind), info.scope_id, index, | |
| static_cast<int>(info.begin_pos.Pos()), | |
| static_cast<int>(info.end_pos.Pos()), var_name.ToCString()); | |
| } else { | |
| return Utils::SNPrint( | |
| buffer, len, | |
| "%2" Pd | |
| " %-13s scope=%-3d index=%-3d" | |
| " begin=%-3d end=%-3d name=%s\n", | |
| i, LocalVarDescriptors::KindToCString(kind), info.scope_id, index, | |
| static_cast<int>(info.begin_pos.Pos()), | |
| static_cast<int>(info.end_pos.Pos()), var_name.ToCString()); | |
| } | |
| } | |
| const char* LocalVarDescriptors::ToCString() const { | |
| if (IsNull()) { | |
| return "LocalVarDescriptors: null"; | |
| } | |
| if (Length() == 0) { | |
| return "empty LocalVarDescriptors"; | |
| } | |
| intptr_t len = 1; // Trailing '\0'. | |
| String& var_name = String::Handle(); | |
| for (intptr_t i = 0; i < Length(); i++) { | |
| UntaggedLocalVarDescriptors::VarInfo info; | |
| var_name = GetName(i); | |
| GetInfo(i, &info); | |
| len += PrintVarInfo(nullptr, 0, i, var_name, info); | |
| } | |
| char* buffer = Thread::Current()->zone()->Alloc<char>(len + 1); | |
| buffer[0] = '\0'; | |
| intptr_t num_chars = 0; | |
| for (intptr_t i = 0; i < Length(); i++) { | |
| UntaggedLocalVarDescriptors::VarInfo info; | |
| var_name = GetName(i); | |
| GetInfo(i, &info); | |
| num_chars += PrintVarInfo((buffer + num_chars), (len - num_chars), i, | |
| var_name, info); | |
| } | |
| return buffer; | |
| } | |
| const char* LocalVarDescriptors::KindToCString( | |
| UntaggedLocalVarDescriptors::VarInfoKind kind) { | |
| switch (kind) { | |
| case UntaggedLocalVarDescriptors::kStackVar: | |
| return "StackVar"; | |
| case UntaggedLocalVarDescriptors::kContextVar: | |
| return "ContextVar"; | |
| case UntaggedLocalVarDescriptors::kContextLevel: | |
| return "ContextLevel"; | |
| case UntaggedLocalVarDescriptors::kSavedCurrentContext: | |
| return "CurrentCtx"; | |
| default: | |
| UNIMPLEMENTED(); | |
| return nullptr; | |
| } | |
| } | |
| LocalVarDescriptorsPtr LocalVarDescriptors::New(intptr_t num_variables) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kLocalVarDescriptorsCid) != | |
| Class::null()); | |
| if (num_variables < 0 || num_variables > kMaxElements) { | |
| // This should be caught before we reach here. | |
| FATAL( | |
| "Fatal error in LocalVarDescriptors::New: " | |
| "invalid num_variables %" Pd ". Maximum is: %d\n", | |
| num_variables, UntaggedLocalVarDescriptors::VarInfo::kMaxIndex); | |
| } | |
| auto raw = Object::Allocate<LocalVarDescriptors>(Heap::kOld, num_variables); | |
| NoSafepointScope no_safepoint; | |
| raw->untag()->num_entries_ = num_variables; | |
| return raw; | |
| } | |
| intptr_t LocalVarDescriptors::Length() const { | |
| return untag()->num_entries_; | |
| } | |
| intptr_t ExceptionHandlers::num_entries() const { | |
| return untag()->num_entries(); | |
| } | |
| bool ExceptionHandlers::has_async_handler() const { | |
| return UntaggedExceptionHandlers::AsyncHandlerBit::decode( | |
| untag()->packed_fields_); | |
| } | |
| void ExceptionHandlers::set_has_async_handler(bool value) const { | |
| StoreNonPointer(&untag()->packed_fields_, | |
| UntaggedExceptionHandlers::AsyncHandlerBit::update( | |
| value, untag()->packed_fields_)); | |
| } | |
| void ExceptionHandlers::SetHandlerInfo(intptr_t try_index, | |
| intptr_t outer_try_index, | |
| uword handler_pc_offset, | |
| bool needs_stacktrace, | |
| bool has_catch_all, | |
| bool is_generated) const { | |
| ASSERT((try_index >= 0) && (try_index < num_entries())); | |
| NoSafepointScope no_safepoint; | |
| ExceptionHandlerInfo* info = | |
| UnsafeMutableNonPointer(&untag()->data()[try_index]); | |
| info->outer_try_index = outer_try_index; | |
| // Some C compilers warn about the comparison always being true when using <= | |
| // due to limited range of data type. | |
| ASSERT((handler_pc_offset == static_cast<uword>(kMaxUint32)) || | |
| (handler_pc_offset < static_cast<uword>(kMaxUint32))); | |
| info->handler_pc_offset = handler_pc_offset; | |
| info->needs_stacktrace = static_cast<int8_t>(needs_stacktrace); | |
| info->has_catch_all = static_cast<int8_t>(has_catch_all); | |
| info->is_generated = static_cast<int8_t>(is_generated); | |
| } | |
| void ExceptionHandlers::GetHandlerInfo(intptr_t try_index, | |
| ExceptionHandlerInfo* info) const { | |
| ASSERT((try_index >= 0) && (try_index < num_entries())); | |
| ASSERT(info != nullptr); | |
| *info = untag()->data()[try_index]; | |
| } | |
| uword ExceptionHandlers::HandlerPCOffset(intptr_t try_index) const { | |
| ASSERT((try_index >= 0) && (try_index < num_entries())); | |
| return untag()->data()[try_index].handler_pc_offset; | |
| } | |
| intptr_t ExceptionHandlers::OuterTryIndex(intptr_t try_index) const { | |
| ASSERT((try_index >= 0) && (try_index < num_entries())); | |
| return untag()->data()[try_index].outer_try_index; | |
| } | |
| bool ExceptionHandlers::NeedsStackTrace(intptr_t try_index) const { | |
| ASSERT((try_index >= 0) && (try_index < num_entries())); | |
| return untag()->data()[try_index].needs_stacktrace != 0; | |
| } | |
| bool ExceptionHandlers::IsGenerated(intptr_t try_index) const { | |
| ASSERT((try_index >= 0) && (try_index < num_entries())); | |
| return untag()->data()[try_index].is_generated != 0; | |
| } | |
| bool ExceptionHandlers::HasCatchAll(intptr_t try_index) const { | |
| ASSERT((try_index >= 0) && (try_index < num_entries())); | |
| return untag()->data()[try_index].has_catch_all != 0; | |
| } | |
| void ExceptionHandlers::SetHandledTypes(intptr_t try_index, | |
| const Array& handled_types) const { | |
| ASSERT((try_index >= 0) && (try_index < num_entries())); | |
| ASSERT(!handled_types.IsNull()); | |
| const Array& handled_types_data = | |
| Array::Handle(untag()->handled_types_data()); | |
| handled_types_data.SetAt(try_index, handled_types); | |
| } | |
| ArrayPtr ExceptionHandlers::GetHandledTypes(intptr_t try_index) const { | |
| ASSERT((try_index >= 0) && (try_index < num_entries())); | |
| Array& array = Array::Handle(untag()->handled_types_data()); | |
| array ^= array.At(try_index); | |
| return array.ptr(); | |
| } | |
| void ExceptionHandlers::set_handled_types_data(const Array& value) const { | |
| untag()->set_handled_types_data(value.ptr()); | |
| } | |
| ExceptionHandlersPtr ExceptionHandlers::New(intptr_t num_handlers) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kExceptionHandlersCid) != | |
| Class::null()); | |
| if ((num_handlers < 0) || (num_handlers >= kMaxHandlers)) { | |
| FATAL( | |
| "Fatal error in ExceptionHandlers::New(): " | |
| "invalid num_handlers %" Pd "\n", | |
| num_handlers); | |
| } | |
| const Array& handled_types_data = | |
| (num_handlers == 0) ? Object::empty_array() | |
| : Array::Handle(Array::New(num_handlers, Heap::kOld)); | |
| return ExceptionHandlers::New(handled_types_data); | |
| } | |
| ExceptionHandlersPtr ExceptionHandlers::New(const Array& handled_types_data) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kExceptionHandlersCid) != | |
| Class::null()); | |
| const intptr_t num_handlers = handled_types_data.Length(); | |
| if ((num_handlers < 0) || (num_handlers >= kMaxHandlers)) { | |
| FATAL( | |
| "Fatal error in ExceptionHandlers::New(): " | |
| "invalid num_handlers %" Pd "\n", | |
| num_handlers); | |
| } | |
| ExceptionHandlers& result = ExceptionHandlers::Handle(); | |
| { | |
| auto raw = Object::Allocate<ExceptionHandlers>(Heap::kOld, num_handlers); | |
| NoSafepointScope no_safepoint; | |
| result = raw; | |
| result.untag()->packed_fields_ = | |
| UntaggedExceptionHandlers::NumEntriesBits::encode(num_handlers); | |
| } | |
| result.set_handled_types_data(handled_types_data); | |
| return result.ptr(); | |
| } | |
| void ExceptionHandlers::WriteToBuffer(BaseTextBuffer* buffer, | |
| uword base) const { | |
| auto& handled_types = Array::Handle(); | |
| auto& type = AbstractType::Handle(); | |
| ExceptionHandlerInfo info; | |
| for (intptr_t i = 0; i < num_entries(); i++) { | |
| GetHandlerInfo(i, &info); | |
| handled_types = GetHandledTypes(i); | |
| const intptr_t num_types = | |
| handled_types.IsNull() ? 0 : handled_types.Length(); | |
| buffer->Printf("%" Pd " => %#" Px " (%" Pd " types) (outer %d)%s%s\n", i, | |
| base + info.handler_pc_offset, num_types, | |
| info.outer_try_index, | |
| ((info.needs_stacktrace != 0) ? " (needs stack trace)" : ""), | |
| ((info.is_generated != 0) ? " (generated)" : "")); | |
| for (int k = 0; k < num_types; k++) { | |
| type ^= handled_types.At(k); | |
| ASSERT(!type.IsNull()); | |
| buffer->Printf(" %d. %s\n", k, type.ToCString()); | |
| } | |
| } | |
| if (has_async_handler()) { | |
| buffer->AddString("<async handler>\n"); | |
| } | |
| } | |
| const char* ExceptionHandlers::ToCString() const { | |
| if (num_entries() == 0) { | |
| return has_async_handler() | |
| ? "empty ExceptionHandlers (with <async handler>)" | |
| : "empty ExceptionHandlers"; | |
| } | |
| ZoneTextBuffer buffer(Thread::Current()->zone()); | |
| WriteToBuffer(&buffer, /*base=*/0); | |
| return buffer.buffer(); | |
| } | |
| void SingleTargetCache::set_target(const Code& value) const { | |
| untag()->set_target(value.ptr()); | |
| } | |
| const char* SingleTargetCache::ToCString() const { | |
| return "SingleTargetCache"; | |
| } | |
| SingleTargetCachePtr SingleTargetCache::New() { | |
| return Object::Allocate<SingleTargetCache>(Heap::kOld); | |
| } | |
| void UnlinkedCall::set_can_patch_to_monomorphic(bool value) const { | |
| StoreNonPointer(&untag()->can_patch_to_monomorphic_, value); | |
| } | |
| uword UnlinkedCall::Hash() const { | |
| return String::Handle(target_name()).Hash(); | |
| } | |
| bool UnlinkedCall::Equals(const UnlinkedCall& other) const { | |
| return (target_name() == other.target_name()) && | |
| (arguments_descriptor() == other.arguments_descriptor()) && | |
| (can_patch_to_monomorphic() == other.can_patch_to_monomorphic()); | |
| } | |
| const char* UnlinkedCall::ToCString() const { | |
| return "UnlinkedCall"; | |
| } | |
| UnlinkedCallPtr UnlinkedCall::New() { | |
| const auto& result = | |
| UnlinkedCall::Handle(Object::Allocate<UnlinkedCall>(Heap::kOld)); | |
| result.set_can_patch_to_monomorphic(!FLAG_precompiled_mode); | |
| return result.ptr(); | |
| } | |
| MonomorphicSmiableCallPtr MonomorphicSmiableCall::New(classid_t expected_cid, | |
| const Code& target) { | |
| const auto& result = MonomorphicSmiableCall::Handle( | |
| Object::Allocate<MonomorphicSmiableCall>(Heap::kOld)); | |
| result.StoreNonPointer(&result.untag()->expected_cid_, expected_cid); | |
| result.StoreNonPointer(&result.untag()->entrypoint_, target.EntryPoint()); | |
| return result.ptr(); | |
| } | |
| const char* MonomorphicSmiableCall::ToCString() const { | |
| return "MonomorphicSmiableCall"; | |
| } | |
| const char* CallSiteData::ToCString() const { | |
| // CallSiteData is an abstract class. We should never reach here. | |
| UNREACHABLE(); | |
| return "CallSiteData"; | |
| } | |
| void CallSiteData::set_target_name(const String& value) const { | |
| ASSERT(!value.IsNull()); | |
| ASSERT(value.IsCanonical()); | |
| untag()->set_target_name(value.ptr()); | |
| } | |
| void CallSiteData::set_arguments_descriptor(const Array& value) const { | |
| ASSERT(!value.IsNull()); | |
| untag()->set_args_descriptor(value.ptr()); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| void ICData::SetReceiversStaticType(const AbstractType& type) const { | |
| untag()->set_receivers_static_type(type.ptr()); | |
| if (!type.IsNull() && type.HasTypeClass() && (NumArgsTested() == 1) && | |
| type.IsInstantiated() && !type.IsFutureOrType()) { | |
| const Class& cls = Class::Handle(type.type_class()); | |
| if (cls.IsGeneric()) { | |
| set_tracking_exactness(true); | |
| } | |
| } | |
| } | |
| #endif | |
| void ICData::SetTargetAtPos(const Array& data, | |
| intptr_t data_pos, | |
| intptr_t num_args_tested, | |
| const Function& target) { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| // JIT | |
| data.SetAt(data_pos + TargetIndexFor(num_args_tested), target); | |
| #else | |
| // AOT | |
| ASSERT(target.HasCode()); | |
| const Code& code = Code::Handle(target.CurrentCode()); | |
| data.SetAt(data_pos + CodeIndexFor(num_args_tested), code); | |
| data.SetAt(data_pos + EntryPointIndexFor(num_args_tested), target); | |
| #endif | |
| } | |
| uword ICData::Hash() const { | |
| return String::HashRawSymbol(target_name()) ^ deopt_id(); | |
| } | |
| const char* ICData::ToCString() const { | |
| Zone* zone = Thread::Current()->zone(); | |
| const String& name = String::Handle(zone, target_name()); | |
| return zone->PrintToString("ICData(%s num-args: %" Pd " num-checks: %" Pd | |
| " type-args-len: %" Pd ", deopt-id: %" Pd ")", | |
| name.ToCString(), NumArgsTested(), | |
| NumberOfChecks(), TypeArgsLen(), deopt_id()); | |
| } | |
| FunctionPtr ICData::Owner() const { | |
| Object& obj = Object::Handle(untag()->owner()); | |
| if (obj.IsNull()) { | |
| return Function::null(); | |
| } else if (obj.IsFunction()) { | |
| return Function::Cast(obj).ptr(); | |
| } else { | |
| ICData& original = ICData::Handle(); | |
| original ^= obj.ptr(); | |
| return original.Owner(); | |
| } | |
| } | |
| ICDataPtr ICData::Original() const { | |
| if (IsNull()) { | |
| return ICData::null(); | |
| } | |
| if (untag()->owner()->IsICData()) { | |
| return static_cast<ICDataPtr>(untag()->owner()); | |
| } | |
| return this->ptr(); | |
| } | |
| void ICData::SetOriginal(const ICData& value) const { | |
| ASSERT(value.IsOriginal()); | |
| ASSERT(!value.IsNull()); | |
| untag()->set_owner(static_cast<ObjectPtr>(value.ptr())); | |
| } | |
| void ICData::set_owner(const Function& value) const { | |
| untag()->set_owner(static_cast<ObjectPtr>(value.ptr())); | |
| } | |
| void ICData::set_deopt_id(intptr_t value) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| ASSERT(value <= kMaxInt32); | |
| StoreNonPointer(&untag()->deopt_id_, value); | |
| #endif | |
| } | |
| void ICData::set_entries(const Array& value) const { | |
| ASSERT(!value.IsNull()); | |
| untag()->set_entries<std::memory_order_release>(value.ptr()); | |
| } | |
| intptr_t ICData::NumArgsTested() const { | |
| return untag()->state_bits_.Read<NumArgsTestedBits>(); | |
| } | |
| void ICData::SetNumArgsTested(intptr_t value) const { | |
| ASSERT(Utils::IsUint(2, value)); | |
| untag()->state_bits_.Update<NumArgsTestedBits>(value); | |
| } | |
| intptr_t CallSiteData::TypeArgsLen() const { | |
| ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor())); | |
| return args_desc.TypeArgsLen(); | |
| } | |
| intptr_t CallSiteData::CountWithTypeArgs() const { | |
| ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor())); | |
| return args_desc.CountWithTypeArgs(); | |
| } | |
| intptr_t CallSiteData::CountWithoutTypeArgs() const { | |
| ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor())); | |
| return args_desc.Count(); | |
| } | |
| intptr_t CallSiteData::SizeWithoutTypeArgs() const { | |
| ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor())); | |
| return args_desc.Size(); | |
| } | |
| intptr_t CallSiteData::SizeWithTypeArgs() const { | |
| ArgumentsDescriptor args_desc(Array::Handle(arguments_descriptor())); | |
| return args_desc.SizeWithTypeArgs(); | |
| } | |
| uint32_t ICData::DeoptReasons() const { | |
| return untag()->state_bits_.Read<DeoptReasonBits>(); | |
| } | |
| void ICData::SetDeoptReasons(uint32_t reasons) const { | |
| untag()->state_bits_.Update<DeoptReasonBits>(reasons); | |
| } | |
| bool ICData::HasDeoptReason(DeoptReasonId reason) const { | |
| ASSERT(reason <= kLastRecordedDeoptReason); | |
| return (DeoptReasons() & (1 << reason)) != 0; | |
| } | |
| void ICData::AddDeoptReason(DeoptReasonId reason) const { | |
| if (reason <= kLastRecordedDeoptReason) { | |
| untag()->state_bits_.FetchOr<DeoptReasonBits>(1 << reason); | |
| } | |
| } | |
| ICData::RebindRule ICData::rebind_rule() const { | |
| return RebindRule(untag()->state_bits_.Read<RebindRuleBits>()); | |
| } | |
| void ICData::set_rebind_rule(uint32_t rebind_rule) const { | |
| untag()->state_bits_.Update<ICData::RebindRuleBits>(rebind_rule); | |
| } | |
| bool ICData::is_static_call() const { | |
| return rebind_rule() != kInstance; | |
| } | |
| void ICData::clear_state_bits() const { | |
| untag()->state_bits_ = 0; | |
| } | |
| intptr_t ICData::TestEntryLengthFor(intptr_t num_args, | |
| bool tracking_exactness) { | |
| return num_args + 1 /* target function*/ + 1 /* frequency */ + | |
| (tracking_exactness ? 1 : 0) /* exactness state */; | |
| } | |
| intptr_t ICData::TestEntryLength() const { | |
| return TestEntryLengthFor(NumArgsTested(), is_tracking_exactness()); | |
| } | |
| intptr_t ICData::Length() const { | |
| return (Smi::Value(entries()->untag()->length()) / TestEntryLength()); | |
| } | |
| intptr_t ICData::NumberOfChecks() const { | |
| DEBUG_ONLY(AssertInvariantsAreSatisfied()); | |
| return Length() - 1; | |
| } | |
| bool ICData::NumberOfChecksIs(intptr_t n) const { | |
| DEBUG_ONLY(AssertInvariantsAreSatisfied()); | |
| return NumberOfChecks() == n; | |
| } | |
| #if defined(DEBUG) | |
| void ICData::AssertInvariantsAreSatisfied() const { | |
| // See layout and invariant of [ICData] in class comment in object.h. | |
| // | |
| // This method can be called without holding any locks, it will grab a | |
| // snapshot of `entries()` and do it's verification logic on that. | |
| auto zone = Thread::Current()->zone(); | |
| const auto& array = Array::Handle(zone, entries()); | |
| const intptr_t entry_length = TestEntryLength(); | |
| const intptr_t num_checks = array.Length() / entry_length - 1; | |
| const intptr_t num_args = NumArgsTested(); | |
| /// Backing store must be multiple of entry length. | |
| ASSERT((array.Length() % entry_length) == 0); | |
| /// Entries must be valid. | |
| for (intptr_t i = 0; i < num_checks; ++i) { | |
| // Should be valid entry. | |
| const intptr_t start = entry_length * i; | |
| for (intptr_t i = 0; i < num_args; ++i) { | |
| ASSERT(!array.At(start + i)->IsHeapObject()); | |
| ASSERT(array.At(start + i) != smi_illegal_cid().ptr()); | |
| } | |
| ASSERT(array.At(start + TargetIndexFor(num_args))->IsHeapObject()); | |
| if (is_tracking_exactness()) { | |
| ASSERT(!array.At(start + ExactnessIndexFor(num_args))->IsHeapObject()); | |
| } | |
| } | |
| /// Sentinel at end must be valid. | |
| const intptr_t sentinel_start = num_checks * entry_length; | |
| for (intptr_t i = 0; i < entry_length - 1; ++i) { | |
| ASSERT(array.At(sentinel_start + i) == smi_illegal_cid().ptr()); | |
| } | |
| if (num_checks == 0) { | |
| ASSERT(array.At(sentinel_start + entry_length - 1) == | |
| smi_illegal_cid().ptr()); | |
| ASSERT(ICData::CachedEmptyICDataArray(num_args, is_tracking_exactness()) == | |
| array.ptr()); | |
| } else { | |
| ASSERT(array.At(sentinel_start + entry_length - 1) == ptr()); | |
| } | |
| // Invariants for ICData of static calls. | |
| if (num_args == 0) { | |
| ASSERT(Length() == 2); | |
| ASSERT(TestEntryLength() == 2); | |
| } | |
| } | |
| #endif // defined(DEBUG) | |
| // Discounts any checks with usage of zero. | |
| intptr_t ICData::NumberOfUsedChecks() const { | |
| const intptr_t n = NumberOfChecks(); | |
| intptr_t count = 0; | |
| for (intptr_t i = 0; i < n; i++) { | |
| if (GetCountAt(i) > 0) { | |
| count++; | |
| } | |
| } | |
| return count; | |
| } | |
| void ICData::WriteSentinel(const Array& data, | |
| intptr_t test_entry_length, | |
| const Object& back_ref) { | |
| ASSERT(!data.IsNull()); | |
| RELEASE_ASSERT(smi_illegal_cid().Value() == kIllegalCid); | |
| const intptr_t entry_start = data.Length() - test_entry_length; | |
| for (intptr_t i = 0; i < test_entry_length - 1; i++) { | |
| data.SetAt(entry_start + i, smi_illegal_cid()); | |
| } | |
| data.SetAt(entry_start + test_entry_length - 1, back_ref); | |
| } | |
| #if defined(DEBUG) | |
| // Used in asserts to verify that a check is not added twice. | |
| bool ICData::HasCheck(const GrowableArray<intptr_t>& cids) const { | |
| return FindCheck(cids) != -1; | |
| } | |
| #endif // DEBUG | |
| intptr_t ICData::FindCheck(const GrowableArray<intptr_t>& cids) const { | |
| const intptr_t len = NumberOfChecks(); | |
| GrowableArray<intptr_t> class_ids; | |
| for (intptr_t i = 0; i < len; i++) { | |
| GetClassIdsAt(i, &class_ids); | |
| bool matches = true; | |
| for (intptr_t k = 0; k < class_ids.length(); k++) { | |
| ASSERT(class_ids[k] != kIllegalCid); | |
| if (class_ids[k] != cids[k]) { | |
| matches = false; | |
| break; | |
| } | |
| } | |
| if (matches) { | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| void ICData::TruncateTo(intptr_t num_checks, | |
| const CallSiteResetter& proof_of_reload) const { | |
| USE(proof_of_reload); // This method can only be called during reload. | |
| DEBUG_ONLY(AssertInvariantsAreSatisfied()); | |
| ASSERT(num_checks <= NumberOfChecks()); | |
| // Nothing to do. | |
| if (NumberOfChecks() == num_checks) return; | |
| auto thread = Thread::Current(); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| auto& array = thread->ArrayHandle(); | |
| // If we make the ICData empty, use the pre-allocated shared backing stores. | |
| const intptr_t num_args = NumArgsTested(); | |
| if (num_checks == 0) { | |
| array = ICData::CachedEmptyICDataArray(num_args, is_tracking_exactness()); | |
| set_entries(array); | |
| return; | |
| } | |
| // Otherwise truncate array and initialize sentinel. | |
| // Use kSmiCid for all slots in the entry except the last, which is a backref | |
| // to ICData. | |
| const intptr_t entry_length = TestEntryLength(); | |
| array = entries(); | |
| array.Truncate((num_checks + 1) * entry_length); | |
| WriteSentinel(array, entry_length, *this); | |
| } | |
| void ICData::ClearCountAt(intptr_t index, | |
| const CallSiteResetter& proof_of_reload) const { | |
| USE(proof_of_reload); // This method can only be called during reload. | |
| ASSERT(index >= 0); | |
| ASSERT(index < NumberOfChecks()); | |
| SetCountAt(index, 0); | |
| } | |
| void ICData::ClearAndSetStaticTarget( | |
| const Function& func, | |
| const CallSiteResetter& proof_of_reload) const { | |
| USE(proof_of_reload); // This method can only be called during reload. | |
| // The final entry is always the sentinel. | |
| DEBUG_ONLY(AssertInvariantsAreSatisfied()); | |
| if (IsImmutable()) return; | |
| if (NumberOfChecks() == 0) return; | |
| // Leave one entry. | |
| TruncateTo(/*num_checks=*/1, proof_of_reload); | |
| // Reinitialize the one and only entry. | |
| const intptr_t num_args = NumArgsTested(); | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| Array& data = thread->ArrayHandle(); | |
| data = entries(); | |
| const Smi& object_cid = Smi::Handle(Smi::New(kObjectCid)); | |
| for (intptr_t i = 0; i < num_args; i++) { | |
| data.SetAt(i, object_cid); | |
| } | |
| data.SetAt(TargetIndexFor(num_args), func); | |
| data.SetAt(CountIndexFor(num_args), Object::smi_zero()); | |
| } | |
| bool ICData::ValidateInterceptor(const Function& target) const { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| const String& name = String::Handle(target_name()); | |
| if (Function::IsDynamicInvocationForwarderName(name)) { | |
| return Function::DemangleDynamicInvocationForwarderName(name) == | |
| target.name(); | |
| } | |
| #endif | |
| ObjectStore* store = IsolateGroup::Current()->object_store(); | |
| ASSERT((target.ptr() == store->simple_instance_of_true_function()) || | |
| (target.ptr() == store->simple_instance_of_false_function())); | |
| const String& instance_of_name = String::Handle( | |
| Library::PrivateCoreLibName(Symbols::_simpleInstanceOf()).ptr()); | |
| ASSERT(target_name() == instance_of_name.ptr()); | |
| return true; | |
| } | |
| void ICData::EnsureHasCheck(const GrowableArray<intptr_t>& class_ids, | |
| const Function& target, | |
| intptr_t count) const { | |
| SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex()); | |
| if (FindCheck(class_ids) != -1) return; | |
| AddCheckInternal(class_ids, target, count); | |
| } | |
| void ICData::AddCheck(const GrowableArray<intptr_t>& class_ids, | |
| const Function& target, | |
| intptr_t count) const { | |
| SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex()); | |
| AddCheckInternal(class_ids, target, count); | |
| } | |
| void ICData::AddCheckInternal(const GrowableArray<intptr_t>& class_ids, | |
| const Function& target, | |
| intptr_t count) const { | |
| ASSERT( | |
| IsolateGroup::Current()->type_feedback_mutex()->IsOwnedByCurrentThread()); | |
| ASSERT(!is_tracking_exactness()); | |
| ASSERT(!target.IsNull()); | |
| ASSERT((target.name() == target_name()) || ValidateInterceptor(target)); | |
| DEBUG_ASSERT(!HasCheck(class_ids)); | |
| ASSERT(NumArgsTested() > 1); // Otherwise use 'AddReceiverCheck'. | |
| const intptr_t num_args_tested = NumArgsTested(); | |
| ASSERT(class_ids.length() == num_args_tested); | |
| const intptr_t old_num = NumberOfChecks(); | |
| Array& data = Array::Handle(entries()); | |
| // ICData of static calls with NumArgsTested() > 0 have initially a | |
| // dummy set of cids entered (see ICData::NewForStaticCall). That entry is | |
| // overwritten by first real type feedback data. | |
| if (old_num == 1 && num_args_tested == 2) { | |
| const bool has_dummy_entry = | |
| Smi::Value(Smi::RawCast(data.At(0))) == kObjectCid && | |
| Smi::Value(Smi::RawCast(data.At(1))) == kObjectCid; | |
| if (has_dummy_entry) { | |
| ASSERT(target.ptr() == data.At(TargetIndexFor(num_args_tested))); | |
| // Replace dummy entry. | |
| Smi& value = Smi::Handle(); | |
| for (intptr_t i = 0; i < NumArgsTested(); i++) { | |
| ASSERT(class_ids[i] != kIllegalCid); | |
| value = Smi::New(class_ids[i]); | |
| data.SetAt(i, value); | |
| } | |
| return; | |
| } | |
| } | |
| intptr_t index = -1; | |
| data = Grow(&index); | |
| ASSERT(!data.IsNull()); | |
| intptr_t data_pos = index * TestEntryLength(); | |
| Smi& value = Smi::Handle(); | |
| for (intptr_t i = 0; i < class_ids.length(); i++) { | |
| // kIllegalCid is used as terminating value, do not add it. | |
| ASSERT(class_ids[i] != kIllegalCid); | |
| value = Smi::New(class_ids[i]); | |
| data.SetAt(data_pos + i, value); | |
| } | |
| ASSERT(!target.IsNull()); | |
| data.SetAt(data_pos + TargetIndexFor(num_args_tested), target); | |
| value = Smi::New(count); | |
| data.SetAt(data_pos + CountIndexFor(num_args_tested), value); | |
| // Multithreaded access to ICData requires setting of array to be the last | |
| // operation. | |
| set_entries(data); | |
| } | |
| ArrayPtr ICData::Grow(intptr_t* index) const { | |
| DEBUG_ONLY(AssertInvariantsAreSatisfied()); | |
| *index = NumberOfChecks(); | |
| Array& data = Array::Handle(entries()); | |
| const intptr_t new_len = data.Length() + TestEntryLength(); | |
| data = Array::Grow(data, new_len, Heap::kOld); | |
| WriteSentinel(data, TestEntryLength(), *this); | |
| return data.ptr(); | |
| } | |
| void ICData::DebugDump() const { | |
| const Function& owner = Function::Handle(Owner()); | |
| THR_Print("ICData::DebugDump\n"); | |
| THR_Print("Owner = %s [deopt=%" Pd "]\n", owner.ToCString(), deopt_id()); | |
| THR_Print("NumArgsTested = %" Pd "\n", NumArgsTested()); | |
| THR_Print("Length = %" Pd "\n", Length()); | |
| THR_Print("NumberOfChecks = %" Pd "\n", NumberOfChecks()); | |
| GrowableArray<intptr_t> class_ids; | |
| for (intptr_t i = 0; i < NumberOfChecks(); i++) { | |
| THR_Print("Check[%" Pd "]:", i); | |
| GetClassIdsAt(i, &class_ids); | |
| for (intptr_t c = 0; c < class_ids.length(); c++) { | |
| THR_Print(" %" Pd "", class_ids[c]); | |
| } | |
| THR_Print("--- %" Pd " hits\n", GetCountAt(i)); | |
| } | |
| } | |
| void ICData::EnsureHasReceiverCheck(intptr_t receiver_class_id, | |
| const Function& target, | |
| intptr_t count, | |
| StaticTypeExactnessState exactness) const { | |
| SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex()); | |
| GrowableArray<intptr_t> class_ids(1); | |
| class_ids.Add(receiver_class_id); | |
| if (FindCheck(class_ids) != -1) return; | |
| AddReceiverCheckInternal(receiver_class_id, target, count, exactness); | |
| } | |
| void ICData::AddReceiverCheck(intptr_t receiver_class_id, | |
| const Function& target, | |
| intptr_t count, | |
| StaticTypeExactnessState exactness) const { | |
| SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex()); | |
| AddReceiverCheckInternal(receiver_class_id, target, count, exactness); | |
| } | |
| void ICData::AddReceiverCheckInternal( | |
| intptr_t receiver_class_id, | |
| const Function& target, | |
| intptr_t count, | |
| StaticTypeExactnessState exactness) const { | |
| #if defined(DEBUG) | |
| GrowableArray<intptr_t> class_ids(1); | |
| class_ids.Add(receiver_class_id); | |
| ASSERT(!HasCheck(class_ids)); | |
| #endif // DEBUG | |
| ASSERT(!target.IsNull()); | |
| const intptr_t kNumArgsTested = 1; | |
| ASSERT(NumArgsTested() == kNumArgsTested); // Otherwise use 'AddCheck'. | |
| ASSERT(receiver_class_id != kIllegalCid); | |
| intptr_t index = -1; | |
| Array& data = Array::Handle(Grow(&index)); | |
| intptr_t data_pos = index * TestEntryLength(); | |
| if ((receiver_class_id == kSmiCid) && (data_pos > 0)) { | |
| ASSERT(GetReceiverClassIdAt(0) != kSmiCid); | |
| // Move class occupying position 0 to the data_pos. | |
| for (intptr_t i = 0; i < TestEntryLength(); i++) { | |
| data.SetAt(data_pos + i, Object::Handle(data.At(i))); | |
| } | |
| // Insert kSmiCid in position 0. | |
| data_pos = 0; | |
| } | |
| data.SetAt(data_pos, Smi::Handle(Smi::New(receiver_class_id))); | |
| SetTargetAtPos(data, data_pos, kNumArgsTested, target); | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| data.SetAt(data_pos + CountIndexFor(kNumArgsTested), | |
| Smi::Handle(Smi::New(count))); | |
| if (is_tracking_exactness()) { | |
| data.SetAt(data_pos + ExactnessIndexFor(kNumArgsTested), | |
| Smi::Handle(Smi::New(exactness.Encode()))); | |
| } | |
| #endif | |
| // Multithreaded access to ICData requires setting of array to be the last | |
| // operation. | |
| set_entries(data); | |
| } | |
| StaticTypeExactnessState ICData::GetExactnessAt(intptr_t index) const { | |
| if (!is_tracking_exactness()) { | |
| return StaticTypeExactnessState::NotTracking(); | |
| } | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| Array& data = thread->ArrayHandle(); | |
| data = entries(); | |
| intptr_t data_pos = | |
| index * TestEntryLength() + ExactnessIndexFor(NumArgsTested()); | |
| return StaticTypeExactnessState::Decode( | |
| Smi::Value(Smi::RawCast(data.At(data_pos)))); | |
| } | |
| void ICData::GetCheckAt(intptr_t index, | |
| GrowableArray<intptr_t>* class_ids, | |
| Function* target) const { | |
| ASSERT(index < NumberOfChecks()); | |
| ASSERT(class_ids != nullptr); | |
| ASSERT(target != nullptr); | |
| class_ids->Clear(); | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| Array& data = thread->ArrayHandle(); | |
| data = entries(); | |
| intptr_t data_pos = index * TestEntryLength(); | |
| for (intptr_t i = 0; i < NumArgsTested(); i++) { | |
| class_ids->Add(Smi::Value(Smi::RawCast(data.At(data_pos + i)))); | |
| } | |
| (*target) ^= data.At(data_pos + TargetIndexFor(NumArgsTested())); | |
| } | |
| void ICData::GetClassIdsAt(intptr_t index, | |
| GrowableArray<intptr_t>* class_ids) const { | |
| ASSERT(index < Length()); | |
| ASSERT(class_ids != nullptr); | |
| ASSERT(IsValidEntryIndex(index)); | |
| class_ids->Clear(); | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| Array& data = thread->ArrayHandle(); | |
| data = entries(); | |
| intptr_t data_pos = index * TestEntryLength(); | |
| for (intptr_t i = 0; i < NumArgsTested(); i++) { | |
| class_ids->Add(Smi::Value(Smi::RawCast(data.At(data_pos++)))); | |
| } | |
| } | |
| void ICData::GetOneClassCheckAt(intptr_t index, | |
| intptr_t* class_id, | |
| Function* target) const { | |
| ASSERT(class_id != nullptr); | |
| ASSERT(target != nullptr); | |
| ASSERT(NumArgsTested() == 1); | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| Array& data = thread->ArrayHandle(); | |
| data = entries(); | |
| const intptr_t data_pos = index * TestEntryLength(); | |
| *class_id = Smi::Value(Smi::RawCast(data.At(data_pos))); | |
| *target ^= data.At(data_pos + TargetIndexFor(NumArgsTested())); | |
| } | |
| intptr_t ICData::GetCidAt(intptr_t index) const { | |
| ASSERT(NumArgsTested() == 1); | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| Array& data = thread->ArrayHandle(); | |
| data = entries(); | |
| const intptr_t data_pos = index * TestEntryLength(); | |
| return Smi::Value(Smi::RawCast(data.At(data_pos))); | |
| } | |
| intptr_t ICData::GetClassIdAt(intptr_t index, intptr_t arg_nr) const { | |
| GrowableArray<intptr_t> class_ids; | |
| GetClassIdsAt(index, &class_ids); | |
| return class_ids[arg_nr]; | |
| } | |
| intptr_t ICData::GetReceiverClassIdAt(intptr_t index) const { | |
| ASSERT(index < Length()); | |
| ASSERT(IsValidEntryIndex(index)); | |
| const intptr_t data_pos = index * TestEntryLength(); | |
| NoSafepointScope no_safepoint; | |
| ArrayPtr raw_data = entries(); | |
| return Smi::Value(Smi::RawCast(raw_data->untag()->element(data_pos))); | |
| } | |
| FunctionPtr ICData::GetTargetAt(intptr_t index) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| return nullptr; | |
| #else | |
| const intptr_t data_pos = | |
| index * TestEntryLength() + TargetIndexFor(NumArgsTested()); | |
| ASSERT(Object::Handle(Array::Handle(entries()).At(data_pos)).IsFunction()); | |
| NoSafepointScope no_safepoint; | |
| ArrayPtr raw_data = entries(); | |
| return static_cast<FunctionPtr>(raw_data->untag()->element(data_pos)); | |
| #endif | |
| } | |
| void ICData::IncrementCountAt(intptr_t index, intptr_t value) const { | |
| ASSERT(0 <= value); | |
| ASSERT(value <= Smi::kMaxValue); | |
| SetCountAt(index, Utils::Minimum(GetCountAt(index) + value, Smi::kMaxValue)); | |
| } | |
| void ICData::SetCountAt(intptr_t index, intptr_t value) const { | |
| ASSERT(0 <= value); | |
| ASSERT(value <= Smi::kMaxValue); | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| Array& data = thread->ArrayHandle(); | |
| data = entries(); | |
| const intptr_t data_pos = | |
| index * TestEntryLength() + CountIndexFor(NumArgsTested()); | |
| data.SetAt(data_pos, Smi::Handle(Smi::New(value))); | |
| } | |
| intptr_t ICData::GetCountAt(intptr_t index) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| return 0; | |
| #else | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_ARRAY_HANDLESCOPE(thread); | |
| Array& data = thread->ArrayHandle(); | |
| data = entries(); | |
| const intptr_t data_pos = | |
| index * TestEntryLength() + CountIndexFor(NumArgsTested()); | |
| intptr_t value = Smi::Value(Smi::RawCast(data.At(data_pos))); | |
| if (value >= 0) return value; | |
| // The counter very rarely overflows to a negative value, but if it does, we | |
| // would rather just reset it to zero. | |
| SetCountAt(index, 0); | |
| return 0; | |
| #endif | |
| } | |
| intptr_t ICData::AggregateCount() const { | |
| if (IsNull()) return 0; | |
| const intptr_t len = NumberOfChecks(); | |
| intptr_t count = 0; | |
| for (intptr_t i = 0; i < len; i++) { | |
| count += GetCountAt(i); | |
| } | |
| return count; | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| ICDataPtr ICData::AsUnaryClassChecksForArgNr(intptr_t arg_nr) const { | |
| ASSERT(!IsNull()); | |
| ASSERT(NumArgsTested() > arg_nr); | |
| if ((arg_nr == 0) && (NumArgsTested() == 1)) { | |
| // Frequent case. | |
| return ptr(); | |
| } | |
| const intptr_t kNumArgsTested = 1; | |
| ICData& result = ICData::Handle(ICData::NewFrom(*this, kNumArgsTested)); | |
| const intptr_t len = NumberOfChecks(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| const intptr_t class_id = GetClassIdAt(i, arg_nr); | |
| const intptr_t count = GetCountAt(i); | |
| if (count == 0) { | |
| continue; | |
| } | |
| intptr_t duplicate_class_id = -1; | |
| const intptr_t result_len = result.NumberOfChecks(); | |
| for (intptr_t k = 0; k < result_len; k++) { | |
| if (class_id == result.GetReceiverClassIdAt(k)) { | |
| duplicate_class_id = k; | |
| break; | |
| } | |
| } | |
| if (duplicate_class_id >= 0) { | |
| // This check is valid only when checking the receiver. | |
| ASSERT((arg_nr != 0) || | |
| (result.GetTargetAt(duplicate_class_id) == GetTargetAt(i))); | |
| result.IncrementCountAt(duplicate_class_id, count); | |
| } else { | |
| // This will make sure that Smi is first if it exists. | |
| result.AddReceiverCheckInternal(class_id, | |
| Function::Handle(GetTargetAt(i)), count, | |
| StaticTypeExactnessState::NotTracking()); | |
| } | |
| } | |
| return result.ptr(); | |
| } | |
| // (cid, count) tuple used to sort ICData by count. | |
| struct CidCount { | |
| CidCount(intptr_t cid_, intptr_t count_, Function* f_) | |
| : cid(cid_), count(count_), function(f_) {} | |
| static int HighestCountFirst(const CidCount* a, const CidCount* b); | |
| intptr_t cid; | |
| intptr_t count; | |
| Function* function; | |
| }; | |
| int CidCount::HighestCountFirst(const CidCount* a, const CidCount* b) { | |
| if (a->count > b->count) { | |
| return -1; | |
| } | |
| return (a->count < b->count) ? 1 : 0; | |
| } | |
| ICDataPtr ICData::AsUnaryClassChecksSortedByCount() const { | |
| ASSERT(!IsNull()); | |
| const intptr_t kNumArgsTested = 1; | |
| const intptr_t len = NumberOfChecks(); | |
| if (len <= 1) { | |
| // No sorting needed. | |
| return AsUnaryClassChecks(); | |
| } | |
| GrowableArray<CidCount> aggregate; | |
| for (intptr_t i = 0; i < len; i++) { | |
| const intptr_t class_id = GetClassIdAt(i, 0); | |
| const intptr_t count = GetCountAt(i); | |
| if (count == 0) { | |
| continue; | |
| } | |
| bool found = false; | |
| for (intptr_t r = 0; r < aggregate.length(); r++) { | |
| if (aggregate[r].cid == class_id) { | |
| aggregate[r].count += count; | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (!found) { | |
| aggregate.Add( | |
| CidCount(class_id, count, &Function::ZoneHandle(GetTargetAt(i)))); | |
| } | |
| } | |
| aggregate.Sort(CidCount::HighestCountFirst); | |
| ICData& result = ICData::Handle(ICData::NewFrom(*this, kNumArgsTested)); | |
| ASSERT(result.NumberOfChecksIs(0)); | |
| // Room for all entries and the sentinel. | |
| const intptr_t data_len = result.TestEntryLength() * (aggregate.length() + 1); | |
| // Allocate the array but do not assign it to result until we have populated | |
| // it with the aggregate data and the terminating sentinel. | |
| const Array& data = Array::Handle(Array::New(data_len, Heap::kOld)); | |
| intptr_t pos = 0; | |
| for (intptr_t i = 0; i < aggregate.length(); i++) { | |
| data.SetAt(pos + 0, Smi::Handle(Smi::New(aggregate[i].cid))); | |
| data.SetAt(pos + TargetIndexFor(1), *aggregate[i].function); | |
| data.SetAt(pos + CountIndexFor(1), | |
| Smi::Handle(Smi::New(aggregate[i].count))); | |
| pos += result.TestEntryLength(); | |
| } | |
| WriteSentinel(data, result.TestEntryLength(), result); | |
| result.set_entries(data); | |
| ASSERT(result.NumberOfChecksIs(aggregate.length())); | |
| return result.ptr(); | |
| } | |
| UnlinkedCallPtr ICData::AsUnlinkedCall() const { | |
| ASSERT(NumArgsTested() == 1); | |
| ASSERT(!is_tracking_exactness()); | |
| const UnlinkedCall& result = UnlinkedCall::Handle(UnlinkedCall::New()); | |
| result.set_target_name(String::Handle(target_name())); | |
| result.set_arguments_descriptor(Array::Handle(arguments_descriptor())); | |
| result.set_can_patch_to_monomorphic(!FLAG_precompiled_mode || | |
| receiver_cannot_be_smi()); | |
| return result.ptr(); | |
| } | |
| bool ICData::HasReceiverClassId(intptr_t class_id) const { | |
| ASSERT(NumArgsTested() > 0); | |
| const intptr_t len = NumberOfChecks(); | |
| for (intptr_t i = 0; i < len; i++) { | |
| if (IsUsedAt(i)) { | |
| const intptr_t test_class_id = GetReceiverClassIdAt(i); | |
| if (test_class_id == class_id) { | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| #endif | |
| bool ICData::IsUsedAt(intptr_t i) const { | |
| if (GetCountAt(i) <= 0) { | |
| // Do not mistake unoptimized static call ICData for unused. | |
| // See ICData::AddTarget. | |
| // TODO(srdjan): Make this test more robust. | |
| if (NumArgsTested() > 0) { | |
| const intptr_t cid = GetReceiverClassIdAt(i); | |
| if (cid == kObjectCid) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| return true; | |
| } | |
| void ICData::Init() { | |
| for (int i = 0; i <= kCachedICDataMaxArgsTestedWithoutExactnessTracking; | |
| i++) { | |
| Roots::set_cached_icdata_array( | |
| kCachedICDataZeroArgTestedWithoutExactnessTrackingIdx + i, | |
| ICData::NewNonCachedEmptyICDataArray(i, false)); | |
| } | |
| Roots::set_cached_icdata_array(kCachedICDataOneArgWithExactnessTrackingIdx, | |
| ICData::NewNonCachedEmptyICDataArray(1, true)); | |
| } | |
| ArrayPtr ICData::NewNonCachedEmptyICDataArray(intptr_t num_args_tested, | |
| bool tracking_exactness) { | |
| // IC data array must be null terminated (sentinel entry). | |
| const intptr_t len = TestEntryLengthFor(num_args_tested, tracking_exactness); | |
| const Array& array = Array::Handle(Array::New(len, Heap::kOld)); | |
| // Only empty [ICData]s are allowed to have a non-ICData backref. | |
| WriteSentinel(array, len, /*back_ref=*/smi_illegal_cid()); | |
| array.MakeImmutable(); | |
| return array.ptr(); | |
| } | |
| ArrayPtr ICData::CachedEmptyICDataArray(intptr_t num_args_tested, | |
| bool tracking_exactness) { | |
| if (tracking_exactness) { | |
| ASSERT(num_args_tested == 1); | |
| return Roots::cached_icdata_array( | |
| kCachedICDataOneArgWithExactnessTrackingIdx); | |
| } else { | |
| ASSERT(num_args_tested >= 0); | |
| ASSERT(num_args_tested <= | |
| kCachedICDataMaxArgsTestedWithoutExactnessTracking); | |
| return Roots::cached_icdata_array( | |
| kCachedICDataZeroArgTestedWithoutExactnessTrackingIdx + | |
| num_args_tested); | |
| } | |
| } | |
| bool ICData::IsCachedEmptyEntry(const Array& array) { | |
| for (int i = 0; i < kCachedICDataArrayCount; ++i) { | |
| if (Roots::cached_icdata_array(i) == array.ptr()) return true; | |
| } | |
| return false; | |
| } | |
| // Does not initialize ICData array. | |
| ICDataPtr ICData::NewDescriptor(Zone* zone, | |
| const Function& owner, | |
| const String& target_name, | |
| const Array& arguments_descriptor, | |
| intptr_t deopt_id, | |
| intptr_t num_args_tested, | |
| RebindRule rebind_rule, | |
| const AbstractType& receivers_static_type) { | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| // We should only have null owners in the precompiled runtime, if the | |
| // owning function for a Code object was optimized out. | |
| ASSERT(!owner.IsNull()); | |
| #endif | |
| ASSERT(!target_name.IsNull()); | |
| ASSERT(!arguments_descriptor.IsNull()); | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kICDataCid) != | |
| Class::null()); | |
| ASSERT(num_args_tested >= 0); | |
| // IC data objects are long living objects, allocate them in old generation. | |
| const auto& result = | |
| ICData::Handle(zone, Object::Allocate<ICData>(Heap::kOld)); | |
| result.set_owner(owner); | |
| result.set_target_name(target_name); | |
| result.set_arguments_descriptor(arguments_descriptor); | |
| NOT_IN_PRECOMPILED(result.set_deopt_id(deopt_id)); | |
| ASSERT_EQUAL(result.untag()->state_bits_, 0); | |
| result.set_rebind_rule(rebind_rule); | |
| result.SetNumArgsTested(num_args_tested); | |
| NOT_IN_PRECOMPILED(result.SetReceiversStaticType(receivers_static_type)); | |
| return result.ptr(); | |
| } | |
| bool ICData::IsImmutable() const { | |
| return entries()->IsImmutableArray(); | |
| } | |
| ICDataPtr ICData::New() { | |
| // IC data objects are long living objects, allocate them in old generation. | |
| const auto& result = ICData::Handle(Object::Allocate<ICData>(Heap::kOld)); | |
| ASSERT_EQUAL(result.untag()->state_bits_, 0); | |
| result.set_deopt_id(DeoptId::kNone); | |
| return result.ptr(); | |
| } | |
| ICDataPtr ICData::New(const Function& owner, | |
| const String& target_name, | |
| const Array& arguments_descriptor, | |
| intptr_t deopt_id, | |
| intptr_t num_args_tested, | |
| RebindRule rebind_rule, | |
| const AbstractType& receivers_static_type) { | |
| Zone* zone = Thread::Current()->zone(); | |
| const ICData& result = ICData::Handle( | |
| zone, | |
| NewDescriptor(zone, owner, target_name, arguments_descriptor, deopt_id, | |
| num_args_tested, rebind_rule, receivers_static_type)); | |
| result.set_entries(Array::Handle( | |
| zone, | |
| CachedEmptyICDataArray(num_args_tested, result.is_tracking_exactness()))); | |
| return result.ptr(); | |
| } | |
| ICDataPtr ICData::NewWithCheck(const Function& owner, | |
| const String& target_name, | |
| const Array& arguments_descriptor, | |
| intptr_t deopt_id, | |
| intptr_t num_args_tested, | |
| RebindRule rebind_rule, | |
| GrowableArray<intptr_t>* cids, | |
| const Function& target, | |
| const AbstractType& receiver_type) { | |
| ASSERT((cids != nullptr) && !target.IsNull()); | |
| ASSERT(cids->length() == num_args_tested); | |
| Zone* zone = Thread::Current()->zone(); | |
| const auto& result = ICData::Handle( | |
| zone, | |
| NewDescriptor(zone, owner, target_name, arguments_descriptor, deopt_id, | |
| num_args_tested, rebind_rule, receiver_type)); | |
| const intptr_t kNumEntries = 2; // 1 entry and a sentinel. | |
| const intptr_t entry_len = | |
| TestEntryLengthFor(num_args_tested, result.is_tracking_exactness()); | |
| const auto& array = | |
| Array::Handle(zone, Array::New(kNumEntries * entry_len, Heap::kOld)); | |
| auto& cid = Smi::Handle(zone); | |
| for (intptr_t i = 0; i < num_args_tested; ++i) { | |
| cid = Smi::New((*cids)[i]); | |
| array.SetAt(i, cid); | |
| } | |
| SetTargetAtPos(array, 0, num_args_tested, target); | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| array.SetAt(CountIndexFor(num_args_tested), Object::smi_zero()); | |
| #endif | |
| WriteSentinel(array, entry_len, result); | |
| result.set_entries(array); | |
| return result.ptr(); | |
| } | |
| ICDataPtr ICData::NewForStaticCall(const Function& owner, | |
| const Function& target, | |
| const Array& arguments_descriptor, | |
| intptr_t deopt_id, | |
| intptr_t num_args_tested, | |
| RebindRule rebind_rule) { | |
| // See `MethodRecognizer::NumArgsCheckedForStaticCall`. | |
| ASSERT(num_args_tested == 0 || num_args_tested == 1 || num_args_tested == 2); | |
| ASSERT(!target.IsNull()); | |
| Zone* zone = Thread::Current()->zone(); | |
| const auto& target_name = String::Handle(zone, target.name()); | |
| GrowableArray<intptr_t> cids(num_args_tested); | |
| for (intptr_t i = 0; i < num_args_tested; ++i) { | |
| cids.Add(kObjectCid); | |
| } | |
| return ICData::NewWithCheck(owner, target_name, arguments_descriptor, | |
| deopt_id, num_args_tested, rebind_rule, &cids, | |
| target, Object::null_abstract_type()); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| ICDataPtr ICData::NewFrom(const ICData& from, intptr_t num_args_tested) { | |
| // See comment in [ICData::Clone] why we access the megamorphic bit first. | |
| const bool is_megamorphic = from.is_megamorphic(); | |
| const ICData& result = ICData::Handle(ICData::New( | |
| Function::Handle(from.Owner()), String::Handle(from.target_name()), | |
| Array::Handle(from.arguments_descriptor()), from.deopt_id(), | |
| num_args_tested, from.rebind_rule(), | |
| AbstractType::Handle(from.receivers_static_type()))); | |
| // Copy deoptimization reasons. | |
| result.SetDeoptReasons(from.DeoptReasons()); | |
| result.set_is_megamorphic(is_megamorphic); | |
| return result.ptr(); | |
| } | |
| ICDataPtr ICData::Clone(const ICData& from) { | |
| Zone* zone = Thread::Current()->zone(); | |
| SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex()); | |
| // We have to check the megamorphic bit before accessing the entries of the | |
| // ICData to ensure all writes to the entries have been flushed and are | |
| // visible at this point. | |
| // | |
| // This will allow us to maintain the invariant that if the megamorphic bit is | |
| // set, the number of entries in the ICData have reached the limit. | |
| const bool is_megamorphic = from.is_megamorphic(); | |
| const ICData& result = ICData::Handle( | |
| zone, ICData::NewDescriptor( | |
| zone, Function::Handle(zone, from.Owner()), | |
| String::Handle(zone, from.target_name()), | |
| Array::Handle(zone, from.arguments_descriptor()), | |
| from.deopt_id(), from.NumArgsTested(), from.rebind_rule(), | |
| AbstractType::Handle(zone, from.receivers_static_type()))); | |
| // Clone entry array. | |
| const Array& from_array = Array::Handle(zone, from.entries()); | |
| if (ICData::IsCachedEmptyEntry(from_array)) { | |
| result.set_entries(from_array); | |
| } else { | |
| const intptr_t len = from_array.Length(); | |
| const Array& cloned_array = | |
| Array::Handle(zone, Array::New(len, Heap::kOld)); | |
| Object& obj = Object::Handle(zone); | |
| for (intptr_t i = 0; i < len; i++) { | |
| obj = from_array.At(i); | |
| cloned_array.SetAt(i, obj); | |
| } | |
| // Update backref in our clone. | |
| cloned_array.SetAt(cloned_array.Length() - 1, result); | |
| result.set_entries(cloned_array); | |
| } | |
| // Copy deoptimization reasons. | |
| result.SetDeoptReasons(from.DeoptReasons()); | |
| result.set_is_megamorphic(is_megamorphic); | |
| DEBUG_ONLY(result.AssertInvariantsAreSatisfied()); | |
| return result.ptr(); | |
| } | |
| #endif | |
| ICDataPtr ICData::ICDataOfEntriesArray(const Array& array) { | |
| const auto& back_ref = Object::Handle(array.At(array.Length() - 1)); | |
| if (back_ref.ptr() == smi_illegal_cid().ptr()) { | |
| ASSERT(IsCachedEmptyEntry(array)); | |
| return ICData::null(); | |
| } | |
| const auto& ic_data = ICData::Cast(back_ref); | |
| DEBUG_ONLY(ic_data.AssertInvariantsAreSatisfied()); | |
| return ic_data.ptr(); | |
| } | |
| const char* WeakSerializationReference::ToCString() const { | |
| return Object::Handle(target()).ToCString(); | |
| } | |
| ObjectPtr WeakSerializationReference::New(const Object& target, | |
| const Object& replacement) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At( | |
| kWeakSerializationReferenceCid) != Class::null()); | |
| // Don't wrap any object in the VM heap, as all objects in the VM isolate | |
| // heap are currently serialized. | |
| // | |
| // Note that we _do_ wrap Smis if requested. Smis are serialized in the Mint | |
| // cluster, and so dropping them if not strongly referenced saves space in | |
| // the snapshot. | |
| // If the target is a WSR that already uses the replacement, then return it. | |
| if (target.IsWeakSerializationReference() && | |
| WeakSerializationReference::Cast(target).replacement() == | |
| replacement.ptr()) { | |
| return target.ptr(); | |
| } | |
| const auto& result = WeakSerializationReference::Handle( | |
| Object::Allocate<WeakSerializationReference>(Heap::kOld)); | |
| // Don't nest WSRs, instead just use the old WSR's target. | |
| result.untag()->set_target(target.IsWeakSerializationReference() | |
| ? WeakSerializationReference::Unwrap(target) | |
| : target.ptr()); | |
| result.untag()->set_replacement(replacement.ptr()); | |
| return result.ptr(); | |
| } | |
| const char* WeakArray::ToCString() const { | |
| return Thread::Current()->zone()->PrintToString("WeakArray len:%" Pd, | |
| Length()); | |
| } | |
| WeakArrayPtr WeakArray::New(intptr_t length, Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kWeakArrayCid) != | |
| Class::null()); | |
| if (!IsValidLength(length)) { | |
| // This should be caught before we reach here. | |
| FATAL("Fatal error in WeakArray::New: invalid len %" Pd "\n", length); | |
| } | |
| auto raw = Object::Allocate<WeakArray>(space, length); | |
| NoSafepointScope no_safepoint; | |
| raw->untag()->set_length(Smi::New(length)); | |
| return raw; | |
| } | |
| #if defined(INCLUDE_IL_PRINTER) | |
| Code::Comments& Code::Comments::New(intptr_t count) { | |
| Comments* comments; | |
| if (count < 0 || count > (kIntptrMax / kNumberOfEntries)) { | |
| // This should be caught before we reach here. | |
| FATAL("Fatal error in Code::Comments::New: invalid count %" Pd "\n", count); | |
| } | |
| if (count == 0) { | |
| comments = new Comments(Object::empty_array()); | |
| } else { | |
| const Array& data = | |
| Array::Handle(Array::New(count * kNumberOfEntries, Heap::kOld)); | |
| comments = new Comments(data); | |
| } | |
| return *comments; | |
| } | |
| intptr_t Code::Comments::Length() const { | |
| if (comments_.IsNull()) { | |
| return 0; | |
| } | |
| return comments_.Length() / kNumberOfEntries; | |
| } | |
| intptr_t Code::Comments::PCOffsetAt(intptr_t idx) const { | |
| return Smi::Value( | |
| Smi::RawCast(comments_.At(idx * kNumberOfEntries + kPCOffsetEntry))); | |
| } | |
| void Code::Comments::SetPCOffsetAt(intptr_t idx, intptr_t pc) { | |
| comments_.SetAt(idx * kNumberOfEntries + kPCOffsetEntry, | |
| Smi::Handle(Smi::New(pc))); | |
| } | |
| const char* Code::Comments::CommentAt(intptr_t idx) const { | |
| string_ ^= comments_.At(idx * kNumberOfEntries + kCommentEntry); | |
| return string_.ToCString(); | |
| } | |
| void Code::Comments::SetCommentAt(intptr_t idx, const String& comment) { | |
| comments_.SetAt(idx * kNumberOfEntries + kCommentEntry, comment); | |
| } | |
| Code::Comments::Comments(const Array& comments) | |
| : comments_(comments), string_(String::Handle()) {} | |
| #endif // defined(INCLUDE_IL_PRINTER) | |
| const char* Code::EntryKindToCString(EntryKind kind) { | |
| switch (kind) { | |
| case EntryKind::kNormal: | |
| return "Normal"; | |
| case EntryKind::kUnchecked: | |
| return "Unchecked"; | |
| case EntryKind::kMonomorphic: | |
| return "Monomorphic"; | |
| case EntryKind::kMonomorphicUnchecked: | |
| return "MonomorphicUnchecked"; | |
| default: | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| } | |
| bool Code::ParseEntryKind(const char* str, EntryKind* out) { | |
| if (strcmp(str, "Normal") == 0) { | |
| *out = EntryKind::kNormal; | |
| return true; | |
| } else if (strcmp(str, "Unchecked") == 0) { | |
| *out = EntryKind::kUnchecked; | |
| return true; | |
| } else if (strcmp(str, "Monomorphic") == 0) { | |
| *out = EntryKind::kMonomorphic; | |
| return true; | |
| } else if (strcmp(str, "MonomorphicUnchecked") == 0) { | |
| *out = EntryKind::kMonomorphicUnchecked; | |
| return true; | |
| } | |
| return false; | |
| } | |
| LocalVarDescriptorsPtr Code::GetLocalVarDescriptors() const { | |
| const LocalVarDescriptors& v = LocalVarDescriptors::Handle(var_descriptors()); | |
| if (v.IsNull()) { | |
| ASSERT(!is_optimized()); | |
| 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_can_be_deoptimized(bool value) const { | |
| set_state_bits(CanBeDeoptimizedBit::update(value, untag()->state_bits_)); | |
| } | |
| void Code::set_is_alive(bool value) const { | |
| set_state_bits(AliveBit::update(value, untag()->state_bits_)); | |
| } | |
| void Code::set_is_discarded(bool value) const { | |
| set_state_bits(DiscardedBit::update(value, untag()->state_bits_)); | |
| } | |
| void Code::set_compressed_stackmaps(const CompressedStackMaps& maps) const { | |
| ASSERT(maps.IsOld()); | |
| untag()->set_compressed_stackmaps(maps.ptr()); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| intptr_t Code::num_variables() const { | |
| ASSERT(!FLAG_precompiled_mode); | |
| return Smi::Value(Smi::RawCast(untag()->catch_entry())); | |
| } | |
| void Code::set_num_variables(intptr_t num_variables) const { | |
| ASSERT(!FLAG_precompiled_mode); | |
| untag()->set_catch_entry(Smi::New(num_variables)); | |
| } | |
| #endif | |
| TypedDataPtr Code::catch_entry_moves_maps() const { | |
| return TypedData::RawCast(untag()->catch_entry()); | |
| } | |
| void Code::set_catch_entry_moves_maps(const TypedData& maps) const { | |
| untag()->set_catch_entry(maps.ptr()); | |
| } | |
| void Code::set_deopt_info_array(const Array& array) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| ASSERT(array.IsOld()); | |
| untag()->set_deopt_info_array(array.ptr()); | |
| #endif | |
| } | |
| void Code::set_static_calls_target_table(const Array& value) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| untag()->set_static_calls_target_table(value.ptr()); | |
| #endif | |
| #if defined(DEBUG) | |
| // Check that the table is sorted by pc offsets. | |
| // FlowGraphCompiler::AddStaticCallTarget adds pc-offsets to the table while | |
| // emitting assembly. This guarantees that every succeeding pc-offset is | |
| // larger than the previously added one. | |
| StaticCallsTable entries(value); | |
| const intptr_t count = entries.Length(); | |
| for (intptr_t i = 0; i < count - 1; ++i) { | |
| auto left = Smi::Value(entries[i].Get<kSCallTableKindAndOffset>()); | |
| auto right = Smi::Value(entries[i + 1].Get<kSCallTableKindAndOffset>()); | |
| ASSERT(OffsetField::decode(left) < OffsetField::decode(right)); | |
| } | |
| #endif // DEBUG | |
| } | |
| ObjectPoolPtr Code::GetObjectPool() const { | |
| #if defined(DART_PRECOMPILER) || defined(DART_PRECOMPILED_RUNTIME) | |
| if (FLAG_precompiled_mode) { | |
| return IsolateGroup::Current()->object_store()->global_object_pool(); | |
| } | |
| #endif | |
| return object_pool(); | |
| } | |
| bool Code::HasBreakpoint() const { | |
| #if defined(PRODUCT) | |
| return false; | |
| #else | |
| return IsolateGroup::Current()->debugger()->HasBreakpointInCode(*this); | |
| #endif | |
| } | |
| TypedDataPtr Code::GetDeoptInfoAtPc(uword pc, | |
| ICData::DeoptReasonId* deopt_reason, | |
| uint32_t* deopt_flags) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| return TypedData::null(); | |
| #else | |
| ASSERT(is_optimized()); | |
| const Instructions& instrs = Instructions::Handle(instructions()); | |
| uword code_entry = instrs.PayloadStart(); | |
| const Array& table = Array::Handle(deopt_info_array()); | |
| if (table.IsNull()) { | |
| return TypedData::null(); | |
| } | |
| // Linear search for the PC offset matching the target PC. | |
| intptr_t length = DeoptTable::GetLength(table); | |
| Smi& offset = Smi::Handle(); | |
| Smi& reason_and_flags = Smi::Handle(); | |
| TypedData& info = TypedData::Handle(); | |
| for (intptr_t i = 0; i < length; ++i) { | |
| DeoptTable::GetEntry(table, i, &offset, &info, &reason_and_flags); | |
| if (pc == (code_entry + offset.Value())) { | |
| ASSERT(!info.IsNull()); | |
| *deopt_reason = DeoptTable::ReasonField::decode(reason_and_flags.Value()); | |
| *deopt_flags = DeoptTable::FlagsField::decode(reason_and_flags.Value()); | |
| return info.ptr(); | |
| } | |
| } | |
| *deopt_reason = ICData::kDeoptUnknown; | |
| return TypedData::null(); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| intptr_t Code::BinarySearchInSCallTable(uword pc) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| NoSafepointScope no_safepoint; | |
| const Array& table = Array::Handle(untag()->static_calls_target_table()); | |
| StaticCallsTable entries(table); | |
| const intptr_t pc_offset = pc - PayloadStart(); | |
| intptr_t imin = 0; | |
| intptr_t imax = (table.Length() / kSCallTableEntryLength) - 1; | |
| while (imax >= imin) { | |
| const intptr_t imid = imin + (imax - imin) / 2; | |
| const auto offset = OffsetField::decode( | |
| Smi::Value(entries[imid].Get<kSCallTableKindAndOffset>())); | |
| if (offset < pc_offset) { | |
| imin = imid + 1; | |
| } else if (offset > pc_offset) { | |
| imax = imid - 1; | |
| } else { | |
| return imid; | |
| } | |
| } | |
| #endif | |
| return -1; | |
| } | |
| FunctionPtr Code::GetStaticCallTargetFunctionAt(uword pc) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| return Function::null(); | |
| #else | |
| const intptr_t i = BinarySearchInSCallTable(pc); | |
| if (i < 0) { | |
| return Function::null(); | |
| } | |
| const Array& array = Array::Handle(untag()->static_calls_target_table()); | |
| StaticCallsTable entries(array); | |
| return entries[i].Get<kSCallTableFunctionTarget>(); | |
| #endif | |
| } | |
| void Code::SetStaticCallTargetCodeAt(uword pc, const Code& code) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| const intptr_t i = BinarySearchInSCallTable(pc); | |
| ASSERT(i >= 0); | |
| const Array& array = Array::Handle(untag()->static_calls_target_table()); | |
| StaticCallsTable entries(array); | |
| ASSERT(code.IsNull() || | |
| (code.function() == entries[i].Get<kSCallTableFunctionTarget>())); | |
| return entries[i].Set<kSCallTableCodeOrTypeTarget>(code); | |
| #endif | |
| } | |
| void Code::SetStubCallTargetCodeAt(uword pc, const Code& code) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| const intptr_t i = BinarySearchInSCallTable(pc); | |
| ASSERT(i >= 0); | |
| const Array& array = Array::Handle(untag()->static_calls_target_table()); | |
| StaticCallsTable entries(array); | |
| #if defined(DEBUG) | |
| if (entries[i].Get<kSCallTableFunctionTarget>() == Function::null()) { | |
| ASSERT(!code.IsNull() && Object::Handle(code.owner()).IsClass()); | |
| } else { | |
| ASSERT(code.IsNull() || | |
| (code.function() == entries[i].Get<kSCallTableFunctionTarget>())); | |
| } | |
| #endif | |
| return entries[i].Set<kSCallTableCodeOrTypeTarget>(code); | |
| #endif | |
| } | |
| void Code::Disassemble(DisassemblyFormatter* formatter) const { | |
| #if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) | |
| if (!FLAG_support_disassembler) { | |
| return; | |
| } | |
| const uword start = PayloadStart(); | |
| if (formatter == nullptr) { | |
| Disassembler::Disassemble(start, start + Size(), *this); | |
| } else { | |
| Disassembler::Disassemble(start, start + Size(), formatter, *this); | |
| } | |
| #endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) | |
| } | |
| #if defined(INCLUDE_IL_PRINTER) | |
| #if defined(PRODUCT) | |
| // In PRODUCT builds we don't have space in Code object to store code comments | |
| // so we move them into malloced heap (and leak them). This functionality | |
| // is only intended to be used in AOT compiler so leaking is fine. | |
| class MallocCodeComments final : public CodeComments { | |
| public: | |
| explicit MallocCodeComments(const CodeComments& comments) | |
| : length_(comments.Length()), comments_(new Comment[comments.Length()]) { | |
| for (intptr_t i = 0; i < length_; i++) { | |
| comments_[i].pc_offset = comments.PCOffsetAt(i); | |
| comments_[i].comment = | |
| CStringUniquePtr(Utils::StrDup(comments.CommentAt(i))); | |
| } | |
| } | |
| intptr_t Length() const override { return length_; } | |
| intptr_t PCOffsetAt(intptr_t i) const override { | |
| return comments_[i].pc_offset; | |
| } | |
| const char* CommentAt(intptr_t i) const override { | |
| return comments_[i].comment.get(); | |
| } | |
| private: | |
| struct Comment { | |
| intptr_t pc_offset; | |
| CStringUniquePtr comment; | |
| }; | |
| intptr_t length_; | |
| std::unique_ptr<Comment[]> comments_; | |
| }; | |
| #endif | |
| const CodeComments& Code::comments() const { | |
| #if defined(PRODUCT) | |
| auto comments = | |
| static_cast<CodeComments*>(Thread::Current()->heap()->GetPeer(ptr())); | |
| return (comments != nullptr) ? *comments : Code::Comments::New(0); | |
| #else | |
| return *new Code::Comments(Array::Handle(untag()->comments())); | |
| #endif | |
| } | |
| void Code::set_comments(const CodeComments& comments) const { | |
| #if !defined(PRODUCT) | |
| auto& wrapper = static_cast<const Code::Comments&>(comments); | |
| ASSERT(wrapper.comments_.IsOld()); | |
| untag()->set_comments(wrapper.comments_.ptr()); | |
| #else | |
| if (IsolateGroup::Current()->code_comments() && comments.Length() > 0) { | |
| Thread::Current()->heap()->SetPeer(ptr(), new MallocCodeComments(comments)); | |
| } else { | |
| Thread::Current()->heap()->SetPeer(ptr(), nullptr); | |
| } | |
| #endif | |
| } | |
| #endif // defined(INCLUDE_IL_PRINTER) | |
| void Code::SetPrologueOffset(intptr_t offset) const { | |
| #if defined(PRODUCT) | |
| UNREACHABLE(); | |
| #else | |
| ASSERT(offset >= 0); | |
| untag()->set_return_address_metadata(Smi::New(offset)); | |
| #endif | |
| } | |
| intptr_t Code::GetPrologueOffset() const { | |
| #if defined(PRODUCT) | |
| return -1; | |
| #else | |
| const Object& object = Object::Handle(untag()->return_address_metadata()); | |
| // In the future we may put something other than a smi in | |
| // |return_address_metadata_|. | |
| if (object.IsNull() || !object.IsSmi()) { | |
| return -1; | |
| } | |
| return Smi::Cast(object).Value(); | |
| #endif | |
| } | |
| ArrayPtr Code::inlined_id_to_function() const { | |
| return untag()->inlined_id_to_function(); | |
| } | |
| void Code::set_inlined_id_to_function(const Array& value) const { | |
| ASSERT(value.IsOld()); | |
| untag()->set_inlined_id_to_function(value.ptr()); | |
| } | |
| CodePtr Code::New(intptr_t pointer_offsets_length) { | |
| if (pointer_offsets_length < 0 || pointer_offsets_length > kMaxElements) { | |
| // This should be caught before we reach here. | |
| FATAL("Fatal error in Code::New: invalid pointer_offsets_length %" Pd "\n", | |
| pointer_offsets_length); | |
| } | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kCodeCid) != Class::null()); | |
| Code& result = Code::Handle(); | |
| { | |
| auto raw = Object::Allocate<Code>(Heap::kOld, pointer_offsets_length); | |
| NoSafepointScope no_safepoint; | |
| result = raw; | |
| ASSERT_EQUAL(result.untag()->state_bits_, 0); | |
| result.set_pointer_offsets_length(pointer_offsets_length); | |
| } | |
| DEBUG_ASSERT(result.compile_timestamp() == 0); | |
| #if defined(INCLUDE_IL_PRINTER) | |
| result.set_comments(Comments::New(0)); | |
| #endif | |
| result.set_pc_descriptors(Object::empty_descriptors()); | |
| result.set_compressed_stackmaps(Object::empty_compressed_stackmaps()); | |
| return result.ptr(); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| CodePtr Code::FinalizeCodeAndNotify(const Function& function, | |
| FlowGraphCompiler* compiler, | |
| compiler::Assembler* assembler, | |
| PoolAttachment pool_attachment, | |
| bool optimized, | |
| CodeStatistics* stats) { | |
| auto thread = Thread::Current(); | |
| ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); | |
| const auto& code = Code::Handle( | |
| FinalizeCode(compiler, assembler, pool_attachment, optimized, stats)); | |
| NotifyCodeObservers(function, code, optimized); | |
| return code.ptr(); | |
| } | |
| CodePtr Code::FinalizeCodeAndNotify(const char* name, | |
| FlowGraphCompiler* compiler, | |
| compiler::Assembler* assembler, | |
| PoolAttachment pool_attachment, | |
| bool optimized, | |
| CodeStatistics* stats) { | |
| auto thread = Thread::Current(); | |
| ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); | |
| const auto& code = Code::Handle( | |
| FinalizeCode(compiler, assembler, pool_attachment, optimized, stats)); | |
| NotifyCodeObservers(name, code, optimized); | |
| return code.ptr(); | |
| } | |
| #if defined(DART_PRECOMPILER) | |
| DECLARE_FLAG(charp, write_v8_snapshot_profile_to); | |
| DECLARE_FLAG(charp, trace_precompiler_to); | |
| #endif // defined(DART_PRECOMPILER) | |
| CodePtr Code::FinalizeCode(FlowGraphCompiler* compiler, | |
| compiler::Assembler* assembler, | |
| PoolAttachment pool_attachment, | |
| bool optimized, | |
| CodeStatistics* stats /* = nullptr */) { | |
| auto thread = Thread::Current(); | |
| ASSERT(thread->isolate_group()->program_lock()->IsCurrentThreadWriter()); | |
| ASSERT(assembler != nullptr); | |
| ObjectPool& object_pool = ObjectPool::Handle(); | |
| if (pool_attachment == PoolAttachment::kAttachPool) { | |
| if (assembler->HasObjectPoolBuilder()) { | |
| object_pool = | |
| ObjectPool::NewFromBuilder(assembler->object_pool_builder()); | |
| } else { | |
| object_pool = ObjectPool::empty_object_pool().ptr(); | |
| } | |
| } else { | |
| #if defined(DART_PRECOMPILER) | |
| if (assembler->HasObjectPoolBuilder() && | |
| assembler->object_pool_builder().HasParent()) { | |
| // We are not going to write this pool into snapshot, but we will use | |
| // it to emit references from this code object to other objects in the | |
| // snapshot that it uses. | |
| object_pool = | |
| ObjectPool::NewFromBuilder(assembler->object_pool_builder()); | |
| } | |
| #endif // defined(DART_PRECOMPILER) | |
| } | |
| // Allocate the Code and Instructions objects. Code is allocated first | |
| // because a GC during allocation of the code will leave the instruction | |
| // pages read-only. | |
| intptr_t pointer_offset_count = assembler->CountPointerOffsets(); | |
| Code& code = Code::ZoneHandle(Code::New(pointer_offset_count)); | |
| #ifdef TARGET_ARCH_IA32 | |
| assembler->GetSelfHandle() = code.ptr(); | |
| #endif | |
| Instructions& instrs = Instructions::ZoneHandle(Instructions::New( | |
| assembler->CodeSize(), assembler->has_monomorphic_entry(), | |
| assembler->should_be_aligned())); | |
| { | |
| // Important: if GC is triggered at any point between Instructions::New | |
| // and here it would write protect instructions object that we are trying | |
| // to fill in. | |
| NoSafepointScope no_safepoint; | |
| // Copy the instructions into the instruction area and apply all fixups. | |
| // Embedded pointers are still in handles at this point. | |
| MemoryRegion region(reinterpret_cast<void*>( | |
| Instructions::WritablePayloadStart(instrs.ptr())), | |
| instrs.Size()); | |
| assembler->FinalizeInstructions(region); | |
| const auto& pointer_offsets = assembler->GetPointerOffsets(); | |
| ASSERT(pointer_offsets.length() == pointer_offset_count); | |
| ASSERT(code.pointer_offsets_length() == pointer_offsets.length()); | |
| // Set pointer offsets list in Code object and resolve all handles in | |
| // the instruction stream to raw objects. | |
| for (intptr_t i = 0; i < pointer_offsets.length(); i++) { | |
| intptr_t offset_in_instrs = pointer_offsets[i]; | |
| code.SetPointerOffsetAt(i, offset_in_instrs); | |
| uword addr = region.start() + offset_in_instrs; | |
| ASSERT(instrs.PayloadStart() <= addr); | |
| ASSERT((instrs.PayloadStart() + instrs.Size()) > addr); | |
| const Object* object = LoadUnaligned(reinterpret_cast<Object**>(addr)); | |
| ASSERT(object->IsOld()); | |
| // N.B. The pointer is embedded in the Instructions object, but visited | |
| // through the Code object. | |
| code.StorePointerUnaligned(reinterpret_cast<ObjectPtr*>(addr), | |
| object->ptr(), thread); | |
| } | |
| // Write protect instructions and, if supported by OS, use dual mapping | |
| // for execution. | |
| if (FLAG_write_protect_code) { | |
| // Note: when dual mapping is used we have separate RX and RW mappings. | |
| // RX mapping never changes protection while RW mapping flips between | |
| // R and RW. | |
| uword address = UntaggedObject::ToAddr(instrs.ptr()); | |
| VirtualMemory::WriteProtectCode(reinterpret_cast<void*>(address), | |
| instrs.ptr()->untag()->HeapSize()); | |
| } | |
| // Hook up Code and Instructions objects. | |
| const uword unchecked_offset = assembler->UncheckedEntryOffset(); | |
| code.SetActiveInstructions(instrs, unchecked_offset); | |
| code.set_instructions(instrs); | |
| NOT_IN_PRECOMPILED(code.set_unchecked_offset(unchecked_offset)); | |
| code.set_is_alive(true); | |
| // Set object pool in Instructions object. | |
| if (!object_pool.IsNull()) { | |
| code.set_object_pool(object_pool.ptr()); | |
| } | |
| #if defined(DART_PRECOMPILER) | |
| if (stats != nullptr) { | |
| stats->Finalize(); | |
| instrs.set_stats(stats); | |
| } | |
| #endif | |
| CPU::FlushICache(region.start(), region.size()); | |
| } | |
| #if defined(INCLUDE_IL_PRINTER) | |
| code.set_comments(CreateCommentsFrom(assembler)); | |
| #endif // defined(INCLUDE_IL_PRINTER) | |
| #ifndef PRODUCT | |
| code.set_compile_timestamp(OS::GetCurrentMonotonicMicros()); | |
| if (assembler->prologue_offset() >= 0) { | |
| code.SetPrologueOffset(assembler->prologue_offset()); | |
| } else { | |
| // No prologue was ever entered, optimistically assume nothing was ever | |
| // pushed onto the stack. | |
| code.SetPrologueOffset(assembler->CodeSize()); | |
| } | |
| #endif | |
| return code.ptr(); | |
| } | |
| void Code::NotifyCodeObservers(const Code& code, bool optimized) { | |
| #if !defined(PRODUCT) | |
| ASSERT(!Thread::Current()->OwnsGCSafepoint()); | |
| if (CodeObservers::AreActive()) { | |
| if (code.IsFunctionCode()) { | |
| const auto& function = Function::Handle(code.function()); | |
| if (!function.IsNull()) { | |
| return NotifyCodeObservers(function, code, optimized); | |
| } | |
| } | |
| NotifyCodeObservers(code.Name(), code, optimized); | |
| } | |
| #endif | |
| } | |
| void Code::NotifyCodeObservers(const Function& function, | |
| const Code& code, | |
| bool optimized) { | |
| #if !defined(PRODUCT) | |
| ASSERT(!function.IsNull()); | |
| // Calling ToLibNamePrefixedQualifiedCString is very expensive, | |
| // try to avoid it. | |
| if (CodeObservers::AreActive()) { | |
| const char* name = function.ToLibNamePrefixedQualifiedCString(); | |
| NotifyCodeObservers(name, code, optimized); | |
| } | |
| #endif | |
| RegisterTsanSymbolize(code); | |
| } | |
| void Code::NotifyCodeObservers(const char* name, | |
| const Code& code, | |
| bool optimized) { | |
| #if !defined(PRODUCT) | |
| ASSERT(name != nullptr); | |
| ASSERT(!code.IsNull()); | |
| if (CodeObservers::AreActive()) { | |
| const auto& instrs = Instructions::Handle(code.instructions()); | |
| CodeObservers::NotifyAll(name, instrs.PayloadStart(), | |
| code.GetPrologueOffset(), instrs.Size(), optimized, | |
| &code.comments()); | |
| } | |
| #endif | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| CodePtr Code::FindCode(uword pc, int64_t timestamp) { | |
| class SlowFindCodeVisitor : public ObjectVisitor { | |
| public: | |
| SlowFindCodeVisitor(uword pc, int64_t timestamp) | |
| : pc_(pc), timestamp_(timestamp), result_(Code::null()) {} | |
| void VisitObject(ObjectPtr obj) { | |
| if (!obj->IsCode()) return; | |
| CodePtr code = static_cast<CodePtr>(obj); | |
| if (Code::PayloadStartOf(code) != pc_) return; | |
| #if !defined(PRODUCT) | |
| if (code->untag()->compile_timestamp_ != timestamp_) return; | |
| #endif | |
| ASSERT(result_ == Code::null()); | |
| result_ = code; | |
| } | |
| CodePtr result() const { return result_; } | |
| private: | |
| uword pc_; | |
| int64_t timestamp_; | |
| CodePtr result_; | |
| }; | |
| HeapIterationScope iteration(Thread::Current()); | |
| SlowFindCodeVisitor visitor(pc, timestamp); | |
| iteration.IterateOldObjectsNoImagePages(&visitor); | |
| return visitor.result(); | |
| } | |
| CodePtr Code::FindCodeUnsafe(uword pc) { | |
| class FindCodeUnsafeVisitor : public ObjectVisitor { | |
| public: | |
| explicit FindCodeUnsafeVisitor(uword pc) : pc_(pc), result_(Code::null()) {} | |
| void VisitObject(ObjectPtr obj) { | |
| if (obj->IsCode()) { | |
| CodePtr code = static_cast<CodePtr>(obj); | |
| if (Code::ContainsInstructionAt(code, pc_)) { | |
| result_ = code; | |
| } | |
| } | |
| } | |
| CodePtr result() { return result_; } | |
| private: | |
| uword pc_; | |
| CodePtr result_; | |
| }; | |
| IsolateGroup* group = IsolateGroup::Current(); | |
| PageSpace* old_space = group->heap()->old_space(); | |
| old_space->MakeIterable(); | |
| FindCodeUnsafeVisitor visitor(pc); | |
| old_space->VisitObjectsUnsafe(&visitor); | |
| return visitor.result(); | |
| } | |
| TokenPosition Code::GetTokenIndexOfPC(uword pc) const { | |
| uword pc_offset = pc - PayloadStart(); | |
| const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors()); | |
| PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kAnyKind); | |
| while (iter.MoveNext()) { | |
| if (iter.PcOffset() == pc_offset) { | |
| return iter.TokenPos(); | |
| } | |
| } | |
| return TokenPosition::kNoSource; | |
| } | |
| uword Code::GetPcForDeoptId(intptr_t deopt_id, | |
| UntaggedPcDescriptors::Kind kind) const { | |
| const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors()); | |
| PcDescriptors::Iterator iter(descriptors, kind); | |
| while (iter.MoveNext()) { | |
| if (iter.DeoptId() == deopt_id) { | |
| uword pc_offset = iter.PcOffset(); | |
| uword pc = PayloadStart() + pc_offset; | |
| ASSERT(ContainsInstructionAt(pc)); | |
| return pc; | |
| } | |
| } | |
| return 0; | |
| } | |
| intptr_t Code::GetDeoptIdForOsr(uword pc) const { | |
| uword pc_offset = pc - PayloadStart(); | |
| const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors()); | |
| PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kOsrEntry); | |
| while (iter.MoveNext()) { | |
| if (iter.PcOffset() == pc_offset) { | |
| return iter.DeoptId(); | |
| } | |
| } | |
| return DeoptId::kNone; | |
| } | |
| const char* Code::ToCString() const { | |
| return OS::SCreate(Thread::Current()->zone(), "Code(%s)", | |
| QualifiedName(NameFormattingParams( | |
| kScrubbedName, NameDisambiguation::kYes))); | |
| } | |
| uint32_t Code::Hash() const { | |
| // PayloadStart() is a tempting hash as Instructions are not moved by the | |
| // compactor, but Instructions are effectively moved between the process | |
| // creating an AppJIT/AOT snapshot and the process loading the snapshot. | |
| const Object& obj = | |
| Object::Handle(WeakSerializationReference::UnwrapIfTarget(owner())); | |
| if (obj.IsClass()) { | |
| return Class::Cast(obj).Hash(); | |
| } else if (obj.IsAbstractType()) { | |
| return AbstractType::Cast(obj).Hash(); | |
| } else if (obj.IsFunction()) { | |
| return Function::Cast(obj).Hash(); | |
| } else { | |
| // E.g., VM stub. | |
| return 42; | |
| } | |
| } | |
| const char* Code::Name() const { | |
| Zone* zone = Thread::Current()->zone(); | |
| if (IsStubCode()) { | |
| // Regular stub. | |
| const char* name = StubCode::NameOfStub(EntryPoint()); | |
| if (name == nullptr) { | |
| return "[unknown stub]"; // Not yet recorded. | |
| } | |
| return OS::SCreate(zone, "[Stub] %s", name); | |
| } | |
| const auto& obj = | |
| Object::Handle(zone, WeakSerializationReference::UnwrapIfTarget(owner())); | |
| if (obj.IsClass()) { | |
| // Allocation stub. | |
| return OS::SCreate(zone, "[Stub] Allocate %s", | |
| Class::Cast(obj).ScrubbedNameCString()); | |
| } else if (obj.IsAbstractType()) { | |
| // Type test stub. | |
| return OS::SCreate(zone, "[Stub] Type Test %s", | |
| AbstractType::Cast(obj).ToCString()); | |
| } else if (obj.IsFunction()) { | |
| // Dart function. | |
| const char* opt = is_optimized() ? "[Optimized]" : "[Unoptimized]"; | |
| const char* function_name = Function::Cast(obj).UserVisibleNameCString(); | |
| return OS::SCreate(zone, "%s %s", opt, function_name); | |
| } else { | |
| // --no_retain_function_objects etc | |
| return "[unknown code]"; | |
| } | |
| } | |
| const char* Code::QualifiedName(const NameFormattingParams& params) const { | |
| Zone* zone = Thread::Current()->zone(); | |
| const Object& obj = | |
| Object::Handle(zone, WeakSerializationReference::UnwrapIfTarget(owner())); | |
| if (obj.IsFunction()) { | |
| ZoneTextBuffer printer(zone); | |
| printer.AddString(is_optimized() ? "[Optimized] " : "[Unoptimized] "); | |
| Function::Cast(obj).PrintName(params, &printer); | |
| return printer.buffer(); | |
| } | |
| return Name(); | |
| } | |
| bool Code::IsStubCode() const { | |
| // We should _not_ unwrap any possible WSRs here, as the null value is never | |
| // wrapped by a WSR. | |
| return owner() == Object::null(); | |
| } | |
| bool Code::IsAllocationStubCode() const { | |
| return OwnerClassId() == kClassCid; | |
| } | |
| bool Code::IsTypeTestStubCode() const { | |
| auto const cid = OwnerClassId(); | |
| return cid == kAbstractTypeCid || cid == kTypeCid || | |
| cid == kFunctionTypeCid || cid == kRecordTypeCid || | |
| cid == kTypeParameterCid; | |
| } | |
| bool Code::IsFunctionCode() const { | |
| return OwnerClassId() == kFunctionCid; | |
| } | |
| bool Code::IsUnknownDartCode(CodePtr code) { | |
| ASSERT(StubCode::UnknownDartCode().ptr() != nullptr); | |
| return code == StubCode::UnknownDartCode().ptr(); | |
| } | |
| void Code::DisableDartCode() const { | |
| GcSafepointOperationScope safepoint(Thread::Current()); | |
| ASSERT(IsFunctionCode()); | |
| ASSERT(instructions() == active_instructions()); | |
| const Code& new_code = StubCode::FixCallersTarget(); | |
| SetActiveInstructions(Instructions::Handle(new_code.instructions()), | |
| new_code.UncheckedEntryPointOffset()); | |
| } | |
| void Code::DisableStubCode(bool is_cls_parameterized) const { | |
| GcSafepointOperationScope safepoint(Thread::Current()); | |
| ASSERT(IsAllocationStubCode()); | |
| ASSERT(instructions() == active_instructions()); | |
| const Code& new_code = is_cls_parameterized | |
| ? StubCode::FixParameterizedAllocationStubTarget() | |
| : StubCode::FixAllocationStubTarget(); | |
| SetActiveInstructions(Instructions::Handle(new_code.instructions()), | |
| new_code.UncheckedEntryPointOffset()); | |
| } | |
| void Code::InitializeCachedEntryPointsFrom(CodePtr code, | |
| InstructionsPtr instructions, | |
| uint32_t unchecked_offset) { | |
| NoSafepointScope _; | |
| const uword entry_point = Instructions::EntryPoint(instructions); | |
| const uword monomorphic_entry_point = | |
| Instructions::MonomorphicEntryPoint(instructions); | |
| code->untag()->entry_point_ = entry_point; | |
| code->untag()->monomorphic_entry_point_ = monomorphic_entry_point; | |
| code->untag()->unchecked_entry_point_ = entry_point + unchecked_offset; | |
| code->untag()->monomorphic_unchecked_entry_point_ = | |
| monomorphic_entry_point + unchecked_offset; | |
| } | |
| void Code::SetActiveInstructions(const Instructions& instructions, | |
| uint32_t unchecked_offset) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| ASSERT(IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter()); | |
| SetActiveInstructionsSafe(instructions, unchecked_offset); | |
| #endif | |
| } | |
| void Code::SetActiveInstructionsSafe(const Instructions& instructions, | |
| uint32_t unchecked_offset) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| // RawInstructions are never allocated in New space and hence a | |
| // store buffer update is not needed here. | |
| untag()->set_active_instructions(instructions.ptr()); | |
| Code::InitializeCachedEntryPointsFrom(ptr(), instructions.ptr(), | |
| unchecked_offset); | |
| #endif | |
| } | |
| void Code::ResetActiveInstructions() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| UNREACHABLE(); | |
| #else | |
| SetActiveInstructions(Instructions::Handle(instructions()), | |
| untag()->unchecked_offset_); | |
| #endif | |
| } | |
| void Code::GetInlinedFunctionsAtInstruction( | |
| intptr_t pc_offset, | |
| GrowableArray<const Function*>* functions, | |
| GrowableArray<TokenPosition>* token_positions) const { | |
| const CodeSourceMap& map = CodeSourceMap::Handle(code_source_map()); | |
| if (map.IsNull()) { | |
| ASSERT(!IsFunctionCode()); | |
| return; // VM stub, allocation stub, or type testing stub. | |
| } | |
| const Array& id_map = Array::Handle(inlined_id_to_function()); | |
| const Function& root = Function::Handle(function()); | |
| CodeSourceMapReader reader(map, id_map, root); | |
| reader.GetInlinedFunctionsAt(pc_offset, functions, token_positions); | |
| } | |
| #ifndef PRODUCT | |
| void Code::PrintJSONInlineIntervals(JSONObject* jsobj) const { | |
| if (!is_optimized()) { | |
| return; // No inlining. | |
| } | |
| const CodeSourceMap& map = CodeSourceMap::Handle(code_source_map()); | |
| const Array& id_map = Array::Handle(inlined_id_to_function()); | |
| const Function& root = Function::Handle(function()); | |
| CodeSourceMapReader reader(map, id_map, root); | |
| reader.PrintJSONInlineIntervals(jsobj); | |
| } | |
| #endif | |
| void Code::DumpInlineIntervals() const { | |
| const CodeSourceMap& map = CodeSourceMap::Handle(code_source_map()); | |
| if (map.IsNull()) { | |
| // Stub code. | |
| return; | |
| } | |
| const Array& id_map = Array::Handle(inlined_id_to_function()); | |
| const Function& root = Function::Handle(function()); | |
| CodeSourceMapReader reader(map, id_map, root); | |
| reader.DumpInlineIntervals(PayloadStart()); | |
| } | |
| void Code::DumpSourcePositions(bool relative_addresses) const { | |
| const CodeSourceMap& map = CodeSourceMap::Handle(code_source_map()); | |
| if (map.IsNull()) { | |
| // Stub code. | |
| return; | |
| } | |
| const Array& id_map = Array::Handle(inlined_id_to_function()); | |
| const Function& root = Function::Handle(function()); | |
| CodeSourceMapReader reader(map, id_map, root); | |
| reader.DumpSourcePositions(relative_addresses ? 0 : PayloadStart()); | |
| } | |
| void Bytecode::Disassemble(DisassemblyFormatter* formatter) const { | |
| #if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) | |
| #if defined(DART_DYNAMIC_MODULES) | |
| if (!FLAG_support_disassembler) { | |
| return; | |
| } | |
| uword start = PayloadStart(); | |
| intptr_t size = Size(); | |
| if (formatter == NULL) { | |
| KernelBytecodeDisassembler::Disassemble(start, start + size, *this); | |
| } else { | |
| KernelBytecodeDisassembler::Disassemble(start, start + size, formatter, | |
| *this); | |
| } | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| #endif // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER) | |
| } | |
| BytecodePtr Bytecode::New(uword instructions, | |
| intptr_t instructions_size, | |
| intptr_t instructions_offset, | |
| const TypedDataBase& binary, | |
| const ObjectPool& object_pool) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kBytecodeCid) != | |
| Class::null()); | |
| Bytecode& result = Bytecode::Handle(); | |
| { | |
| auto raw = Object::Allocate<Bytecode>(Heap::kOld); | |
| NoSafepointScope no_safepoint; | |
| result = raw; | |
| result.set_instructions(instructions); | |
| result.set_instructions_size(instructions_size); | |
| result.set_object_pool(object_pool); | |
| result.set_pc_descriptors(Object::empty_descriptors()); | |
| result.set_binary(binary); | |
| result.set_instructions_binary_offset(instructions_offset); | |
| result.set_code_offset(0); | |
| result.set_source_positions_binary_offset(0); | |
| } | |
| return result.ptr(); | |
| } | |
| TokenPosition Bytecode::GetTokenIndexOfPC(uword return_address) const { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| if (!HasSourcePositions()) { | |
| return TokenPosition::kNoSource; | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| uword pc_offset = return_address - PayloadStart(); | |
| // pc_offset could equal to bytecode size if the last instruction is Throw. | |
| ASSERT(pc_offset <= static_cast<uword>(Size())); | |
| bytecode::BytecodeSourcePositionsIterator iter(zone, *this); | |
| TokenPosition token_pos = TokenPosition::kNoSource; | |
| while (iter.MoveNext()) { | |
| if (pc_offset <= iter.PcOffset()) { | |
| break; | |
| } | |
| token_pos = iter.TokenPos(); | |
| } | |
| return token_pos; | |
| #else | |
| UNREACHABLE(); | |
| #endif | |
| } | |
| intptr_t Bytecode::GetTryIndexAtPc(uword return_address) const { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| intptr_t try_index = -1; | |
| const uword pc_offset = return_address - PayloadStart(); | |
| const PcDescriptors& descriptors = PcDescriptors::Handle(pc_descriptors()); | |
| PcDescriptors::Iterator iter(descriptors, UntaggedPcDescriptors::kAnyKind); | |
| while (iter.MoveNext()) { | |
| // PC descriptors for try blocks in bytecode are generated in pairs, | |
| // marking start and end of a try block. | |
| // See BytecodeReaderHelper::ReadExceptionsTable for details. | |
| const intptr_t current_try_index = iter.TryIndex(); | |
| const uword start_pc = iter.PcOffset(); | |
| if (pc_offset < start_pc) { | |
| break; | |
| } | |
| const bool has_next = iter.MoveNext(); | |
| ASSERT(has_next); | |
| const uword end_pc = iter.PcOffset(); | |
| if (start_pc <= pc_offset && pc_offset < end_pc) { | |
| ASSERT(try_index < current_try_index); | |
| try_index = current_try_index; | |
| } | |
| } | |
| return try_index; | |
| #else | |
| UNREACHABLE(); | |
| #endif | |
| } | |
| uword Bytecode::GetInstructionBefore(uword return_address) const { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| const uword start = PayloadStart(); | |
| // return_address could be the end of the bytecode instructions | |
| // if the last instruction is Throw. | |
| if (return_address <= start || return_address > start + Size()) { | |
| return 0; | |
| } | |
| uword prev = start; | |
| uword current = KernelBytecode::Next(start); | |
| while (current < return_address) { | |
| prev = current; | |
| current = KernelBytecode::Next(prev); | |
| } | |
| // Any valid return address should be on an instruction boundary. | |
| return current == return_address ? prev : 0; | |
| #else | |
| UNREACHABLE(); | |
| return 0; | |
| #endif | |
| } | |
| #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| LocalVarDescriptorsPtr Bytecode::GetLocalVarDescriptors() const { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| Zone* zone = Thread::Current()->zone(); | |
| auto& var_descs = LocalVarDescriptors::Handle(zone, var_descriptors()); | |
| if (var_descs.IsNull()) { | |
| const auto& func = Function::Handle(zone, function()); | |
| ASSERT(!func.IsNull()); | |
| var_descs = | |
| bytecode::BytecodeReader::ComputeLocalVarDescriptors(zone, func, *this); | |
| ASSERT(!var_descs.IsNull()); | |
| set_var_descriptors(var_descs); | |
| } | |
| return var_descs.ptr(); | |
| #else | |
| UNREACHABLE(); | |
| #endif | |
| } | |
| TypedDataPtr Bytecode::EnsureCoverageArray(Thread* thread) const { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| // Should only be called for bytecode with RecordCoverage instructions. | |
| ASSERT(HasRecordedCoverage()); | |
| if (coverage_array() == TypedData::null()) { | |
| Zone* const zone = thread->zone(); | |
| bytecode::BytecodeRecordedCoverageIterator it(zone, *this); | |
| const auto& array = TypedData::Handle( | |
| zone, TypedData::New(kTypedDataUint32ArrayCid, 2 * it.NumEntries(), | |
| Heap::kOld)); | |
| // The coverage array has two consecutive entries for each logical | |
| // index: the encoded coverage position and the hit count. | |
| for (intptr_t i = 0; it.MoveNext(); i += 2) { | |
| array.SetUint32(i * kInt32Size, it.EncodedCoveragePosition()); | |
| array.SetUint32((i + 1) * kInt32Size, 0); | |
| } | |
| SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock()); | |
| if (coverage_array() == TypedData::null()) { | |
| untag()->set_coverage_array(array.ptr()); | |
| } | |
| } | |
| return coverage_array(); | |
| #else | |
| UNREACHABLE(); | |
| #endif | |
| } | |
| #endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) | |
| const char* Bytecode::ToCString() const { | |
| return Thread::Current()->zone()->PrintToString("Bytecode(%s)", | |
| QualifiedName()); | |
| } | |
| bool Bytecode::IsSharedBytecodeStub() const { | |
| #define CHECK_SHARED_BYTECODE(Name, _) \ | |
| if (ptr() == OBJECT_SHARED_BYTECODE_GETTER(Name).ptr()) { \ | |
| return true; \ | |
| } | |
| FOR_EACH_SHARED_BYTECODE_STUB(CHECK_SHARED_BYTECODE) | |
| #undef CHECK_SHARED_BYTECODE | |
| return false; | |
| } | |
| static const char* BytecodeStubName(const Bytecode& bytecode) { | |
| #define RETURN_SHARED_BYTECODE_STUB_NAME(Name, Instruction) \ | |
| if (bytecode.ptr() == OBJECT_SHARED_BYTECODE_GETTER(Name).ptr()) { \ | |
| return "[Bytecode Stub] " #Instruction; \ | |
| } | |
| FOR_EACH_SHARED_BYTECODE_STUB(RETURN_SHARED_BYTECODE_STUB_NAME) | |
| #undef RETURN_SHARED_BYTECODE_STUB_NAME | |
| return "[unknown stub]"; | |
| } | |
| const char* Bytecode::Name() const { | |
| if (IsSharedBytecodeStub()) { | |
| return BytecodeStubName(*this); | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| const Function& fun = Function::Handle(zone, function()); | |
| const char* function_name = fun.UserVisibleNameCString(); | |
| return zone->PrintToString("[Bytecode] %s", function_name); | |
| } | |
| const char* Bytecode::QualifiedName() const { | |
| if (IsSharedBytecodeStub()) { | |
| return BytecodeStubName(*this); | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| const Function& fun = Function::Handle(zone, function()); | |
| const char* function_name = fun.QualifiedScrubbedNameCString(); | |
| return zone->PrintToString("[Bytecode] %s", function_name); | |
| } | |
| const char* Bytecode::FullyQualifiedName() const { | |
| if (IsSharedBytecodeStub()) { | |
| return BytecodeStubName(*this); | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| const Function& fun = Function::Handle(zone, function()); | |
| const char* function_name = fun.ToFullyQualifiedCString(); | |
| return zone->PrintToString("[Bytecode] %s", function_name); | |
| } | |
| BytecodePtr Bytecode::FindBytecode(uword pc) { | |
| #if defined(DART_DYNAMIC_MODULES) | |
| class SlowFindBytecodeVisitor : public ObjectVisitor { | |
| public: | |
| explicit SlowFindBytecodeVisitor(uword pc) | |
| : pc_(pc), result_(Bytecode::null()) {} | |
| void VisitObject(ObjectPtr obj) { | |
| if (!obj->IsBytecode()) return; | |
| BytecodePtr bytecode = static_cast<BytecodePtr>(obj); | |
| if (PayloadStartOf(bytecode) != pc_) return; | |
| ASSERT(result_ == Bytecode::null()); | |
| result_ = bytecode; | |
| } | |
| BytecodePtr result() const { return result_; } | |
| private: | |
| uword pc_; | |
| BytecodePtr result_; | |
| }; | |
| HeapIterationScope iteration(Thread::Current()); | |
| SlowFindBytecodeVisitor visitor(pc); | |
| iteration.IterateOldObjectsNoImagePages(&visitor); | |
| return visitor.result(); | |
| #else | |
| UNREACHABLE(); | |
| return Bytecode::null(); | |
| #endif | |
| } | |
| void Bytecode::set_binary(const TypedDataBase& binary) const { | |
| ASSERT(binary.IsNull() || binary.IsExternalOrExternalView()); | |
| untag()->set_binary(binary.ptr()); | |
| } | |
| intptr_t Context::GetLevel() const { | |
| intptr_t level = 0; | |
| Context& parent_ctx = Context::Handle(parent()); | |
| while (!parent_ctx.IsNull()) { | |
| level++; | |
| parent_ctx = parent_ctx.parent(); | |
| } | |
| return level; | |
| } | |
| ContextPtr Context::New(intptr_t num_variables, Heap::Space space) { | |
| ASSERT(num_variables >= 0); | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kContextCid) != | |
| Class::null()); | |
| if (!IsValidLength(num_variables)) { | |
| // This should be caught before we reach here. | |
| FATAL("Fatal error in Context::New: invalid num_variables %" Pd "\n", | |
| num_variables); | |
| } | |
| auto raw = Object::Allocate<Context>(space, num_variables); | |
| NoSafepointScope no_safepoint; | |
| raw->untag()->num_variables_ = num_variables; | |
| return raw; | |
| } | |
| const char* Context::ToCString() const { | |
| if (IsNull()) { | |
| return "Context: null"; | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| const Context& parent_ctx = Context::Handle(parent()); | |
| if (parent_ctx.IsNull()) { | |
| return zone->PrintToString("Context num_variables: %" Pd "", | |
| num_variables()); | |
| } else { | |
| const char* parent_str = parent_ctx.ToCString(); | |
| return zone->PrintToString("Context num_variables: %" Pd " parent:{ %s }", | |
| num_variables(), parent_str); | |
| } | |
| } | |
| static void IndentN(int count) { | |
| for (int i = 0; i < count; i++) { | |
| THR_Print(" "); | |
| } | |
| } | |
| void Context::Dump(int indent) const { | |
| if (IsNull()) { | |
| IndentN(indent); | |
| THR_Print("Context@null\n"); | |
| return; | |
| } | |
| IndentN(indent); | |
| THR_Print("Context vars(%" Pd ") {\n", num_variables()); | |
| Object& obj = Object::Handle(); | |
| for (intptr_t i = 0; i < num_variables(); i++) { | |
| IndentN(indent + 2); | |
| obj = At(i); | |
| const char* s = obj.ToCString(); | |
| if (strlen(s) > 50) { | |
| THR_Print("[%" Pd "] = [first 50 chars:] %.50s...\n", i, s); | |
| } else { | |
| THR_Print("[%" Pd "] = %s\n", i, s); | |
| } | |
| } | |
| const Context& parent_ctx = Context::Handle(parent()); | |
| if (!parent_ctx.IsNull()) { | |
| parent_ctx.Dump(indent + 2); | |
| } | |
| IndentN(indent); | |
| THR_Print("}\n"); | |
| } | |
| ContextScopePtr ContextScope::New(intptr_t num_variables, bool is_implicit) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kContextScopeCid) != | |
| Class::null()); | |
| if (num_variables < 0 || num_variables > kMaxElements) { | |
| // This should be caught before we reach here. | |
| FATAL("Fatal error in ContextScope::New: invalid num_variables %" Pd "\n", | |
| num_variables); | |
| } | |
| ContextScope& result = ContextScope::Handle(); | |
| { | |
| auto raw = Object::Allocate<ContextScope>(Heap::kOld, num_variables); | |
| NoSafepointScope no_safepoint; | |
| result = raw; | |
| result.set_num_variables(num_variables); | |
| } | |
| result.set_is_implicit(is_implicit); | |
| return result.ptr(); | |
| } | |
| TokenPosition ContextScope::TokenIndexAt(intptr_t scope_index) const { | |
| return TokenPosition::Deserialize( | |
| Smi::Value(untag()->token_pos_at(scope_index))); | |
| } | |
| void ContextScope::SetTokenIndexAt(intptr_t scope_index, | |
| TokenPosition token_pos) const { | |
| untag()->set_token_pos_at(scope_index, Smi::New(token_pos.Serialize())); | |
| } | |
| TokenPosition ContextScope::DeclarationTokenIndexAt( | |
| intptr_t scope_index) const { | |
| return TokenPosition::Deserialize( | |
| Smi::Value(untag()->declaration_token_pos_at(scope_index))); | |
| } | |
| void ContextScope::SetDeclarationTokenIndexAt( | |
| intptr_t scope_index, | |
| TokenPosition declaration_token_pos) const { | |
| untag()->set_declaration_token_pos_at( | |
| scope_index, Smi::New(declaration_token_pos.Serialize())); | |
| } | |
| StringPtr ContextScope::NameAt(intptr_t scope_index) const { | |
| return untag()->name_at(scope_index); | |
| } | |
| void ContextScope::SetNameAt(intptr_t scope_index, const String& name) const { | |
| untag()->set_name_at(scope_index, name.ptr()); | |
| } | |
| void ContextScope::ClearFlagsAt(intptr_t scope_index) const { | |
| untag()->set_flags_at(scope_index, Smi::New(0)); | |
| } | |
| bool ContextScope::GetFlagAt(intptr_t scope_index, intptr_t bit_index) const { | |
| const intptr_t mask = 1 << bit_index; | |
| return (Smi::Value(untag()->flags_at(scope_index)) & mask) != 0; | |
| } | |
| void ContextScope::SetFlagAt(intptr_t scope_index, | |
| intptr_t bit_index, | |
| bool value) const { | |
| const intptr_t mask = 1 << bit_index; | |
| intptr_t flags = Smi::Value(untag()->flags_at(scope_index)); | |
| untag()->set_flags_at(scope_index, | |
| Smi::New(value ? flags | mask : flags & ~mask)); | |
| } | |
| #define DEFINE_FLAG_ACCESSORS(Name) \ | |
| bool ContextScope::Is##Name##At(intptr_t scope_index) const { \ | |
| return GetFlagAt(scope_index, \ | |
| UntaggedContextScope::VariableDesc::kIs##Name); \ | |
| } \ | |
| \ | |
| void ContextScope::SetIs##Name##At(intptr_t scope_index, bool value) const { \ | |
| SetFlagAt(scope_index, UntaggedContextScope::VariableDesc::kIs##Name, \ | |
| value); \ | |
| } | |
| CONTEXT_SCOPE_VARIABLE_DESC_FLAG_LIST(DEFINE_FLAG_ACCESSORS) | |
| #undef DEFINE_FLAG_ACCESSORS | |
| intptr_t ContextScope::LateInitOffsetAt(intptr_t scope_index) const { | |
| return Smi::Value(untag()->late_init_offset_at(scope_index)); | |
| } | |
| void ContextScope::SetLateInitOffsetAt(intptr_t scope_index, | |
| intptr_t late_init_offset) const { | |
| untag()->set_late_init_offset_at(scope_index, Smi::New(late_init_offset)); | |
| } | |
| AbstractTypePtr ContextScope::TypeAt(intptr_t scope_index) const { | |
| return untag()->type_at(scope_index); | |
| } | |
| void ContextScope::SetTypeAt(intptr_t scope_index, | |
| const AbstractType& type) const { | |
| untag()->set_type_at(scope_index, type.ptr()); | |
| } | |
| intptr_t ContextScope::CidAt(intptr_t scope_index) const { | |
| return Smi::Value(untag()->cid_at(scope_index)); | |
| } | |
| void ContextScope::SetCidAt(intptr_t scope_index, intptr_t cid) const { | |
| untag()->set_cid_at(scope_index, Smi::New(cid)); | |
| } | |
| intptr_t ContextScope::ContextIndexAt(intptr_t scope_index) const { | |
| return Smi::Value(untag()->context_index_at(scope_index)); | |
| } | |
| void ContextScope::SetContextIndexAt(intptr_t scope_index, | |
| intptr_t context_index) const { | |
| untag()->set_context_index_at(scope_index, Smi::New(context_index)); | |
| } | |
| intptr_t ContextScope::ContextLevelAt(intptr_t scope_index) const { | |
| return Smi::Value(untag()->context_level_at(scope_index)); | |
| } | |
| void ContextScope::SetContextLevelAt(intptr_t scope_index, | |
| intptr_t context_level) const { | |
| untag()->set_context_level_at(scope_index, Smi::New(context_level)); | |
| } | |
| intptr_t ContextScope::KernelOffsetAt(intptr_t scope_index) const { | |
| return Smi::Value(untag()->kernel_offset_at(scope_index)); | |
| } | |
| void ContextScope::SetKernelOffsetAt(intptr_t scope_index, | |
| intptr_t kernel_offset) const { | |
| untag()->set_kernel_offset_at(scope_index, Smi::New(kernel_offset)); | |
| } | |
| const char* ContextScope::ToCString() const { | |
| const char* prev_cstr = "ContextScope:"; | |
| String& name = String::Handle(); | |
| for (int i = 0; i < num_variables(); i++) { | |
| name = NameAt(i); | |
| const char* cname = name.ToCString(); | |
| TokenPosition pos = TokenIndexAt(i); | |
| intptr_t idx = ContextIndexAt(i); | |
| intptr_t lvl = ContextLevelAt(i); | |
| char* chars = | |
| OS::SCreate(Thread::Current()->zone(), | |
| "%s\nvar %s token-pos %s ctx lvl %" Pd " index %" Pd "", | |
| prev_cstr, cname, pos.ToCString(), lvl, idx); | |
| prev_cstr = chars; | |
| } | |
| return prev_cstr; | |
| } | |
| SentinelPtr Sentinel::New() { | |
| return Object::Allocate<Sentinel>(Heap::kOld); | |
| } | |
| const char* Sentinel::ToCString() const { | |
| if (ptr() == Object::sentinel().ptr()) { | |
| return "sentinel"; | |
| } else if (ptr() == Object::unknown_constant().ptr()) { | |
| return "unknown_constant"; | |
| } else if (ptr() == Object::non_constant().ptr()) { | |
| return "non_constant"; | |
| } else if (ptr() == Object::optimized_out().ptr()) { | |
| return "<optimized out>"; | |
| } | |
| return "Sentinel(unknown)"; | |
| } | |
| ArrayPtr MegamorphicCache::buckets() const { | |
| return untag()->buckets(); | |
| } | |
| void MegamorphicCache::set_buckets(const Array& buckets) const { | |
| untag()->set_buckets(buckets.ptr()); | |
| } | |
| // Class IDs in the table are smi-tagged, so we use a smi-tagged mask | |
| // and target class ID to avoid untagging (on each iteration of the | |
| // test loop) in generated code. | |
| intptr_t MegamorphicCache::mask() const { | |
| return Smi::Value(untag()->mask()); | |
| } | |
| void MegamorphicCache::set_mask(intptr_t mask) const { | |
| untag()->set_mask(Smi::New(mask)); | |
| } | |
| intptr_t MegamorphicCache::filled_entry_count() const { | |
| return untag()->filled_entry_count_; | |
| } | |
| void MegamorphicCache::set_filled_entry_count(intptr_t count) const { | |
| StoreNonPointer(&untag()->filled_entry_count_, count); | |
| } | |
| MegamorphicCachePtr MegamorphicCache::New() { | |
| return Object::Allocate<MegamorphicCache>(Heap::kOld); | |
| } | |
| MegamorphicCachePtr MegamorphicCache::New(const String& target_name, | |
| const Array& arguments_descriptor) { | |
| auto* const zone = Thread::Current()->zone(); | |
| const auto& result = MegamorphicCache::Handle( | |
| zone, Object::Allocate<MegamorphicCache>(Heap::kOld)); | |
| const intptr_t capacity = kInitialCapacity; | |
| const Array& buckets = | |
| Array::Handle(zone, Array::New(kEntryLength * capacity, Heap::kOld)); | |
| const Object& handler = Object::Handle(zone); | |
| for (intptr_t i = 0; i < capacity; ++i) { | |
| SetEntry(buckets, i, smi_illegal_cid(), handler); | |
| } | |
| result.set_buckets(buckets); | |
| result.set_mask(capacity - 1); | |
| result.set_target_name(target_name); | |
| result.set_arguments_descriptor(arguments_descriptor); | |
| result.set_filled_entry_count(0); | |
| return result.ptr(); | |
| } | |
| void MegamorphicCache::EnsureContains(const Smi& class_id, | |
| const Object& target) const { | |
| SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex()); | |
| if (LookupLocked(class_id) == Object::null()) { | |
| InsertLocked(class_id, target); | |
| } | |
| #if defined(DEBUG) | |
| ASSERT(LookupLocked(class_id) == target.ptr()); | |
| #endif // define(DEBUG) | |
| } | |
| ObjectPtr MegamorphicCache::Lookup(const Smi& class_id) const { | |
| SafepointMutexLocker ml(IsolateGroup::Current()->type_feedback_mutex()); | |
| return LookupLocked(class_id); | |
| } | |
| ObjectPtr MegamorphicCache::LookupLocked(const Smi& class_id) const { | |
| auto thread = Thread::Current(); | |
| auto isolate_group = thread->isolate_group(); | |
| auto zone = thread->zone(); | |
| ASSERT(thread->IsDartMutatorThread()); | |
| ASSERT(isolate_group->type_feedback_mutex()->IsOwnedByCurrentThread()); | |
| const auto& backing_array = Array::Handle(zone, buckets()); | |
| intptr_t id_mask = mask(); | |
| intptr_t index = (class_id.Value() * kSpreadFactor) & id_mask; | |
| intptr_t i = index; | |
| do { | |
| const classid_t current_cid = | |
| Smi::Value(Smi::RawCast(GetClassId(backing_array, i))); | |
| if (current_cid == class_id.Value()) { | |
| return GetTargetFunction(backing_array, i); | |
| } else if (current_cid == kIllegalCid) { | |
| return Object::null(); | |
| } | |
| i = (i + 1) & id_mask; | |
| } while (i != index); | |
| UNREACHABLE(); | |
| } | |
| // Note this may run with Dart execution reading this cache. | |
| void MegamorphicCache::InsertLocked(const Smi& class_id, | |
| const Object& target) const { | |
| auto thread = Thread::Current(); | |
| auto zone = thread->zone(); | |
| auto isolate_group = thread->isolate_group(); | |
| ASSERT(isolate_group->type_feedback_mutex()->IsOwnedByCurrentThread()); | |
| intptr_t old_capacity = mask() + 1; | |
| double load_limit = kLoadFactor * static_cast<double>(old_capacity); | |
| if (static_cast<double>(filled_entry_count() + 1) > load_limit) { | |
| const Array& old_buckets = Array::Handle(zone, buckets()); | |
| intptr_t new_capacity = old_capacity * 2; | |
| intptr_t new_mask = new_capacity - 1; | |
| const Array& new_buckets = | |
| Array::Handle(zone, Array::New(kEntryLength * new_capacity)); | |
| auto& target = Object::Handle(zone); | |
| for (intptr_t i = 0; i < new_capacity; ++i) { | |
| SetEntry(new_buckets, i, smi_illegal_cid(), target); | |
| } | |
| // Rehash the valid entries. | |
| Smi& class_id = Smi::Handle(zone); | |
| for (intptr_t i = 0; i < old_capacity; ++i) { | |
| class_id ^= GetClassId(old_buckets, i); | |
| if (class_id.Value() != kIllegalCid) { | |
| target = GetTargetFunction(old_buckets, i); | |
| InsertEntryLocked<std::memory_order_relaxed>(new_buckets, new_mask, | |
| class_id, target); | |
| } | |
| } | |
| // Publish buckets first. Old mask with new buckets is just a spurious miss. | |
| untag()->set_buckets<std::memory_order_release>(new_buckets.ptr()); | |
| untag()->set_mask<std::memory_order_release>(Smi::New(new_mask)); | |
| } | |
| const Array& new_buckets = Array::Handle(zone, buckets()); | |
| InsertEntryLocked<std::memory_order_release>(new_buckets, mask(), class_id, | |
| target); | |
| set_filled_entry_count(filled_entry_count() + 1); | |
| } | |
| template <std::memory_order order> | |
| void MegamorphicCache::InsertEntryLocked(const Array& backing_array, | |
| intptr_t mask, | |
| const Smi& class_id, | |
| const Object& target) { | |
| const intptr_t start = (class_id.Value() * kSpreadFactor) & mask; | |
| intptr_t i = start; | |
| do { | |
| if (Smi::Value(Smi::RawCast(GetClassId(backing_array, i))) == kIllegalCid) { | |
| // Publish target first. Old class id with new target is just a spurious | |
| // miss. | |
| SetTargetFunction<order>(backing_array, i, target); | |
| SetClassId<order>(backing_array, i, class_id); | |
| return; | |
| } | |
| i = (i + 1) & mask; | |
| } while (i != start); | |
| UNREACHABLE(); | |
| } | |
| const char* MegamorphicCache::ToCString() const { | |
| const String& name = String::Handle(target_name()); | |
| return OS::SCreate(Thread::Current()->zone(), "MegamorphicCache(%s)", | |
| name.ToCString()); | |
| } | |
| SubtypeTestCachePtr SubtypeTestCache::New(intptr_t num_inputs) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kSubtypeTestCacheCid) != | |
| Class::null()); | |
| ASSERT(num_inputs >= 1); | |
| ASSERT(num_inputs <= kMaxInputs); | |
| // SubtypeTestCache objects are long living objects, allocate them in the | |
| // old generation. | |
| const auto& result = | |
| SubtypeTestCache::Handle(Object::Allocate<SubtypeTestCache>(Heap::kOld)); | |
| ASSERT_EQUAL(result.num_occupied(), 0); | |
| result.untag()->num_inputs_ = num_inputs; | |
| result.set_cache(Object::empty_subtype_test_cache_array()); | |
| return result.ptr(); | |
| } | |
| ArrayPtr SubtypeTestCache::cache() const { | |
| return untag()->cache<std::memory_order_acquire>(); | |
| } | |
| void SubtypeTestCache::set_cache(const Array& value) const { | |
| // We have to ensure that initializing stores to the array are available | |
| // when releasing the pointer to the array pointer. | |
| // => We have to use store-release here. | |
| untag()->set_cache<std::memory_order_release>(value.ptr()); | |
| } | |
| void SubtypeTestCache::set_num_occupied(intptr_t value) const { | |
| ASSERT(Utils::IsUint(32, value)); | |
| untag()->num_occupied_ = value; | |
| } | |
| intptr_t SubtypeTestCache::NumberOfChecks() const { | |
| ASSERT(!IsNull()); | |
| return num_occupied(); | |
| } | |
| intptr_t SubtypeTestCache::NumEntries(const ArrayPtr array) { | |
| ASSERT(array != Array::null()); | |
| return Array::LengthOf(array) / kTestEntryLength; | |
| } | |
| bool SubtypeTestCache::IsHash(const ArrayPtr array) { | |
| ASSERT(array != Array::null()); | |
| return Array::LengthOf(array) > kMaxLinearCacheSize; | |
| } | |
| intptr_t SubtypeTestCache::AddCheck( | |
| const Object& instance_class_id_or_signature, | |
| const AbstractType& destination_type, | |
| const TypeArguments& instance_type_arguments, | |
| const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments, | |
| const TypeArguments& instance_parent_function_type_arguments, | |
| const TypeArguments& instance_delayed_type_arguments, | |
| const Bool& test_result) const { | |
| ASSERT(Thread::Current() | |
| ->isolate_group() | |
| ->subtype_test_cache_mutex() | |
| ->IsOwnedByCurrentThread()); | |
| ASSERT(!test_result.IsNull()); | |
| ASSERT(Smi::New(kRecordCid) != instance_class_id_or_signature.ptr()); | |
| const intptr_t old_num = NumberOfChecks(); | |
| Zone* const zone = Thread::Current()->zone(); | |
| Array& data = Array::Handle(zone, cache()); | |
| bool was_grown; | |
| data = EnsureCapacity(zone, data, old_num + 1, &was_grown); | |
| ASSERT(data.ptr() != Object::empty_subtype_test_cache_array().ptr()); | |
| const auto& loc = FindKeyOrUnused( | |
| data, num_inputs(), instance_class_id_or_signature, destination_type, | |
| instance_type_arguments, instantiator_type_arguments, | |
| function_type_arguments, instance_parent_function_type_arguments, | |
| instance_delayed_type_arguments); | |
| SubtypeTestCacheTable entries(data); | |
| const auto& entry = entries[loc.entry]; | |
| if (loc.present) { | |
| if (entry.Get<kTestResult>() != test_result.ptr()) { | |
| const auto& old_result = Bool::Handle(zone, entry.Get<kTestResult>()); | |
| FATAL("Existing subtype test cache entry has result %s, not %s", | |
| old_result.ToCString(), test_result.ToCString()); | |
| } | |
| return loc.entry; | |
| } | |
| // Set the used elements in the entry in reverse order, so that the instance | |
| // cid or signature is last, then increment the number of entries. | |
| entry.Set<kTestResult>(test_result); | |
| switch (num_inputs()) { | |
| case 7: | |
| entry.Set<kDestinationType>(destination_type); | |
| FALL_THROUGH; | |
| case 6: | |
| entry.Set<kInstanceDelayedFunctionTypeArguments>( | |
| instance_delayed_type_arguments); | |
| FALL_THROUGH; | |
| case 5: | |
| entry.Set<kInstanceParentFunctionTypeArguments>( | |
| instance_parent_function_type_arguments); | |
| FALL_THROUGH; | |
| case 4: | |
| entry.Set<kFunctionTypeArguments>(function_type_arguments); | |
| FALL_THROUGH; | |
| case 3: | |
| entry.Set<kInstantiatorTypeArguments>(instantiator_type_arguments); | |
| FALL_THROUGH; | |
| case 2: | |
| entry.Set<kInstanceTypeArguments>(instance_type_arguments); | |
| FALL_THROUGH; | |
| case 1: | |
| // If this is a new backing array, we don't need store-release barriers, | |
| // as no reader has access to the array until it is set as the backing | |
| // store (which is done with a store-release barrier). | |
| // | |
| // Otherwise, the instance cid or signature must be set last with a | |
| // store-release barrier, so concurrent readers can depend on a non-null | |
| // value meaning the rest of the entry is safe to load without barriers. | |
| if (was_grown) { | |
| entry.Set<kInstanceCidOrSignature>(instance_class_id_or_signature); | |
| } else { | |
| entry.Set<kInstanceCidOrSignature, std::memory_order_release>( | |
| instance_class_id_or_signature); | |
| } | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| set_num_occupied(old_num + 1); | |
| if (was_grown) { | |
| set_cache(data); | |
| } | |
| return loc.entry; | |
| } | |
| SubtypeTestCache::KeyLocation SubtypeTestCache::FindKeyOrUnused( | |
| const Array& array, | |
| intptr_t num_inputs, | |
| const Object& instance_class_id_or_signature, | |
| const AbstractType& destination_type, | |
| const TypeArguments& instance_type_arguments, | |
| const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments, | |
| const TypeArguments& instance_parent_function_type_arguments, | |
| const TypeArguments& instance_delayed_type_arguments) { | |
| // Fast case for empty STCs. | |
| if (array.ptr() == Object::empty_subtype_test_cache_array().ptr()) { | |
| return {0, false}; | |
| } | |
| const bool is_hash = IsHash(array.ptr()); | |
| SubtypeTestCacheTable table(array); | |
| const intptr_t num_entries = table.Length(); | |
| // For a linear cache, start at the first entry and probe linearly. This can | |
| // be done because a linear cache always has at least one unoccupied entry | |
| // after all the occupied ones. | |
| intptr_t probe = 0; | |
| if (is_hash) { | |
| // For a hash-based cache, instead start at an entry determined by the hash | |
| // of the keys. | |
| // | |
| // If we have an instance cid, then just use that as our starting hash. | |
| uint32_t hash = | |
| instance_class_id_or_signature.IsFunctionType() | |
| ? FunctionType::Cast(instance_class_id_or_signature).Hash() | |
| : Smi::Cast(instance_class_id_or_signature).Value(); | |
| switch (num_inputs) { | |
| case 7: | |
| hash = CombineHashes(hash, destination_type.Hash()); | |
| FALL_THROUGH; | |
| case 6: | |
| hash = CombineHashes(hash, instance_delayed_type_arguments.Hash()); | |
| FALL_THROUGH; | |
| case 5: | |
| hash = | |
| CombineHashes(hash, instance_parent_function_type_arguments.Hash()); | |
| FALL_THROUGH; | |
| case 4: | |
| hash = CombineHashes(hash, function_type_arguments.Hash()); | |
| FALL_THROUGH; | |
| case 3: | |
| hash = CombineHashes(hash, instantiator_type_arguments.Hash()); | |
| FALL_THROUGH; | |
| case 2: | |
| hash = CombineHashes(hash, instance_type_arguments.Hash()); | |
| FALL_THROUGH; | |
| case 1: | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| hash = FinalizeHash(hash); | |
| probe = hash & (num_entries - 1); | |
| } | |
| NoSafepointScope scope; | |
| return FindKeyOrUnusedFromProbe( | |
| array.ptr(), num_inputs, probe, instance_class_id_or_signature.ptr(), | |
| destination_type.ptr(), instance_type_arguments.ptr(), | |
| instantiator_type_arguments.ptr(), function_type_arguments.ptr(), | |
| instance_parent_function_type_arguments.ptr(), | |
| instance_delayed_type_arguments.ptr()); | |
| } | |
| static inline bool CheckSubtypeTestCacheEntry( | |
| ArrayPtr array, | |
| intptr_t num_inputs, | |
| intptr_t entry_start, | |
| ObjectPtr instance_class_id_or_signature, | |
| AbstractTypePtr destination_type, | |
| TypeArgumentsPtr instance_type_arguments, | |
| TypeArgumentsPtr instantiator_type_arguments, | |
| TypeArgumentsPtr function_type_arguments, | |
| TypeArgumentsPtr instance_parent_function_type_arguments, | |
| TypeArgumentsPtr instance_delayed_type_arguments) { | |
| // This function is only called when the entry slot that determines occupancy | |
| // is non-null. This means that the elements do not need to be load-acquired | |
| // here, as entries in STC backing arrays are never changed once added. | |
| switch (num_inputs) { | |
| case 7: | |
| if (Array::ElementAt(array, | |
| entry_start + SubtypeTestCache::kDestinationType) != | |
| destination_type) { | |
| return false; | |
| } | |
| FALL_THROUGH; | |
| case 6: | |
| if (Array::ElementAt( | |
| array, | |
| entry_start + | |
| SubtypeTestCache::kInstanceDelayedFunctionTypeArguments) != | |
| instance_delayed_type_arguments) { | |
| return false; | |
| } | |
| FALL_THROUGH; | |
| case 5: | |
| if (Array::ElementAt( | |
| array, | |
| entry_start + | |
| SubtypeTestCache::kInstanceParentFunctionTypeArguments) != | |
| instance_parent_function_type_arguments) { | |
| return false; | |
| } | |
| FALL_THROUGH; | |
| case 4: | |
| if (Array::ElementAt( | |
| array, entry_start + SubtypeTestCache::kFunctionTypeArguments) != | |
| function_type_arguments) { | |
| return false; | |
| } | |
| FALL_THROUGH; | |
| case 3: | |
| if (Array::ElementAt( | |
| array, | |
| entry_start + SubtypeTestCache::kInstantiatorTypeArguments) != | |
| instantiator_type_arguments) { | |
| return false; | |
| } | |
| FALL_THROUGH; | |
| case 2: | |
| if (Array::ElementAt( | |
| array, entry_start + SubtypeTestCache::kInstanceTypeArguments) != | |
| instance_type_arguments) { | |
| return false; | |
| } | |
| FALL_THROUGH; | |
| case 1: | |
| return Array::ElementAt( | |
| array, | |
| entry_start + SubtypeTestCache::kInstanceCidOrSignature) == | |
| instance_class_id_or_signature; | |
| } | |
| UNREACHABLE(); | |
| return false; | |
| } | |
| SubtypeTestCache::KeyLocation SubtypeTestCache::FindKeyOrUnusedFromProbe( | |
| ArrayPtr array, | |
| intptr_t num_inputs, | |
| intptr_t probe, | |
| ObjectPtr instance_class_id_or_signature, | |
| AbstractTypePtr destination_type, | |
| TypeArgumentsPtr instance_type_arguments, | |
| TypeArgumentsPtr instantiator_type_arguments, | |
| TypeArgumentsPtr function_type_arguments, | |
| TypeArgumentsPtr instance_parent_function_type_arguments, | |
| TypeArgumentsPtr instance_delayed_type_arguments, | |
| BoolPtr* test_result) { | |
| // Fast case for empty STCs. | |
| if (array == Object::empty_subtype_test_cache_array().ptr()) { | |
| return {probe, false}; | |
| } | |
| const bool is_hash = IsHash(array); | |
| const intptr_t num_entries = NumEntries(array); | |
| intptr_t probe_distance = 1; | |
| while (true) { | |
| intptr_t entry_start = probe * SubtypeTestCache::kTestEntryLength; | |
| // First check the entry slot that determines occupancy, which requires | |
| // load-acquire semantics. | |
| ObjectPtr cid_or_sig = Array::ElementAt<std::memory_order_acquire>( | |
| array, entry_start + SubtypeTestCache::kInstanceCidOrSignature); | |
| if (cid_or_sig == Object::null()) { | |
| // The appropriate location was found, but there's no existing entry. | |
| return {probe, false}; | |
| } | |
| if (CheckSubtypeTestCacheEntry( | |
| array, num_inputs, entry_start, instance_class_id_or_signature, | |
| destination_type, instance_type_arguments, | |
| instantiator_type_arguments, function_type_arguments, | |
| instance_parent_function_type_arguments, | |
| instance_delayed_type_arguments)) { | |
| if (test_result != nullptr) { | |
| *test_result = Bool::RawCast(Array::ElementAt( | |
| array, entry_start + SubtypeTestCache::kTestResult)); | |
| } | |
| return {probe, true}; | |
| } | |
| // Advance probe by the current probing distance. | |
| probe = probe + probe_distance; | |
| if (is_hash) { | |
| // Wrap around if the probe goes off the end of the entries array. | |
| probe = probe & (num_entries - 1); | |
| // We had a collision, so increase the probe distance. See comment in | |
| // EnsureCapacityLocked for an explanation of how this hits all slots. | |
| probe_distance++; | |
| } | |
| } | |
| UNREACHABLE(); | |
| return {probe, false}; | |
| } | |
| ArrayPtr SubtypeTestCache::EnsureCapacity(Zone* zone, | |
| const Array& array, | |
| intptr_t new_occupied, | |
| bool* was_grown) const { | |
| ASSERT(new_occupied > NumberOfChecks()); | |
| ASSERT(was_grown != nullptr); | |
| // How many entries are in the current array (including unoccupied entries). | |
| const intptr_t current_capacity = NumEntries(array.ptr()); | |
| // Early returns for cases where no growth is needed. | |
| *was_grown = false; | |
| const bool is_linear = IsLinear(array.ptr()); | |
| if (is_linear) { | |
| // We need at least one unoccupied entry in addition to the occupied ones. | |
| if (current_capacity > new_occupied) return array.ptr(); | |
| } else { | |
| if (LoadFactor(new_occupied, current_capacity) < kMaxLoadFactor) { | |
| return array.ptr(); | |
| } | |
| } | |
| // Every path from here should result in a new backing array. | |
| *was_grown = true; | |
| // Initially null for initializing unoccupied entries. | |
| auto& instance_cid_or_signature = Object::Handle(zone); | |
| if (new_occupied <= kMaxLinearCacheEntries) { | |
| ASSERT(is_linear); | |
| // Not enough room for both the new entry and at least one unoccupied | |
| // entry, so grow the tuple capacity of the linear cache by about 50%, | |
| // ensuring that space for at least one new tuple is added, capping the | |
| // total number of occupied entries to the max allowed. | |
| const intptr_t new_capacity = | |
| Utils::Minimum(current_capacity + (current_capacity >> 1), | |
| kMaxLinearCacheEntries) + | |
| 1; | |
| const intptr_t cache_size = new_capacity * kTestEntryLength; | |
| ASSERT(cache_size <= kMaxLinearCacheSize); | |
| const auto& new_data = | |
| Array::Handle(zone, Array::Grow(array, cache_size, Heap::kOld)); | |
| ASSERT(!new_data.IsNull()); | |
| // No need to adjust old entries, as they are copied over by Array::Grow. | |
| // Just mark any new entries as unoccupied. | |
| SubtypeTestCacheTable table(new_data); | |
| for (intptr_t i = current_capacity; i < new_capacity; i++) { | |
| const auto& tuple = table.At(i); | |
| tuple.Set<kInstanceCidOrSignature>(instance_cid_or_signature); | |
| } | |
| return new_data.ptr(); | |
| } | |
| // Either we're converting a linear cache into a hash-based cache, or the | |
| // load factor of the hash-based cache has increased to the point where we | |
| // need to grow it. | |
| const intptr_t new_capacity = | |
| is_linear ? kNumInitialHashCacheEntries : 2 * current_capacity; | |
| // Because we use quadratic (actually triangle number) probing it is | |
| // important that the size is a power of two (otherwise we could fail to | |
| // find an empty slot). This is described in Knuth's The Art of Computer | |
| // Programming Volume 2, Chapter 6.4, exercise 20 (solution in the | |
| // appendix, 2nd edition). | |
| // | |
| // This is also important because when we do hash probing, we take the | |
| // calculated hash from the inputs and then calculate (hash % capacity) to get | |
| // the initial probe index. To ensure this is a fast calculation in the stubs, | |
| // we ensure the capacity is a power of 2, which allows (hash % capacity) to | |
| // be calculated as (hash & (capacity - 1)). | |
| ASSERT(Utils::IsPowerOfTwo(new_capacity)); | |
| ASSERT(LoadFactor(new_occupied, new_capacity) < kMaxLoadFactor); | |
| const intptr_t new_size = new_capacity * kTestEntryLength; | |
| const auto& new_data = | |
| Array::Handle(zone, Array::NewUninitialized(new_size, Heap::kOld)); | |
| ASSERT(!new_data.IsNull()); | |
| // Mark all the entries in new_data as unoccupied. | |
| SubtypeTestCacheTable to_table(new_data); | |
| for (const auto& tuple : to_table) { | |
| tuple.Set<kInstanceCidOrSignature>(instance_cid_or_signature); | |
| } | |
| // Finally, copy over the entries. | |
| auto& destination_type = AbstractType::Handle(zone); | |
| auto& instance_type_arguments = TypeArguments::Handle(zone); | |
| auto& instantiator_type_arguments = TypeArguments::Handle(zone); | |
| auto& function_type_arguments = TypeArguments::Handle(zone); | |
| auto& instance_parent_function_type_arguments = TypeArguments::Handle(zone); | |
| auto& instance_delayed_type_arguments = TypeArguments::Handle(zone); | |
| auto& test_result = Bool::Handle(zone); | |
| const SubtypeTestCacheTable from_table(array); | |
| const intptr_t used_inputs = num_inputs(); | |
| for (intptr_t i = 0; i < current_capacity; i++) { | |
| const auto& from_tuple = from_table.At(i); | |
| // Skip unoccupied entries. | |
| if (from_tuple.Get<kInstanceCidOrSignature>() == Object::null()) continue; | |
| GetCheckFromArray(array, used_inputs, i, &instance_cid_or_signature, | |
| &destination_type, &instance_type_arguments, | |
| &instantiator_type_arguments, &function_type_arguments, | |
| &instance_parent_function_type_arguments, | |
| &instance_delayed_type_arguments, &test_result); | |
| // Since new_data has a different total capacity, we can't use the old | |
| // entry indexes, but must recalculate them. | |
| auto loc = FindKeyOrUnused( | |
| new_data, used_inputs, instance_cid_or_signature, destination_type, | |
| instance_type_arguments, instantiator_type_arguments, | |
| function_type_arguments, instance_parent_function_type_arguments, | |
| instance_delayed_type_arguments); | |
| ASSERT(!loc.present); | |
| const auto& to_tuple = to_table.At(loc.entry); | |
| to_tuple.Set<kTestResult>(test_result); | |
| switch (used_inputs) { | |
| case 7: | |
| to_tuple.Set<kDestinationType>(destination_type); | |
| FALL_THROUGH; | |
| case 6: | |
| to_tuple.Set<kInstanceDelayedFunctionTypeArguments>( | |
| instance_delayed_type_arguments); | |
| FALL_THROUGH; | |
| case 5: | |
| to_tuple.Set<kInstanceParentFunctionTypeArguments>( | |
| instance_parent_function_type_arguments); | |
| FALL_THROUGH; | |
| case 4: | |
| to_tuple.Set<kFunctionTypeArguments>(function_type_arguments); | |
| FALL_THROUGH; | |
| case 3: | |
| to_tuple.Set<kInstantiatorTypeArguments>(instantiator_type_arguments); | |
| FALL_THROUGH; | |
| case 2: | |
| to_tuple.Set<kInstanceTypeArguments>(instance_type_arguments); | |
| FALL_THROUGH; | |
| case 1: | |
| to_tuple.Set<kInstanceCidOrSignature>(instance_cid_or_signature); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| return new_data.ptr(); | |
| } | |
| void SubtypeTestCache::GetCheck( | |
| intptr_t ix, | |
| Object* instance_class_id_or_signature, | |
| AbstractType* destination_type, | |
| TypeArguments* instance_type_arguments, | |
| TypeArguments* instantiator_type_arguments, | |
| TypeArguments* function_type_arguments, | |
| TypeArguments* instance_parent_function_type_arguments, | |
| TypeArguments* instance_delayed_type_arguments, | |
| Bool* test_result) const { | |
| ASSERT(Thread::Current() | |
| ->isolate_group() | |
| ->subtype_test_cache_mutex() | |
| ->IsOwnedByCurrentThread()); | |
| GetCurrentCheck(ix, instance_class_id_or_signature, destination_type, | |
| instance_type_arguments, instantiator_type_arguments, | |
| function_type_arguments, | |
| instance_parent_function_type_arguments, | |
| instance_delayed_type_arguments, test_result); | |
| } | |
| void SubtypeTestCache::GetCurrentCheck( | |
| intptr_t ix, | |
| Object* instance_class_id_or_signature, | |
| AbstractType* destination_type, | |
| TypeArguments* instance_type_arguments, | |
| TypeArguments* instantiator_type_arguments, | |
| TypeArguments* function_type_arguments, | |
| TypeArguments* instance_parent_function_type_arguments, | |
| TypeArguments* instance_delayed_type_arguments, | |
| Bool* test_result) const { | |
| const Array& array = Array::Handle(cache()); | |
| GetCheckFromArray(array, num_inputs(), ix, instance_class_id_or_signature, | |
| destination_type, instance_type_arguments, | |
| instantiator_type_arguments, function_type_arguments, | |
| instance_parent_function_type_arguments, | |
| instance_delayed_type_arguments, test_result); | |
| } | |
| void SubtypeTestCache::GetCheckFromArray( | |
| const Array& array, | |
| intptr_t num_inputs, | |
| intptr_t ix, | |
| Object* instance_class_id_or_signature, | |
| AbstractType* destination_type, | |
| TypeArguments* instance_type_arguments, | |
| TypeArguments* instantiator_type_arguments, | |
| TypeArguments* function_type_arguments, | |
| TypeArguments* instance_parent_function_type_arguments, | |
| TypeArguments* instance_delayed_type_arguments, | |
| Bool* test_result) { | |
| ASSERT(array.ptr() != Object::empty_subtype_test_cache_array().ptr()); | |
| SubtypeTestCacheTable entries(array); | |
| auto entry = entries[ix]; | |
| // First get the field that determines occupancy. We have to do this with | |
| // load-acquire because some callers may not have the subtype test cache lock. | |
| *instance_class_id_or_signature = | |
| entry.Get<kInstanceCidOrSignature, std::memory_order_acquire>(); | |
| // We should not be retrieving unoccupied entries. | |
| ASSERT(!instance_class_id_or_signature->IsNull()); | |
| switch (num_inputs) { | |
| case 7: | |
| *destination_type = entry.Get<kDestinationType>(); | |
| FALL_THROUGH; | |
| case 6: | |
| *instance_delayed_type_arguments = | |
| entry.Get<kInstanceDelayedFunctionTypeArguments>(); | |
| FALL_THROUGH; | |
| case 5: | |
| *instance_parent_function_type_arguments = | |
| entry.Get<kInstanceParentFunctionTypeArguments>(); | |
| FALL_THROUGH; | |
| case 4: | |
| *function_type_arguments = entry.Get<kFunctionTypeArguments>(); | |
| FALL_THROUGH; | |
| case 3: | |
| *instantiator_type_arguments = entry.Get<kInstantiatorTypeArguments>(); | |
| FALL_THROUGH; | |
| case 2: | |
| *instance_type_arguments = entry.Get<kInstanceTypeArguments>(); | |
| FALL_THROUGH; | |
| case 1: | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| *test_result = entry.Get<kTestResult>(); | |
| } | |
| bool SubtypeTestCache::GetNextCheck( | |
| intptr_t* ix, | |
| Object* instance_class_id_or_signature, | |
| AbstractType* destination_type, | |
| TypeArguments* instance_type_arguments, | |
| TypeArguments* instantiator_type_arguments, | |
| TypeArguments* function_type_arguments, | |
| TypeArguments* instance_parent_function_type_arguments, | |
| TypeArguments* instance_delayed_type_arguments, | |
| Bool* test_result) const { | |
| ASSERT(ix != nullptr); | |
| for (intptr_t i = *ix; i < NumEntries(); i++) { | |
| ASSERT(Thread::Current() | |
| ->isolate_group() | |
| ->subtype_test_cache_mutex() | |
| ->IsOwnedByCurrentThread()); | |
| if (IsOccupied(i)) { | |
| GetCurrentCheck(i, instance_class_id_or_signature, destination_type, | |
| instance_type_arguments, instantiator_type_arguments, | |
| function_type_arguments, | |
| instance_parent_function_type_arguments, | |
| instance_delayed_type_arguments, test_result); | |
| *ix = i + 1; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| bool SubtypeTestCache::HasCheck( | |
| const Object& instance_class_id_or_signature, | |
| const AbstractType& destination_type, | |
| const TypeArguments& instance_type_arguments, | |
| const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments, | |
| const TypeArguments& instance_parent_function_type_arguments, | |
| const TypeArguments& instance_delayed_type_arguments, | |
| intptr_t* index, | |
| Bool* result) const { | |
| const auto& data = Array::Handle(cache()); | |
| auto loc = FindKeyOrUnused( | |
| data, num_inputs(), instance_class_id_or_signature, destination_type, | |
| instance_type_arguments, instantiator_type_arguments, | |
| function_type_arguments, instance_parent_function_type_arguments, | |
| instance_delayed_type_arguments); | |
| if (loc.present) { | |
| if (index != nullptr) { | |
| *index = loc.entry; | |
| } | |
| if (result != nullptr) { | |
| SubtypeTestCacheTable entries(data); | |
| const auto& entry = entries[loc.entry]; | |
| // A positive result from FindKeyOrUnused means that load-acquire is not | |
| // needed, as an occupied entry never changes for a given backing array. | |
| *result = entry.Get<kTestResult>(); | |
| ASSERT(!result->IsNull()); | |
| } | |
| } | |
| return loc.present; | |
| } | |
| void SubtypeTestCache::WriteEntryToBuffer(Zone* zone, | |
| BaseTextBuffer* buffer, | |
| intptr_t index, | |
| const char* line_prefix) const { | |
| ASSERT(Thread::Current() | |
| ->isolate_group() | |
| ->subtype_test_cache_mutex() | |
| ->IsOwnedByCurrentThread()); | |
| WriteCurrentEntryToBuffer(zone, buffer, index, line_prefix); | |
| } | |
| void SubtypeTestCache::WriteToBuffer(Zone* zone, | |
| BaseTextBuffer* buffer, | |
| const char* line_prefix) const { | |
| ASSERT(Thread::Current() | |
| ->isolate_group() | |
| ->subtype_test_cache_mutex() | |
| ->IsOwnedByCurrentThread()); | |
| WriteToBufferUnlocked(zone, buffer, line_prefix); | |
| } | |
| void SubtypeTestCache::WriteCurrentEntryToBuffer( | |
| Zone* zone, | |
| BaseTextBuffer* buffer, | |
| intptr_t index, | |
| const char* line_prefix) const { | |
| const char* separator = | |
| line_prefix == nullptr ? ", " : OS::SCreate(zone, "\n%s", line_prefix); | |
| auto& instance_class_id_or_signature = Object::Handle(zone); | |
| auto& destination_type = AbstractType::Handle(zone); | |
| auto& instance_type_arguments = TypeArguments::Handle(zone); | |
| auto& instantiator_type_arguments = TypeArguments::Handle(zone); | |
| auto& function_type_arguments = TypeArguments::Handle(zone); | |
| auto& instance_parent_function_type_arguments = TypeArguments::Handle(zone); | |
| auto& instance_delayed_type_arguments = TypeArguments::Handle(zone); | |
| auto& result = Bool::Handle(zone); | |
| GetCurrentCheck(index, &instance_class_id_or_signature, &destination_type, | |
| &instance_type_arguments, &instantiator_type_arguments, | |
| &function_type_arguments, | |
| &instance_parent_function_type_arguments, | |
| &instance_delayed_type_arguments, &result); | |
| buffer->Printf( | |
| "%" Pd ": [ %#" Px ", %#" Px ", %#" Px ", %#" Px ", %#" Px ", %#" Px | |
| ", %#" Px ", %#" Px " ]", | |
| index, static_cast<uword>(instance_class_id_or_signature.ptr()), | |
| static_cast<uword>(instance_type_arguments.ptr()), | |
| static_cast<uword>(instantiator_type_arguments.ptr()), | |
| static_cast<uword>(function_type_arguments.ptr()), | |
| static_cast<uword>(instance_parent_function_type_arguments.ptr()), | |
| static_cast<uword>(instance_delayed_type_arguments.ptr()), | |
| static_cast<uword>(destination_type.ptr()), | |
| static_cast<uword>(result.ptr())); | |
| if (instance_class_id_or_signature.IsSmi()) { | |
| buffer->Printf("%sclass id: %" Pd "", separator, | |
| Smi::Cast(instance_class_id_or_signature).Value()); | |
| } else { | |
| buffer->Printf( | |
| "%ssignature: %s", separator, | |
| FunctionType::Cast(instance_class_id_or_signature).ToCString()); | |
| } | |
| if (!instance_type_arguments.IsNull()) { | |
| if (instance_class_id_or_signature.IsSmi()) { | |
| buffer->Printf("%sinstance type arguments: %s", separator, | |
| instance_type_arguments.ToCString()); | |
| } else { | |
| ASSERT(instance_class_id_or_signature.IsFunctionType()); | |
| buffer->Printf("%sclosure instantiator function type arguments: %s", | |
| separator, instance_type_arguments.ToCString()); | |
| } | |
| } | |
| if (!instantiator_type_arguments.IsNull()) { | |
| buffer->Printf("%sinstantiator type arguments: %s", separator, | |
| instantiator_type_arguments.ToCString()); | |
| } | |
| if (!function_type_arguments.IsNull()) { | |
| buffer->Printf("%sfunction type arguments: %s", separator, | |
| function_type_arguments.ToCString()); | |
| } | |
| if (!instance_parent_function_type_arguments.IsNull()) { | |
| buffer->Printf("%sclosure parent function type arguments: %s", separator, | |
| instance_parent_function_type_arguments.ToCString()); | |
| } | |
| if (!instance_delayed_type_arguments.IsNull()) { | |
| buffer->Printf("%sclosure delayed function type arguments: %s", separator, | |
| instance_delayed_type_arguments.ToCString()); | |
| } | |
| if (!destination_type.IsNull()) { | |
| buffer->Printf("%sdestination type: %s", separator, | |
| destination_type.ToCString()); | |
| if (!destination_type.IsInstantiated()) { | |
| AbstractType& test_type = AbstractType::Handle( | |
| zone, destination_type.InstantiateFrom(instantiator_type_arguments, | |
| function_type_arguments, | |
| kAllFree, Heap::kNew)); | |
| const auto type_class_id = test_type.type_class_id(); | |
| buffer->Printf("%sinstantiated type: %s", separator, | |
| test_type.ToCString()); | |
| buffer->Printf("%sinstantiated type class id: %d", separator, | |
| type_class_id); | |
| } | |
| } | |
| buffer->Printf("%sresult: %s", separator, result.ToCString()); | |
| } | |
| void SubtypeTestCache::WriteToBufferUnlocked(Zone* zone, | |
| BaseTextBuffer* buffer, | |
| const char* line_prefix) const { | |
| const char* separator = | |
| line_prefix == nullptr ? " " : OS::SCreate(zone, "\n%s", line_prefix); | |
| const char* internal_line_prefix = | |
| line_prefix == nullptr | |
| ? nullptr | |
| : OS::SCreate(zone, "%s%s", line_prefix, line_prefix); | |
| const intptr_t num_entries = NumEntries(); | |
| buffer->Printf("SubtypeTestCache(%" Pd ", %" Pd "", num_inputs(), | |
| num_occupied()); | |
| for (intptr_t i = 0; i < num_entries; i++) { | |
| if (!IsOccupied(i)) continue; | |
| buffer->Printf(",%s{", separator); | |
| WriteCurrentEntryToBuffer(zone, buffer, i, internal_line_prefix); | |
| buffer->Printf(line_prefix != nullptr ? "}" : " }"); | |
| } | |
| buffer->AddString(line_prefix != nullptr && num_entries != 0 ? "\n)" : ")"); | |
| } | |
| void SubtypeTestCache::Reset() const { | |
| set_num_occupied(0); | |
| set_cache(Object::empty_subtype_test_cache_array()); | |
| } | |
| bool SubtypeTestCache::Equals(const SubtypeTestCache& other) const { | |
| ASSERT(Thread::Current() | |
| ->isolate_group() | |
| ->subtype_test_cache_mutex() | |
| ->IsOwnedByCurrentThread()); | |
| if (ptr() == other.ptr()) { | |
| return true; | |
| } | |
| if (num_inputs() != other.num_inputs()) return false; | |
| if (num_occupied() != other.num_occupied()) return false; | |
| return Array::Handle(cache()).Equals(Array::Handle(other.cache())); | |
| } | |
| SubtypeTestCachePtr SubtypeTestCache::Copy(Thread* thread) const { | |
| ASSERT(thread->isolate_group() | |
| ->subtype_test_cache_mutex() | |
| ->IsOwnedByCurrentThread()); | |
| if (IsNull()) { | |
| return SubtypeTestCache::null(); | |
| } | |
| Zone* const zone = thread->zone(); | |
| // STC caches are only copied on write if there are not enough unoccupied | |
| // entries to store a new one, so we need to copy the array. | |
| const auto& result = | |
| SubtypeTestCache::Handle(zone, SubtypeTestCache::New(num_inputs())); | |
| auto& entry_cache = Array::Handle(zone, cache()); | |
| entry_cache = entry_cache.Copy(); | |
| result.set_cache(entry_cache); | |
| result.set_num_occupied(num_occupied()); | |
| return result.ptr(); | |
| } | |
| bool SubtypeTestCache::IsOccupied(intptr_t index) const { | |
| ASSERT(!IsNull()); | |
| ASSERT(index < NumEntries()); | |
| const intptr_t cache_index = | |
| index * kTestEntryLength + kInstanceCidOrSignature; | |
| NoSafepointScope no_safepoint; | |
| return cache()->untag()->element<std::memory_order_acquire>(cache_index) != | |
| Object::null(); | |
| } | |
| intptr_t SubtypeTestCache::UsedInputsForType(const AbstractType& type) { | |
| if (type.IsType()) { | |
| if (type.IsInstantiated()) return 2; | |
| if (type.IsInstantiated(kFunctions)) return 3; | |
| return 4; | |
| } | |
| // Default to all inputs except for the destination type, which must be | |
| // statically known, otherwise this method wouldn't be called. | |
| static_assert(kDestinationType == kMaxInputs - 1, | |
| "destination type is not last input"); | |
| return kMaxInputs - 1; | |
| } | |
| const char* SubtypeTestCache::ToCString() const { | |
| auto const zone = Thread::Current()->zone(); | |
| ZoneTextBuffer buffer(zone); | |
| WriteToBufferUnlocked(zone, &buffer); | |
| return buffer.buffer(); | |
| } | |
| LoadingUnitPtr LoadingUnit::New(intptr_t id, const LoadingUnit& parent) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kLoadingUnitCid) != | |
| Class::null()); | |
| // LoadingUnit objects are long living objects, allocate them in the | |
| // old generation. | |
| auto result = Object::Allocate<LoadingUnit>(Heap::kOld); | |
| NoSafepointScope scope; | |
| ASSERT(Utils::IsInt(UntaggedLoadingUnit::IdBits::bitsize(), id)); | |
| result->untag()->packed_fields_.Update<UntaggedLoadingUnit::IdBits>(id); | |
| result->untag()->set_parent(parent.ptr()); | |
| return result; | |
| } | |
| void LoadingUnit::set_base_objects(const Array& value) const { | |
| untag()->set_base_objects(value.ptr()); | |
| } | |
| const char* LoadingUnit::ToCString() const { | |
| return "LoadingUnit"; | |
| } | |
| ObjectPtr LoadingUnit::IssueLoad() const { | |
| set_load_outstanding(); | |
| return Isolate::Current()->CallDeferredLoadHandler(id()); | |
| } | |
| ObjectPtr LoadingUnit::CompleteLoad(const String& error_message, | |
| bool transient_error) const { | |
| set_loaded(error_message.IsNull()); | |
| const Library& lib = Library::Handle(Library::CoreLibrary()); | |
| const String& sel = String::Handle(String::New("_completeLoads")); | |
| const Function& func = Function::Handle(lib.LookupFunctionAllowPrivate(sel)); | |
| ASSERT(!func.IsNull()); | |
| const Array& args = Array::Handle(Array::New(3)); | |
| args.SetAt(0, Smi::Handle(Smi::New(id()))); | |
| args.SetAt(1, error_message); | |
| args.SetAt(2, Bool::Get(transient_error)); | |
| return DartEntry::InvokeFunction(func, args); | |
| } | |
| // The assignment to loading units here must match that in | |
| // AssignLoadingUnitsCodeVisitor, which runs after compilation is done. | |
| intptr_t LoadingUnit::LoadingUnitOf(const Function& function) { | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_CLASS_HANDLESCOPE(thread); | |
| REUSABLE_LIBRARY_HANDLESCOPE(thread); | |
| REUSABLE_LOADING_UNIT_HANDLESCOPE(thread); | |
| Class& cls = thread->ClassHandle(); | |
| Library& lib = thread->LibraryHandle(); | |
| LoadingUnit& unit = thread->LoadingUnitHandle(); | |
| cls = function.Owner(); | |
| lib = cls.library(); | |
| unit = lib.loading_unit(); | |
| if (unit.IsNull()) { | |
| FATAL("Unable to find loading unit of %s (class %s, library %s)", | |
| function.ToFullyQualifiedCString(), cls.ToCString(), lib.ToCString()); | |
| } | |
| return unit.id(); | |
| } | |
| intptr_t LoadingUnit::LoadingUnitOf(const Code& code) { | |
| if (code.IsStubCode() || code.IsTypeTestStubCode() || | |
| code.IsAllocationStubCode()) { | |
| return LoadingUnit::kRootId; | |
| } else { | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_FUNCTION_HANDLESCOPE(thread); | |
| REUSABLE_CLASS_HANDLESCOPE(thread); | |
| REUSABLE_LIBRARY_HANDLESCOPE(thread); | |
| REUSABLE_LOADING_UNIT_HANDLESCOPE(thread); | |
| Class& cls = thread->ClassHandle(); | |
| Library& lib = thread->LibraryHandle(); | |
| LoadingUnit& unit = thread->LoadingUnitHandle(); | |
| Function& func = thread->FunctionHandle(); | |
| if (code.IsFunctionCode()) { | |
| func ^= code.function(); | |
| cls = func.Owner(); | |
| lib = cls.library(); | |
| unit = lib.loading_unit(); | |
| ASSERT(!unit.IsNull()); | |
| return unit.id(); | |
| } else { | |
| UNREACHABLE(); | |
| return LoadingUnit::kIllegalId; | |
| } | |
| } | |
| } | |
| const char* Error::ToErrorCString() const { | |
| if (IsNull()) { | |
| return "Error: null"; | |
| } | |
| UNREACHABLE(); | |
| return "Error"; | |
| } | |
| const char* Error::ToCString() const { | |
| if (IsNull()) { | |
| return "Error: null"; | |
| } | |
| // Error is an abstract class. We should never reach here. | |
| UNREACHABLE(); | |
| return "Error"; | |
| } | |
| ApiErrorPtr ApiError::New() { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kApiErrorCid) != | |
| Class::null()); | |
| return Object::Allocate<ApiError>(Heap::kOld); | |
| } | |
| ApiErrorPtr ApiError::New(const String& message, Heap::Space space) { | |
| #ifndef PRODUCT | |
| if (FLAG_print_stacktrace_at_api_error) { | |
| OS::PrintErr("ApiError: %s\n", message.ToCString()); | |
| Profiler::DumpStackTrace(false /* for_crash */); | |
| } | |
| #endif // !PRODUCT | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kApiErrorCid) != | |
| Class::null()); | |
| const auto& result = ApiError::Handle(Object::Allocate<ApiError>(space)); | |
| result.set_message(message); | |
| return result.ptr(); | |
| } | |
| void ApiError::set_message(const String& message) const { | |
| untag()->set_message(message.ptr()); | |
| } | |
| const char* ApiError::ToErrorCString() const { | |
| const String& msg_str = String::Handle(message()); | |
| return msg_str.ToCString(); | |
| } | |
| const char* ApiError::ToCString() const { | |
| return "ApiError"; | |
| } | |
| LanguageErrorPtr LanguageError::New() { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kLanguageErrorCid) != | |
| Class::null()); | |
| return Object::Allocate<LanguageError>(Heap::kOld); | |
| } | |
| LanguageErrorPtr LanguageError::NewFormattedV(const Error& prev_error, | |
| const Script& script, | |
| TokenPosition token_pos, | |
| bool report_after_token, | |
| Report::Kind kind, | |
| Heap::Space space, | |
| const char* format, | |
| va_list args) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kLanguageErrorCid) != | |
| Class::null()); | |
| const auto& result = | |
| LanguageError::Handle(Object::Allocate<LanguageError>(space)); | |
| result.set_previous_error(prev_error); | |
| result.set_script(script); | |
| result.set_token_pos(token_pos); | |
| result.set_report_after_token(report_after_token); | |
| result.set_kind(kind); | |
| result.set_message( | |
| String::Handle(String::NewFormattedV(format, args, space))); | |
| return result.ptr(); | |
| } | |
| LanguageErrorPtr LanguageError::NewFormatted(const Error& prev_error, | |
| const Script& script, | |
| TokenPosition token_pos, | |
| bool report_after_token, | |
| Report::Kind kind, | |
| Heap::Space space, | |
| const char* format, | |
| ...) { | |
| va_list args; | |
| va_start(args, format); | |
| LanguageErrorPtr result = LanguageError::NewFormattedV( | |
| prev_error, script, token_pos, report_after_token, kind, space, format, | |
| args); | |
| NoSafepointScope no_safepoint; | |
| va_end(args); | |
| return result; | |
| } | |
| LanguageErrorPtr LanguageError::New(const String& formatted_message, | |
| Report::Kind kind, | |
| Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kLanguageErrorCid) != | |
| Class::null()); | |
| const auto& result = | |
| LanguageError::Handle(Object::Allocate<LanguageError>(space)); | |
| result.set_formatted_message(formatted_message); | |
| result.set_kind(kind); | |
| return result.ptr(); | |
| } | |
| void LanguageError::set_previous_error(const Error& value) const { | |
| untag()->set_previous_error(value.ptr()); | |
| } | |
| void LanguageError::set_script(const Script& value) const { | |
| untag()->set_script(value.ptr()); | |
| } | |
| void LanguageError::set_token_pos(TokenPosition token_pos) const { | |
| ASSERT(!token_pos.IsClassifying()); | |
| StoreNonPointer(&untag()->token_pos_, token_pos); | |
| } | |
| void LanguageError::set_report_after_token(bool value) const { | |
| StoreNonPointer(&untag()->report_after_token_, value); | |
| } | |
| void LanguageError::set_kind(uint8_t value) const { | |
| StoreNonPointer(&untag()->kind_, value); | |
| } | |
| void LanguageError::set_message(const String& value) const { | |
| untag()->set_message(value.ptr()); | |
| } | |
| void LanguageError::set_formatted_message(const String& value) const { | |
| untag()->set_formatted_message(value.ptr()); | |
| } | |
| StringPtr LanguageError::FormatMessage() const { | |
| if (formatted_message() != String::null()) { | |
| return formatted_message(); | |
| } | |
| String& result = String::Handle( | |
| Report::PrependSnippet(kind(), Script::Handle(script()), token_pos(), | |
| report_after_token(), String::Handle(message()))); | |
| // Prepend previous error message. | |
| const Error& prev_error = Error::Handle(previous_error()); | |
| if (!prev_error.IsNull()) { | |
| result = String::Concat( | |
| String::Handle(String::New(prev_error.ToErrorCString())), result); | |
| } | |
| set_formatted_message(result); | |
| return result.ptr(); | |
| } | |
| const char* LanguageError::ToErrorCString() const { | |
| const String& msg_str = String::Handle(FormatMessage()); | |
| return msg_str.ToCString(); | |
| } | |
| const char* LanguageError::ToCString() const { | |
| return "LanguageError"; | |
| } | |
| UnhandledExceptionPtr UnhandledException::New(const Instance& exception, | |
| const Instance& stacktrace, | |
| Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kUnhandledExceptionCid) != | |
| Class::null()); | |
| const auto& result = | |
| UnhandledException::Handle(Object::Allocate<UnhandledException>(space)); | |
| result.set_exception(exception); | |
| result.set_stacktrace(stacktrace); | |
| return result.ptr(); | |
| } | |
| UnhandledExceptionPtr UnhandledException::New(Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kUnhandledExceptionCid) != | |
| Class::null()); | |
| return Object::Allocate<UnhandledException>(space); | |
| } | |
| void UnhandledException::set_exception(const Instance& exception) const { | |
| untag()->set_exception(exception.ptr()); | |
| } | |
| void UnhandledException::set_stacktrace(const Instance& stacktrace) const { | |
| untag()->set_stacktrace(stacktrace.ptr()); | |
| } | |
| const char* UnhandledException::ToErrorCString() const { | |
| Thread* thread = Thread::Current(); | |
| auto isolate_group = thread->isolate_group(); | |
| NoReloadScope no_reload_scope(thread); | |
| HANDLESCOPE(thread); | |
| Object& strtmp = Object::Handle(); | |
| const char* exc_str; | |
| if (exception() == isolate_group->object_store()->out_of_memory()) { | |
| exc_str = "Out of Memory"; | |
| } else if (exception() == isolate_group->object_store()->stack_overflow()) { | |
| exc_str = "Stack Overflow"; | |
| } else { | |
| const Instance& exc = Instance::Handle(exception()); | |
| strtmp = DartLibraryCalls::ToString(exc); | |
| if (!strtmp.IsError()) { | |
| exc_str = strtmp.ToCString(); | |
| } else { | |
| exc_str = "<Received error while converting exception to string>"; | |
| } | |
| } | |
| const Instance& stack = Instance::Handle(stacktrace()); | |
| const char* stack_str; | |
| if (stack.IsNull()) { | |
| stack_str = "null"; | |
| } else if (stack.IsStackTrace()) { | |
| stack_str = StackTrace::Cast(stack).ToCString(); | |
| } else { | |
| strtmp = DartLibraryCalls::ToString(stack); | |
| if (!strtmp.IsError()) { | |
| stack_str = strtmp.ToCString(); | |
| } else { | |
| stack_str = "<Received error while converting stack trace to string>"; | |
| } | |
| } | |
| return OS::SCreate(thread->zone(), "Unhandled exception:\n%s\n%s", exc_str, | |
| stack_str); | |
| } | |
| const char* UnhandledException::ToCString() const { | |
| return "UnhandledException"; | |
| } | |
| UnwindErrorPtr UnwindError::New(const String& message, Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->class_table()->At(kUnwindErrorCid) != | |
| Class::null()); | |
| const auto& result = | |
| UnwindError::Handle(Object::Allocate<UnwindError>(space)); | |
| result.set_message(message); | |
| ASSERT_EQUAL(result.is_user_initiated(), false); | |
| return result.ptr(); | |
| } | |
| void UnwindError::set_message(const String& message) const { | |
| untag()->set_message(message.ptr()); | |
| } | |
| void UnwindError::set_is_user_initiated(bool value) const { | |
| StoreNonPointer(&untag()->is_user_initiated_, value); | |
| } | |
| const char* UnwindError::ToErrorCString() const { | |
| const String& msg_str = String::Handle(message()); | |
| return msg_str.ToCString(); | |
| } | |
| const char* UnwindError::ToCString() const { | |
| return "UnwindError"; | |
| } | |
| ObjectPtr Instance::InvokeGetter(const String& getter_name, | |
| bool check_is_entrypoint, | |
| bool respect_reflectable) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| Class& klass = Class::Handle(zone, clazz()); | |
| CHECK_ERROR(klass.EnsureIsFinalized(thread)); | |
| const auto& inst_type_args = | |
| klass.NumTypeArguments() > 0 | |
| ? TypeArguments::Handle(zone, GetTypeArguments()) | |
| : Object::null_type_arguments(); | |
| const String& internal_getter_name = | |
| String::Handle(zone, Field::GetterName(getter_name)); | |
| Function& function = Function::Handle( | |
| zone, | |
| Resolver::ResolveDynamicAnyArgs(zone, klass, internal_getter_name, | |
| /*allow_add=*/!FLAG_precompiled_mode)); | |
| // Check for method extraction when method extractors are not lazily created. | |
| if (function.IsNull() && FLAG_precompiled_mode) { | |
| function = Resolver::ResolveDynamicAnyArgs(zone, klass, getter_name, | |
| /*allow_add=*/false); | |
| if (!function.IsNull() && function.SafeToClosurize()) { | |
| if (check_is_entrypoint) { | |
| CHECK_ERROR(function.VerifyEntryPoint(EntryPointPragma::kGetterOnly)); | |
| } | |
| const Function& closure_function = | |
| Function::Handle(zone, function.ImplicitClosureFunction()); | |
| return closure_function.ImplicitInstanceClosure(*this); | |
| } | |
| } | |
| const int kTypeArgsLen = 0; | |
| const int kNumArgs = 1; | |
| const Array& args = Array::Handle(zone, Array::New(kNumArgs)); | |
| args.SetAt(0, *this); | |
| const Array& args_descriptor = Array::Handle( | |
| zone, | |
| ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(), Heap::kNew)); | |
| return InvokeInstanceFunction(thread, *this, function, internal_getter_name, | |
| args, args_descriptor, check_is_entrypoint, | |
| EntryPointPragma::kGetterOnly, | |
| respect_reflectable, inst_type_args); | |
| } | |
| ObjectPtr Instance::InvokeSetter(const String& setter_name, | |
| const Instance& value, | |
| bool check_is_entrypoint, | |
| bool respect_reflectable) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const Class& klass = Class::Handle(zone, clazz()); | |
| CHECK_ERROR(klass.EnsureIsFinalized(thread)); | |
| const auto& inst_type_args = | |
| klass.NumTypeArguments() > 0 | |
| ? TypeArguments::Handle(zone, GetTypeArguments()) | |
| : Object::null_type_arguments(); | |
| const String& internal_setter_name = | |
| String::Handle(zone, Field::SetterName(setter_name)); | |
| const Function& setter = Function::Handle( | |
| zone, | |
| Resolver::ResolveDynamicAnyArgs(zone, klass, internal_setter_name, | |
| /*allow_add=*/!FLAG_precompiled_mode)); | |
| const int kTypeArgsLen = 0; | |
| const int kNumArgs = 2; | |
| const Array& args = Array::Handle(zone, Array::New(kNumArgs)); | |
| args.SetAt(0, *this); | |
| args.SetAt(1, value); | |
| const Array& args_descriptor = Array::Handle( | |
| zone, | |
| ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(), Heap::kNew)); | |
| return InvokeInstanceFunction(thread, *this, setter, internal_setter_name, | |
| args, args_descriptor, check_is_entrypoint, | |
| EntryPointPragma::kSetterOnly, | |
| respect_reflectable, inst_type_args); | |
| } | |
| ObjectPtr Instance::Invoke(const String& function_name, | |
| const Array& args, | |
| const Array& arg_names, | |
| bool check_is_entrypoint, | |
| bool respect_reflectable) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| Class& klass = Class::Handle(zone, clazz()); | |
| CHECK_ERROR(klass.EnsureIsFinalized(thread)); | |
| Function& function = Function::Handle( | |
| zone, | |
| Resolver::ResolveDynamicAnyArgs(zone, klass, function_name, | |
| /*allow_add=*/!FLAG_precompiled_mode)); | |
| // We don't pass any explicit type arguments, which will be understood as | |
| // using dynamic for any function type arguments by lower layers. | |
| const int kTypeArgsLen = 0; | |
| const Array& args_descriptor = Array::Handle( | |
| zone, ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(), | |
| arg_names, Heap::kNew)); | |
| const auto& inst_type_args = | |
| klass.NumTypeArguments() > 0 | |
| ? TypeArguments::Handle(zone, GetTypeArguments()) | |
| : Object::null_type_arguments(); | |
| if (function.IsNull()) { | |
| // Didn't find a method: try to find a getter and invoke call on its result. | |
| const String& getter_name = | |
| String::Handle(zone, Field::GetterName(function_name)); | |
| function = | |
| Resolver::ResolveDynamicAnyArgs(zone, klass, getter_name, | |
| /*allow_add=*/!FLAG_precompiled_mode); | |
| if (!function.IsNull()) { | |
| ASSERT(function.kind() != UntaggedFunction::kMethodExtractor); | |
| // Invoke the getter. | |
| const int kNumArgs = 1; | |
| const Array& getter_args = Array::Handle(zone, Array::New(kNumArgs)); | |
| getter_args.SetAt(0, *this); | |
| const Array& getter_args_descriptor = Array::Handle( | |
| zone, ArgumentsDescriptor::NewBoxed( | |
| kTypeArgsLen, getter_args.Length(), Heap::kNew)); | |
| const Object& getter_result = Object::Handle( | |
| zone, InvokeInstanceFunction(thread, *this, function, getter_name, | |
| getter_args, getter_args_descriptor, | |
| check_is_entrypoint, | |
| EntryPointPragma::kGetterOnly, | |
| respect_reflectable, inst_type_args)); | |
| if (getter_result.IsError()) { | |
| return getter_result.ptr(); | |
| } | |
| // Replace the closure as the receiver in the arguments list. | |
| args.SetAt(0, getter_result); | |
| return DartEntry::InvokeClosure(thread, args, args_descriptor); | |
| } | |
| } | |
| // Found an ordinary method. | |
| return InvokeInstanceFunction(thread, *this, function, function_name, args, | |
| args_descriptor, check_is_entrypoint, | |
| EntryPointPragma::kCallOnly, | |
| respect_reflectable, inst_type_args); | |
| } | |
| ObjectPtr Instance::HashCode() const { | |
| // TODO(koda): Optimize for all builtin classes and all classes | |
| // that do not override hashCode. | |
| return DartLibraryCalls::HashCode(*this); | |
| } | |
| // Keep in sync with AsmIntrinsifier::Object_getHash. | |
| IntegerPtr Instance::IdentityHashCode(Thread* thread) const { | |
| if (IsInteger()) return Integer::Cast(*this).ptr(); | |
| if (IsString()) { | |
| return Smi::New(String::Cast(*this).Hash()); | |
| } | |
| #if defined(HASH_IN_OBJECT_HEADER) | |
| intptr_t hash = Object::GetCachedHash(ptr()); | |
| #else | |
| intptr_t hash = thread->heap()->GetHash(ptr()); | |
| #endif | |
| if (hash == 0) { | |
| if (IsNull()) { | |
| hash = kNullIdentityHash; | |
| } else if (IsBool()) { | |
| hash = Bool::Cast(*this).value() ? kTrueIdentityHash : kFalseIdentityHash; | |
| } else if (IsDouble()) { | |
| double val = Double::Cast(*this).value(); | |
| if ((val >= kMinInt64RepresentableAsDouble) && | |
| (val <= kMaxInt64RepresentableAsDouble)) { | |
| int64_t ival = static_cast<int64_t>(val); | |
| if (static_cast<double>(ival) == val) { | |
| return Integer::New(ival); | |
| } | |
| } | |
| uint64_t uval = bit_cast<uint64_t>(val); | |
| hash = ((uval >> 32) ^ (uval)) & kSmiMax; | |
| } else { | |
| do { | |
| hash = thread->random()->NextUInt32() & 0x3FFFFFFF; | |
| } while (hash == 0); | |
| } | |
| #if defined(HASH_IN_OBJECT_HEADER) | |
| hash = Object::SetCachedHashIfNotSet(ptr(), hash); | |
| #else | |
| hash = thread->heap()->SetHashIfNotSet(ptr(), hash); | |
| #endif | |
| } | |
| return Smi::New(hash); | |
| } | |
| bool Instance::CanonicalizeEquals(const Instance& other) const { | |
| if (this->ptr() == other.ptr()) { | |
| return true; // "===". | |
| } | |
| if (other.IsNull() || (this->clazz() != other.clazz())) { | |
| return false; | |
| } | |
| { | |
| NoSafepointScope no_safepoint; | |
| // Raw bits compare. | |
| const intptr_t instance_size = SizeFromClass(); | |
| ASSERT(instance_size != 0); | |
| const intptr_t other_instance_size = other.SizeFromClass(); | |
| ASSERT(other_instance_size != 0); | |
| if (instance_size != other_instance_size) { | |
| return false; | |
| } | |
| uword this_addr = reinterpret_cast<uword>(this->untag()); | |
| uword other_addr = reinterpret_cast<uword>(other.untag()); | |
| for (intptr_t offset = Instance::NextFieldOffset(); offset < instance_size; | |
| offset += kCompressedWordSize) { | |
| if ((reinterpret_cast<CompressedObjectPtr*>(this_addr + offset) | |
| ->Decompress(untag()->heap_base())) != | |
| (reinterpret_cast<CompressedObjectPtr*>(other_addr + offset) | |
| ->Decompress(untag()->heap_base()))) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool Symbol::IsSymbolCid(Thread* thread, classid_t class_id) { | |
| auto object_store = thread->isolate_group()->object_store(); | |
| return Class::GetClassId(object_store->symbol_class()) == class_id; | |
| } | |
| // Must be kept in sync with Symbol.hashCode in symbol_patch.dart | |
| uint32_t Symbol::CanonicalizeHash(Thread* thread, const Instance& instance) { | |
| ASSERT(IsSymbolCid(thread, instance.GetClassId())); | |
| auto zone = thread->zone(); | |
| auto object_store = thread->isolate_group()->object_store(); | |
| const auto& symbol_name_field = | |
| Field::Handle(zone, object_store->symbol_name_field()); | |
| ASSERT(!symbol_name_field.IsNull()); | |
| // Keep in sync with sdk/lib/_internal/vm/lib/symbol_patch.dart. | |
| const auto& name = | |
| String::Cast(Object::Handle(zone, instance.GetField(symbol_name_field))); | |
| const uint32_t arbitrary_prime = 664597; | |
| return 0x1fffffff & (arbitrary_prime * name.CanonicalizeHash()); | |
| } | |
| uint32_t Instance::CanonicalizeHash() const { | |
| if (GetClassId() == kNullCid) { | |
| return kNullIdentityHash; | |
| } | |
| Thread* thread = Thread::Current(); | |
| uint32_t hash = thread->heap()->GetCanonicalHash(ptr()); | |
| if (hash != 0) { | |
| return hash; | |
| } | |
| Zone* zone = thread->zone(); | |
| const Class& cls = Class::Handle(zone, clazz()); | |
| const bool is_symbol = Symbol::IsSymbolCid(thread, cls.id()); | |
| NoSafepointScope no_safepoint(thread); | |
| if (is_symbol) { | |
| hash = Symbol::CanonicalizeHash(thread, *this); | |
| } else { | |
| const intptr_t class_id = cls.id(); | |
| ASSERT(class_id != 0); | |
| hash = class_id; | |
| uword this_addr = reinterpret_cast<uword>(this->untag()); | |
| Object& obj = Object::Handle(zone); | |
| Instance& instance = Instance::Handle(zone); | |
| const auto unboxed_fields_bitmap = | |
| thread->isolate_group()->class_table()->GetUnboxedFieldsMapAt( | |
| GetClassId()); | |
| for (intptr_t offset = Instance::NextFieldOffset(); | |
| offset < cls.host_next_field_offset(); offset += kCompressedWordSize) { | |
| if (unboxed_fields_bitmap.Get(offset / kCompressedWordSize)) { | |
| if (kCompressedWordSize == 8) { | |
| hash = CombineHashes( | |
| hash, *reinterpret_cast<uint32_t*>(this_addr + offset)); | |
| hash = CombineHashes( | |
| hash, *reinterpret_cast<uint32_t*>(this_addr + offset + 4)); | |
| } else { | |
| hash = CombineHashes( | |
| hash, *reinterpret_cast<uint32_t*>(this_addr + offset)); | |
| } | |
| } else { | |
| obj = reinterpret_cast<CompressedObjectPtr*>(this_addr + offset) | |
| ->Decompress(untag()->heap_base()); | |
| if (obj.IsSentinel()) { | |
| hash = CombineHashes(hash, 11); | |
| } else { | |
| instance ^= obj.ptr(); | |
| hash = CombineHashes(hash, instance.CanonicalizeHash()); | |
| } | |
| } | |
| } | |
| hash = FinalizeHash(hash, String::kHashBits); | |
| } | |
| thread->heap()->SetCanonicalHash(ptr(), hash); | |
| return hash; | |
| } | |
| #if defined(DEBUG) | |
| class CheckForPointers : public ObjectPointerVisitor { | |
| public: | |
| explicit CheckForPointers(IsolateGroup* isolate_group) | |
| : ObjectPointerVisitor(isolate_group), has_pointers_(false) {} | |
| bool has_pointers() const { return has_pointers_; } | |
| void VisitPointers(ObjectPtr* first, ObjectPtr* last) override { | |
| if (last >= first) { | |
| has_pointers_ = true; | |
| } | |
| } | |
| #if defined(DART_COMPRESSED_POINTERS) | |
| void VisitCompressedPointers(uword heap_base, | |
| CompressedObjectPtr* first, | |
| CompressedObjectPtr* last) override { | |
| if (last >= first) { | |
| has_pointers_ = true; | |
| } | |
| } | |
| #endif | |
| private: | |
| bool has_pointers_; | |
| DISALLOW_COPY_AND_ASSIGN(CheckForPointers); | |
| }; | |
| #endif // DEBUG | |
| void Instance::CanonicalizeFieldsLocked(Thread* thread) const { | |
| const intptr_t class_id = GetClassId(); | |
| if (class_id >= kNumPredefinedCids) { | |
| // Iterate over all fields, canonicalize numbers and strings, expect all | |
| // other instances to be canonical otherwise report error (return false). | |
| Zone* zone = thread->zone(); | |
| Object& obj = Object::Handle(zone); | |
| const intptr_t instance_size = SizeFromClass(); | |
| ASSERT(instance_size != 0); | |
| const auto unboxed_fields_bitmap = | |
| thread->isolate_group()->class_table()->GetUnboxedFieldsMapAt(class_id); | |
| for (intptr_t offset = Instance::NextFieldOffset(); offset < instance_size; | |
| offset += kCompressedWordSize) { | |
| if (unboxed_fields_bitmap.Get(offset / kCompressedWordSize)) { | |
| continue; | |
| } | |
| obj = this->FieldAddrAtOffset(offset)->Decompress(untag()->heap_base()); | |
| if (obj.IsInstance()) { | |
| obj = Instance::Cast(obj).CanonicalizeLocked(thread); | |
| this->SetFieldAtOffset(offset, obj); | |
| } else { | |
| ASSERT(obj.IsNull() || obj.IsSentinel()); | |
| } | |
| } | |
| } else { | |
| #if defined(DEBUG) && !defined(DART_COMPRESSED_POINTERS) | |
| // Make sure that we are not missing any fields. | |
| IsolateGroup* group = IsolateGroup::Current(); | |
| CheckForPointers has_pointers(group); | |
| this->ptr()->untag()->VisitPointersPrecise(&has_pointers); | |
| ASSERT(!has_pointers.has_pointers()); | |
| #endif // DEBUG | |
| } | |
| } | |
| InstancePtr Instance::CopyShallowToOldSpace(Thread* thread) const { | |
| return Instance::RawCast(Object::Clone(*this, Heap::kOld)); | |
| } | |
| InstancePtr Instance::Canonicalize(Thread* thread) const { | |
| SafepointMutexLocker ml( | |
| thread->isolate_group()->constant_canonicalization_mutex()); | |
| return CanonicalizeLocked(thread); | |
| } | |
| InstancePtr Instance::CanonicalizeLocked(Thread* thread) const { | |
| if (!this->ptr()->IsHeapObject() || this->IsCanonical()) { | |
| return this->ptr(); | |
| } | |
| ASSERT(!IsNull()); | |
| CanonicalizeFieldsLocked(thread); | |
| Zone* zone = thread->zone(); | |
| const Class& cls = Class::Handle(zone, this->clazz()); | |
| Instance& result = | |
| Instance::Handle(zone, cls.LookupCanonicalInstance(zone, *this)); | |
| if (!result.IsNull()) { | |
| return result.ptr(); | |
| } | |
| if (IsNew()) { | |
| // Create a canonical object in old space. | |
| result ^= Object::Clone(*this, Heap::kOld); | |
| } else { | |
| result = this->ptr(); | |
| } | |
| ASSERT(result.IsOld()); | |
| result.SetCanonical(); | |
| result.SetDeeplyImmutable(); | |
| return cls.InsertCanonicalConstant(zone, result); | |
| } | |
| ObjectPtr Instance::GetField(const Field& field) const { | |
| if (field.is_unboxed()) { | |
| // TODO(63699): Align unboxed fields. | |
| switch (field.guarded_cid()) { | |
| case kDoubleCid: | |
| return Double::New( | |
| LoadUnaligned(reinterpret_cast<double_t*>(FieldAddr(field)))); | |
| case kFloat32x4Cid: | |
| return Float32x4::New(LoadUnaligned( | |
| reinterpret_cast<simd128_value_t*>(FieldAddr(field)))); | |
| case kFloat64x2Cid: | |
| return Float64x2::New(LoadUnaligned( | |
| reinterpret_cast<simd128_value_t*>(FieldAddr(field)))); | |
| case kInt32x4Cid: | |
| return Int32x4::New(LoadUnaligned( | |
| reinterpret_cast<simd128_value_t*>(FieldAddr(field)))); | |
| default: | |
| return Integer::New( | |
| LoadUnaligned(reinterpret_cast<int64_t*>(FieldAddr(field)))); | |
| } | |
| } else { | |
| return FieldAddr(field)->Decompress(untag()->heap_base()); | |
| } | |
| } | |
| void Instance::SetField(const Field& field, const Object& value) const { | |
| if (field.is_unboxed()) { | |
| // TODO(63699): Align unboxed fields. | |
| switch (field.guarded_cid()) { | |
| case kDoubleCid: | |
| StoreNonPointerUnaligned(reinterpret_cast<double_t*>(FieldAddr(field)), | |
| Double::Cast(value).value()); | |
| break; | |
| case kFloat32x4Cid: | |
| StoreNonPointerUnaligned( | |
| reinterpret_cast<simd128_value_t*>(FieldAddr(field)), | |
| Float32x4::Cast(value).value()); | |
| break; | |
| case kFloat64x2Cid: | |
| StoreNonPointerUnaligned( | |
| reinterpret_cast<simd128_value_t*>(FieldAddr(field)), | |
| Float64x2::Cast(value).value()); | |
| break; | |
| case kInt32x4Cid: | |
| StoreNonPointerUnaligned( | |
| reinterpret_cast<simd128_value_t*>(FieldAddr(field)), | |
| Int32x4::Cast(value).value()); | |
| break; | |
| default: | |
| StoreNonPointerUnaligned(reinterpret_cast<int64_t*>(FieldAddr(field)), | |
| Integer::Cast(value).Value()); | |
| break; | |
| } | |
| } else { | |
| field.RecordStore(value); | |
| StoreCompressedPointer(FieldAddr(field), value.ptr()); | |
| } | |
| } | |
| void Instance::SetFieldWithoutFieldGuard(const Field& field, | |
| const Object& value) const { | |
| if (field.is_unboxed()) { | |
| switch (field.guarded_cid()) { | |
| case kDoubleCid: | |
| StoreNonPointer(reinterpret_cast<double_t*>(FieldAddr(field)), | |
| Double::Cast(value).value()); | |
| break; | |
| case kFloat32x4Cid: | |
| StoreNonPointer(reinterpret_cast<simd128_value_t*>(FieldAddr(field)), | |
| Float32x4::Cast(value).value()); | |
| break; | |
| case kFloat64x2Cid: | |
| StoreNonPointer(reinterpret_cast<simd128_value_t*>(FieldAddr(field)), | |
| Float64x2::Cast(value).value()); | |
| break; | |
| case kInt32x4Cid: | |
| StoreNonPointer(reinterpret_cast<simd128_value_t*>(FieldAddr(field)), | |
| Int32x4::Cast(value).value()); | |
| break; | |
| default: | |
| StoreNonPointer(reinterpret_cast<int64_t*>(FieldAddr(field)), | |
| Integer::Cast(value).Value()); | |
| break; | |
| } | |
| } else { | |
| // Some basic validation that we are not violating guarded cid. | |
| RELEASE_ASSERT(!Thread::Current()->isolate_group()->use_field_guards() || | |
| field.guarded_cid() == kDynamicCid || | |
| field.guarded_cid() == value.GetClassId() || | |
| (field.is_nullable() && value.IsNull())); | |
| StoreCompressedPointer(FieldAddr(field), value.ptr()); | |
| } | |
| } | |
| AbstractTypePtr Instance::GetType(Heap::Space space, | |
| TypeVisibility visibility) const { | |
| if (IsNull()) { | |
| return Type::NullType(); | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| if (visibility == TypeVisibility::kUserVisibleType) { | |
| // VM hides certain internal implementation classes | |
| // and substitutes them with public user-visible types | |
| // (String, int, double, Type, List). | |
| if (IsString()) { | |
| return Type::StringType(); | |
| } else if (IsInteger()) { | |
| return Type::IntType(); | |
| } else if (IsDouble()) { | |
| return Type::Double(); | |
| } else if (IsAbstractType()) { | |
| return Type::DartTypeType(); | |
| } else if (IsArrayClassId(GetClassId())) { | |
| const auto& cls = Class::Handle( | |
| zone, thread->isolate_group()->object_store()->list_class()); | |
| auto& type_arguments = TypeArguments::Handle(zone, GetTypeArguments()); | |
| type_arguments = type_arguments.FromInstanceTypeArguments(thread, cls); | |
| // Assume internal VM types are properly encapsulated in the core | |
| // libraries and cannot appear in type arguments. | |
| const auto& type = | |
| Type::Handle(zone, Type::New(cls, type_arguments, | |
| Nullability::kNonNullable, Heap::kNew)); | |
| type.SetIsFinalized(); | |
| return type.Canonicalize(thread); | |
| } | |
| } | |
| const Class& cls = Class::Handle(zone, clazz()); | |
| if (!cls.is_finalized()) { | |
| // Various predefined classes can be instantiated by the VM or | |
| // Dart_NewString/Integer/TypedData/... before the class is finalized. | |
| ASSERT(cls.is_prefinalized()); | |
| cls.EnsureDeclarationLoaded(); | |
| } | |
| if (cls.IsClosureClass()) { | |
| FunctionType& signature = FunctionType::Handle( | |
| zone, Closure::Cast(*this).GetInstantiatedSignature(zone)); | |
| if (!signature.IsFinalized()) { | |
| signature.SetIsFinalized(); | |
| } | |
| signature ^= signature.Canonicalize(thread); | |
| return signature.ptr(); | |
| } | |
| if (IsRecord()) { | |
| ASSERT(cls.IsRecordClass()); | |
| auto& record_type = | |
| RecordType::Handle(zone, Record::Cast(*this).GetRecordType(visibility)); | |
| ASSERT(record_type.IsFinalized()); | |
| ASSERT(record_type.IsCanonical()); | |
| return record_type.ptr(); | |
| } | |
| Type& type = Type::Handle(zone); | |
| if (!cls.IsGeneric()) { | |
| type = cls.DeclarationType(); | |
| } | |
| if (type.IsNull()) { | |
| TypeArguments& type_arguments = TypeArguments::Handle(zone); | |
| const intptr_t num_type_arguments = cls.NumTypeArguments(); | |
| if (num_type_arguments > 0) { | |
| type_arguments = GetTypeArguments(); | |
| if (!type_arguments.IsNull()) { | |
| type_arguments = type_arguments.FromInstanceTypeArguments(thread, cls); | |
| } | |
| } | |
| type = Type::New(cls, type_arguments, Nullability::kNonNullable, space); | |
| type.SetIsFinalized(); | |
| type ^= type.Canonicalize(thread); | |
| } | |
| return type.ptr(); | |
| } | |
| TypeArgumentsPtr Instance::GetTypeArguments() const { | |
| ASSERT(!IsType()); | |
| const Class& cls = Class::Handle(clazz()); | |
| intptr_t field_offset = cls.host_type_arguments_field_offset(); | |
| ASSERT(field_offset != Class::kNoTypeArguments); | |
| TypeArguments& type_arguments = TypeArguments::Handle(); | |
| type_arguments ^= | |
| FieldAddrAtOffset(field_offset)->Decompress(untag()->heap_base()); | |
| return type_arguments.ptr(); | |
| } | |
| void Instance::SetTypeArguments(const TypeArguments& value) const { | |
| ASSERT(!IsType()); | |
| ASSERT(value.IsNull() || value.IsCanonical()); | |
| const Class& cls = Class::Handle(clazz()); | |
| intptr_t field_offset = cls.host_type_arguments_field_offset(); | |
| ASSERT(field_offset != Class::kNoTypeArguments); | |
| SetFieldAtOffset(field_offset, value); | |
| } | |
| bool Instance::IsInstanceOf( | |
| const AbstractType& other, | |
| const TypeArguments& other_instantiator_type_arguments, | |
| const TypeArguments& other_function_type_arguments) const { | |
| ASSERT(!other.IsDynamicType()); | |
| return RuntimeTypeIsSubtypeOf(other, other_instantiator_type_arguments, | |
| other_function_type_arguments); | |
| } | |
| // Must be kept in sync with GenerateNullIsAssignableToType in | |
| // stub_code_compiler.cc if any changes are made. | |
| bool Instance::NullIsAssignableTo(const AbstractType& other) { | |
| // "Left Null" rule: null is assignable when destination type is | |
| // nullable. Otherwise it is not assignable or we cannot tell | |
| // without instantiating type parameter. | |
| if (other.IsNullable()) { | |
| return true; | |
| } | |
| if (other.IsFutureOrType()) { | |
| return NullIsAssignableTo(AbstractType::Handle(other.UnwrapFutureOr())); | |
| } | |
| // Since the TAVs are not available, for non-nullable type parameters | |
| // this returns a conservative approximation of "not assignable" . | |
| return false; | |
| } | |
| // Must be kept in sync with GenerateNullIsAssignableToType in | |
| // stub_code_compiler.cc if any changes are made. | |
| bool Instance::NullIsAssignableTo( | |
| const AbstractType& other, | |
| const TypeArguments& other_instantiator_type_arguments, | |
| const TypeArguments& other_function_type_arguments) { | |
| // Do checks that don't require instantiation first. | |
| if (NullIsAssignableTo(other)) return true; | |
| if (!other.IsTypeParameter()) return false; | |
| const auto& type = AbstractType::Handle(other.InstantiateFrom( | |
| other_instantiator_type_arguments, other_function_type_arguments, | |
| kAllFree, Heap::kNew)); | |
| return NullIsAssignableTo(type); | |
| } | |
| bool Instance::RuntimeTypeIsSubtypeOf( | |
| const AbstractType& other, | |
| const TypeArguments& other_instantiator_type_arguments, | |
| const TypeArguments& other_function_type_arguments) const { | |
| ASSERT(other.IsFinalized()); | |
| ASSERT(ptr() != Object::sentinel().ptr()); | |
| // Instance may not have runtimeType dynamic, void, or Never. | |
| if (other.IsTopType()) { | |
| return true; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const Class& cls = Class::Handle(zone, clazz()); | |
| if (cls.IsClosureClass()) { | |
| if (other.IsDartFunctionType() || other.IsDartClosureType() || | |
| other.IsObjectType()) { | |
| return true; | |
| } | |
| AbstractType& instantiated_other = AbstractType::Handle(zone, other.ptr()); | |
| if (!other.IsInstantiated()) { | |
| instantiated_other = other.InstantiateFrom( | |
| other_instantiator_type_arguments, other_function_type_arguments, | |
| kAllFree, Heap::kOld); | |
| if (instantiated_other.IsTopType() || instantiated_other.IsObjectType() || | |
| instantiated_other.IsDartFunctionType()) { | |
| return true; | |
| } | |
| } | |
| if (RuntimeTypeIsSubtypeOfFutureOr(zone, instantiated_other)) { | |
| return true; | |
| } | |
| if (!instantiated_other.IsFunctionType()) { | |
| return false; | |
| } | |
| const FunctionType& sig = FunctionType::Handle( | |
| Closure::Cast(*this).GetInstantiatedSignature(zone)); | |
| return sig.IsSubtypeOf(FunctionType::Cast(instantiated_other), Heap::kOld); | |
| } | |
| if (cls.IsRecordClass()) { | |
| if (other.IsDartRecordType() || other.IsObjectType()) { | |
| return true; | |
| } | |
| AbstractType& instantiated_other = AbstractType::Handle(zone, other.ptr()); | |
| if (!other.IsInstantiated()) { | |
| instantiated_other = other.InstantiateFrom( | |
| other_instantiator_type_arguments, other_function_type_arguments, | |
| kAllFree, Heap::kOld); | |
| if (instantiated_other.IsTopType() || instantiated_other.IsObjectType() || | |
| instantiated_other.IsDartRecordType()) { | |
| return true; | |
| } | |
| } | |
| if (RuntimeTypeIsSubtypeOfFutureOr(zone, instantiated_other)) { | |
| return true; | |
| } | |
| if (!instantiated_other.IsRecordType()) { | |
| return false; | |
| } | |
| const Record& record = Record::Cast(*this); | |
| const RecordType& record_type = RecordType::Cast(instantiated_other); | |
| if (record.shape() != record_type.shape()) { | |
| return false; | |
| } | |
| Instance& field_value = Instance::Handle(zone); | |
| AbstractType& field_type = AbstractType::Handle(zone); | |
| const intptr_t num_fields = record.num_fields(); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| field_value ^= record.FieldAt(i); | |
| field_type = record_type.FieldTypeAt(i); | |
| if (!field_value.RuntimeTypeIsSubtypeOf(field_type, | |
| Object::null_type_arguments(), | |
| Object::null_type_arguments())) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| TypeArguments& type_arguments = TypeArguments::Handle(zone); | |
| const intptr_t num_type_arguments = cls.NumTypeArguments(); | |
| if (num_type_arguments > 0) { | |
| type_arguments = GetTypeArguments(); | |
| ASSERT(type_arguments.IsNull() || type_arguments.IsCanonical()); | |
| // The number of type arguments in the instance must be greater or equal to | |
| // the number of type arguments expected by the instance class. | |
| // A discrepancy is allowed for closures, which borrow the type argument | |
| // vector of their instantiator, which may be of a subclass of the class | |
| // defining the closure. Truncating the vector to the correct length on | |
| // instantiation is unnecessary. The vector may therefore be longer. | |
| // Also, an optimization reuses the type argument vector of the instantiator | |
| // of generic instances when its layout is compatible. | |
| ASSERT(type_arguments.IsNull() || | |
| (type_arguments.Length() >= num_type_arguments)); | |
| } | |
| AbstractType& instantiated_other = AbstractType::Handle(zone, other.ptr()); | |
| if (!other.IsInstantiated()) { | |
| instantiated_other = other.InstantiateFrom( | |
| other_instantiator_type_arguments, other_function_type_arguments, | |
| kAllFree, Heap::kOld); | |
| if (instantiated_other.IsTopType()) { | |
| return true; | |
| } | |
| } | |
| if (IsNull()) { | |
| return NullIsAssignableTo(instantiated_other); | |
| } | |
| 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.IsTopType()) { | |
| return true; | |
| } | |
| if (Class::Handle(zone, clazz()).IsFutureClass()) { | |
| const TypeArguments& type_arguments = | |
| TypeArguments::Handle(zone, GetTypeArguments()); | |
| const AbstractType& type_arg = | |
| AbstractType::Handle(zone, type_arguments.TypeAtNullSafe(0)); | |
| if (type_arg.IsSubtypeOf(other_type_arg, Heap::kOld)) { | |
| return true; | |
| } | |
| } | |
| // Retry RuntimeTypeIsSubtypeOf after unwrapping type arg of FutureOr. | |
| if (RuntimeTypeIsSubtypeOf(other_type_arg, Object::null_type_arguments(), | |
| Object::null_type_arguments())) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| bool Instance::OperatorEquals(const Instance& other) const { | |
| // TODO(koda): Optimize for all builtin classes and all classes | |
| // that do not override operator==. | |
| return DartLibraryCalls::Equals(*this, other) == Object::bool_true().ptr(); | |
| } | |
| bool Instance::IsIdenticalTo(const Instance& other) const { | |
| if (ptr() == other.ptr()) return true; | |
| if (IsInteger() && other.IsInteger()) { | |
| return Integer::Cast(*this).Equals(other); | |
| } | |
| if (IsDouble() && other.IsDouble()) { | |
| double other_value = Double::Cast(other).value(); | |
| return Double::Cast(*this).BitwiseEqualsToDouble(other_value); | |
| } | |
| return false; | |
| } | |
| intptr_t* Instance::NativeFieldsDataAddr() const { | |
| ASSERT(Thread::Current()->no_safepoint_scope_depth() > 0); | |
| TypedDataPtr native_fields = static_cast<TypedDataPtr>( | |
| NativeFieldsAddr()->Decompress(untag()->heap_base())); | |
| if (native_fields == TypedData::null()) { | |
| return nullptr; | |
| } | |
| return reinterpret_cast<intptr_t*>(native_fields->untag()->data()); | |
| } | |
| void Instance::SetNativeField(int index, intptr_t value) const { | |
| ASSERT(IsValidNativeIndex(index)); | |
| Object& native_fields = | |
| Object::Handle(NativeFieldsAddr()->Decompress(untag()->heap_base())); | |
| if (native_fields.IsNull()) { | |
| // Allocate backing storage for the native fields. | |
| native_fields = TypedData::New(kIntPtrCid, NumNativeFields()); | |
| StoreCompressedPointer(NativeFieldsAddr(), native_fields.ptr()); | |
| } | |
| intptr_t byte_offset = index * sizeof(intptr_t); | |
| TypedData::Cast(native_fields).SetIntPtr(byte_offset, value); | |
| } | |
| void Instance::SetNativeFields(uint16_t num_native_fields, | |
| const intptr_t* field_values) const { | |
| ASSERT(num_native_fields == NumNativeFields()); | |
| ASSERT(field_values != nullptr); | |
| Object& native_fields = | |
| Object::Handle(NativeFieldsAddr()->Decompress(untag()->heap_base())); | |
| if (native_fields.IsNull()) { | |
| // Allocate backing storage for the native fields. | |
| native_fields = TypedData::New(kIntPtrCid, NumNativeFields()); | |
| StoreCompressedPointer(NativeFieldsAddr(), native_fields.ptr()); | |
| } | |
| for (uint16_t i = 0; i < num_native_fields; i++) { | |
| intptr_t byte_offset = i * sizeof(intptr_t); | |
| TypedData::Cast(native_fields).SetIntPtr(byte_offset, field_values[i]); | |
| } | |
| } | |
| bool Instance::IsCallable(Function* function) const { | |
| Class& cls = Class::Handle(clazz()); | |
| if (cls.IsClosureClass()) { | |
| if (function != nullptr) { | |
| *function = Closure::Cast(*this).function(); | |
| } | |
| return true; | |
| } | |
| // Try to resolve a "call" method. | |
| Zone* zone = Thread::Current()->zone(); | |
| Function& call_function = Function::Handle( | |
| zone, Resolver::ResolveDynamicAnyArgs(zone, cls, Symbols::DynamicCall(), | |
| /*allow_add=*/false)); | |
| if (call_function.IsNull()) { | |
| return false; | |
| } | |
| if (function != nullptr) { | |
| *function = call_function.ptr(); | |
| } | |
| return true; | |
| } | |
| InstancePtr Instance::New(const Class& cls, Heap::Space space) { | |
| Thread* thread = Thread::Current(); | |
| if (cls.EnsureIsAllocateFinalized(thread) != Error::null()) { | |
| return Instance::null(); | |
| } | |
| return NewAlreadyFinalized(cls, space); | |
| } | |
| InstancePtr Instance::NewAlreadyFinalized(const Class& cls, Heap::Space space) { | |
| ASSERT(cls.is_allocate_finalized()); | |
| intptr_t instance_size = cls.host_instance_size(); | |
| ASSERT(instance_size > 0); | |
| // Initialize everything after the object header with Object::null(), since | |
| // this isn't a predefined class. | |
| const uword ptr_field_end_offset = | |
| instance_size - (Instance::ContainsCompressedPointers() | |
| ? kCompressedWordSize | |
| : kWordSize); | |
| return static_cast<InstancePtr>(Object::Allocate( | |
| cls.id(), instance_size, space, Instance::ContainsCompressedPointers(), | |
| from_offset<Instance>(), ptr_field_end_offset)); | |
| } | |
| bool Instance::IsValidFieldOffset(intptr_t offset) const { | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_CLASS_HANDLESCOPE(thread); | |
| Class& cls = thread->ClassHandle(); | |
| cls = clazz(); | |
| return (offset >= 0 && | |
| offset <= (cls.host_instance_size() - kCompressedWordSize)); | |
| } | |
| intptr_t Instance::ElementSizeFor(intptr_t cid) { | |
| if (IsExternalTypedDataClassId(cid) || IsTypedDataClassId(cid) || | |
| IsTypedDataViewClassId(cid) || IsUnmodifiableTypedDataViewClassId(cid)) { | |
| return TypedDataBase::ElementSizeInBytes(cid); | |
| } | |
| switch (cid) { | |
| case kArrayCid: | |
| case kImmutableArrayCid: | |
| return Array::kBytesPerElement; | |
| case kTypeArgumentsCid: | |
| return TypeArguments::ArrayTraits::kElementSize; | |
| case kOneByteStringCid: | |
| return OneByteString::kBytesPerElement; | |
| case kTwoByteStringCid: | |
| return TwoByteString::kBytesPerElement; | |
| default: | |
| UNIMPLEMENTED(); | |
| return 0; | |
| } | |
| } | |
| intptr_t Instance::DataOffsetFor(intptr_t cid) { | |
| if (IsExternalTypedDataClassId(cid)) { | |
| // Elements start at offset 0 of the external data. | |
| return 0; | |
| } | |
| if (IsTypedDataClassId(cid)) { | |
| return TypedData::payload_offset(); | |
| } | |
| switch (cid) { | |
| case kArrayCid: | |
| case kImmutableArrayCid: | |
| return Array::data_offset(); | |
| case kTypeArgumentsCid: | |
| return TypeArguments::types_offset(); | |
| case kOneByteStringCid: | |
| return OneByteString::data_offset(); | |
| case kTwoByteStringCid: | |
| return TwoByteString::data_offset(); | |
| case kRecordCid: | |
| return Record::field_offset(0); | |
| default: | |
| UNIMPLEMENTED(); | |
| return Array::data_offset(); | |
| } | |
| } | |
| const char* Instance::ToCString() const { | |
| if (IsNull()) { | |
| return "null"; | |
| } else if (Thread::Current()->no_safepoint_scope_depth() > 0) { | |
| // Can occur when running disassembler. | |
| return "Instance"; | |
| } else { | |
| if (IsClosure()) { | |
| return Closure::Cast(*this).ToCString(); | |
| } | |
| // Background compiler disassembly of instructions referring to pool objects | |
| // calls this function and requires allocation of Type in old space. | |
| const AbstractType& type = AbstractType::Handle( | |
| GetType(Heap::kOld, TypeVisibility::kUserVisibleType)); | |
| const String& type_name = String::Handle(type.UserVisibleName()); | |
| return OS::SCreate(Thread::Current()->zone(), "Instance of '%s'", | |
| type_name.ToCString()); | |
| } | |
| } | |
| classid_t AbstractType::type_class_id() const { | |
| // All subclasses should implement this appropriately, so the only value that | |
| // should reach this implementation should be the null value. | |
| ASSERT(IsNull()); | |
| // AbstractType is an abstract class. | |
| UNREACHABLE(); | |
| return kIllegalCid; | |
| } | |
| ClassPtr AbstractType::type_class() const { | |
| // All subclasses should implement this appropriately, so the only value that | |
| // should reach this implementation should be the null value. | |
| ASSERT(IsNull()); | |
| // AbstractType is an abstract class. | |
| UNREACHABLE(); | |
| return Class::null(); | |
| } | |
| TypeArgumentsPtr AbstractType::arguments() const { | |
| // All subclasses should implement this appropriately, so the only value that | |
| // should reach this implementation should be the null value. | |
| ASSERT(IsNull()); | |
| // AbstractType is an abstract class. | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| bool AbstractType::IsStrictlyNonNullable() const { | |
| if (IsNullable()) { | |
| return false; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| if (IsTypeParameter()) { | |
| const auto& bound = | |
| AbstractType::Handle(zone, TypeParameter::Cast(*this).bound()); | |
| ASSERT(!bound.IsNull()); | |
| return bound.IsStrictlyNonNullable(); | |
| } | |
| if (IsFutureOrType()) { | |
| return AbstractType::Handle(zone, UnwrapFutureOr()).IsStrictlyNonNullable(); | |
| } | |
| return true; | |
| } | |
| AbstractTypePtr AbstractType::SetInstantiatedNullability( | |
| const TypeParameter& type_param, | |
| Heap::Space space) const { | |
| Nullability result_nullability; | |
| const Nullability arg_nullability = nullability(); | |
| const Nullability var_nullability = type_param.nullability(); | |
| // Adjust nullability of result 'arg' instantiated from 'var'. | |
| // arg/var ! ? | |
| // ! ! ? | |
| // ? ? ? | |
| if (var_nullability == Nullability::kNullable) { | |
| result_nullability = Nullability::kNullable; | |
| } else { | |
| // Keep arg nullability. | |
| return ptr(); | |
| } | |
| if (arg_nullability == result_nullability) { | |
| return ptr(); | |
| } | |
| if (IsType()) { | |
| return Type::Cast(*this).ToNullability(result_nullability, space); | |
| } | |
| if (IsFunctionType()) { | |
| return FunctionType::Cast(*this).ToNullability(result_nullability, space); | |
| } | |
| if (IsRecordType()) { | |
| return RecordType::Cast(*this).ToNullability(result_nullability, space); | |
| } | |
| if (IsTypeParameter()) { | |
| return TypeParameter::Cast(*this).ToNullability(result_nullability, space); | |
| } | |
| UNREACHABLE(); | |
| } | |
| AbstractTypePtr AbstractType::NormalizeFutureOrType(Heap::Space space) const { | |
| if (IsFutureOrType()) { | |
| Zone* zone = Thread::Current()->zone(); | |
| const AbstractType& unwrapped_type = | |
| AbstractType::Handle(zone, UnwrapFutureOr()); | |
| const classid_t cid = unwrapped_type.type_class_id(); | |
| if (cid == kDynamicCid || cid == kVoidCid) { | |
| return unwrapped_type.ptr(); | |
| } | |
| if (cid == kInstanceCid) { | |
| if (IsNullable()) { | |
| return Type::Cast(unwrapped_type) | |
| .ToNullability(Nullability::kNullable, space); | |
| } else { | |
| return unwrapped_type.ptr(); | |
| } | |
| } | |
| if (cid == kNeverCid && unwrapped_type.IsNonNullable()) { | |
| ObjectStore* object_store = IsolateGroup::Current()->object_store(); | |
| const Type& future_never_type = | |
| Type::Handle(zone, object_store->non_nullable_future_never_type()); | |
| ASSERT(!future_never_type.IsNull()); | |
| return future_never_type.ToNullability(nullability(), space); | |
| } | |
| if (cid == kNullCid) { | |
| ObjectStore* object_store = IsolateGroup::Current()->object_store(); | |
| ASSERT(object_store->nullable_future_null_type() != Type::null()); | |
| return object_store->nullable_future_null_type(); | |
| } | |
| if (IsNullable() && unwrapped_type.IsNullable()) { | |
| return Type::Cast(*this).ToNullability(Nullability::kNonNullable, space); | |
| } | |
| } | |
| return ptr(); | |
| } | |
| bool AbstractType::IsInstantiated(Genericity genericity, | |
| intptr_t num_free_fun_type_params) const { | |
| // All subclasses should implement this appropriately, so the only value that | |
| // should reach this implementation should be the null value. | |
| ASSERT(IsNull()); | |
| // AbstractType is an abstract class. | |
| UNREACHABLE(); | |
| return false; | |
| } | |
| void AbstractType::SetIsFinalized() const { | |
| ASSERT(!IsFinalized()); | |
| set_type_state(IsInstantiated() | |
| ? UntaggedAbstractType::kFinalizedInstantiated | |
| : UntaggedAbstractType::kFinalizedUninstantiated); | |
| } | |
| void AbstractType::set_flags(uint32_t value) const { | |
| untag()->set_flags(value); | |
| } | |
| void AbstractType::set_type_state(UntaggedAbstractType::TypeState value) const { | |
| ASSERT(!IsCanonical()); | |
| set_flags( | |
| UntaggedAbstractType::TypeStateBits::update(value, untag()->flags())); | |
| } | |
| void AbstractType::set_nullability(Nullability value) const { | |
| ASSERT(!IsCanonical()); | |
| set_flags(UntaggedAbstractType::NullabilityBit::update( | |
| static_cast<uint8_t>(value), untag()->flags())); | |
| } | |
| bool AbstractType::IsEquivalent( | |
| const Instance& other, | |
| TypeEquality kind, | |
| FunctionTypeMapping* function_type_equivalence) const { | |
| // All subclasses should implement this appropriately, so the only value that | |
| // should reach this implementation should be the null value. | |
| ASSERT(IsNull()); | |
| // AbstractType is an abstract class. | |
| UNREACHABLE(); | |
| return false; | |
| } | |
| bool AbstractType::IsNullabilityEquivalent(Thread* thread, | |
| const AbstractType& other_type, | |
| TypeEquality kind) const { | |
| Nullability this_type_nullability = nullability(); | |
| Nullability other_type_nullability = other_type.nullability(); | |
| if (kind == TypeEquality::kInSubtypeTest) { | |
| if (this_type_nullability == Nullability::kNullable && | |
| other_type_nullability == Nullability::kNonNullable) { | |
| return false; | |
| } | |
| } else { | |
| ASSERT((kind == TypeEquality::kSyntactical) || | |
| (kind == TypeEquality::kCanonical)); | |
| if (this_type_nullability != other_type_nullability) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| AbstractTypePtr AbstractType::InstantiateFrom( | |
| const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments, | |
| intptr_t num_free_fun_type_params, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_mapping, | |
| intptr_t num_parent_type_args_adjustment) const { | |
| // All subclasses should implement this appropriately, so the only value that | |
| // should reach this implementation should be the null value. | |
| ASSERT(IsNull()); | |
| // AbstractType is an abstract class. | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| AbstractTypePtr AbstractType::UpdateFunctionTypes( | |
| intptr_t num_parent_type_args_adjustment, | |
| intptr_t num_free_fun_type_params, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_mapping) const { | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| AbstractTypePtr AbstractType::Canonicalize(Thread* thread) const { | |
| // All subclasses should implement this appropriately, so the only value that | |
| // should reach this implementation should be the null value. | |
| ASSERT(IsNull()); | |
| // AbstractType is an abstract class. | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| void AbstractType::EnumerateURIs(URIs* uris) const { | |
| // All subclasses should implement this appropriately, so the only value that | |
| // should reach this implementation should be the null value. | |
| ASSERT(IsNull()); | |
| // AbstractType is an abstract class. | |
| UNREACHABLE(); | |
| } | |
| void AbstractType::AddURI(URIs* uris, const String& name, const String& uri) { | |
| ASSERT(uris != nullptr); | |
| const intptr_t len = uris->length(); | |
| ASSERT((len % 3) == 0); | |
| bool print_uri = false; | |
| for (intptr_t i = 0; i < len; i += 3) { | |
| if (uris->At(i).Equals(name)) { | |
| if (uris->At(i + 1).Equals(uri)) { | |
| // Same name and same URI: no need to add this already listed URI. | |
| return; // No state change is possible. | |
| } else { | |
| // Same name and different URI: the name is ambiguous, print both URIs. | |
| print_uri = true; | |
| uris->SetAt(i + 2, Symbols::print()); | |
| } | |
| } | |
| } | |
| uris->Add(name); | |
| uris->Add(uri); | |
| if (print_uri) { | |
| uris->Add(Symbols::print()); | |
| } else { | |
| uris->Add(Symbols::Empty()); | |
| } | |
| } | |
| StringPtr AbstractType::PrintURIs(URIs* uris) { | |
| ASSERT(uris != nullptr); | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const intptr_t len = uris->length(); | |
| ASSERT((len % 3) == 0); | |
| GrowableHandlePtrArray<const String> pieces(zone, 5 * (len / 3)); | |
| for (intptr_t i = 0; i < len; i += 3) { | |
| // Only print URIs that have been marked. | |
| if (uris->At(i + 2).ptr() == Symbols::print().ptr()) { | |
| pieces.Add(Symbols::TwoSpaces()); | |
| pieces.Add(uris->At(i)); | |
| pieces.Add(Symbols::SpaceIsFromSpace()); | |
| pieces.Add(uris->At(i + 1)); | |
| pieces.Add(Symbols::NewLine()); | |
| } | |
| } | |
| return Symbols::FromConcatAll(thread, pieces); | |
| } | |
| const char* AbstractType::NullabilitySuffix( | |
| NameVisibility name_visibility) const { | |
| if (IsDynamicType() || IsVoidType() || IsNullType()) { | |
| // Hide nullable suffix. | |
| return ""; | |
| } | |
| // Keep in sync with Nullability enum in runtime/vm/object.h. | |
| switch (nullability()) { | |
| case Nullability::kNullable: | |
| return "?"; | |
| case Nullability::kNonNullable: | |
| return ""; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| StringPtr AbstractType::Name() const { | |
| return Symbols::New(Thread::Current(), NameCString()); | |
| } | |
| const char* AbstractType::NameCString() const { | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| PrintName(kInternalName, &printer); | |
| return printer.buffer(); | |
| } | |
| StringPtr AbstractType::UserVisibleName() const { | |
| return Symbols::New(Thread::Current(), UserVisibleNameCString()); | |
| } | |
| const char* AbstractType::UserVisibleNameCString() const { | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| PrintName(kUserVisibleName, &printer); | |
| return printer.buffer(); | |
| } | |
| StringPtr AbstractType::ScrubbedName() const { | |
| return Symbols::New(Thread::Current(), ScrubbedNameCString()); | |
| } | |
| const char* AbstractType::ScrubbedNameCString() const { | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| PrintName(kScrubbedName, &printer); | |
| return printer.buffer(); | |
| } | |
| void AbstractType::PrintName(NameVisibility name_visibility, | |
| BaseTextBuffer* printer) const { | |
| // All subclasses should implement this appropriately, so the only value that | |
| // should reach this implementation should be the null value. | |
| ASSERT(IsNull()); | |
| // AbstractType is an abstract class. | |
| UNREACHABLE(); | |
| } | |
| StringPtr AbstractType::ClassName() const { | |
| ASSERT(!IsFunctionType() && !IsRecordType()); | |
| return Class::Handle(type_class()).Name(); | |
| } | |
| bool AbstractType::IsNullType() const { | |
| return type_class_id() == kNullCid; | |
| } | |
| bool AbstractType::IsNeverType() const { | |
| return type_class_id() == kNeverCid; | |
| } | |
| bool AbstractType::IsSentinelType() const { | |
| return type_class_id() == kSentinelCid; | |
| } | |
| // Must be kept in sync with StubCodeCompiler::GenerateIsTopTypeStub | |
| // if any changes are made. | |
| bool AbstractType::IsTopType() const { | |
| const classid_t cid = type_class_id(); | |
| if (cid == kDynamicCid || cid == kVoidCid) { | |
| return true; | |
| } | |
| if (cid == kInstanceCid) { // Object type. | |
| return IsNullable(); | |
| } | |
| if (cid == kFutureOrCid) { | |
| // FutureOr<T> where T is a top type behaves as a top type. | |
| return AbstractType::Handle(UnwrapFutureOr()).IsTopType(); | |
| } | |
| return false; | |
| } | |
| bool AbstractType::IsIntType() const { | |
| return HasTypeClass() && | |
| (type_class() == Type::Handle(Type::IntType()).type_class()); | |
| } | |
| bool AbstractType::IsIntegerImplementationType() const { | |
| return HasTypeClass() && | |
| (type_class() == IsolateGroup::Current() | |
| ->object_store() | |
| ->integer_implementation_class()); | |
| } | |
| bool AbstractType::IsDoubleType() const { | |
| return HasTypeClass() && | |
| (type_class() == Type::Handle(Type::Double()).type_class()); | |
| } | |
| bool AbstractType::IsFloat32x4Type() const { | |
| // kFloat32x4Cid refers to the private class and cannot be used here. | |
| return HasTypeClass() && | |
| (type_class() == Type::Handle(Type::Float32x4()).type_class()); | |
| } | |
| bool AbstractType::IsFloat64x2Type() const { | |
| // kFloat64x2Cid refers to the private class and cannot be used here. | |
| return HasTypeClass() && | |
| (type_class() == Type::Handle(Type::Float64x2()).type_class()); | |
| } | |
| bool AbstractType::IsInt32x4Type() const { | |
| // kInt32x4Cid refers to the private class and cannot be used here. | |
| return HasTypeClass() && | |
| (type_class() == Type::Handle(Type::Int32x4()).type_class()); | |
| } | |
| bool AbstractType::IsStringType() const { | |
| return HasTypeClass() && | |
| (type_class() == Type::Handle(Type::StringType()).type_class()); | |
| } | |
| bool AbstractType::IsDartFunctionType() const { | |
| return HasTypeClass() && | |
| (type_class() == Type::Handle(Type::DartFunctionType()).type_class()); | |
| } | |
| bool AbstractType::IsDartClosureType() const { | |
| return (type_class_id() == kClosureCid); | |
| } | |
| bool AbstractType::IsDartRecordType() const { | |
| if (!HasTypeClass()) return false; | |
| const auto cid = type_class_id(); | |
| return ((cid == kRecordCid) || | |
| (cid == Class::Handle( | |
| IsolateGroup::Current()->object_store()->record_class()) | |
| .id())); | |
| } | |
| bool AbstractType::IsFfiPointerType() const { | |
| return HasTypeClass() && type_class_id() == kPointerCid; | |
| } | |
| bool AbstractType::IsTypeClassAllowedBySpawnUri() const { | |
| if (!HasTypeClass()) return false; | |
| intptr_t cid = type_class_id(); | |
| if (cid == kBoolCid) return true; | |
| if (cid == kDynamicCid) return true; | |
| if (cid == kInstanceCid) return true; // Object. | |
| if (cid == kNeverCid) return true; | |
| if (cid == kNullCid) return true; | |
| if (cid == kVoidCid) return true; | |
| // These are not constant CID checks because kDoubleCid refers to _Double | |
| // not double, etc. | |
| ObjectStore* object_store = IsolateGroup::Current()->object_store(); | |
| Type& candidate_type = Type::Handle(); | |
| candidate_type = object_store->int_type(); | |
| if (cid == candidate_type.type_class_id()) return true; | |
| candidate_type = object_store->double_type(); | |
| if (cid == candidate_type.type_class_id()) return true; | |
| candidate_type = object_store->number_type(); | |
| if (cid == candidate_type.type_class_id()) return true; | |
| candidate_type = object_store->string_type(); | |
| if (cid == candidate_type.type_class_id()) return true; | |
| Class& candidate_cls = Class::Handle(); | |
| candidate_cls = object_store->list_class(); | |
| if (cid == candidate_cls.id()) return true; | |
| candidate_cls = object_store->map_class(); | |
| if (cid == candidate_cls.id()) return true; | |
| candidate_cls = object_store->set_class(); | |
| if (cid == candidate_cls.id()) return true; | |
| candidate_cls = object_store->capability_class(); | |
| if (cid == candidate_cls.id()) return true; | |
| candidate_cls = object_store->send_port_class(); | |
| if (cid == candidate_cls.id()) return true; | |
| candidate_cls = object_store->transferable_class(); | |
| if (cid == candidate_cls.id()) return true; | |
| const auto& typed_data_lib = | |
| Library::Handle(object_store->typed_data_library()); | |
| #define IS_CHECK(name) \ | |
| candidate_cls = typed_data_lib.LookupClass(Symbols::name##List()); \ | |
| if (cid == candidate_cls.id()) { \ | |
| return true; \ | |
| } | |
| DART_CLASS_LIST_TYPED_DATA(IS_CHECK) | |
| #undef IS_CHECK | |
| return false; | |
| } | |
| AbstractTypePtr AbstractType::UnwrapFutureOr() const { | |
| if (!IsFutureOrType()) { | |
| return ptr(); | |
| } | |
| if (arguments() == TypeArguments::null()) { | |
| return Type::dynamic_type().ptr(); | |
| } | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_TYPE_ARGUMENTS_HANDLESCOPE(thread); | |
| TypeArguments& type_args = thread->TypeArgumentsHandle(); | |
| type_args = arguments(); | |
| REUSABLE_ABSTRACT_TYPE_HANDLESCOPE(thread); | |
| AbstractType& type_arg = thread->AbstractTypeHandle(); | |
| type_arg = type_args.TypeAt(0); | |
| while (type_arg.IsFutureOrType()) { | |
| if (type_arg.arguments() == TypeArguments::null()) { | |
| return Type::dynamic_type().ptr(); | |
| } | |
| type_args = type_arg.arguments(); | |
| type_arg = type_args.TypeAt(0); | |
| } | |
| return type_arg.ptr(); | |
| } | |
| bool AbstractType::IsSubtypeOf( | |
| const AbstractType& other, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_equivalence) const { | |
| TRACE_TYPE_CHECKS_VERBOSE(" AbstractType::IsSubtypeOf(%s, %s)\n", | |
| ToCString(), other.ToCString()); | |
| ASSERT(IsFinalized()); | |
| ASSERT(other.IsFinalized()); | |
| // Reflexivity. | |
| if (ptr() == other.ptr()) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: true (same types)\n"); | |
| return true; | |
| } | |
| // Right top type. | |
| if (other.IsTopType()) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: true (right is top)\n"); | |
| return true; | |
| } | |
| // Left bottom type. | |
| // Note that we cannot encounter Never?, as it is normalized to Null. | |
| if (IsNeverType()) { | |
| ASSERT(!IsNullable()); | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: true (left is Never)\n"); | |
| return true; | |
| } | |
| // Left top type. | |
| if (IsDynamicType() || IsVoidType()) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: false (left is top)\n"); | |
| return false; | |
| } | |
| // Left Null type. | |
| if (IsNullType()) { | |
| const bool result = Instance::NullIsAssignableTo(other); | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: %s (left is Null)\n", | |
| (result ? "true" : "false")); | |
| return result; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| // Type parameters cannot be handled by Class::IsSubtypeOf(). | |
| // When comparing two uninstantiated function types, one returning type | |
| // parameter K, the other returning type parameter V, we cannot assume that | |
| // K is a subtype of V, or vice versa. We only return true if K equals V, as | |
| // defined by TypeParameter::Equals. | |
| // The same rule applies when checking the upper bound of a still | |
| // uninstantiated type at compile time. Returning false will defer the test | |
| // to run time. | |
| // There are however some cases that can be decided at compile time. | |
| // For example, with class A<K, V extends K>, new A<T, T> called from within | |
| // a class B<T> will never require a run time bound check, even if T is | |
| // uninstantiated at compile time. | |
| if (IsTypeParameter()) { | |
| const TypeParameter& type_param = TypeParameter::Cast(*this); | |
| if (other.IsTypeParameter()) { | |
| const TypeParameter& other_type_param = TypeParameter::Cast(other); | |
| if (type_param.IsEquivalent(other_type_param, | |
| TypeEquality::kInSubtypeTest, | |
| function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: true (equivalent type parameters)\n"); | |
| return true; | |
| } | |
| } | |
| const AbstractType& bound = AbstractType::Handle(zone, type_param.bound()); | |
| ASSERT(bound.IsFinalized()); | |
| if (bound.IsSubtypeOf(other, space, function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: true (bound is a subtype)\n"); | |
| return true; | |
| } | |
| // Apply additional subtyping rules if 'other' is 'FutureOr'. | |
| if (IsSubtypeOfFutureOr(zone, other, space, function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: true (type parameter is a subtype of FutureOr)\n"); | |
| return true; | |
| } | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (left is a type parameter)\n"); | |
| return false; | |
| } | |
| if (other.IsTypeParameter()) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (right is a type parameter)\n"); | |
| return false; | |
| } | |
| // Function types cannot be handled by Class::IsSubtypeOf(). | |
| if (IsFunctionType()) { | |
| // Any type that can be the type of a closure is a subtype of Function or | |
| // non-nullable Object. | |
| if (other.IsObjectType() || other.IsDartFunctionType()) { | |
| const bool result = !IsNullable() || !other.IsNonNullable(); | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: %s (function vs non-function)\n", | |
| (result ? "true" : "false")); | |
| return result; | |
| } | |
| if (other.IsFunctionType()) { | |
| // Check for two function types. | |
| if (IsNullable() && other.IsNonNullable()) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (function nullability)\n"); | |
| return false; | |
| } | |
| const bool result = FunctionType::Cast(*this).IsSubtypeOf( | |
| FunctionType::Cast(other), space, function_type_equivalence); | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: %s (function types)\n", | |
| (result ? "true" : "false")); | |
| return result; | |
| } | |
| // Apply additional subtyping rules if 'other' is 'FutureOr'. | |
| if (IsSubtypeOfFutureOr(zone, other, space, function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: true (function type is a subtype of FutureOr)\n"); | |
| return true; | |
| } | |
| // All possible supertypes for FunctionType have been checked. | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: false (function type)\n"); | |
| return false; | |
| } else if (other.IsFunctionType()) { | |
| // FunctionTypes can only be subtyped by other FunctionTypes, so don't | |
| // fall through to class-based type tests. | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (right is a function type)\n"); | |
| return false; | |
| } | |
| // Record types cannot be handled by Class::IsSubtypeOf(). | |
| if (IsRecordType()) { | |
| if (other.IsObjectType() || other.IsDartRecordType()) { | |
| const bool result = !IsNullable() || !other.IsNonNullable(); | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: %s (record vs non-record)\n", | |
| (result ? "true" : "false")); | |
| return result; | |
| } | |
| if (other.IsRecordType()) { | |
| // Check for two record types. | |
| if (IsNullable() && other.IsNonNullable()) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: false (record nullability)\n"); | |
| return false; | |
| } | |
| const bool result = RecordType::Cast(*this).IsSubtypeOf( | |
| RecordType::Cast(other), space, function_type_equivalence); | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: %s (record types)\n", | |
| (result ? "true" : "false")); | |
| return result; | |
| } | |
| // Apply additional subtyping rules if 'other' is 'FutureOr'. | |
| if (IsSubtypeOfFutureOr(zone, other, space, function_type_equivalence)) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: true (record type is a subtype of FutureOr)\n"); | |
| return true; | |
| } | |
| // All possible supertypes for record type have been checked. | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: false (record type)\n"); | |
| return false; | |
| } else if (other.IsRecordType()) { | |
| // RecordTypes can only be subtyped by other RecordTypes, so don't | |
| // fall through to class-based type tests. | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: false (right is a record type)\n"); | |
| return false; | |
| } | |
| ASSERT(IsType()); | |
| const Class& type_cls = Class::Handle(zone, type_class()); | |
| const bool result = Class::IsSubtypeOf( | |
| type_cls, | |
| TypeArguments::Handle(zone, Type::Cast(*this).GetInstanceTypeArguments( | |
| thread, /*canonicalize=*/false)), | |
| nullability(), other, space, function_type_equivalence); | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: %s (class type check)\n", | |
| (result ? "true" : "false")); | |
| return result; | |
| } | |
| bool AbstractType::IsSubtypeOfFutureOr( | |
| Zone* zone, | |
| const AbstractType& other, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_equivalence) const { | |
| if (other.IsFutureOrType()) { | |
| // This function is only called with a receiver that is either a function | |
| // type, record type, or an uninstantiated type parameter. | |
| // Therefore, it cannot be of class Future and we can spare the check. | |
| ASSERT(IsFunctionType() || IsRecordType() || IsTypeParameter()); | |
| const TypeArguments& other_type_arguments = | |
| TypeArguments::Handle(zone, other.arguments()); | |
| const AbstractType& other_type_arg = | |
| AbstractType::Handle(zone, other_type_arguments.TypeAtNullSafe(0)); | |
| if (other_type_arg.IsTopType()) { | |
| return true; | |
| } | |
| // Retry the IsSubtypeOf check after unwrapping type arg of FutureOr. | |
| if (IsSubtypeOf(other_type_arg, space, function_type_equivalence)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| uword AbstractType::ComputeHash() const { | |
| // AbstractType is an abstract class. | |
| UNREACHABLE(); | |
| return 0; | |
| } | |
| const char* AbstractType::ToCString() const { | |
| // All subclasses should implement this appropriately, so the only value that | |
| // should reach this implementation should be the null value. | |
| ASSERT(IsNull()); | |
| return "AbstractType: null"; | |
| } | |
| void AbstractType::SetTypeTestingStub(const Code& stub) const { | |
| if (stub.IsNull()) { | |
| InitializeTypeTestingStubNonAtomic(stub); | |
| return; | |
| } | |
| auto& old = Code::Handle(Thread::Current()->zone()); | |
| while (true) { | |
| // We load the old TTS and it's entrypoint. | |
| old = untag()->type_test_stub<std::memory_order_acquire>(); | |
| uword old_entry_point = old.IsNull() ? 0 : old.EntryPoint(); | |
| // If we can successfully update the entrypoint of the TTS, we will | |
| // unconditionally also set the [Code] of the TTS. | |
| // | |
| // Any competing writer would do the same, lose the compare-exchange, loop | |
| // around and continue loading the old [Code] TTS and continue to lose the | |
| // race until we have finally also updated the [Code] TTS. | |
| if (untag()->type_test_stub_entry_point_.compare_exchange_strong( | |
| old_entry_point, stub.EntryPoint())) { | |
| untag()->set_type_test_stub<std::memory_order_release>(stub.ptr()); | |
| return; | |
| } | |
| } | |
| } | |
| void AbstractType::InitializeTypeTestingStubNonAtomic(const Code& stub) const { | |
| if (stub.IsNull()) { | |
| // This only happens during bootstrapping when creating Type objects before | |
| // we have the instructions. | |
| ASSERT(type_class_id() == kDynamicCid || type_class_id() == kVoidCid); | |
| StoreNonPointer(&untag()->type_test_stub_entry_point_, 0); | |
| untag()->set_type_test_stub(stub.ptr()); | |
| return; | |
| } | |
| StoreNonPointer(&untag()->type_test_stub_entry_point_, stub.EntryPoint()); | |
| untag()->set_type_test_stub(stub.ptr()); | |
| } | |
| TypePtr Type::NullType() { | |
| return IsolateGroup::Current()->object_store()->null_type(); | |
| } | |
| TypePtr Type::DynamicType() { | |
| return Object::dynamic_type().ptr(); | |
| } | |
| TypePtr Type::VoidType() { | |
| return Object::void_type().ptr(); | |
| } | |
| TypePtr Type::NeverType() { | |
| return IsolateGroup::Current()->object_store()->never_type(); | |
| } | |
| TypePtr Type::ObjectType() { | |
| return IsolateGroup::Current()->object_store()->object_type(); | |
| } | |
| TypePtr Type::BoolType() { | |
| return IsolateGroup::Current()->object_store()->bool_type(); | |
| } | |
| TypePtr Type::IntType() { | |
| return IsolateGroup::Current()->object_store()->int_type(); | |
| } | |
| TypePtr Type::NullableIntType() { | |
| return IsolateGroup::Current()->object_store()->nullable_int_type(); | |
| } | |
| TypePtr Type::SmiType() { | |
| return IsolateGroup::Current()->object_store()->smi_type(); | |
| } | |
| TypePtr Type::MintType() { | |
| return IsolateGroup::Current()->object_store()->mint_type(); | |
| } | |
| TypePtr Type::Double() { | |
| return IsolateGroup::Current()->object_store()->double_type(); | |
| } | |
| TypePtr Type::NullableDouble() { | |
| return IsolateGroup::Current()->object_store()->nullable_double_type(); | |
| } | |
| TypePtr Type::Float32x4() { | |
| return IsolateGroup::Current()->object_store()->float32x4_type(); | |
| } | |
| TypePtr Type::Float64x2() { | |
| return IsolateGroup::Current()->object_store()->float64x2_type(); | |
| } | |
| TypePtr Type::Int32x4() { | |
| return IsolateGroup::Current()->object_store()->int32x4_type(); | |
| } | |
| TypePtr Type::Number() { | |
| return IsolateGroup::Current()->object_store()->number_type(); | |
| } | |
| TypePtr Type::NullableNumber() { | |
| return IsolateGroup::Current()->object_store()->nullable_number_type(); | |
| } | |
| TypePtr Type::StringType() { | |
| return IsolateGroup::Current()->object_store()->string_type(); | |
| } | |
| TypePtr Type::ArrayType() { | |
| return IsolateGroup::Current()->object_store()->array_type(); | |
| } | |
| TypePtr Type::DartFunctionType() { | |
| return IsolateGroup::Current()->object_store()->function_type(); | |
| } | |
| TypePtr Type::DartTypeType() { | |
| return IsolateGroup::Current()->object_store()->type_type(); | |
| } | |
| TypePtr Type::NewNonParameterizedType(const Class& type_class) { | |
| ASSERT(type_class.NumTypeArguments() == 0); | |
| if (type_class.IsNullClass()) { | |
| return Type::NullType(); | |
| } | |
| if (type_class.IsDynamicClass()) { | |
| return Type::DynamicType(); | |
| } | |
| if (type_class.IsVoidClass()) { | |
| return Type::VoidType(); | |
| } | |
| // It is too early to use the class finalizer, as type_class may not be named | |
| // yet, so do not call DeclarationType(). | |
| Type& type = Type::Handle(type_class.declaration_type()); | |
| if (type.IsNull()) { | |
| type = Type::New(Class::Handle(type_class.ptr()), | |
| Object::null_type_arguments(), Nullability::kNonNullable); | |
| type.SetIsFinalized(); | |
| type ^= type.Canonicalize(Thread::Current()); | |
| type_class.set_declaration_type(type); | |
| } | |
| ASSERT(type.IsFinalized()); | |
| return type.ptr(); | |
| } | |
| TypePtr Type::ToNullability(Nullability value, Heap::Space space) const { | |
| if (nullability() == value) { | |
| return ptr(); | |
| } | |
| // Type parameter instantiation may request a nullability change, which should | |
| // be ignored for types dynamic and void. Type Null cannot be the result of | |
| // instantiating a non-nullable type parameter (TypeError thrown). | |
| const classid_t cid = type_class_id(); | |
| if (cid == kDynamicCid || cid == kVoidCid || cid == kNullCid) { | |
| return ptr(); | |
| } | |
| if (cid == kNeverCid && value == Nullability::kNullable) { | |
| // Normalize Never? to Null. | |
| return Type::NullType(); | |
| } | |
| // Clone type and set new nullability. | |
| Type& type = Type::Handle(); | |
| // Always cloning in old space and removing space parameter would not satisfy | |
| // currently existing requests for type instantiation in new space. | |
| // Load with relaxed atomics to prevent data race with updating type | |
| // testing stub. | |
| type ^= Object::Clone(*this, space, /*load_with_relaxed_atomics=*/true); | |
| type.set_nullability(value); | |
| type.SetHash(0); | |
| type.InitializeTypeTestingStubNonAtomic( | |
| Code::Handle(TypeTestingStubGenerator::DefaultCodeForType(type))); | |
| if (IsCanonical()) { | |
| // Object::Clone does not clone canonical bit. | |
| ASSERT(!type.IsCanonical()); | |
| type ^= type.Canonicalize(Thread::Current()); | |
| } | |
| return type.ptr(); | |
| } | |
| FunctionTypePtr FunctionType::ToNullability(Nullability value, | |
| Heap::Space space) const { | |
| if (nullability() == value) { | |
| return ptr(); | |
| } | |
| // Clone function type and set new nullability. | |
| FunctionType& type = FunctionType::Handle(FunctionType::Clone(*this, space)); | |
| type.set_nullability(value); | |
| type.SetHash(0); | |
| type.InitializeTypeTestingStubNonAtomic( | |
| Code::Handle(TypeTestingStubGenerator::DefaultCodeForType(type))); | |
| if (IsCanonical()) { | |
| // Object::Clone does not clone canonical bit. | |
| ASSERT(!type.IsCanonical()); | |
| type ^= type.Canonicalize(Thread::Current()); | |
| } | |
| return type.ptr(); | |
| } | |
| classid_t Type::type_class_id() const { | |
| return untag()->type_class_id(); | |
| } | |
| ClassPtr Type::type_class() const { | |
| return IsolateGroup::Current()->class_table()->At(type_class_id()); | |
| } | |
| bool Type::IsInstantiated(Genericity genericity, | |
| intptr_t num_free_fun_type_params) const { | |
| if (type_state() == UntaggedType::kFinalizedInstantiated) { | |
| return true; | |
| } | |
| if ((genericity == kAny) && (num_free_fun_type_params == kAllFree) && | |
| (type_state() == UntaggedType::kFinalizedUninstantiated)) { | |
| return false; | |
| } | |
| if (arguments() == TypeArguments::null()) { | |
| return true; | |
| } | |
| const TypeArguments& args = TypeArguments::Handle(arguments()); | |
| return args.IsSubvectorInstantiated(0, args.Length(), genericity, | |
| num_free_fun_type_params); | |
| } | |
| AbstractTypePtr Type::InstantiateFrom( | |
| const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments, | |
| intptr_t num_free_fun_type_params, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_mapping, | |
| intptr_t num_parent_type_args_adjustment) const { | |
| Zone* zone = Thread::Current()->zone(); | |
| ASSERT(IsFinalized()); | |
| ASSERT(!IsInstantiated()); | |
| // Note that the type class has to be resolved at this time, but not | |
| // necessarily finalized yet. We may be checking bounds at compile time or | |
| // finalizing the type argument vector of a recursive type. | |
| const Class& cls = Class::Handle(zone, type_class()); | |
| TypeArguments& type_arguments = TypeArguments::Handle(zone, arguments()); | |
| ASSERT(type_arguments.Length() == cls.NumTypeParameters()); | |
| type_arguments = type_arguments.InstantiateFrom( | |
| instantiator_type_arguments, function_type_arguments, | |
| num_free_fun_type_params, space, function_type_mapping, | |
| num_parent_type_args_adjustment); | |
| // A returned empty_type_arguments indicates a failed instantiation in dead | |
| // code that must be propagated up to the caller, the optimizing compiler. | |
| if (type_arguments.ptr() == Object::empty_type_arguments().ptr()) { | |
| return Type::null(); | |
| } | |
| // This uninstantiated type is not modified, as it can be instantiated | |
| // with different instantiators. Allocate a new instantiated version of it. | |
| const Type& instantiated_type = | |
| Type::Handle(zone, Type::New(cls, type_arguments, nullability(), space)); | |
| instantiated_type.SetIsFinalized(); | |
| // Canonicalization is not part of instantiation. | |
| return instantiated_type.NormalizeFutureOrType(space); | |
| } | |
| AbstractTypePtr Type::UpdateFunctionTypes( | |
| intptr_t num_parent_type_args_adjustment, | |
| intptr_t num_free_fun_type_params, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_mapping) const { | |
| ASSERT(IsFinalized()); | |
| ASSERT(num_parent_type_args_adjustment >= 0); | |
| if (arguments() == Object::null()) { | |
| return ptr(); | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| const auto& type_args = TypeArguments::Handle(zone, arguments()); | |
| const auto& updated_type_args = TypeArguments::Handle( | |
| zone, type_args.UpdateFunctionTypes(num_parent_type_args_adjustment, | |
| num_free_fun_type_params, space, | |
| function_type_mapping)); | |
| if (type_args.ptr() == updated_type_args.ptr()) { | |
| return ptr(); | |
| } | |
| const Class& cls = Class::Handle(zone, type_class()); | |
| const Type& new_type = Type::Handle( | |
| zone, Type::New(cls, updated_type_args, nullability(), space)); | |
| new_type.SetIsFinalized(); | |
| return new_type.ptr(); | |
| } | |
| // Certain built-in classes are treated as syntactically equivalent. | |
| static classid_t NormalizeClassIdForSyntacticalTypeEquality(classid_t cid) { | |
| if (IsIntegerClassId(cid)) { | |
| return Type::Handle(Type::IntType()).type_class_id(); | |
| } else if (IsStringClassId(cid)) { | |
| return Type::Handle(Type::StringType()).type_class_id(); | |
| } else if (cid == kDoubleCid) { | |
| return Type::Handle(Type::Double()).type_class_id(); | |
| } else if (IsTypeClassId(cid)) { | |
| return Type::Handle(Type::DartTypeType()).type_class_id(); | |
| } else if (IsArrayClassId(cid)) { | |
| return Class::Handle(IsolateGroup::Current()->object_store()->list_class()) | |
| .id(); | |
| } | |
| return cid; | |
| } | |
| bool Type::IsEquivalent(const Instance& other, | |
| TypeEquality kind, | |
| FunctionTypeMapping* function_type_equivalence) const { | |
| ASSERT(!IsNull()); | |
| if (ptr() == other.ptr()) { | |
| return true; | |
| } | |
| if (!other.IsType()) { | |
| return false; | |
| } | |
| const Type& other_type = Type::Cast(other); | |
| const classid_t type_cid = type_class_id(); | |
| const classid_t other_type_cid = other_type.type_class_id(); | |
| if (type_cid != other_type_cid) { | |
| if ((kind != TypeEquality::kSyntactical) || | |
| (NormalizeClassIdForSyntacticalTypeEquality(type_cid) != | |
| NormalizeClassIdForSyntacticalTypeEquality(other_type_cid))) { | |
| return false; | |
| } | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| ASSERT( | |
| Class::Handle(zone, type_class()).NumTypeParameters(thread) == | |
| Class::Handle(zone, other_type.type_class()).NumTypeParameters(thread)); | |
| if (!IsNullabilityEquivalent(thread, other_type, kind)) { | |
| return false; | |
| } | |
| if (!IsFinalized() || !other_type.IsFinalized()) { | |
| ASSERT(kind != TypeEquality::kCanonical); | |
| return false; // Too early to decide if equal. | |
| } | |
| if (arguments() == other_type.arguments()) { | |
| return true; | |
| } | |
| const TypeArguments& type_args = | |
| TypeArguments::Handle(zone, this->arguments()); | |
| const TypeArguments& other_type_args = | |
| TypeArguments::Handle(zone, other_type.arguments()); | |
| return type_args.IsEquivalent(other_type_args, kind, | |
| function_type_equivalence); | |
| } | |
| bool FunctionType::IsEquivalent( | |
| const Instance& other, | |
| TypeEquality kind, | |
| FunctionTypeMapping* function_type_equivalence) const { | |
| ASSERT(!IsNull()); | |
| if (ptr() == other.ptr()) { | |
| return true; | |
| } | |
| if (!other.IsFunctionType()) { | |
| return false; | |
| } | |
| const FunctionType& other_type = FunctionType::Cast(other); | |
| if ((packed_parameter_counts() != other_type.packed_parameter_counts()) || | |
| (packed_type_parameter_counts() != | |
| other_type.packed_type_parameter_counts())) { | |
| // Different number of type parameters or parameters. | |
| return false; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| if (!IsNullabilityEquivalent(thread, other_type, kind)) { | |
| return false; | |
| } | |
| if (!IsFinalized() || !other_type.IsFinalized()) { | |
| ASSERT(kind != TypeEquality::kCanonical); | |
| return false; // Too early to decide if equal. | |
| } | |
| FunctionTypeMapping scope(zone, &function_type_equivalence, *this, | |
| other_type); | |
| // Equal function types must have equal signature types and equal optional | |
| // named arguments. | |
| // Compare function type parameters and their bounds. | |
| // Check the type parameters and bounds of generic functions. | |
| if (!HasSameTypeParametersAndBounds(other_type, kind, | |
| function_type_equivalence)) { | |
| return false; | |
| } | |
| AbstractType& param_type = Type::Handle(zone); | |
| AbstractType& other_param_type = Type::Handle(zone); | |
| // Check the result type. | |
| param_type = result_type(); | |
| other_param_type = other_type.result_type(); | |
| if (!param_type.IsEquivalent(other_param_type, kind, | |
| function_type_equivalence)) { | |
| return false; | |
| } | |
| // Check the types of all parameters. | |
| const intptr_t num_params = NumParameters(); | |
| ASSERT(other_type.NumParameters() == num_params); | |
| for (intptr_t i = 0; i < num_params; i++) { | |
| param_type = ParameterTypeAt(i); | |
| other_param_type = other_type.ParameterTypeAt(i); | |
| // Use contravariant order in case we test for subtyping. | |
| if (!other_param_type.IsEquivalent(param_type, kind, | |
| function_type_equivalence)) { | |
| return false; | |
| } | |
| } | |
| if (HasOptionalNamedParameters()) { | |
| ASSERT(other_type.HasOptionalNamedParameters()); // Same packed counts. | |
| for (intptr_t i = num_fixed_parameters(); i < num_params; i++) { | |
| if (ParameterNameAt(i) != other_type.ParameterNameAt(i)) { | |
| return false; | |
| } | |
| if (IsRequiredAt(i) != other_type.IsRequiredAt(i)) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool Type::IsDeclarationTypeOf(const Class& cls) const { | |
| ASSERT(type_class() == cls.ptr()); | |
| if (cls.IsNullClass()) { | |
| return true; | |
| } | |
| if (cls.IsGeneric() || cls.IsClosureClass()) { | |
| return false; | |
| } | |
| return nullability() == Nullability::kNonNullable; | |
| } | |
| // Keep in sync with TypeSerializationCluster::IsInCanonicalSet. | |
| AbstractTypePtr Type::Canonicalize(Thread* thread) const { | |
| Zone* zone = thread->zone(); | |
| ASSERT(IsFinalized()); | |
| if (IsCanonical()) { | |
| #ifdef DEBUG | |
| TypeArguments& type_args = TypeArguments::Handle(zone, arguments()); | |
| ASSERT(type_args.IsCanonical()); | |
| ASSERT(type_args.IsOld()); | |
| #endif | |
| return this->ptr(); | |
| } | |
| auto isolate_group = thread->isolate_group(); | |
| const classid_t cid = type_class_id(); | |
| if (cid == kDynamicCid) { | |
| ASSERT(Object::dynamic_type().IsCanonical()); | |
| return Object::dynamic_type().ptr(); | |
| } | |
| if (cid == kVoidCid) { | |
| ASSERT(Object::void_type().IsCanonical()); | |
| return Object::void_type().ptr(); | |
| } | |
| const Class& cls = Class::Handle(zone, type_class()); | |
| // Fast canonical lookup/registry for simple types. | |
| if (IsDeclarationTypeOf(cls)) { | |
| ASSERT(!cls.IsNullClass() || IsNullable()); | |
| Type& type = Type::Handle(zone, cls.declaration_type()); | |
| if (type.IsNull()) { | |
| // Canonicalize the type arguments of the supertype, if any. | |
| TypeArguments& type_args = TypeArguments::Handle(zone, arguments()); | |
| type_args = type_args.Canonicalize(thread); | |
| set_arguments(type_args); | |
| type = cls.declaration_type(); | |
| // May be set while canonicalizing type args. | |
| if (type.IsNull()) { | |
| SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex()); | |
| // Recheck if type exists. | |
| type = cls.declaration_type(); | |
| if (type.IsNull()) { | |
| if (this->IsNew()) { | |
| type ^= Object::Clone(*this, Heap::kOld); | |
| } else { | |
| type = this->ptr(); | |
| } | |
| ASSERT(type.IsOld()); | |
| type.ComputeHash(); | |
| type.SetCanonical(); | |
| cls.set_declaration_type(type); | |
| return type.ptr(); | |
| } | |
| } | |
| } | |
| ASSERT(this->Equals(type)); | |
| ASSERT(type.IsOld()); | |
| if (type.IsCanonical()) { | |
| return type.ptr(); | |
| } | |
| } | |
| Type& type = Type::Handle(zone); | |
| ObjectStore* object_store = isolate_group->object_store(); | |
| { | |
| SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex()); | |
| CanonicalTypeSet table(zone, object_store->canonical_types()); | |
| type ^= table.GetOrNull(CanonicalTypeKey(*this)); | |
| ASSERT(object_store->canonical_types() == table.Release().ptr()); | |
| } | |
| if (type.IsNull()) { | |
| // The type was not found in the table. It is not canonical yet. | |
| // Canonicalize the type arguments. | |
| TypeArguments& type_args = TypeArguments::Handle(zone, arguments()); | |
| ASSERT(type_args.IsNull() || | |
| (type_args.Length() == cls.NumTypeParameters())); | |
| type_args = type_args.Canonicalize(thread); | |
| set_arguments(type_args); | |
| ASSERT(type_args.IsNull() || type_args.IsOld()); | |
| // Check to see if the type got added to canonical table as part of the | |
| // type arguments canonicalization. | |
| SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex()); | |
| CanonicalTypeSet table(zone, object_store->canonical_types()); | |
| type ^= table.GetOrNull(CanonicalTypeKey(*this)); | |
| if (type.IsNull()) { | |
| // Add this type into the canonical table of types. | |
| if (this->IsNew()) { | |
| type ^= Object::Clone(*this, Heap::kOld); | |
| } else { | |
| type = this->ptr(); | |
| } | |
| ASSERT(type.IsOld()); | |
| type.SetCanonical(); // Mark object as being canonical. | |
| bool present = table.Insert(type); | |
| ASSERT(!present); | |
| } | |
| object_store->set_canonical_types(table.Release()); | |
| } | |
| return type.ptr(); | |
| } | |
| void Type::EnumerateURIs(URIs* uris) const { | |
| if (IsDynamicType() || IsVoidType() || IsNeverType()) { | |
| return; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const Class& cls = Class::Handle(zone, type_class()); | |
| const String& name = String::Handle(zone, cls.UserVisibleName()); | |
| const Library& library = Library::Handle(zone, cls.library()); | |
| const String& uri = String::Handle(zone, library.url()); | |
| AddURI(uris, name, uri); | |
| const TypeArguments& type_args = TypeArguments::Handle(zone, arguments()); | |
| type_args.EnumerateURIs(uris); | |
| } | |
| void Type::PrintName(NameVisibility name_visibility, | |
| BaseTextBuffer* printer) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const Class& cls = Class::Handle(zone, type_class()); | |
| const TypeParameters& params = | |
| TypeParameters::Handle(zone, cls.type_parameters()); | |
| printer->AddString(cls.NameCString(name_visibility)); | |
| const TypeArguments& args = TypeArguments::Handle(zone, arguments()); | |
| intptr_t num_type_params = 0; | |
| if (cls.is_declaration_loaded()) { | |
| num_type_params = cls.NumTypeParameters(thread); | |
| } else if (!args.IsNull() || args.ptr() != params.defaults()) { | |
| num_type_params = args.Length(); | |
| } | |
| if (num_type_params == 0) { | |
| // Do nothing. | |
| } else { | |
| args.PrintSubvectorName(0, num_type_params, name_visibility, printer); | |
| } | |
| printer->AddString(NullabilitySuffix(name_visibility)); | |
| // The name is only used for type checking and debugging purposes. | |
| // Unless profiling data shows otherwise, it is not worth caching the name in | |
| // the type. | |
| } | |
| uword Type::ComputeHash() const { | |
| ASSERT(IsFinalized()); | |
| uint32_t result = type_class_id(); | |
| result = CombineHashes(result, static_cast<uint32_t>(nullability())); | |
| uint32_t type_args_hash = TypeArguments::kAllDynamicHash; | |
| if (arguments() != TypeArguments::null()) { | |
| const TypeArguments& args = TypeArguments::Handle(arguments()); | |
| type_args_hash = args.Hash(); | |
| } | |
| result = CombineHashes(result, type_args_hash); | |
| result = FinalizeHash(result, kHashBits); | |
| SetHash(result); | |
| return result; | |
| } | |
| uword FunctionType::ComputeHash() const { | |
| ASSERT(IsFinalized()); | |
| uint32_t result = | |
| CombineHashes(packed_parameter_counts(), packed_type_parameter_counts()); | |
| result = CombineHashes(result, static_cast<uint32_t>(nullability())); | |
| AbstractType& type = AbstractType::Handle(); | |
| const intptr_t num_type_params = NumTypeParameters(); | |
| if (num_type_params > 0) { | |
| const TypeParameters& type_params = | |
| TypeParameters::Handle(type_parameters()); | |
| const TypeArguments& bounds = TypeArguments::Handle(type_params.bounds()); | |
| result = CombineHashes(result, bounds.Hash()); | |
| // Since the default arguments are ignored when comparing two generic | |
| // function types for type equality, the hash does not depend on them. | |
| } | |
| type = result_type(); | |
| result = CombineHashes(result, type.Hash()); | |
| const intptr_t num_params = NumParameters(); | |
| for (intptr_t i = 0; i < num_params; i++) { | |
| type = ParameterTypeAt(i); | |
| result = CombineHashes(result, type.Hash()); | |
| } | |
| if (HasOptionalNamedParameters()) { | |
| String& param_name = String::Handle(); | |
| for (intptr_t i = num_fixed_parameters(); i < num_params; i++) { | |
| param_name = ParameterNameAt(i); | |
| result = CombineHashes(result, param_name.Hash()); | |
| } | |
| // Required flag is not hashed, see comment above about legacy type. | |
| } | |
| result = FinalizeHash(result, kHashBits); | |
| SetHash(result); | |
| return result; | |
| } | |
| void Type::set_type_class(const Class& value) const { | |
| ASSERT(!value.IsNull()); | |
| set_type_class_id(value.id()); | |
| } | |
| void Type::set_arguments(const TypeArguments& value) const { | |
| ASSERT(!IsCanonical()); | |
| ASSERT(value.IsNull() || | |
| // Do not attempt to query number of type parameters | |
| // before class declaration is fully loaded. | |
| !Class::Handle(type_class()).is_declaration_loaded() || | |
| // Relax assertion in order to support invalid generic types | |
| // created in ClosureMirror_function. | |
| (type_class_id() == kInstanceCid) || | |
| value.Length() == Class::Handle(type_class()).NumTypeParameters()); | |
| untag()->set_arguments(value.ptr()); | |
| } | |
| TypeArgumentsPtr Type::GetInstanceTypeArguments(Thread* thread, | |
| bool canonicalize) const { | |
| Zone* zone = thread->zone(); | |
| const auto& cls = Class::Handle(zone, type_class()); | |
| const auto& args = TypeArguments::Handle(zone, arguments()); | |
| return cls.GetInstanceTypeArguments(thread, args, canonicalize); | |
| } | |
| TypePtr Type::New(Heap::Space space) { | |
| return Object::Allocate<Type>(space); | |
| } | |
| TypePtr Type::New(const Class& clazz, | |
| const TypeArguments& arguments, | |
| Nullability nullability, | |
| Heap::Space space) { | |
| Zone* Z = Thread::Current()->zone(); | |
| const Type& result = Type::Handle(Z, Type::New(space)); | |
| result.SetHash(0); | |
| result.set_flags(0); | |
| result.set_nullability(nullability); | |
| result.set_type_state(UntaggedAbstractType::kAllocated); | |
| result.set_type_class(clazz); | |
| result.set_arguments(arguments); | |
| result.InitializeTypeTestingStubNonAtomic( | |
| Code::Handle(Z, TypeTestingStubGenerator::DefaultCodeForType(result))); | |
| return result.ptr(); | |
| } | |
| void Type::set_type_class_id(intptr_t id) const { | |
| ASSERT(Utils::IsUint(UntaggedObject::kClassIdTagSize, id)); | |
| // We should never need a Type object for a top-level class. | |
| ASSERT(!ClassTable::IsTopLevelCid(id)); | |
| ASSERT(id != kIllegalCid); | |
| ASSERT(!IsInternalOnlyClassId(id)); | |
| ASSERT(id >= kInstanceCid); | |
| untag()->set_type_class_id(id); | |
| } | |
| const char* Type::ToCString() const { | |
| if (IsNull()) { | |
| return "Type: null"; | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| ZoneTextBuffer args(zone); | |
| const TypeArguments& type_args = TypeArguments::Handle(zone, arguments()); | |
| const char* args_cstr = ""; | |
| if (!type_args.IsNull()) { | |
| type_args.PrintSubvectorName(0, type_args.Length(), kInternalName, &args); | |
| args_cstr = args.buffer(); | |
| } | |
| const Class& cls = Class::Handle(zone, type_class()); | |
| const char* class_name; | |
| const String& name = String::Handle(zone, cls.Name()); | |
| class_name = name.IsNull() ? "<null>" : name.ToCString(); | |
| const char* suffix = NullabilitySuffix(kInternalName); | |
| return OS::SCreate(zone, "Type: %s%s%s", class_name, args_cstr, suffix); | |
| } | |
| AbstractTypePtr FunctionType::Canonicalize(Thread* thread) const { | |
| ASSERT(IsFinalized()); | |
| Zone* zone = thread->zone(); | |
| if (IsCanonical()) { | |
| #ifdef DEBUG | |
| // Verify that all fields are allocated in old space and are canonical. | |
| if (IsGeneric()) { | |
| const TypeParameters& type_params = | |
| TypeParameters::Handle(zone, type_parameters()); | |
| ASSERT(type_params.IsOld()); | |
| TypeArguments& type_args = TypeArguments::Handle(zone); | |
| type_args = type_params.bounds(); | |
| ASSERT(type_args.IsOld()); | |
| ASSERT(type_args.IsCanonical()); | |
| type_args = type_params.defaults(); | |
| ASSERT(type_args.IsOld()); | |
| ASSERT(type_args.IsCanonical()); | |
| } | |
| AbstractType& type = AbstractType::Handle(zone); | |
| type = result_type(); | |
| ASSERT(type.IsOld()); | |
| ASSERT(type.IsCanonical()); | |
| ASSERT(Array::Handle(zone, parameter_types()).IsOld()); | |
| ASSERT(Array::Handle(zone, named_parameter_names()).IsOld()); | |
| const intptr_t num_params = NumParameters(); | |
| for (intptr_t i = 0; i < num_params; i++) { | |
| type = ParameterTypeAt(i); | |
| ASSERT(type.IsOld()); | |
| ASSERT(type.IsCanonical()); | |
| } | |
| #endif | |
| return ptr(); | |
| } | |
| auto isolate_group = thread->isolate_group(); | |
| ObjectStore* object_store = isolate_group->object_store(); | |
| FunctionType& sig = FunctionType::Handle(zone); | |
| { | |
| SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex()); | |
| CanonicalFunctionTypeSet table(zone, | |
| object_store->canonical_function_types()); | |
| sig ^= table.GetOrNull(CanonicalFunctionTypeKey(*this)); | |
| ASSERT(object_store->canonical_function_types() == table.Release().ptr()); | |
| } | |
| if (sig.IsNull()) { | |
| // The function type was not found in the table. It is not canonical yet. | |
| // Canonicalize its type parameters and types. | |
| // Clone this function type to the old heap and update | |
| // owners of type parameters. | |
| FunctionType& new_sig = FunctionType::Handle(zone); | |
| if (this->IsNew()) { | |
| new_sig ^= FunctionType::Clone(*this, Heap::kOld); | |
| } else { | |
| new_sig ^= this->ptr(); | |
| } | |
| ASSERT(new_sig.IsOld()); | |
| if (new_sig.IsGeneric()) { | |
| const TypeParameters& type_params = | |
| TypeParameters::Handle(zone, new_sig.type_parameters()); | |
| ASSERT(type_params.IsOld()); | |
| TypeArguments& type_args = TypeArguments::Handle(zone); | |
| type_args = type_params.bounds(); | |
| if (!type_args.IsCanonical()) { | |
| type_args = type_args.Canonicalize(thread); | |
| type_params.set_bounds(type_args); | |
| } | |
| type_args = type_params.defaults(); | |
| if (!type_args.IsCanonical()) { | |
| type_args = type_args.Canonicalize(thread); | |
| type_params.set_defaults(type_args); | |
| } | |
| } | |
| AbstractType& type = AbstractType::Handle(zone); | |
| type = new_sig.result_type(); | |
| if (!type.IsCanonical()) { | |
| type = type.Canonicalize(thread); | |
| new_sig.set_result_type(type); | |
| } | |
| ASSERT(Array::Handle(zone, new_sig.parameter_types()).IsOld()); | |
| ASSERT(Array::Handle(zone, new_sig.named_parameter_names()).IsOld()); | |
| const intptr_t num_params = new_sig.NumParameters(); | |
| for (intptr_t i = 0; i < num_params; i++) { | |
| type = new_sig.ParameterTypeAt(i); | |
| if (!type.IsCanonical()) { | |
| type = type.Canonicalize(thread); | |
| new_sig.SetParameterTypeAt(i, type); | |
| } | |
| } | |
| // Check to see if the function type got added to canonical table | |
| // during canonicalization of its signature types. | |
| SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex()); | |
| CanonicalFunctionTypeSet table(zone, | |
| object_store->canonical_function_types()); | |
| sig ^= table.GetOrNull(CanonicalFunctionTypeKey(new_sig)); | |
| if (sig.IsNull()) { | |
| // Add this function type into the canonical table of function types. | |
| sig = new_sig.ptr(); | |
| ASSERT(sig.IsOld()); | |
| sig.SetCanonical(); // Mark object as being canonical. | |
| bool present = table.Insert(sig); | |
| ASSERT(!present); | |
| } | |
| object_store->set_canonical_function_types(table.Release()); | |
| } | |
| return sig.ptr(); | |
| } | |
| void FunctionType::EnumerateURIs(URIs* uris) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| AbstractType& type = AbstractType::Handle(zone); | |
| const intptr_t num_params = NumParameters(); | |
| for (intptr_t i = 0; i < num_params; i++) { | |
| type = ParameterTypeAt(i); | |
| type.EnumerateURIs(uris); | |
| } | |
| // Handle result type last, since it appears last in the user visible name. | |
| type = result_type(); | |
| type.EnumerateURIs(uris); | |
| } | |
| void FunctionType::PrintName(NameVisibility name_visibility, | |
| BaseTextBuffer* printer) const { | |
| const char* suffix = NullabilitySuffix(name_visibility); | |
| if (suffix[0] != '\0') { | |
| printer->AddString("("); | |
| } | |
| FunctionType::Cast(*this).Print(name_visibility, printer); | |
| if (suffix[0] != '\0') { | |
| printer->AddString(")"); | |
| printer->AddString(suffix); | |
| } | |
| } | |
| TypeParameterPtr TypeParameter::ToNullability(Nullability value, | |
| Heap::Space space) const { | |
| if (nullability() == value) { | |
| return ptr(); | |
| } | |
| // Clone type parameter and set new nullability. | |
| TypeParameter& type_parameter = TypeParameter::Handle(); | |
| type_parameter ^= Object::Clone(*this, space); | |
| type_parameter.set_nullability(value); | |
| type_parameter.SetHash(0); | |
| type_parameter.InitializeTypeTestingStubNonAtomic(Code::Handle( | |
| TypeTestingStubGenerator::DefaultCodeForType(type_parameter))); | |
| if (IsCanonical()) { | |
| // Object::Clone does not clone canonical bit. | |
| ASSERT(!type_parameter.IsCanonical()); | |
| ASSERT(IsFinalized()); | |
| ASSERT(type_parameter.IsFinalized()); | |
| type_parameter ^= type_parameter.Canonicalize(Thread::Current()); | |
| } | |
| return type_parameter.ptr(); | |
| } | |
| bool TypeParameter::IsInstantiated(Genericity genericity, | |
| intptr_t num_free_fun_type_params) const { | |
| // Bounds of class type parameters are ignored in the VM. | |
| if (IsClassTypeParameter()) { | |
| return genericity == kFunctions; | |
| } | |
| ASSERT(IsFunctionTypeParameter()); | |
| return (genericity == kCurrentClass) || (index() >= num_free_fun_type_params); | |
| } | |
| bool TypeParameter::IsEquivalent( | |
| const Instance& other, | |
| TypeEquality kind, | |
| FunctionTypeMapping* function_type_equivalence) const { | |
| TRACE_TYPE_CHECKS_VERBOSE(" TypeParameter::IsEquivalent(%s, %s, kind %d)\n", | |
| ToCString(), other.ToCString(), | |
| static_cast<int>(kind)); | |
| if (ptr() == other.ptr()) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: true (same types)\n"); | |
| return true; | |
| } | |
| if (!other.IsTypeParameter()) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (other is not a type parameter)\n"); | |
| return false; | |
| } | |
| const TypeParameter& other_type_param = TypeParameter::Cast(other); | |
| ASSERT(IsFinalized() && other_type_param.IsFinalized()); | |
| // Compare index, base and owner. | |
| if (IsFunctionTypeParameter()) { | |
| if (!other_type_param.IsFunctionTypeParameter()) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (other is not a function type parameter)\n"); | |
| return false; | |
| } | |
| if ((parameterized_function_type() != | |
| other_type_param.parameterized_function_type()) && | |
| ((function_type_equivalence == nullptr) || | |
| !function_type_equivalence->ContainsOwnersOfTypeParameters( | |
| *this, other_type_param))) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (owners are not equivalent)\n"); | |
| return false; | |
| } | |
| } else { | |
| if (!other_type_param.IsClassTypeParameter()) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (other is not a class type parameter)\n"); | |
| return false; | |
| } | |
| if (parameterized_class_id() != other_type_param.parameterized_class_id()) { | |
| TRACE_TYPE_CHECKS_VERBOSE( | |
| " - result: false (parameterized class id)\n"); | |
| return false; | |
| } | |
| } | |
| if (base() != other_type_param.base() || | |
| index() != other_type_param.index()) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: false (mismatch base/index)\n"); | |
| return false; | |
| } | |
| if (!IsNullabilityEquivalent(Thread::Current(), other_type_param, kind)) { | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: false (mismatch nullability)\n"); | |
| return false; | |
| } | |
| TRACE_TYPE_CHECKS_VERBOSE(" - result: true\n"); | |
| return true; | |
| } | |
| void TypeParameter::set_owner(const Object& value) const { | |
| ASSERT((IsFunctionTypeParameter() && value.IsFunctionType()) || | |
| (IsClassTypeParameter() && value.IsSmi())); | |
| untag()->set_owner(value.ptr()); | |
| } | |
| classid_t TypeParameter::parameterized_class_id() const { | |
| if (IsClassTypeParameter()) { | |
| return Smi::Value(Smi::RawCast(untag()->owner())); | |
| } else { | |
| return kFunctionCid; | |
| } | |
| } | |
| void TypeParameter::set_parameterized_class_id(classid_t value) const { | |
| ASSERT(IsClassTypeParameter()); | |
| untag()->set_owner(Smi::New(value)); | |
| } | |
| ClassPtr TypeParameter::parameterized_class() const { | |
| if (IsClassTypeParameter()) { | |
| const classid_t cid = parameterized_class_id(); | |
| if (cid != kIllegalCid) { | |
| return IsolateGroup::Current()->class_table()->At(cid); | |
| } | |
| } | |
| return Class::null(); | |
| } | |
| FunctionTypePtr TypeParameter::parameterized_function_type() const { | |
| ASSERT(IsFunctionTypeParameter()); | |
| return FunctionType::RawCast(untag()->owner()); | |
| } | |
| void TypeParameter::set_base(intptr_t value) const { | |
| ASSERT(value >= 0); | |
| ASSERT(Utils::IsUint(16, value)); | |
| StoreNonPointer(&untag()->base_, value); | |
| } | |
| void TypeParameter::set_index(intptr_t value) const { | |
| ASSERT(value >= 0); | |
| ASSERT(Utils::IsUint(16, value)); | |
| StoreNonPointer(&untag()->index_, value); | |
| } | |
| AbstractTypePtr TypeParameter::bound() const { | |
| if (IsFunctionTypeParameter()) { | |
| const auto& owner = FunctionType::Handle(parameterized_function_type()); | |
| const auto& type_parameters = | |
| TypeParameters::Handle(owner.type_parameters()); | |
| return type_parameters.BoundAt(index() - base()); | |
| } else { | |
| const auto& owner = Class::Handle(parameterized_class()); | |
| if (owner.IsNull()) { | |
| return IsolateGroup::Current()->object_store()->nullable_object_type(); | |
| } | |
| const auto& type_parameters = | |
| TypeParameters::Handle(owner.type_parameters()); | |
| return type_parameters.BoundAt(index() - base()); | |
| } | |
| } | |
| AbstractTypePtr TypeParameter::GetFromTypeArguments( | |
| const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments) const { | |
| ASSERT(IsFinalized()); | |
| const TypeArguments& type_args = IsFunctionTypeParameter() | |
| ? function_type_arguments | |
| : instantiator_type_arguments; | |
| return type_args.TypeAtNullSafe(index()); | |
| } | |
| AbstractTypePtr TypeParameter::InstantiateFrom( | |
| const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments, | |
| intptr_t num_free_fun_type_params, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_mapping, | |
| intptr_t num_parent_type_args_adjustment) const { | |
| Zone* zone = Thread::Current()->zone(); | |
| AbstractType& result = AbstractType::Handle(zone); | |
| bool substituted = false; | |
| if (IsFunctionTypeParameter()) { | |
| ASSERT(IsFinalized()); | |
| if (index() >= num_free_fun_type_params) { | |
| // Do not instantiate the function type parameter. | |
| // Get a replacement from the updated function type. | |
| ASSERT(function_type_mapping != nullptr); | |
| result = function_type_mapping->MapTypeParameter(*this); | |
| ASSERT(TypeParameter::Cast(result).index() == | |
| index() - num_free_fun_type_params); | |
| ASSERT(TypeParameter::Cast(result).base() == | |
| base() - num_free_fun_type_params); | |
| ASSERT(TypeParameter::Cast(result).nullability() == nullability()); | |
| AbstractType& upper_bound = AbstractType::Handle(zone, bound()); | |
| if (!upper_bound.IsInstantiated()) { | |
| upper_bound = upper_bound.InstantiateFrom( | |
| instantiator_type_arguments, function_type_arguments, | |
| num_free_fun_type_params, space, function_type_mapping, | |
| num_parent_type_args_adjustment); | |
| } | |
| if (upper_bound.ptr() == Type::NeverType()) { | |
| // Normalize 'X extends Never' to 'Never'. | |
| result = Type::NeverType(); | |
| } | |
| } else if (function_type_arguments.IsNull()) { | |
| return Type::DynamicType(); | |
| } else { | |
| result = function_type_arguments.TypeAt(index()); | |
| substituted = true; | |
| } | |
| } else { | |
| ASSERT(IsClassTypeParameter()); | |
| ASSERT(IsFinalized()); | |
| if (instantiator_type_arguments.IsNull()) { | |
| return Type::DynamicType(); | |
| } | |
| if (instantiator_type_arguments.Length() <= index()) { | |
| // InstantiateFrom can be invoked from a compilation pipeline with | |
| // mismatching type arguments vector. This can only happen for | |
| // a dynamically unreachable code - which compiler can't remove | |
| // statically for some reason. | |
| // To prevent crashes we return AbstractType::null(), understood by caller | |
| // (see AssertAssignableInstr::Canonicalize). | |
| return AbstractType::null(); | |
| } | |
| result = instantiator_type_arguments.TypeAt(index()); | |
| substituted = true; | |
| // Instantiating a class type parameter cannot result in a | |
| // function type parameter. | |
| // Bounds of class type parameters are ignored in the VM. | |
| } | |
| result = result.SetInstantiatedNullability(*this, space); | |
| if (substituted && (num_parent_type_args_adjustment != 0)) { | |
| // This type parameter is used inside a generic function type. | |
| // A type being substituted can have nested function types, | |
| // whose number of parent function type arguments should be adjusted | |
| // after the substitution. | |
| result = result.UpdateFunctionTypes(num_parent_type_args_adjustment, | |
| kAllFree, space, function_type_mapping); | |
| } | |
| // Canonicalization is not part of instantiation. | |
| return result.NormalizeFutureOrType(space); | |
| } | |
| AbstractTypePtr TypeParameter::UpdateFunctionTypes( | |
| intptr_t num_parent_type_args_adjustment, | |
| intptr_t num_free_fun_type_params, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_mapping) const { | |
| ASSERT(IsFinalized()); | |
| ASSERT(num_parent_type_args_adjustment >= 0); | |
| if (IsFunctionTypeParameter() && (index() >= num_free_fun_type_params)) { | |
| Zone* zone = Thread::Current()->zone(); | |
| ASSERT(function_type_mapping != nullptr); | |
| const auto& new_tp = TypeParameter::Handle( | |
| zone, function_type_mapping->MapTypeParameter(*this)); | |
| ASSERT(new_tp.base() == base() + num_parent_type_args_adjustment); | |
| ASSERT(new_tp.index() == index() + num_parent_type_args_adjustment); | |
| ASSERT(new_tp.nullability() == nullability()); | |
| ASSERT(new_tp.IsFinalized()); | |
| return new_tp.ptr(); | |
| } else { | |
| return ptr(); | |
| } | |
| } | |
| AbstractTypePtr TypeParameter::Canonicalize(Thread* thread) const { | |
| ASSERT(IsFinalized()); | |
| Zone* zone = thread->zone(); | |
| if (IsCanonical()) { | |
| #ifdef DEBUG | |
| if (IsFunctionTypeParameter()) { | |
| ASSERT(FunctionType::Handle(zone, parameterized_function_type()).IsOld()); | |
| } | |
| #endif | |
| return this->ptr(); | |
| } | |
| auto isolate_group = thread->isolate_group(); | |
| ObjectStore* object_store = isolate_group->object_store(); | |
| TypeParameter& type_parameter = TypeParameter::Handle(zone); | |
| { | |
| SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex()); | |
| CanonicalTypeParameterSet table(zone, | |
| object_store->canonical_type_parameters()); | |
| type_parameter ^= table.GetOrNull(CanonicalTypeParameterKey(*this)); | |
| if (type_parameter.IsNull()) { | |
| // Add this type parameter into the canonical table of type parameters. | |
| if (this->IsNew()) { | |
| type_parameter ^= Object::Clone(*this, Heap::kOld); | |
| } else { | |
| type_parameter = this->ptr(); | |
| } | |
| ASSERT(type_parameter.IsOld()); | |
| type_parameter.SetCanonical(); // Mark object as being canonical. | |
| bool present = table.Insert(type_parameter); | |
| ASSERT(!present); | |
| } | |
| object_store->set_canonical_type_parameters(table.Release()); | |
| } | |
| return type_parameter.ptr(); | |
| } | |
| void TypeParameter::PrintName(NameVisibility name_visibility, | |
| BaseTextBuffer* printer) const { | |
| const TypeParameter& type_param = TypeParameter::Cast(*this); | |
| // Type parameter names are meaningless after canonicalization. | |
| printer->AddString(type_param.CanonicalNameCString()); | |
| printer->AddString(NullabilitySuffix(name_visibility)); | |
| } | |
| uword TypeParameter::ComputeHash() const { | |
| ASSERT(IsFinalized()); | |
| uint32_t result = base(); | |
| result = CombineHashes(result, index()); | |
| result = CombineHashes(result, static_cast<uint32_t>(nullability())); | |
| if (IsFunctionTypeParameter()) { | |
| const FunctionType& func = | |
| FunctionType::Handle(parameterized_function_type()); | |
| result = CombineHashes(result, func.packed_parameter_counts()); | |
| result = CombineHashes(result, func.packed_type_parameter_counts()); | |
| } else { | |
| ASSERT(IsClassTypeParameter()); | |
| result = CombineHashes(result, parameterized_class_id()); | |
| } | |
| result = FinalizeHash(result, kHashBits); | |
| SetHash(result); | |
| return result; | |
| } | |
| TypeParameterPtr TypeParameter::New() { | |
| return Object::Allocate<TypeParameter>(Heap::kOld); | |
| } | |
| TypeParameterPtr TypeParameter::New(const Object& owner, | |
| intptr_t base, | |
| intptr_t index, | |
| Nullability nullability) { | |
| ASSERT(owner.IsNull() || owner.IsClass() || owner.IsFunctionType()); | |
| const bool is_function_type_parameter = owner.IsFunctionType(); | |
| const uint32_t flags = UntaggedTypeParameter::IsFunctionTypeParameter::encode( | |
| is_function_type_parameter); | |
| Zone* Z = Thread::Current()->zone(); | |
| const TypeParameter& result = TypeParameter::Handle(Z, TypeParameter::New()); | |
| result.set_flags(flags); | |
| if (is_function_type_parameter) { | |
| result.set_owner(owner); | |
| } else { | |
| result.set_parameterized_class_id(owner.IsNull() ? kIllegalCid | |
| : Class::Cast(owner).id()); | |
| } | |
| result.set_base(base); | |
| result.set_index(index); | |
| result.SetHash(0); | |
| result.set_nullability(nullability); | |
| result.set_type_state(UntaggedAbstractType::kAllocated); | |
| result.InitializeTypeTestingStubNonAtomic( | |
| Code::Handle(Z, TypeTestingStubGenerator::DefaultCodeForType(result))); | |
| return result.ptr(); | |
| } | |
| const char* TypeParameter::CanonicalNameCString(bool is_class_type_parameter, | |
| intptr_t base, | |
| intptr_t index) { | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| const char* base_fmt = is_class_type_parameter ? "C%" Pd : "F%" Pd; | |
| const char* index_fmt = is_class_type_parameter ? "X%" Pd : "Y%" Pd; | |
| if (base != 0) { | |
| printer.Printf(base_fmt, base); | |
| } | |
| printer.Printf(index_fmt, index - base); | |
| return printer.buffer(); | |
| } | |
| const char* TypeParameter::ToCString() const { | |
| if (IsNull()) { | |
| return "TypeParameter: null"; | |
| } | |
| Thread* thread = Thread::Current(); | |
| ZoneTextBuffer printer(thread->zone()); | |
| printer.Printf("TypeParameter: "); | |
| printer.AddString(CanonicalNameCString()); | |
| printer.AddString(NullabilitySuffix(kInternalName)); | |
| return printer.buffer(); | |
| } | |
| const char* Number::ToCString() const { | |
| // Number is an interface. No instances of Number should exist. | |
| UNREACHABLE(); | |
| return "Number"; | |
| } | |
| const char* Integer::ToCString() const { | |
| // Integer is an interface. No instances of Integer should exist except null. | |
| ASSERT(IsNull()); | |
| return "nullptr Integer"; | |
| } | |
| IntegerPtr Integer::New(const String& str, Heap::Space space) { | |
| // We are not supposed to have integers represented as two byte strings. | |
| ASSERT(str.IsOneByteString()); | |
| if (str.IsNull() || (str.Length() == 0)) { | |
| return Integer::null(); | |
| } | |
| int64_t value = 0; | |
| const char* cstr = str.ToCString(); | |
| if (!OS::StringToInt64(cstr, &value)) { | |
| // Out of range. | |
| return Integer::null(); | |
| } | |
| return Integer::New(value, space); | |
| } | |
| IntegerPtr Integer::NewCanonical(const String& str) { | |
| // We are not supposed to have integers represented as two byte strings. | |
| ASSERT(str.IsOneByteString()); | |
| int64_t value = 0; | |
| const char* cstr = str.ToCString(); | |
| if (!OS::StringToInt64(cstr, &value)) { | |
| // Out of range. | |
| return Integer::null(); | |
| } | |
| return NewCanonical(value); | |
| } | |
| IntegerPtr Integer::NewCanonical(int64_t value) { | |
| if (Smi::IsValid(value)) { | |
| return Smi::New(static_cast<intptr_t>(value)); | |
| } | |
| return Mint::NewCanonical(value); | |
| } | |
| IntegerPtr Integer::New(int64_t value, Heap::Space space) { | |
| const bool is_smi = Smi::IsValid(value); | |
| if (is_smi) { | |
| return Smi::New(static_cast<intptr_t>(value)); | |
| } | |
| return Mint::New(value, space); | |
| } | |
| IntegerPtr Integer::NewFromUint64(uint64_t value, Heap::Space space) { | |
| return Integer::New(static_cast<int64_t>(value), space); | |
| } | |
| bool Integer::IsValueInRange(uint64_t value) { | |
| return (value <= static_cast<uint64_t>(Mint::kMaxValue)); | |
| } | |
| bool Integer::Equals(const Instance& other) const { | |
| // Integer is an abstract class. | |
| UNREACHABLE(); | |
| return false; | |
| } | |
| int Integer::CompareWith(const Integer& other) const { | |
| int64_t a = Value(); | |
| int64_t b = other.Value(); | |
| if (a < b) { | |
| return -1; | |
| } else if (a > b) { | |
| return 1; | |
| } else { | |
| return 0; | |
| } | |
| } | |
| uint32_t Integer::CanonicalizeHash() const { | |
| return Multiply64Hash(Value()); | |
| } | |
| const char* Integer::ToHexCString(Zone* zone) const { | |
| ASSERT(IsSmi() || IsMint()); | |
| int64_t value = Value(); | |
| if (value < 0) { | |
| return OS::SCreate(zone, "-0x%" PX64, -static_cast<uint64_t>(value)); | |
| } else { | |
| return OS::SCreate(zone, "0x%" PX64, static_cast<uint64_t>(value)); | |
| } | |
| } | |
| IntegerPtr Integer::ArithmeticOp(Token::Kind operation, | |
| const Integer& other, | |
| Heap::Space space) const { | |
| // In 32-bit mode, the result of any operation between two Smis will fit in a | |
| // 32-bit signed result, except the product of two Smis, which will be 64-bit. | |
| // In 64-bit mode, the result of any operation between two Smis will fit in a | |
| // 64-bit signed result, except the product of two Smis (see below). | |
| if (IsSmi() && other.IsSmi()) { | |
| const intptr_t left_value = Smi::Value(Smi::RawCast(ptr())); | |
| const intptr_t right_value = Smi::Value(Smi::RawCast(other.ptr())); | |
| switch (operation) { | |
| case Token::kADD: | |
| return Integer::New(left_value + right_value, space); | |
| case Token::kSUB: | |
| return Integer::New(left_value - right_value, space); | |
| case Token::kMUL: | |
| return Integer::New( | |
| Utils::MulWithWrapAround(static_cast<int64_t>(left_value), | |
| static_cast<int64_t>(right_value)), | |
| space); | |
| case Token::kTRUNCDIV: | |
| return Integer::New(left_value / right_value, space); | |
| case Token::kMOD: { | |
| const intptr_t remainder = left_value % right_value; | |
| if (remainder < 0) { | |
| if (right_value < 0) { | |
| return Integer::New(remainder - right_value, space); | |
| } else { | |
| return Integer::New(remainder + right_value, space); | |
| } | |
| } | |
| return Integer::New(remainder, space); | |
| } | |
| default: | |
| UNIMPLEMENTED(); | |
| } | |
| } | |
| const int64_t left_value = Value(); | |
| const int64_t right_value = other.Value(); | |
| switch (operation) { | |
| case Token::kADD: | |
| return Integer::New(Utils::AddWithWrapAround(left_value, right_value), | |
| space); | |
| case Token::kSUB: | |
| return Integer::New(Utils::SubWithWrapAround(left_value, right_value), | |
| space); | |
| case Token::kMUL: | |
| return Integer::New(Utils::MulWithWrapAround(left_value, right_value), | |
| space); | |
| case Token::kTRUNCDIV: | |
| if ((left_value == Mint::kMinValue) && (right_value == -1)) { | |
| // Division special case: overflow in int64_t. | |
| // MIN_VALUE / -1 = (MAX_VALUE + 1), which wraps around to MIN_VALUE | |
| return Integer::New(Mint::kMinValue, space); | |
| } | |
| return Integer::New(left_value / right_value, space); | |
| case Token::kMOD: { | |
| if ((left_value == Mint::kMinValue) && (right_value == -1)) { | |
| // Modulo special case: overflow in int64_t. | |
| // MIN_VALUE % -1 = 0 for reason given above. | |
| return Integer::New(0, space); | |
| } | |
| const int64_t remainder = left_value % right_value; | |
| if (remainder < 0) { | |
| if (right_value < 0) { | |
| return Integer::New(remainder - right_value, space); | |
| } else { | |
| return Integer::New(remainder + right_value, space); | |
| } | |
| } | |
| return Integer::New(remainder, space); | |
| } | |
| default: | |
| UNIMPLEMENTED(); | |
| return Integer::null(); | |
| } | |
| } | |
| IntegerPtr Integer::BitOp(Token::Kind kind, | |
| const Integer& other, | |
| Heap::Space space) const { | |
| if (IsSmi() && other.IsSmi()) { | |
| intptr_t op1_value = Smi::Value(Smi::RawCast(ptr())); | |
| intptr_t op2_value = Smi::Value(Smi::RawCast(other.ptr())); | |
| intptr_t result = 0; | |
| switch (kind) { | |
| case Token::kBIT_AND: | |
| result = op1_value & op2_value; | |
| break; | |
| case Token::kBIT_OR: | |
| result = op1_value | op2_value; | |
| break; | |
| case Token::kBIT_XOR: | |
| result = op1_value ^ op2_value; | |
| break; | |
| default: | |
| UNIMPLEMENTED(); | |
| } | |
| ASSERT(Smi::IsValid(result)); | |
| return Smi::New(result); | |
| } else { | |
| int64_t a = Value(); | |
| int64_t b = other.Value(); | |
| switch (kind) { | |
| case Token::kBIT_AND: | |
| return Integer::New(a & b, space); | |
| case Token::kBIT_OR: | |
| return Integer::New(a | b, space); | |
| case Token::kBIT_XOR: | |
| return Integer::New(a ^ b, space); | |
| default: | |
| UNIMPLEMENTED(); | |
| return Integer::null(); | |
| } | |
| } | |
| } | |
| IntegerPtr Integer::ShiftOp(Token::Kind kind, | |
| const Integer& other, | |
| Heap::Space space) const { | |
| int64_t a = Value(); | |
| int64_t b = other.Value(); | |
| ASSERT(b >= 0); | |
| switch (kind) { | |
| case Token::kSHL: | |
| return Integer::New(Utils::ShiftLeftWithTruncation(a, b), space); | |
| case Token::kSHR: | |
| return Integer::New(a >> Utils::Minimum<int64_t>(b, Mint::kBits), space); | |
| case Token::kUSHR: | |
| return Integer::New( | |
| (b >= kBitsPerInt64) ? 0 : static_cast<uint64_t>(a) >> b, space); | |
| default: | |
| UNIMPLEMENTED(); | |
| return Integer::null(); | |
| } | |
| } | |
| bool Smi::Equals(const Instance& other) const { | |
| if (other.IsNull() || !other.IsSmi()) { | |
| return false; | |
| } | |
| return (this->Value() == Smi::Cast(other).Value()); | |
| } | |
| const char* Smi::ToCString() const { | |
| return OS::SCreate(Thread::Current()->zone(), "%" Pd "", Value()); | |
| } | |
| ClassPtr Smi::Class() { | |
| return IsolateGroup::Current()->object_store()->smi_class(); | |
| } | |
| void Mint::set_value(int64_t value) const { | |
| StoreNonPointer(&untag()->value_, value); | |
| } | |
| MintPtr Mint::New(int64_t val, Heap::Space space) { | |
| // Do not allocate a Mint if Smi would do. | |
| ASSERT(!Smi::IsValid(val)); | |
| ASSERT(IsolateGroup::Current()->object_store()->mint_class() != | |
| Class::null()); | |
| const auto& result = Mint::Handle(Object::Allocate<Mint>(space)); | |
| result.set_value(val); | |
| return result.ptr(); | |
| } | |
| MintPtr Mint::NewCanonical(int64_t value) { | |
| Thread* thread = Thread::Current(); | |
| Mint& mint = Mint::Handle(thread->zone(), Mint::New(value, Heap::kOld)); | |
| mint ^= mint.Canonicalize(thread); | |
| return mint.ptr(); | |
| } | |
| bool Mint::Equals(const Instance& other) const { | |
| if (this->ptr() == other.ptr()) { | |
| // Both handles point to the same raw instance. | |
| return true; | |
| } | |
| if (!other.IsMint() || other.IsNull()) { | |
| return false; | |
| } | |
| return Value() == Mint::Cast(other).Value(); | |
| } | |
| const char* Mint::ToCString() const { | |
| return OS::SCreate(Thread::Current()->zone(), "%" Pd64 "", Value()); | |
| } | |
| void Double::set_value(double value) const { | |
| StoreNonPointer(&untag()->value_, value); | |
| } | |
| bool Double::BitwiseEqualsToDouble(double value) const { | |
| intptr_t value_offset = Double::value_offset(); | |
| void* this_addr = reinterpret_cast<void*>( | |
| reinterpret_cast<uword>(this->untag()) + value_offset); | |
| void* other_addr = reinterpret_cast<void*>(&value); | |
| return (memcmp(this_addr, other_addr, sizeof(value)) == 0); | |
| } | |
| bool Double::OperatorEquals(const Instance& other) const { | |
| if (this->IsNull() || other.IsNull()) { | |
| return (this->IsNull() && other.IsNull()); | |
| } | |
| if (!other.IsDouble()) { | |
| return false; | |
| } | |
| return this->value() == Double::Cast(other).value(); | |
| } | |
| bool Double::CanonicalizeEquals(const Instance& other) const { | |
| if (this->ptr() == other.ptr()) { | |
| return true; // "===". | |
| } | |
| if (other.IsNull() || !other.IsDouble()) { | |
| return false; | |
| } | |
| return BitwiseEqualsToDouble(Double::Cast(other).value()); | |
| } | |
| uint32_t Double::CanonicalizeHash() const { | |
| return Hash64To32(bit_cast<uint64_t>(value())); | |
| } | |
| DoublePtr Double::New(double d, Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->double_class() != | |
| Class::null()); | |
| const auto& result = Double::Handle(Object::Allocate<Double>(space)); | |
| result.set_value(d); | |
| return result.ptr(); | |
| } | |
| DoublePtr Double::New(const String& str, Heap::Space space) { | |
| double double_value; | |
| if (!CStringToDouble(str.ToCString(), str.Length(), &double_value)) { | |
| return Double::Handle().ptr(); | |
| } | |
| return New(double_value, space); | |
| } | |
| DoublePtr Double::NewCanonical(double value) { | |
| Thread* thread = Thread::Current(); | |
| Double& dbl = Double::Handle(thread->zone(), Double::New(value, Heap::kOld)); | |
| dbl ^= dbl.Canonicalize(thread); | |
| return dbl.ptr(); | |
| } | |
| DoublePtr Double::NewCanonical(const String& str) { | |
| double double_value; | |
| if (!CStringToDouble(str.ToCString(), str.Length(), &double_value)) { | |
| return Double::Handle().ptr(); | |
| } | |
| return NewCanonical(double_value); | |
| } | |
| StringPtr Number::ToString(Heap::Space space) const { | |
| // Refactoring can avoid Zone::Alloc and strlen, but gains are insignificant. | |
| const char* cstr = ToCString(); | |
| intptr_t len = strlen(cstr); | |
| // Resulting string is ASCII ... | |
| #ifdef DEBUG | |
| for (intptr_t i = 0; i < len; ++i) { | |
| ASSERT(static_cast<uint8_t>(cstr[i]) < 128); | |
| } | |
| #endif // DEBUG | |
| // ... which is a subset of Latin-1. | |
| return String::FromLatin1(reinterpret_cast<const uint8_t*>(cstr), len, space); | |
| } | |
| const char* Double::ToCString() const { | |
| if (isnan(value())) { | |
| return "NaN"; | |
| } | |
| if (isinf(value())) { | |
| return value() < 0 ? "-Infinity" : "Infinity"; | |
| } | |
| const int kBufferSize = 128; | |
| char* buffer = Thread::Current()->zone()->Alloc<char>(kBufferSize); | |
| buffer[kBufferSize - 1] = '\0'; | |
| DoubleToCString(value(), buffer, kBufferSize); | |
| return buffer; | |
| } | |
| void StringHasher::Add(const String& str, intptr_t begin_index, intptr_t len) { | |
| ASSERT(begin_index >= 0); | |
| ASSERT(len >= 0); | |
| ASSERT((begin_index + len) <= str.Length()); | |
| if (len == 0) { | |
| return; | |
| } | |
| if (str.IsOneByteString()) { | |
| NoSafepointScope no_safepoint; | |
| Add(OneByteString::CharAddr(str, begin_index), len); | |
| } else if (str.IsTwoByteString()) { | |
| NoSafepointScope no_safepoint; | |
| Add(TwoByteString::CharAddr(str, begin_index), len); | |
| } else { | |
| UNREACHABLE(); | |
| } | |
| } | |
| uword String::Hash(const String& str, intptr_t begin_index, intptr_t len) { | |
| StringHasher hasher; | |
| hasher.Add(str, begin_index, len); | |
| return hasher.Finalize(); | |
| } | |
| uword String::HashConcat(const String& str1, const String& str2) { | |
| StringHasher hasher; | |
| hasher.Add(str1, 0, str1.Length()); | |
| hasher.Add(str2, 0, str2.Length()); | |
| return hasher.Finalize(); | |
| } | |
| uword String::Hash(StringPtr raw) { | |
| StringHasher hasher; | |
| uword length = Smi::Value(raw->untag()->length()); | |
| if (raw->IsOneByteString()) { | |
| const uint8_t* data = static_cast<OneByteStringPtr>(raw)->untag()->data(); | |
| return String::Hash(data, length); | |
| } else { | |
| const uint16_t* data = static_cast<TwoByteStringPtr>(raw)->untag()->data(); | |
| return String::Hash(data, length); | |
| } | |
| } | |
| uword String::Hash(const char* characters, intptr_t len) { | |
| StringHasher hasher; | |
| hasher.Add(reinterpret_cast<const uint8_t*>(characters), len); | |
| return hasher.Finalize(); | |
| } | |
| uword String::Hash(const uint8_t* characters, intptr_t len) { | |
| StringHasher hasher; | |
| hasher.Add(characters, len); | |
| return hasher.Finalize(); | |
| } | |
| uword String::Hash(const uint16_t* characters, intptr_t len) { | |
| StringHasher hasher; | |
| hasher.Add(characters, len); | |
| return hasher.Finalize(); | |
| } | |
| intptr_t String::CharSize() const { | |
| intptr_t class_id = ptr()->GetClassIdOfHeapObject(); | |
| if (class_id == kOneByteStringCid) { | |
| return kOneByteChar; | |
| } | |
| ASSERT(class_id == kTwoByteStringCid); | |
| return kTwoByteChar; | |
| } | |
| bool String::Equals(const Instance& other) const { | |
| if (this->ptr() == other.ptr()) { | |
| // Both handles point to the same raw instance. | |
| return true; | |
| } | |
| if (!other.IsString()) { | |
| return false; | |
| } | |
| const String& other_string = String::Cast(other); | |
| return Equals(other_string); | |
| } | |
| bool String::Equals(const String& str, | |
| intptr_t begin_index, | |
| intptr_t len) const { | |
| ASSERT(begin_index >= 0); | |
| ASSERT((begin_index == 0) || (begin_index < str.Length())); | |
| ASSERT(len >= 0); | |
| ASSERT(len <= str.Length()); | |
| if (len != this->Length()) { | |
| return false; // Lengths don't match. | |
| } | |
| for (intptr_t i = 0; i < len; i++) { | |
| if (CharAt(i) != str.CharAt(begin_index + i)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool String::Equals(const char* cstr) const { | |
| ASSERT(cstr != nullptr); | |
| CodePointIterator it(*this); | |
| intptr_t len = strlen(cstr); | |
| while (it.Next()) { | |
| if (*cstr == '\0') { | |
| // Lengths don't match. | |
| return false; | |
| } | |
| int32_t ch; | |
| intptr_t consumed = | |
| Utf8::Decode(reinterpret_cast<const uint8_t*>(cstr), len, &ch); | |
| if (consumed == 0 || it.Current() != ch) { | |
| return false; | |
| } | |
| cstr += consumed; | |
| len -= consumed; | |
| } | |
| return *cstr == '\0'; | |
| } | |
| bool String::Equals(const uint8_t* latin1_array, intptr_t len) const { | |
| if (len != this->Length()) { | |
| // Lengths don't match. | |
| return false; | |
| } | |
| for (intptr_t i = 0; i < len; i++) { | |
| if (this->CharAt(i) != latin1_array[i]) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool String::Equals(const uint16_t* utf16_array, intptr_t len) const { | |
| if (len != this->Length()) { | |
| // Lengths don't match. | |
| return false; | |
| } | |
| for (intptr_t i = 0; i < len; i++) { | |
| if (this->CharAt(i) != LoadUnaligned(&utf16_array[i])) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool String::Equals(const int32_t* utf32_array, intptr_t len) const { | |
| if (len < 0) return false; | |
| intptr_t j = 0; | |
| for (intptr_t i = 0; i < len; ++i) { | |
| if (Utf::IsSupplementary(utf32_array[i])) { | |
| uint16_t encoded[2]; | |
| Utf16::Encode(utf32_array[i], &encoded[0]); | |
| if (j + 1 >= Length()) return false; | |
| if (CharAt(j++) != encoded[0]) return false; | |
| if (CharAt(j++) != encoded[1]) return false; | |
| } else { | |
| if (j >= Length()) return false; | |
| if (CharAt(j++) != utf32_array[i]) return false; | |
| } | |
| } | |
| return j == Length(); | |
| } | |
| bool String::EqualsConcat(const String& str1, const String& str2) const { | |
| return (Length() == str1.Length() + str2.Length()) && | |
| str1.Equals(*this, 0, str1.Length()) && | |
| str2.Equals(*this, str1.Length(), str2.Length()); | |
| } | |
| intptr_t String::CompareTo(const String& other) const { | |
| const intptr_t this_len = this->Length(); | |
| const intptr_t other_len = other.IsNull() ? 0 : other.Length(); | |
| const intptr_t len = (this_len < other_len) ? this_len : other_len; | |
| for (intptr_t i = 0; i < len; i++) { | |
| uint16_t this_code_unit = this->CharAt(i); | |
| uint16_t other_code_unit = other.CharAt(i); | |
| if (this_code_unit < other_code_unit) { | |
| return -1; | |
| } | |
| if (this_code_unit > other_code_unit) { | |
| return 1; | |
| } | |
| } | |
| if (this_len < other_len) return -1; | |
| if (this_len > other_len) return 1; | |
| return 0; | |
| } | |
| bool String::StartsWith(StringPtr str, StringPtr prefix) { | |
| if (prefix == String::null()) return false; | |
| const intptr_t length = String::LengthOf(str); | |
| const intptr_t prefix_length = String::LengthOf(prefix); | |
| if (prefix_length > length) return false; | |
| for (intptr_t i = 0; i < prefix_length; i++) { | |
| if (String::CharAt(str, i) != String::CharAt(prefix, i)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool String::EndsWith(const String& other) const { | |
| if (other.IsNull()) { | |
| return false; | |
| } | |
| const intptr_t len = this->Length(); | |
| const intptr_t other_len = other.Length(); | |
| const intptr_t offset = len - other_len; | |
| if ((other_len == 0) || (other_len > len)) { | |
| return false; | |
| } | |
| for (int i = offset; i < len; i++) { | |
| if (this->CharAt(i) != other.CharAt(i - offset)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| InstancePtr String::CanonicalizeLocked(Thread* thread) const { | |
| if (IsCanonical()) { | |
| return this->ptr(); | |
| } | |
| return Symbols::New(Thread::Current(), *this); | |
| } | |
| StringPtr String::New(const char* cstr, Heap::Space space) { | |
| ASSERT(cstr != nullptr); | |
| intptr_t array_len = strlen(cstr); | |
| const uint8_t* utf8_array = reinterpret_cast<const uint8_t*>(cstr); | |
| return String::FromUTF8(utf8_array, array_len, space); | |
| } | |
| StringPtr String::FromUTF8(const uint8_t* utf8_array, | |
| intptr_t array_len, | |
| Heap::Space space) { | |
| Utf8::Type type; | |
| intptr_t len = Utf8::CodeUnitCount(utf8_array, array_len, &type); | |
| if (type == Utf8::kLatin1) { | |
| const String& strobj = String::Handle(OneByteString::New(len, space)); | |
| if (len > 0) { | |
| NoSafepointScope no_safepoint; | |
| if (!Utf8::DecodeToLatin1(utf8_array, array_len, | |
| OneByteString::DataStart(strobj), len)) { | |
| Utf8::ReportInvalidByte(utf8_array, array_len, len); | |
| return String::null(); | |
| } | |
| } | |
| return strobj.ptr(); | |
| } | |
| ASSERT((type == Utf8::kBMP) || (type == Utf8::kSupplementary)); | |
| const String& strobj = String::Handle(TwoByteString::New(len, space)); | |
| NoSafepointScope no_safepoint; | |
| if (!Utf8::DecodeToUTF16(utf8_array, array_len, | |
| TwoByteString::DataStart(strobj), len)) { | |
| Utf8::ReportInvalidByte(utf8_array, array_len, len); | |
| return String::null(); | |
| } | |
| return strobj.ptr(); | |
| } | |
| StringPtr String::FromLatin1(const uint8_t* latin1_array, | |
| intptr_t array_len, | |
| Heap::Space space) { | |
| return OneByteString::New(latin1_array, array_len, space); | |
| } | |
| StringPtr String::FromUTF16(const uint16_t* utf16_array, | |
| intptr_t array_len, | |
| Heap::Space space) { | |
| bool is_one_byte_string = true; | |
| for (intptr_t i = 0; i < array_len; ++i) { | |
| if (!Utf::IsLatin1(LoadUnaligned(&utf16_array[i]))) { | |
| is_one_byte_string = false; | |
| break; | |
| } | |
| } | |
| if (is_one_byte_string) { | |
| return OneByteString::New(utf16_array, array_len, space); | |
| } | |
| return TwoByteString::New(utf16_array, array_len, space); | |
| } | |
| StringPtr String::FromUTF32(const int32_t* utf32_array, | |
| intptr_t array_len, | |
| Heap::Space space) { | |
| bool is_one_byte_string = true; | |
| intptr_t utf16_len = array_len; | |
| for (intptr_t i = 0; i < array_len; ++i) { | |
| if (!Utf::IsLatin1(utf32_array[i])) { | |
| is_one_byte_string = false; | |
| if (Utf::IsSupplementary(utf32_array[i])) { | |
| utf16_len += 1; | |
| } | |
| } | |
| } | |
| if (is_one_byte_string) { | |
| return OneByteString::New(utf32_array, array_len, space); | |
| } | |
| return TwoByteString::New(utf16_len, utf32_array, array_len, space); | |
| } | |
| StringPtr String::New(const String& str, Heap::Space space) { | |
| // Currently this just creates a copy of the string in the correct space. | |
| // Once we have external string support, this will also create a heap copy of | |
| // the string if necessary. Some optimizations are possible, such as not | |
| // copying internal strings into the same space. | |
| intptr_t len = str.Length(); | |
| String& result = String::Handle(); | |
| intptr_t char_size = str.CharSize(); | |
| if (char_size == kOneByteChar) { | |
| result = OneByteString::New(len, space); | |
| } else { | |
| ASSERT(char_size == kTwoByteChar); | |
| result = TwoByteString::New(len, space); | |
| } | |
| String::Copy(result, 0, str, 0, len); | |
| return result.ptr(); | |
| } | |
| void String::Copy(const String& dst, | |
| intptr_t dst_offset, | |
| const uint8_t* characters, | |
| intptr_t len) { | |
| ASSERT(dst_offset >= 0); | |
| ASSERT(len >= 0); | |
| ASSERT(len <= (dst.Length() - dst_offset)); | |
| if (dst.IsOneByteString()) { | |
| NoSafepointScope no_safepoint; | |
| if (len > 0) { | |
| memmove(OneByteString::CharAddr(dst, dst_offset), characters, len); | |
| } | |
| } else if (dst.IsTwoByteString()) { | |
| for (intptr_t i = 0; i < len; ++i) { | |
| *TwoByteString::CharAddr(dst, i + dst_offset) = characters[i]; | |
| } | |
| } | |
| } | |
| void String::Copy(const String& dst, | |
| intptr_t dst_offset, | |
| const uint16_t* utf16_array, | |
| intptr_t array_len) { | |
| ASSERT(dst_offset >= 0); | |
| ASSERT(array_len >= 0); | |
| ASSERT(array_len <= (dst.Length() - dst_offset)); | |
| if (dst.IsOneByteString()) { | |
| NoSafepointScope no_safepoint; | |
| for (intptr_t i = 0; i < array_len; ++i) { | |
| ASSERT(Utf::IsLatin1(LoadUnaligned(&utf16_array[i]))); | |
| *OneByteString::CharAddr(dst, i + dst_offset) = utf16_array[i]; | |
| } | |
| } else { | |
| ASSERT(dst.IsTwoByteString()); | |
| NoSafepointScope no_safepoint; | |
| if (array_len > 0) { | |
| memmove(TwoByteString::CharAddr(dst, dst_offset), utf16_array, | |
| array_len * 2); | |
| } | |
| } | |
| } | |
| void String::Copy(const String& dst, | |
| intptr_t dst_offset, | |
| const String& src, | |
| intptr_t src_offset, | |
| intptr_t len) { | |
| ASSERT(dst_offset >= 0); | |
| ASSERT(src_offset >= 0); | |
| ASSERT(len >= 0); | |
| ASSERT(len <= (dst.Length() - dst_offset)); | |
| ASSERT(len <= (src.Length() - src_offset)); | |
| if (len > 0) { | |
| intptr_t char_size = src.CharSize(); | |
| if (char_size == kOneByteChar) { | |
| ASSERT(src.IsOneByteString()); | |
| NoSafepointScope no_safepoint; | |
| String::Copy(dst, dst_offset, OneByteString::CharAddr(src, src_offset), | |
| len); | |
| } else { | |
| ASSERT(char_size == kTwoByteChar); | |
| ASSERT(src.IsTwoByteString()); | |
| NoSafepointScope no_safepoint; | |
| String::Copy(dst, dst_offset, TwoByteString::CharAddr(src, src_offset), | |
| len); | |
| } | |
| } | |
| } | |
| StringPtr String::EscapeSpecialCharacters(const String& str) { | |
| if (str.IsOneByteString()) { | |
| return OneByteString::EscapeSpecialCharacters(str); | |
| } | |
| ASSERT(str.IsTwoByteString()); | |
| return TwoByteString::EscapeSpecialCharacters(str); | |
| } | |
| static bool IsPercent(int32_t c) { | |
| return c == '%'; | |
| } | |
| static bool IsHexCharacter(int32_t c) { | |
| if (c >= '0' && c <= '9') { | |
| return true; | |
| } | |
| if (c >= 'A' && c <= 'F') { | |
| return true; | |
| } | |
| return false; | |
| } | |
| static bool IsURISafeCharacter(int32_t c) { | |
| if ((c >= '0') && (c <= '9')) { | |
| return true; | |
| } | |
| if ((c >= 'a') && (c <= 'z')) { | |
| return true; | |
| } | |
| if ((c >= 'A') && (c <= 'Z')) { | |
| return true; | |
| } | |
| return (c == '-') || (c == '_') || (c == '.') || (c == '~'); | |
| } | |
| static int32_t GetHexCharacter(int32_t c) { | |
| ASSERT(c >= 0); | |
| ASSERT(c < 16); | |
| const char* hex = "0123456789ABCDEF"; | |
| return hex[c]; | |
| } | |
| static int32_t GetHexValue(int32_t c) { | |
| if (c >= '0' && c <= '9') { | |
| return c - '0'; | |
| } | |
| if (c >= 'A' && c <= 'F') { | |
| return c - 'A' + 10; | |
| } | |
| UNREACHABLE(); | |
| return 0; | |
| } | |
| static int32_t MergeHexCharacters(int32_t c1, int32_t c2) { | |
| return GetHexValue(c1) << 4 | GetHexValue(c2); | |
| } | |
| const char* String::EncodeIRI(const String& str) { | |
| const intptr_t len = Utf8::Length(str); | |
| Zone* zone = Thread::Current()->zone(); | |
| uint8_t* utf8 = zone->Alloc<uint8_t>(len); | |
| str.ToUTF8(utf8, len); | |
| intptr_t num_escapes = 0; | |
| for (int i = 0; i < len; ++i) { | |
| uint8_t byte = utf8[i]; | |
| if (!IsURISafeCharacter(byte)) { | |
| num_escapes += 2; | |
| } | |
| } | |
| intptr_t cstr_len = len + num_escapes + 1; | |
| char* cstr = zone->Alloc<char>(cstr_len); | |
| intptr_t index = 0; | |
| for (int i = 0; i < len; ++i) { | |
| uint8_t byte = utf8[i]; | |
| if (!IsURISafeCharacter(byte)) { | |
| cstr[index++] = '%'; | |
| cstr[index++] = GetHexCharacter(byte >> 4); | |
| cstr[index++] = GetHexCharacter(byte & 0xF); | |
| } else { | |
| ASSERT(byte <= 127); | |
| cstr[index++] = byte; | |
| } | |
| } | |
| cstr[index] = '\0'; | |
| return cstr; | |
| } | |
| StringPtr String::DecodeIRI(const String& str) { | |
| CodePointIterator cpi(str); | |
| intptr_t num_escapes = 0; | |
| intptr_t len = str.Length(); | |
| { | |
| CodePointIterator cpi(str); | |
| while (cpi.Next()) { | |
| int32_t code_point = cpi.Current(); | |
| if (IsPercent(code_point)) { | |
| // Verify that the two characters following the % are hex digits. | |
| if (!cpi.Next()) { | |
| return String::null(); | |
| } | |
| int32_t code_point = cpi.Current(); | |
| if (!IsHexCharacter(code_point)) { | |
| return String::null(); | |
| } | |
| if (!cpi.Next()) { | |
| return String::null(); | |
| } | |
| code_point = cpi.Current(); | |
| if (!IsHexCharacter(code_point)) { | |
| return String::null(); | |
| } | |
| num_escapes += 2; | |
| } | |
| } | |
| } | |
| intptr_t utf8_len = len - num_escapes; | |
| ASSERT(utf8_len >= 0); | |
| Zone* zone = Thread::Current()->zone(); | |
| uint8_t* utf8 = zone->Alloc<uint8_t>(utf8_len); | |
| { | |
| intptr_t index = 0; | |
| CodePointIterator cpi(str); | |
| while (cpi.Next()) { | |
| ASSERT(index < utf8_len); | |
| int32_t code_point = cpi.Current(); | |
| if (IsPercent(code_point)) { | |
| cpi.Next(); | |
| int32_t ch1 = cpi.Current(); | |
| cpi.Next(); | |
| int32_t ch2 = cpi.Current(); | |
| int32_t merged = MergeHexCharacters(ch1, ch2); | |
| ASSERT(merged >= 0 && merged < 256); | |
| utf8[index] = static_cast<uint8_t>(merged); | |
| } else { | |
| ASSERT(code_point >= 0 && code_point < 256); | |
| utf8[index] = static_cast<uint8_t>(code_point); | |
| } | |
| index++; | |
| } | |
| } | |
| return FromUTF8(utf8, utf8_len); | |
| } | |
| StringPtr String::NewFormatted(const char* format, ...) { | |
| va_list args; | |
| va_start(args, format); | |
| StringPtr result = NewFormattedV(format, args); | |
| NoSafepointScope no_safepoint; | |
| va_end(args); | |
| return result; | |
| } | |
| StringPtr String::NewFormatted(Heap::Space space, const char* format, ...) { | |
| va_list args; | |
| va_start(args, format); | |
| StringPtr result = NewFormattedV(format, args, space); | |
| NoSafepointScope no_safepoint; | |
| va_end(args); | |
| return result; | |
| } | |
| StringPtr String::NewFormattedV(const char* format, | |
| va_list args, | |
| Heap::Space space) { | |
| va_list args_copy; | |
| va_copy(args_copy, args); | |
| intptr_t len = Utils::VSNPrint(nullptr, 0, format, args_copy); | |
| va_end(args_copy); | |
| Zone* zone = Thread::Current()->zone(); | |
| char* buffer = zone->Alloc<char>(len + 1); | |
| Utils::VSNPrint(buffer, (len + 1), format, args); | |
| return String::New(buffer, space); | |
| } | |
| StringPtr String::Concat(const String& str1, | |
| const String& str2, | |
| Heap::Space space) { | |
| ASSERT(!str1.IsNull() && !str2.IsNull()); | |
| intptr_t char_size = Utils::Maximum(str1.CharSize(), str2.CharSize()); | |
| if (char_size == kTwoByteChar) { | |
| return TwoByteString::Concat(str1, str2, space); | |
| } | |
| return OneByteString::Concat(str1, str2, space); | |
| } | |
| StringPtr String::ConcatAll(const Array& strings, Heap::Space space) { | |
| return ConcatAllRange(strings, 0, strings.Length(), space); | |
| } | |
| StringPtr String::ConcatAllRange(const Array& strings, | |
| intptr_t start, | |
| intptr_t end, | |
| Heap::Space space) { | |
| ASSERT(!strings.IsNull()); | |
| ASSERT(start >= 0); | |
| ASSERT(end <= strings.Length()); | |
| intptr_t result_len = 0; | |
| String& str = String::Handle(); | |
| intptr_t char_size = kOneByteChar; | |
| // Compute 'char_size' and 'result_len'. | |
| for (intptr_t i = start; i < end; i++) { | |
| str ^= strings.At(i); | |
| const intptr_t str_len = str.Length(); | |
| if ((kMaxElements - result_len) < str_len) { | |
| Exceptions::ThrowOOM(); | |
| UNREACHABLE(); | |
| } | |
| result_len += str_len; | |
| char_size = Utils::Maximum(char_size, str.CharSize()); | |
| } | |
| if (char_size == kOneByteChar) { | |
| return OneByteString::ConcatAll(strings, start, end, result_len, space); | |
| } | |
| ASSERT(char_size == kTwoByteChar); | |
| return TwoByteString::ConcatAll(strings, start, end, result_len, space); | |
| } | |
| StringPtr String::SubString(const String& str, | |
| intptr_t begin_index, | |
| Heap::Space space) { | |
| ASSERT(!str.IsNull()); | |
| if (begin_index >= str.Length()) { | |
| return String::null(); | |
| } | |
| return String::SubString(str, begin_index, (str.Length() - begin_index), | |
| space); | |
| } | |
| StringPtr String::SubString(Thread* thread, | |
| const String& str, | |
| intptr_t begin_index, | |
| intptr_t length, | |
| Heap::Space space) { | |
| ASSERT(!str.IsNull()); | |
| ASSERT(begin_index >= 0); | |
| ASSERT(length >= 0); | |
| if (begin_index <= str.Length() && length == 0) { | |
| return Symbols::Empty().ptr(); | |
| } | |
| if (begin_index > str.Length()) { | |
| return String::null(); | |
| } | |
| bool is_one_byte_string = true; | |
| intptr_t char_size = str.CharSize(); | |
| if (char_size == kTwoByteChar) { | |
| for (intptr_t i = begin_index; i < begin_index + length; ++i) { | |
| if (!Utf::IsLatin1(str.CharAt(i))) { | |
| is_one_byte_string = false; | |
| break; | |
| } | |
| } | |
| } | |
| REUSABLE_STRING_HANDLESCOPE(thread); | |
| String& result = thread->StringHandle(); | |
| if (is_one_byte_string) { | |
| result = OneByteString::New(length, space); | |
| } else { | |
| result = TwoByteString::New(length, space); | |
| } | |
| String::Copy(result, 0, str, begin_index, length); | |
| return result.ptr(); | |
| } | |
| const char* String::ToCString() const { | |
| if (IsNull()) { | |
| return "String: null"; | |
| } | |
| const intptr_t len = Utf8::Length(*this); | |
| Zone* zone = Thread::Current()->zone(); | |
| uint8_t* result = zone->Alloc<uint8_t>(len + 1); | |
| ToUTF8(result, len); | |
| result[len] = 0; | |
| return reinterpret_cast<const char*>(result); | |
| } | |
| char* String::ToMallocCString() const { | |
| const intptr_t len = Utf8::Length(*this); | |
| uint8_t* result = reinterpret_cast<uint8_t*>(malloc(len + 1)); | |
| ToUTF8(result, len); | |
| result[len] = 0; | |
| return reinterpret_cast<char*>(result); | |
| } | |
| void String::ToUTF8(uint8_t* utf8_array, intptr_t array_len) const { | |
| ASSERT(array_len >= Utf8::Length(*this)); | |
| Utf8::Encode(*this, reinterpret_cast<char*>(utf8_array), array_len); | |
| } | |
| const char* String::ToCString(Thread* thread, StringPtr ptr) { | |
| if (ptr == nullptr) return nullptr; | |
| REUSABLE_STRING_HANDLESCOPE(thread); | |
| String& str = reused_string_handle.Handle(); | |
| str = ptr; | |
| return str.ToCString(); | |
| } | |
| static FinalizablePersistentHandle* AddFinalizer(const Object& referent, | |
| void* peer, | |
| Dart_HandleFinalizer callback, | |
| intptr_t external_size) { | |
| ASSERT(callback != nullptr); | |
| FinalizablePersistentHandle* finalizable_ref = | |
| FinalizablePersistentHandle::New(IsolateGroup::Current(), referent, peer, | |
| callback, external_size, | |
| /*auto_delete=*/true); | |
| ASSERT(finalizable_ref != nullptr); | |
| return finalizable_ref; | |
| } | |
| StringPtr String::Transform(int32_t (*mapping)(int32_t ch), | |
| const String& str, | |
| Heap::Space space) { | |
| ASSERT(!str.IsNull()); | |
| bool has_mapping = false; | |
| int32_t dst_max = 0; | |
| CodePointIterator it(str); | |
| while (it.Next()) { | |
| int32_t src = it.Current(); | |
| int32_t dst = mapping(src); | |
| if (src != dst) { | |
| has_mapping = true; | |
| } | |
| dst_max = Utils::Maximum(dst_max, dst); | |
| } | |
| if (!has_mapping) { | |
| return str.ptr(); | |
| } | |
| if (Utf::IsLatin1(dst_max)) { | |
| return OneByteString::Transform(mapping, str, space); | |
| } | |
| ASSERT(Utf::IsBmp(dst_max) || Utf::IsSupplementary(dst_max)); | |
| return TwoByteString::Transform(mapping, str, space); | |
| } | |
| StringPtr String::ToUpperCase(const String& str, Heap::Space space) { | |
| // TODO(cshapiro): create a fast-path for OneByteString instances. | |
| return Transform(CaseMapping::ToUpper, str, space); | |
| } | |
| StringPtr String::ToLowerCase(const String& str, Heap::Space space) { | |
| // TODO(cshapiro): create a fast-path for OneByteString instances. | |
| return Transform(CaseMapping::ToLower, str, space); | |
| } | |
| bool String::ParseDouble(const String& str, | |
| intptr_t start, | |
| intptr_t end, | |
| double* result) { | |
| ASSERT(0 <= start); | |
| ASSERT(start <= end); | |
| ASSERT(end <= str.Length()); | |
| intptr_t length = end - start; | |
| NoSafepointScope no_safepoint; | |
| const uint8_t* startChar; | |
| if (str.IsOneByteString()) { | |
| startChar = OneByteString::CharAddr(str, start); | |
| } else { | |
| uint8_t* chars = Thread::Current()->zone()->Alloc<uint8_t>(length); | |
| for (intptr_t i = 0; i < length; i++) { | |
| int32_t ch = str.CharAt(start + i); | |
| if (ch < 128) { | |
| chars[i] = ch; | |
| } else { | |
| return false; // Not ASCII, so definitely not valid double numeral. | |
| } | |
| } | |
| startChar = chars; | |
| } | |
| return CStringToDouble(reinterpret_cast<const char*>(startChar), length, | |
| result); | |
| } | |
| // Check to see if 'str1' matches 'str2' as is or | |
| // once the private key separator is stripped from str2. | |
| // | |
| // Things are made more complicated by the fact that constructors are | |
| // added *after* the private suffix, so "foo@123.named" should match | |
| // "foo.named". | |
| // | |
| // Also, the private suffix can occur more than once in the name, as in: | |
| // | |
| // _ReceivePortImpl@6be832b._internal@6be832b | |
| // | |
| template <typename T1, typename T2> | |
| static bool EqualsIgnoringPrivateKey(const String& str1, const String& str2) { | |
| intptr_t len = str1.Length(); | |
| intptr_t str2_len = str2.Length(); | |
| if (len == str2_len) { | |
| for (intptr_t i = 0; i < len; i++) { | |
| if (T1::CharAt(str1, i) != T2::CharAt(str2, i)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| if (len < str2_len) { | |
| return false; // No way they can match. | |
| } | |
| intptr_t pos = 0; | |
| intptr_t str2_pos = 0; | |
| while (pos < len) { | |
| int32_t ch = T1::CharAt(str1, pos); | |
| pos++; | |
| if ((str2_pos < str2_len) && (ch == T2::CharAt(str2, str2_pos))) { | |
| str2_pos++; | |
| continue; | |
| } | |
| if (ch == Library::kPrivateKeySeparator) { | |
| // Consume a private key separator if str1 has it but str2 does not. | |
| while ((pos < len) && (T1::CharAt(str1, pos) != '.') && | |
| (T1::CharAt(str1, pos) != '&')) { | |
| pos++; | |
| } | |
| // Resume matching characters. | |
| continue; | |
| } | |
| return false; | |
| } | |
| // We have reached the end of mangled_name string. | |
| ASSERT(pos == len); | |
| return (str2_pos == str2_len); | |
| } | |
| #define EQUALS_IGNORING_PRIVATE_KEY(class_id, type, str1, str2) \ | |
| switch (class_id) { \ | |
| case kOneByteStringCid: \ | |
| return dart::EqualsIgnoringPrivateKey<type, OneByteString>(str1, str2); \ | |
| case kTwoByteStringCid: \ | |
| return dart::EqualsIgnoringPrivateKey<type, TwoByteString>(str1, str2); \ | |
| } \ | |
| UNREACHABLE(); | |
| bool String::EqualsIgnoringPrivateKey(const String& str1, const String& str2) { | |
| if (str1.ptr() == str2.ptr()) { | |
| return true; // Both handles point to the same raw instance. | |
| } | |
| NoSafepointScope no_safepoint; | |
| intptr_t str1_class_id = str1.ptr()->GetClassIdOfHeapObject(); | |
| intptr_t str2_class_id = str2.ptr()->GetClassIdOfHeapObject(); | |
| switch (str1_class_id) { | |
| case kOneByteStringCid: | |
| EQUALS_IGNORING_PRIVATE_KEY(str2_class_id, OneByteString, str1, str2); | |
| break; | |
| case kTwoByteStringCid: | |
| EQUALS_IGNORING_PRIVATE_KEY(str2_class_id, TwoByteString, str1, str2); | |
| break; | |
| } | |
| UNREACHABLE(); | |
| return false; | |
| } | |
| bool String::CodePointIterator::Next() { | |
| ASSERT(index_ >= -1); | |
| intptr_t length = Utf16::Length(ch_); | |
| if (index_ < (end_ - length)) { | |
| index_ += length; | |
| ch_ = str_.CharAt(index_); | |
| if (Utf16::IsLeadSurrogate(ch_) && (index_ < (end_ - 1))) { | |
| int32_t ch2 = str_.CharAt(index_ + 1); | |
| if (Utf16::IsTrailSurrogate(ch2)) { | |
| ch_ = Utf16::Decode(ch_, ch2); | |
| } | |
| } | |
| return true; | |
| } | |
| index_ = end_; | |
| return false; | |
| } | |
| OneByteStringPtr OneByteString::EscapeSpecialCharacters(const String& str) { | |
| intptr_t len = str.Length(); | |
| if (len > 0) { | |
| intptr_t num_escapes = 0; | |
| for (intptr_t i = 0; i < len; i++) { | |
| num_escapes += EscapeOverhead(CharAt(str, i)); | |
| } | |
| const String& dststr = | |
| String::Handle(OneByteString::New(len + num_escapes, Heap::kNew)); | |
| intptr_t index = 0; | |
| for (intptr_t i = 0; i < len; i++) { | |
| uint8_t ch = CharAt(str, i); | |
| if (IsSpecialCharacter(ch)) { | |
| SetCharAt(dststr, index, '\\'); | |
| SetCharAt(dststr, index + 1, SpecialCharacter(ch)); | |
| index += 2; | |
| } else if (IsAsciiNonprintable(ch)) { | |
| SetCharAt(dststr, index, '\\'); | |
| SetCharAt(dststr, index + 1, 'x'); | |
| SetCharAt(dststr, index + 2, GetHexCharacter(ch >> 4)); | |
| SetCharAt(dststr, index + 3, GetHexCharacter(ch & 0xF)); | |
| index += 4; | |
| } else { | |
| SetCharAt(dststr, index, ch); | |
| index += 1; | |
| } | |
| } | |
| return OneByteString::raw(dststr); | |
| } | |
| return OneByteString::raw(Symbols::Empty()); | |
| } | |
| OneByteStringPtr OneByteString::New(intptr_t len, Heap::Space space) { | |
| if (len < 0 || len > kMaxElements) { | |
| // This should be caught before we reach here. | |
| FATAL("Fatal error in OneByteString::New: invalid len %" Pd "\n", len); | |
| } | |
| auto result = Object::Allocate<OneByteString>(space, len); | |
| NoSafepointScope no_safepoint; | |
| result->untag()->set_length(Smi::New(len)); | |
| #if !defined(HASH_IN_OBJECT_HEADER) | |
| result->untag()->set_hash(Smi::New(0)); | |
| #endif | |
| intptr_t size = OneByteString::UnroundedSize(result); | |
| ASSERT(size <= result->untag()->HeapSize()); | |
| memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(result) + size), 0, | |
| result->untag()->HeapSize() - size); | |
| return result; | |
| } | |
| OneByteStringPtr OneByteString::New(const uint8_t* characters, | |
| intptr_t len, | |
| Heap::Space space) { | |
| const String& result = String::Handle(OneByteString::New(len, space)); | |
| if (len > 0) { | |
| NoSafepointScope no_safepoint; | |
| memmove(DataStart(result), characters, len); | |
| } | |
| return OneByteString::raw(result); | |
| } | |
| OneByteStringPtr OneByteString::New(const uint16_t* characters, | |
| intptr_t len, | |
| Heap::Space space) { | |
| const String& result = String::Handle(OneByteString::New(len, space)); | |
| NoSafepointScope no_safepoint; | |
| for (intptr_t i = 0; i < len; ++i) { | |
| ASSERT(Utf::IsLatin1(characters[i])); | |
| *CharAddr(result, i) = characters[i]; | |
| } | |
| return OneByteString::raw(result); | |
| } | |
| OneByteStringPtr OneByteString::New(const int32_t* characters, | |
| intptr_t len, | |
| Heap::Space space) { | |
| const String& result = String::Handle(OneByteString::New(len, space)); | |
| NoSafepointScope no_safepoint; | |
| for (intptr_t i = 0; i < len; ++i) { | |
| ASSERT(Utf::IsLatin1(characters[i])); | |
| *CharAddr(result, i) = characters[i]; | |
| } | |
| return OneByteString::raw(result); | |
| } | |
| OneByteStringPtr OneByteString::New(const String& str, Heap::Space space) { | |
| intptr_t len = str.Length(); | |
| const String& result = String::Handle(OneByteString::New(len, space)); | |
| String::Copy(result, 0, str, 0, len); | |
| return OneByteString::raw(result); | |
| } | |
| OneByteStringPtr OneByteString::New(const String& other_one_byte_string, | |
| intptr_t other_start_index, | |
| intptr_t other_len, | |
| Heap::Space space) { | |
| const String& result = String::Handle(OneByteString::New(other_len, space)); | |
| ASSERT(other_one_byte_string.IsOneByteString()); | |
| if (other_len > 0) { | |
| NoSafepointScope no_safepoint; | |
| memmove(OneByteString::DataStart(result), | |
| OneByteString::CharAddr(other_one_byte_string, other_start_index), | |
| other_len); | |
| } | |
| return OneByteString::raw(result); | |
| } | |
| OneByteStringPtr OneByteString::New(const TypedDataBase& other_typed_data, | |
| intptr_t other_start_index, | |
| intptr_t other_len, | |
| Heap::Space space) { | |
| const String& result = String::Handle(OneByteString::New(other_len, space)); | |
| ASSERT(other_typed_data.ElementSizeInBytes() == 1); | |
| if (other_len > 0) { | |
| NoSafepointScope no_safepoint; | |
| memmove(OneByteString::DataStart(result), | |
| other_typed_data.DataAddr(other_start_index), other_len); | |
| } | |
| return OneByteString::raw(result); | |
| } | |
| OneByteStringPtr OneByteString::Concat(const String& str1, | |
| const String& str2, | |
| Heap::Space space) { | |
| intptr_t len1 = str1.Length(); | |
| intptr_t len2 = str2.Length(); | |
| intptr_t len = len1 + len2; | |
| const String& result = String::Handle(OneByteString::New(len, space)); | |
| String::Copy(result, 0, str1, 0, len1); | |
| String::Copy(result, len1, str2, 0, len2); | |
| return OneByteString::raw(result); | |
| } | |
| OneByteStringPtr OneByteString::ConcatAll(const Array& strings, | |
| intptr_t start, | |
| intptr_t end, | |
| intptr_t len, | |
| Heap::Space space) { | |
| ASSERT(!strings.IsNull()); | |
| ASSERT(start >= 0); | |
| ASSERT(end <= strings.Length()); | |
| const String& result = String::Handle(OneByteString::New(len, space)); | |
| String& str = String::Handle(); | |
| intptr_t pos = 0; | |
| for (intptr_t i = start; i < end; i++) { | |
| str ^= strings.At(i); | |
| const intptr_t str_len = str.Length(); | |
| String::Copy(result, pos, str, 0, str_len); | |
| ASSERT((kMaxElements - pos) >= str_len); | |
| pos += str_len; | |
| } | |
| return OneByteString::raw(result); | |
| } | |
| OneByteStringPtr OneByteString::Transform(int32_t (*mapping)(int32_t ch), | |
| const String& str, | |
| Heap::Space space) { | |
| ASSERT(!str.IsNull()); | |
| intptr_t len = str.Length(); | |
| const String& result = String::Handle(OneByteString::New(len, space)); | |
| NoSafepointScope no_safepoint; | |
| for (intptr_t i = 0; i < len; ++i) { | |
| int32_t ch = mapping(str.CharAt(i)); | |
| ASSERT(Utf::IsLatin1(ch)); | |
| *CharAddr(result, i) = ch; | |
| } | |
| return OneByteString::raw(result); | |
| } | |
| OneByteStringPtr OneByteString::SubStringUnchecked(const String& str, | |
| intptr_t begin_index, | |
| intptr_t length, | |
| Heap::Space space) { | |
| ASSERT(!str.IsNull() && str.IsOneByteString()); | |
| ASSERT(begin_index >= 0); | |
| ASSERT(length >= 0); | |
| if (begin_index <= str.Length() && length == 0) { | |
| return OneByteString::raw(Symbols::Empty()); | |
| } | |
| ASSERT(begin_index < str.Length()); | |
| OneByteStringPtr result = OneByteString::New(length, space); | |
| NoSafepointScope no_safepoint; | |
| if (length > 0) { | |
| uint8_t* dest = &result->untag()->data()[0]; | |
| const uint8_t* src = &untag(str)->data()[begin_index]; | |
| memmove(dest, src, length); | |
| } | |
| return result; | |
| } | |
| TwoByteStringPtr TwoByteString::EscapeSpecialCharacters(const String& str) { | |
| intptr_t len = str.Length(); | |
| if (len > 0) { | |
| intptr_t num_escapes = 0; | |
| for (intptr_t i = 0; i < len; i++) { | |
| num_escapes += EscapeOverhead(CharAt(str, i)); | |
| } | |
| const String& dststr = | |
| String::Handle(TwoByteString::New(len + num_escapes, Heap::kNew)); | |
| intptr_t index = 0; | |
| for (intptr_t i = 0; i < len; i++) { | |
| uint16_t ch = CharAt(str, i); | |
| if (IsSpecialCharacter(ch)) { | |
| SetCharAt(dststr, index, '\\'); | |
| SetCharAt(dststr, index + 1, SpecialCharacter(ch)); | |
| index += 2; | |
| } else if (IsAsciiNonprintable(ch)) { | |
| SetCharAt(dststr, index, '\\'); | |
| SetCharAt(dststr, index + 1, 'x'); | |
| SetCharAt(dststr, index + 2, GetHexCharacter(ch >> 4)); | |
| SetCharAt(dststr, index + 3, GetHexCharacter(ch & 0xF)); | |
| index += 4; | |
| } else { | |
| SetCharAt(dststr, index, ch); | |
| index += 1; | |
| } | |
| } | |
| return TwoByteString::raw(dststr); | |
| } | |
| return TwoByteString::New(0, Heap::kNew); | |
| } | |
| TwoByteStringPtr TwoByteString::New(intptr_t len, Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->two_byte_string_class() != | |
| nullptr); | |
| if (len < 0 || len > kMaxElements) { | |
| // This should be caught before we reach here. | |
| FATAL("Fatal error in TwoByteString::New: invalid len %" Pd "\n", len); | |
| } | |
| auto s = Object::Allocate<TwoByteString>(space, len); | |
| NoSafepointScope no_safepoint; | |
| s->untag()->set_length(Smi::New(len)); | |
| #if !defined(HASH_IN_OBJECT_HEADER) | |
| s->untag()->set_hash(Smi::New(0)); | |
| #endif | |
| intptr_t size = TwoByteString::UnroundedSize(s); | |
| ASSERT(size <= s->untag()->HeapSize()); | |
| memset(reinterpret_cast<void*>(UntaggedObject::ToAddr(s) + size), 0, | |
| s->untag()->HeapSize() - size); | |
| return s; | |
| } | |
| TwoByteStringPtr TwoByteString::New(const uint16_t* utf16_array, | |
| intptr_t array_len, | |
| Heap::Space space) { | |
| ASSERT(array_len > 0); | |
| const String& result = String::Handle(TwoByteString::New(array_len, space)); | |
| { | |
| NoSafepointScope no_safepoint; | |
| memmove(reinterpret_cast<void*>(DataStart(result)), | |
| reinterpret_cast<const void*>(utf16_array), (array_len * 2)); | |
| } | |
| return TwoByteString::raw(result); | |
| } | |
| TwoByteStringPtr TwoByteString::New(intptr_t utf16_len, | |
| const int32_t* utf32_array, | |
| intptr_t array_len, | |
| Heap::Space space) { | |
| ASSERT((array_len > 0) && (utf16_len >= array_len)); | |
| const String& result = String::Handle(TwoByteString::New(utf16_len, space)); | |
| { | |
| NoSafepointScope no_safepoint; | |
| intptr_t j = 0; | |
| for (intptr_t i = 0; i < array_len; ++i) { | |
| if (Utf::IsSupplementary(utf32_array[i])) { | |
| ASSERT(j < (utf16_len - 1)); | |
| Utf16::Encode(utf32_array[i], CharAddr(result, j)); | |
| j += 2; | |
| } else { | |
| ASSERT(j < utf16_len); | |
| *CharAddr(result, j) = utf32_array[i]; | |
| j += 1; | |
| } | |
| } | |
| } | |
| return TwoByteString::raw(result); | |
| } | |
| TwoByteStringPtr TwoByteString::New(const String& str, Heap::Space space) { | |
| intptr_t len = str.Length(); | |
| const String& result = String::Handle(TwoByteString::New(len, space)); | |
| String::Copy(result, 0, str, 0, len); | |
| return TwoByteString::raw(result); | |
| } | |
| TwoByteStringPtr TwoByteString::New(const TypedDataBase& other_typed_data, | |
| intptr_t other_start_index, | |
| intptr_t other_len, | |
| Heap::Space space) { | |
| const String& result = String::Handle(TwoByteString::New(other_len, space)); | |
| if (other_len > 0) { | |
| NoSafepointScope no_safepoint; | |
| memmove(TwoByteString::DataStart(result), | |
| other_typed_data.DataAddr(other_start_index), | |
| other_len * sizeof(uint16_t)); | |
| } | |
| return TwoByteString::raw(result); | |
| } | |
| TwoByteStringPtr TwoByteString::Concat(const String& str1, | |
| const String& str2, | |
| Heap::Space space) { | |
| intptr_t len1 = str1.Length(); | |
| intptr_t len2 = str2.Length(); | |
| intptr_t len = len1 + len2; | |
| const String& result = String::Handle(TwoByteString::New(len, space)); | |
| String::Copy(result, 0, str1, 0, len1); | |
| String::Copy(result, len1, str2, 0, len2); | |
| return TwoByteString::raw(result); | |
| } | |
| TwoByteStringPtr TwoByteString::ConcatAll(const Array& strings, | |
| intptr_t start, | |
| intptr_t end, | |
| intptr_t len, | |
| Heap::Space space) { | |
| ASSERT(!strings.IsNull()); | |
| ASSERT(start >= 0); | |
| ASSERT(end <= strings.Length()); | |
| const String& result = String::Handle(TwoByteString::New(len, space)); | |
| String& str = String::Handle(); | |
| intptr_t pos = 0; | |
| for (intptr_t i = start; i < end; i++) { | |
| str ^= strings.At(i); | |
| const intptr_t str_len = str.Length(); | |
| String::Copy(result, pos, str, 0, str_len); | |
| ASSERT((kMaxElements - pos) >= str_len); | |
| pos += str_len; | |
| } | |
| return TwoByteString::raw(result); | |
| } | |
| TwoByteStringPtr TwoByteString::Transform(int32_t (*mapping)(int32_t ch), | |
| const String& str, | |
| Heap::Space space) { | |
| ASSERT(!str.IsNull()); | |
| intptr_t len = str.Length(); | |
| const String& result = String::Handle(TwoByteString::New(len, space)); | |
| String::CodePointIterator it(str); | |
| intptr_t i = 0; | |
| NoSafepointScope no_safepoint; | |
| while (it.Next()) { | |
| int32_t src = it.Current(); | |
| int32_t dst = mapping(src); | |
| ASSERT(dst >= 0 && dst <= 0x10FFFF); | |
| intptr_t len = Utf16::Length(dst); | |
| if (len == 1) { | |
| *CharAddr(result, i) = dst; | |
| } else { | |
| ASSERT(len == 2); | |
| Utf16::Encode(dst, CharAddr(result, i)); | |
| } | |
| i += len; | |
| } | |
| return TwoByteString::raw(result); | |
| } | |
| const char* Bool::ToCString() const { | |
| return value() ? "true" : "false"; | |
| } | |
| bool Array::CanonicalizeEquals(const Instance& other) const { | |
| if (this->ptr() == other.ptr()) { | |
| // Both handles point to the same raw instance. | |
| return true; | |
| } | |
| // An Array may be compared to an ImmutableArray. | |
| if (!other.IsArray() || other.IsNull()) { | |
| return false; | |
| } | |
| // First check if both arrays have the same length and elements. | |
| const Array& other_arr = Array::Cast(other); | |
| intptr_t len = this->Length(); | |
| if (len != other_arr.Length()) { | |
| return false; | |
| } | |
| for (intptr_t i = 0; i < len; i++) { | |
| if (this->At(i) != other_arr.At(i)) { | |
| return false; | |
| } | |
| } | |
| // Now check if both arrays have the same type arguments. | |
| if (GetTypeArguments() == other.GetTypeArguments()) { | |
| return true; | |
| } | |
| const TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments()); | |
| const TypeArguments& other_type_args = | |
| TypeArguments::Handle(other.GetTypeArguments()); | |
| if (!type_args.Equals(other_type_args)) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| uint32_t Array::CanonicalizeHash() const { | |
| intptr_t len = Length(); | |
| if (len == 0) { | |
| return 1; | |
| } | |
| Thread* thread = Thread::Current(); | |
| uint32_t hash = thread->heap()->GetCanonicalHash(ptr()); | |
| if (hash != 0) { | |
| return hash; | |
| } | |
| hash = len; | |
| Instance& member = Instance::Handle(GetTypeArguments()); | |
| hash = CombineHashes(hash, member.CanonicalizeHash()); | |
| for (intptr_t i = 0; i < len; i++) { | |
| member ^= At(i); | |
| hash = CombineHashes(hash, member.CanonicalizeHash()); | |
| } | |
| hash = FinalizeHash(hash, kHashBits); | |
| thread->heap()->SetCanonicalHash(ptr(), hash); | |
| return hash; | |
| } | |
| ArrayPtr Array::New(intptr_t len, | |
| const AbstractType& element_type, | |
| Heap::Space space) { | |
| const Array& result = Array::Handle(Array::New(len, space)); | |
| if (!element_type.IsDynamicType()) { | |
| TypeArguments& type_args = TypeArguments::Handle(TypeArguments::New(1)); | |
| type_args.SetTypeAt(0, element_type); | |
| type_args = type_args.Canonicalize(Thread::Current()); | |
| result.SetTypeArguments(type_args); | |
| } | |
| return result.ptr(); | |
| } | |
| ArrayPtr Array::NewUninitialized(intptr_t class_id, | |
| intptr_t len, | |
| Heap::Space space) { | |
| if (!IsValidLength(len)) { | |
| // This should be caught before we reach here. | |
| FATAL("Fatal error in Array::New: invalid len %" Pd "\n", len); | |
| } | |
| auto raw = Object::AllocateVariant<Array>(class_id, space, len); | |
| NoSafepointScope no_safepoint; | |
| raw->untag()->set_length(Smi::New(len)); | |
| if (UseCardMarkingForAllocation(len)) { | |
| ASSERT(raw->IsOldObject()); | |
| raw->untag()->SetCardRememberedBitUnsynchronized(); | |
| Page::Of(raw)->AllocateCardTable(); | |
| } | |
| return raw; | |
| } | |
| ArrayPtr Array::New(intptr_t class_id, intptr_t len, Heap::Space space) { | |
| if (!UseCardMarkingForAllocation(len)) { | |
| return NewUninitialized(class_id, len, space); | |
| } | |
| Thread* thread = Thread::Current(); | |
| Array& result = | |
| Array::Handle(thread->zone(), NewUninitialized(class_id, len, space)); | |
| result.SetTypeArguments(Object::null_type_arguments()); | |
| for (intptr_t i = 0; i < len; i++) { | |
| result.SetAt(i, Object::null_object(), thread); | |
| if (((i + 1) % kSlotsPerInterruptCheck) == 0) { | |
| thread->CheckForSafepoint(); | |
| } | |
| } | |
| return result.ptr(); | |
| } | |
| ArrayPtr Array::Slice(intptr_t start, | |
| intptr_t count, | |
| bool with_type_argument) const { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const Array& dest = Array::Handle(zone, Array::NewUninitialized(count)); | |
| if (with_type_argument) { | |
| dest.SetTypeArguments(TypeArguments::Handle(zone, GetTypeArguments())); | |
| } else { | |
| dest.SetTypeArguments(Object::null_type_arguments()); | |
| } | |
| if (!UseCardMarkingForAllocation(count)) { | |
| NoSafepointScope no_safepoint(thread); | |
| for (int i = 0; i < count; i++) { | |
| dest.untag()->set_element(i, untag()->element(i + start), thread); | |
| } | |
| } else { | |
| for (int i = 0; i < count; i++) { | |
| dest.untag()->set_element(i, untag()->element(i + start), thread); | |
| if (((i + 1) % kSlotsPerInterruptCheck) == 0) { | |
| thread->CheckForSafepoint(); | |
| } | |
| } | |
| } | |
| return dest.ptr(); | |
| } | |
| void Array::MakeImmutable() const { | |
| if (IsImmutable()) return; | |
| ASSERT(!IsCanonical()); | |
| untag()->SetClassId(kImmutableArrayCid); | |
| } | |
| const char* Array::ToCString() const { | |
| if (IsNull()) { | |
| return IsImmutable() ? "_ImmutableList nullptr" : "_List nullptr"; | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| const char* format = | |
| IsImmutable() ? "_ImmutableList len:%" Pd : "_List len:%" Pd; | |
| return zone->PrintToString(format, Length()); | |
| } | |
| ArrayPtr Array::Grow(const Array& source, | |
| intptr_t new_length, | |
| Heap::Space space) { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const Array& result = | |
| Array::Handle(zone, Array::NewUninitialized(new_length, space)); | |
| intptr_t old_length = 0; | |
| if (!source.IsNull()) { | |
| old_length = source.Length(); | |
| result.SetTypeArguments( | |
| TypeArguments::Handle(zone, source.GetTypeArguments())); | |
| } else { | |
| result.SetTypeArguments(Object::null_type_arguments()); | |
| } | |
| ASSERT(new_length > old_length); // Unnecessary copying of array. | |
| if (!UseCardMarkingForAllocation(new_length)) { | |
| NoSafepointScope no_safepoint(thread); | |
| for (intptr_t i = 0; i < old_length; i++) { | |
| result.untag()->set_element(i, source.untag()->element(i), thread); | |
| } | |
| for (intptr_t i = old_length; i < new_length; i++) { | |
| ASSERT(result.untag()->element(i) == Object::null()); | |
| } | |
| } else { | |
| for (intptr_t i = 0; i < old_length; i++) { | |
| result.untag()->set_element(i, source.untag()->element(i), thread); | |
| if (((i + 1) % kSlotsPerInterruptCheck) == 0) { | |
| thread->CheckForSafepoint(); | |
| } | |
| } | |
| for (intptr_t i = old_length; i < new_length; i++) { | |
| result.untag()->set_element(i, Object::null(), thread); | |
| if (((i + 1) % kSlotsPerInterruptCheck) == 0) { | |
| thread->CheckForSafepoint(); | |
| } | |
| } | |
| } | |
| return result.ptr(); | |
| } | |
| void Array::Truncate(intptr_t new_len) const { | |
| if (IsNull()) { | |
| return; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const Array& array = Array::Handle(zone, this->ptr()); | |
| intptr_t old_len = array.Length(); | |
| ASSERT(new_len <= old_len); | |
| if (old_len == new_len) { | |
| return; | |
| } | |
| intptr_t old_size = Array::InstanceSize(old_len); | |
| intptr_t new_size = Array::InstanceSize(new_len); | |
| NoSafepointScope no_safepoint(thread); | |
| // If there is any left over space fill it with either an Array object or | |
| // just a plain object (depending on the amount of left over space) so | |
| // that it can be traversed over successfully during garbage collection. | |
| Object::MakeUnusedSpaceTraversable(array, old_size, new_size); | |
| // Update the size in the header field and length of the array object. | |
| // These release operations are balanced by acquire operations in the | |
| // concurrent sweeper. | |
| array.untag() | |
| ->tags_.Update<UntaggedObject::SizeTag, std::memory_order_release>( | |
| new_size); | |
| // Between the CAS of the header above and the SetLength below, the array is | |
| // temporarily in an inconsistent state. The header is considered the | |
| // overriding source of object size by UntaggedObject::HeapSize, but the | |
| // ASSERTs in UntaggedObject::HeapSizeFromClass must handle this special case. | |
| array.SetLengthRelease(new_len); | |
| } | |
| ArrayPtr Array::MakeFixedLength(const GrowableObjectArray& growable_array, | |
| bool unique) { | |
| ASSERT(!growable_array.IsNull()); | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| intptr_t used_len = growable_array.Length(); | |
| // Get the type arguments and prepare to copy them. | |
| const TypeArguments& type_arguments = | |
| TypeArguments::Handle(growable_array.GetTypeArguments()); | |
| if (used_len == 0) { | |
| if (type_arguments.IsNull() && !unique) { | |
| // This is a raw List (as in no type arguments), so we can return the | |
| // simple empty array. | |
| return Object::empty_array().ptr(); | |
| } | |
| // The backing array may be a shared instance, or may not have correct | |
| // type parameters. Create a new empty array. | |
| Heap::Space space = thread->HasDartMutatorStack() ? Heap::kNew : Heap::kOld; | |
| Array& array = Array::Handle(zone, Array::New(0, space)); | |
| array.SetTypeArguments(type_arguments); | |
| return array.ptr(); | |
| } | |
| const Array& array = Array::Handle(zone, growable_array.data()); | |
| ASSERT(array.IsArray()); | |
| array.SetTypeArguments(type_arguments); | |
| // Null the GrowableObjectArray, we are removing its backing array. | |
| growable_array.SetLength(0); | |
| growable_array.SetData(Object::empty_array()); | |
| // Truncate the old backing array and return it. | |
| array.Truncate(used_len); | |
| return array.ptr(); | |
| } | |
| void Array::CanonicalizeFieldsLocked(Thread* thread) const { | |
| ASSERT(IsImmutable()); | |
| intptr_t len = Length(); | |
| if (len > 0) { | |
| Zone* zone = thread->zone(); | |
| Instance& obj = Instance::Handle(zone); | |
| for (intptr_t i = 0; i < len; i++) { | |
| obj ^= At(i); | |
| obj = obj.CanonicalizeLocked(thread); | |
| this->SetAt(i, obj); | |
| } | |
| } | |
| } | |
| ImmutableArrayPtr ImmutableArray::New(intptr_t len, Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->immutable_array_class() != | |
| Class::null()); | |
| return static_cast<ImmutableArrayPtr>(Array::New(kClassId, len, space)); | |
| } | |
| void GrowableObjectArray::Add(const Object& value, Heap::Space space) const { | |
| ASSERT(!IsNull()); | |
| if (Length() == Capacity()) { | |
| // Grow from 0 to 3, and then double + 1. | |
| intptr_t new_capacity = (Capacity() * 2) | 3; | |
| if (new_capacity <= Capacity()) { | |
| Exceptions::ThrowOOM(); | |
| UNREACHABLE(); | |
| } | |
| Grow(new_capacity, space); | |
| } | |
| ASSERT(Length() < Capacity()); | |
| intptr_t index = Length(); | |
| SetLength(index + 1); | |
| SetAt(index, value); | |
| } | |
| void GrowableObjectArray::Grow(intptr_t new_capacity, Heap::Space space) const { | |
| ASSERT(new_capacity > Capacity()); | |
| const Array& contents = Array::Handle(data()); | |
| const Array& new_contents = | |
| Array::Handle(Array::Grow(contents, new_capacity, space)); | |
| untag()->set_data(new_contents.ptr()); | |
| } | |
| ObjectPtr GrowableObjectArray::RemoveLast() const { | |
| ASSERT(!IsNull()); | |
| ASSERT(Length() > 0); | |
| intptr_t index = Length() - 1; | |
| const Array& contents = Array::Handle(data()); | |
| const PassiveObject& obj = PassiveObject::Handle(contents.At(index)); | |
| contents.SetAt(index, Object::null_object()); | |
| SetLength(index); | |
| return obj.ptr(); | |
| } | |
| GrowableObjectArrayPtr GrowableObjectArray::New(intptr_t capacity, | |
| Heap::Space space) { | |
| ArrayPtr raw_data = (capacity == 0) ? Object::empty_array().ptr() | |
| : Array::New(capacity, space); | |
| const Array& data = Array::Handle(raw_data); | |
| return New(data, space); | |
| } | |
| GrowableObjectArrayPtr GrowableObjectArray::New(const Array& array, | |
| Heap::Space space) { | |
| ASSERT( | |
| IsolateGroup::Current()->object_store()->growable_object_array_class() != | |
| Class::null()); | |
| const auto& result = | |
| GrowableObjectArray::Handle(Object::Allocate<GrowableObjectArray>(space)); | |
| result.SetLength(0); | |
| result.SetData(array); | |
| return result.ptr(); | |
| } | |
| const char* GrowableObjectArray::ToCString() const { | |
| if (IsNull()) { | |
| return "_GrowableList: null"; | |
| } | |
| return OS::SCreate(Thread::Current()->zone(), | |
| "Instance(length:%" Pd ") of '_GrowableList'", Length()); | |
| } | |
| // Equivalent to Dart's operator "==" and hashCode. | |
| class DefaultHashTraits { | |
| public: | |
| static const char* Name() { return "DefaultHashTraits"; } | |
| static bool ReportStats() { return false; } | |
| static bool IsMatch(const Object& a, const Object& b) { | |
| if (a.IsNull() || b.IsNull()) { | |
| return (a.IsNull() && b.IsNull()); | |
| } else { | |
| return Instance::Cast(a).OperatorEquals(Instance::Cast(b)); | |
| } | |
| } | |
| static uword Hash(const Object& obj) { | |
| if (obj.IsNull()) { | |
| return 0; | |
| } | |
| Thread* thread = Thread::Current(); | |
| REUSABLE_INSTANCE_HANDLESCOPE(thread); | |
| Instance& hash_code = thread->InstanceHandle(); | |
| hash_code ^= Instance::Cast(obj).HashCode(); | |
| if (hash_code.IsInteger()) { | |
| return static_cast<uword>(Integer::Cast(hash_code).Value() & 0xFFFFFFFF); | |
| } else { | |
| return 0; | |
| } | |
| } | |
| }; | |
| MapPtr Map::NewDefault(intptr_t class_id, Heap::Space space) { | |
| const Array& data = Array::Handle(Array::New(kInitialIndexSize, space)); | |
| const TypedData& index = TypedData::Handle( | |
| TypedData::New(kTypedDataUint32ArrayCid, kInitialIndexSize, space)); | |
| // On 32-bit, the top bits are wasted to avoid Mint allocation. | |
| const intptr_t kAvailableBits = (kSmiBits >= 32) ? 32 : kSmiBits; | |
| const intptr_t kInitialHashMask = | |
| (1 << (kAvailableBits - kInitialIndexBits)) - 1; | |
| return Map::New(class_id, data, index, kInitialHashMask, 0, 0, space); | |
| } | |
| MapPtr Map::New(intptr_t class_id, | |
| const Array& data, | |
| const TypedData& index, | |
| intptr_t hash_mask, | |
| intptr_t used_data, | |
| intptr_t deleted_keys, | |
| Heap::Space space) { | |
| ASSERT(class_id == kMapCid || class_id == kConstMapCid); | |
| ASSERT(IsolateGroup::Current()->object_store()->map_impl_class() != | |
| Class::null()); | |
| Map& result = Map::Handle(Map::NewUninitialized(class_id, space)); | |
| result.set_data(data); | |
| result.set_index(index); | |
| result.set_hash_mask(hash_mask); | |
| result.set_used_data(used_data); | |
| result.set_deleted_keys(deleted_keys); | |
| return result.ptr(); | |
| } | |
| MapPtr Map::NewUninitialized(intptr_t class_id, Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->map_impl_class() != | |
| Class::null()); | |
| return Object::AllocateVariant<Map>(class_id, space); | |
| } | |
| const char* Map::ToCString() const { | |
| Zone* zone = Thread::Current()->zone(); | |
| return zone->PrintToString( | |
| "%s len:%" Pd, GetClassId() == kConstMapCid ? "_ConstMap" : "_Map", | |
| Length()); | |
| } | |
| void LinkedHashBase::ComputeAndSetHashMask() const { | |
| ASSERT(IsImmutable()); | |
| ASSERT_EQUAL(Smi::Value(deleted_keys()), 0); | |
| Thread* const thread = Thread::Current(); | |
| Zone* const zone = thread->zone(); | |
| const auto& data_array = Array::Handle(zone, data()); | |
| const intptr_t data_length = Utils::RoundUpToPowerOfTwo(data_array.Length()); | |
| const intptr_t index_size_mult = IsMap() ? 1 : 2; | |
| const intptr_t index_size = Utils::Maximum(LinkedHashBase::kInitialIndexSize, | |
| data_length * index_size_mult); | |
| ASSERT(Utils::IsPowerOfTwo(index_size)); | |
| const intptr_t hash_mask = IndexSizeToHashMask(index_size); | |
| set_hash_mask(hash_mask); | |
| } | |
| bool LinkedHashBase::CanonicalizeEquals(const Instance& other) const { | |
| ASSERT(IsImmutable()); | |
| if (this->ptr() == other.ptr()) { | |
| // Both handles point to the same raw instance. | |
| return true; | |
| } | |
| if (other.IsNull()) { | |
| return false; | |
| } | |
| if (GetClassId() != other.GetClassId()) { | |
| return false; | |
| } | |
| Zone* zone = Thread::Current()->zone(); | |
| const LinkedHashBase& other_map = LinkedHashBase::Cast(other); | |
| if (!Smi::Handle(zone, used_data()) | |
| .Equals(Smi::Handle(zone, other_map.used_data()))) { | |
| return false; | |
| } | |
| // Immutable maps and sets do not have deleted keys. | |
| ASSERT_EQUAL(RawSmiValue(deleted_keys()), 0); | |
| if (!Array::Handle(zone, data()) | |
| .CanonicalizeEquals(Array::Handle(zone, other_map.data()))) { | |
| return false; | |
| } | |
| if (GetTypeArguments() == other.GetTypeArguments()) { | |
| return true; | |
| } | |
| const TypeArguments& type_args = | |
| TypeArguments::Handle(zone, GetTypeArguments()); | |
| const TypeArguments& other_type_args = | |
| TypeArguments::Handle(zone, other.GetTypeArguments()); | |
| return type_args.Equals(other_type_args); | |
| } | |
| uint32_t LinkedHashBase::CanonicalizeHash() const { | |
| ASSERT(IsImmutable()); | |
| Thread* thread = Thread::Current(); | |
| uint32_t hash = thread->heap()->GetCanonicalHash(ptr()); | |
| if (hash != 0) { | |
| return hash; | |
| } | |
| // Immutable maps and sets do not have deleted keys. | |
| ASSERT_EQUAL(RawSmiValue(deleted_keys()), 0); | |
| Zone* zone = thread->zone(); | |
| auto& member = Instance::Handle(zone, GetTypeArguments()); | |
| hash = member.CanonicalizeHash(); | |
| member = data(); | |
| hash = CombineHashes(hash, member.CanonicalizeHash()); | |
| member = used_data(); | |
| hash = CombineHashes(hash, member.CanonicalizeHash()); | |
| hash = FinalizeHash(hash, kHashBits); | |
| thread->heap()->SetCanonicalHash(ptr(), hash); | |
| return hash; | |
| } | |
| void LinkedHashBase::CanonicalizeFieldsLocked(Thread* thread) const { | |
| ASSERT(IsImmutable()); | |
| Zone* zone = thread->zone(); | |
| TypeArguments& type_args = TypeArguments::Handle(zone, GetTypeArguments()); | |
| if (!type_args.IsNull()) { | |
| type_args = type_args.Canonicalize(thread); | |
| SetTypeArguments(type_args); | |
| } | |
| auto& data_array = Array::Handle(zone, data()); | |
| data_array.MakeImmutable(); | |
| data_array ^= data_array.CanonicalizeLocked(thread); | |
| set_data(data_array); | |
| // Ignoring index. It will be initially null, created on first use, and | |
| // possibly non-null here if we are rehashing. | |
| } | |
| ConstMapPtr ConstMap::NewDefault(Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->const_map_impl_class() != | |
| Class::null()); | |
| return static_cast<ConstMapPtr>(Map::NewDefault(kClassId, space)); | |
| } | |
| ConstMapPtr ConstMap::NewUninitialized(Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->const_map_impl_class() != | |
| Class::null()); | |
| return static_cast<ConstMapPtr>(Map::NewUninitialized(kClassId, space)); | |
| } | |
| SetPtr Set::New(intptr_t class_id, | |
| const Array& data, | |
| const TypedData& index, | |
| intptr_t hash_mask, | |
| intptr_t used_data, | |
| intptr_t deleted_keys, | |
| Heap::Space space) { | |
| ASSERT(class_id == kSetCid || class_id == kConstSetCid); | |
| ASSERT(IsolateGroup::Current()->object_store()->set_impl_class() != | |
| Class::null()); | |
| Set& result = Set::Handle(Set::NewUninitialized(class_id, space)); | |
| result.set_data(data); | |
| result.set_index(index); | |
| result.set_hash_mask(hash_mask); | |
| result.set_used_data(used_data); | |
| result.set_deleted_keys(deleted_keys); | |
| return result.ptr(); | |
| } | |
| SetPtr Set::NewDefault(intptr_t class_id, Heap::Space space) { | |
| const Array& data = Array::Handle(Array::New(kInitialIndexSize, space)); | |
| const TypedData& index = TypedData::Handle( | |
| TypedData::New(kTypedDataUint32ArrayCid, kInitialIndexSize, space)); | |
| // On 32-bit, the top bits are wasted to avoid Mint allocation. | |
| const intptr_t kAvailableBits = (kSmiBits >= 32) ? 32 : kSmiBits; | |
| const intptr_t kInitialHashMask = | |
| (1 << (kAvailableBits - kInitialIndexBits)) - 1; | |
| return Set::New(class_id, data, index, kInitialHashMask, 0, 0, space); | |
| } | |
| SetPtr Set::NewUninitialized(intptr_t class_id, Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->set_impl_class() != | |
| Class::null()); | |
| return Object::AllocateVariant<Set>(class_id, space); | |
| } | |
| ConstSetPtr ConstSet::NewDefault(Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->const_set_impl_class() != | |
| Class::null()); | |
| return static_cast<ConstSetPtr>(Set::NewDefault(kClassId, space)); | |
| } | |
| ConstSetPtr ConstSet::NewUninitialized(Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->const_set_impl_class() != | |
| Class::null()); | |
| return static_cast<ConstSetPtr>(Set::NewUninitialized(kClassId, space)); | |
| } | |
| const char* Set::ToCString() const { | |
| Zone* zone = Thread::Current()->zone(); | |
| return zone->PrintToString( | |
| "%s len:%" Pd, GetClassId() == kConstSetCid ? "_ConstSet" : "_Set", | |
| Length()); | |
| } | |
| const char* FutureOr::ToCString() const { | |
| // FutureOr is an abstract class. | |
| UNREACHABLE(); | |
| } | |
| Float32x4Ptr Float32x4::New(float v0, | |
| float v1, | |
| float v2, | |
| float v3, | |
| Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->float32x4_class() != | |
| Class::null()); | |
| const auto& result = Float32x4::Handle(Object::Allocate<Float32x4>(space)); | |
| result.set_x(v0); | |
| result.set_y(v1); | |
| result.set_z(v2); | |
| result.set_w(v3); | |
| return result.ptr(); | |
| } | |
| Float32x4Ptr Float32x4::New(simd128_value_t value, Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->float32x4_class() != | |
| Class::null()); | |
| const auto& result = Float32x4::Handle(Object::Allocate<Float32x4>(space)); | |
| result.set_value(value); | |
| return result.ptr(); | |
| } | |
| simd128_value_t Float32x4::value() const { | |
| return LoadUnaligned( | |
| reinterpret_cast<const simd128_value_t*>(&untag()->value_)); | |
| } | |
| void Float32x4::set_value(simd128_value_t value) const { | |
| StoreUnaligned(reinterpret_cast<simd128_value_t*>(&ptr()->untag()->value_), | |
| value); | |
| } | |
| void Float32x4::set_x(float value) const { | |
| StoreNonPointer(&untag()->value_[0], value); | |
| } | |
| void Float32x4::set_y(float value) const { | |
| StoreNonPointer(&untag()->value_[1], value); | |
| } | |
| void Float32x4::set_z(float value) const { | |
| StoreNonPointer(&untag()->value_[2], value); | |
| } | |
| void Float32x4::set_w(float value) const { | |
| StoreNonPointer(&untag()->value_[3], value); | |
| } | |
| float Float32x4::x() const { | |
| return untag()->value_[0]; | |
| } | |
| float Float32x4::y() const { | |
| return untag()->value_[1]; | |
| } | |
| float Float32x4::z() const { | |
| return untag()->value_[2]; | |
| } | |
| float Float32x4::w() const { | |
| return untag()->value_[3]; | |
| } | |
| bool Float32x4::CanonicalizeEquals(const Instance& other) const { | |
| NoSafepointScope no_safepoint; | |
| return memcmp(&untag()->value_, Float32x4::Cast(other).untag()->value_, | |
| sizeof(simd128_value_t)) == 0; | |
| } | |
| uint32_t Float32x4::CanonicalizeHash() const { | |
| NoSafepointScope no_safepoint; | |
| return HashBytes(&untag()->value_, sizeof(simd128_value_t), kHashBits); | |
| } | |
| const char* Float32x4::ToCString() const { | |
| return "V128"; | |
| } | |
| Int32x4Ptr Int32x4::New(int32_t v0, | |
| int32_t v1, | |
| int32_t v2, | |
| int32_t v3, | |
| Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->int32x4_class() != | |
| Class::null()); | |
| const auto& result = Int32x4::Handle(Object::Allocate<Int32x4>(space)); | |
| result.set_x(v0); | |
| result.set_y(v1); | |
| result.set_z(v2); | |
| result.set_w(v3); | |
| return result.ptr(); | |
| } | |
| Int32x4Ptr Int32x4::New(simd128_value_t value, Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->int32x4_class() != | |
| Class::null()); | |
| const auto& result = Int32x4::Handle(Object::Allocate<Int32x4>(space)); | |
| result.set_value(value); | |
| return result.ptr(); | |
| } | |
| void Int32x4::set_x(int32_t value) const { | |
| StoreNonPointer(&untag()->value_[0], value); | |
| } | |
| void Int32x4::set_y(int32_t value) const { | |
| StoreNonPointer(&untag()->value_[1], value); | |
| } | |
| void Int32x4::set_z(int32_t value) const { | |
| StoreNonPointer(&untag()->value_[2], value); | |
| } | |
| void Int32x4::set_w(int32_t value) const { | |
| StoreNonPointer(&untag()->value_[3], value); | |
| } | |
| int32_t Int32x4::x() const { | |
| return untag()->value_[0]; | |
| } | |
| int32_t Int32x4::y() const { | |
| return untag()->value_[1]; | |
| } | |
| int32_t Int32x4::z() const { | |
| return untag()->value_[2]; | |
| } | |
| int32_t Int32x4::w() const { | |
| return untag()->value_[3]; | |
| } | |
| simd128_value_t Int32x4::value() const { | |
| return LoadUnaligned( | |
| reinterpret_cast<const simd128_value_t*>(&untag()->value_)); | |
| } | |
| void Int32x4::set_value(simd128_value_t value) const { | |
| StoreUnaligned(reinterpret_cast<simd128_value_t*>(&ptr()->untag()->value_), | |
| value); | |
| } | |
| bool Int32x4::CanonicalizeEquals(const Instance& other) const { | |
| NoSafepointScope no_safepoint; | |
| return memcmp(&untag()->value_, Int32x4::Cast(other).untag()->value_, | |
| sizeof(simd128_value_t)) == 0; | |
| } | |
| uint32_t Int32x4::CanonicalizeHash() const { | |
| NoSafepointScope no_safepoint; | |
| return HashBytes(&untag()->value_, sizeof(simd128_value_t), kHashBits); | |
| } | |
| const char* Int32x4::ToCString() const { | |
| return "V128"; | |
| } | |
| Float64x2Ptr Float64x2::New(double value0, double value1, Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->float64x2_class() != | |
| Class::null()); | |
| const auto& result = Float64x2::Handle(Object::Allocate<Float64x2>(space)); | |
| result.set_x(value0); | |
| result.set_y(value1); | |
| return result.ptr(); | |
| } | |
| Float64x2Ptr Float64x2::New(simd128_value_t value, Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->float64x2_class() != | |
| Class::null()); | |
| const auto& result = Float64x2::Handle(Object::Allocate<Float64x2>(space)); | |
| result.set_value(value); | |
| return result.ptr(); | |
| } | |
| double Float64x2::x() const { | |
| return untag()->value_[0]; | |
| } | |
| double Float64x2::y() const { | |
| return untag()->value_[1]; | |
| } | |
| void Float64x2::set_x(double x) const { | |
| StoreNonPointer(&untag()->value_[0], x); | |
| } | |
| void Float64x2::set_y(double y) const { | |
| StoreNonPointer(&untag()->value_[1], y); | |
| } | |
| simd128_value_t Float64x2::value() const { | |
| return simd128_value_t().readFrom(&untag()->value_[0]); | |
| } | |
| void Float64x2::set_value(simd128_value_t value) const { | |
| StoreSimd128(&untag()->value_[0], value); | |
| } | |
| bool Float64x2::CanonicalizeEquals(const Instance& other) const { | |
| NoSafepointScope no_safepoint; | |
| return memcmp(&untag()->value_, Float64x2::Cast(other).untag()->value_, | |
| sizeof(simd128_value_t)) == 0; | |
| } | |
| uint32_t Float64x2::CanonicalizeHash() const { | |
| NoSafepointScope no_safepoint; | |
| return HashBytes(&untag()->value_, sizeof(simd128_value_t), kHashBits); | |
| } | |
| const char* Float64x2::ToCString() const { | |
| return "V128"; | |
| } | |
| const intptr_t | |
| TypedDataBase::element_size_table[TypedDataBase::kNumElementSizes] = { | |
| 1, // kTypedDataInt8ArrayCid. | |
| 1, // kTypedDataUint8ArrayCid. | |
| 1, // kTypedDataUint8ClampedArrayCid. | |
| 2, // kTypedDataInt16ArrayCid. | |
| 2, // kTypedDataUint16ArrayCid. | |
| 4, // kTypedDataInt32ArrayCid. | |
| 4, // kTypedDataUint32ArrayCid. | |
| 8, // kTypedDataInt64ArrayCid. | |
| 8, // kTypedDataUint64ArrayCid. | |
| 4, // kTypedDataFloat32ArrayCid. | |
| 8, // kTypedDataFloat64ArrayCid. | |
| 16, // kTypedDataFloat32x4ArrayCid. | |
| 16, // kTypedDataInt32x4ArrayCid. | |
| 16, // kTypedDataFloat64x2ArrayCid, | |
| }; | |
| bool TypedData::CanonicalizeEquals(const Instance& other) const { | |
| if (this->ptr() == other.ptr()) { | |
| // Both handles point to the same raw instance. | |
| return true; | |
| } | |
| if (!other.IsTypedData() || other.IsNull()) { | |
| return false; | |
| } | |
| const TypedData& other_typed_data = TypedData::Cast(other); | |
| if (this->ElementType() != other_typed_data.ElementType()) { | |
| return false; | |
| } | |
| const intptr_t len = this->LengthInBytes(); | |
| if (len != other_typed_data.LengthInBytes()) { | |
| return false; | |
| } | |
| NoSafepointScope no_safepoint; | |
| return (len == 0) || | |
| (memcmp(DataAddr(0), other_typed_data.DataAddr(0), len) == 0); | |
| } | |
| uint32_t TypedData::CanonicalizeHash() const { | |
| uint32_t hash = kEmptyContainerHash; | |
| const intptr_t len = LengthInBytes(); | |
| if (len != 0) { | |
| auto* const thread = Thread::Current(); | |
| hash = thread->heap()->GetCanonicalHash(ptr()); | |
| if (hash == 0) { | |
| { | |
| NoSafepointScope no_safepoint; | |
| hash = HashBytes(DataAddr(0), len, kHashBits); | |
| } | |
| thread->heap()->SetCanonicalHash(ptr(), hash); | |
| } | |
| } | |
| return hash; | |
| } | |
| TypedDataPtr TypedData::New(intptr_t class_id, | |
| intptr_t len, | |
| Heap::Space space) { | |
| if (len < 0 || len > TypedData::MaxElements(class_id)) { | |
| FATAL("Fatal error in TypedData::New: invalid len %" Pd "\n", len); | |
| } | |
| auto raw = Object::AllocateVariant<TypedData>( | |
| class_id, space, len * ElementSizeInBytes(class_id)); | |
| NoSafepointScope no_safepoint; | |
| raw->untag()->set_length(Smi::New(len)); | |
| raw->untag()->RecomputeDataField(); | |
| return raw; | |
| } | |
| TypedDataPtr TypedData::Grow(const TypedData& current, | |
| intptr_t len, | |
| Heap::Space space) { | |
| ASSERT(len > current.Length()); | |
| const auto& new_td = | |
| TypedData::Handle(TypedData::New(current.GetClassId(), len, space)); | |
| { | |
| NoSafepointScope no_safepoint_scope; | |
| memcpy(new_td.DataAddr(0), current.DataAddr(0), current.LengthInBytes()); | |
| } | |
| return new_td.ptr(); | |
| } | |
| const char* TypedData::ToCString() const { | |
| const Class& cls = Class::Handle(clazz()); | |
| return cls.ScrubbedNameCString(); | |
| } | |
| FinalizablePersistentHandle* ExternalTypedData::AddFinalizer( | |
| void* peer, | |
| Dart_HandleFinalizer callback, | |
| intptr_t external_size) const { | |
| return dart::AddFinalizer(*this, peer, callback, external_size); | |
| } | |
| ExternalTypedDataPtr ExternalTypedData::New( | |
| intptr_t class_id, | |
| uint8_t* data, | |
| intptr_t len, | |
| Heap::Space space, | |
| bool perform_eager_msan_initialization_check) { | |
| if (len < 0 || len > ExternalTypedData::MaxElements(class_id)) { | |
| FATAL("Fatal error in ExternalTypedData::New: invalid len %" Pd "\n", len); | |
| } | |
| if (perform_eager_msan_initialization_check) { | |
| // Once the TypedData is created, Dart might read this memory. Check for | |
| // initialization at construction to make it easier to track the source. | |
| MSAN_CHECK_INITIALIZED(data, len); | |
| } | |
| const auto& result = ExternalTypedData::Handle( | |
| Object::AllocateVariant<ExternalTypedData>(class_id, space)); | |
| result.SetLength(len); | |
| result.SetData(data); | |
| return result.ptr(); | |
| } | |
| ExternalTypedDataPtr ExternalTypedData::NewFinalizeWithFree(uint8_t* data, | |
| intptr_t len) { | |
| ExternalTypedData& result = ExternalTypedData::Handle(ExternalTypedData::New( | |
| kExternalTypedDataUint8ArrayCid, data, len, Heap::kOld)); | |
| result.AddFinalizer( | |
| data, [](void* isolate_callback_data, void* data) { free(data); }, len); | |
| return result.ptr(); | |
| } | |
| TypedDataViewPtr TypedDataView::New(intptr_t class_id, Heap::Space space) { | |
| return Object::AllocateVariant<TypedDataView>(class_id, space); | |
| } | |
| TypedDataViewPtr TypedDataView::New(intptr_t class_id, | |
| const TypedDataBase& typed_data, | |
| intptr_t offset_in_bytes, | |
| intptr_t length, | |
| Heap::Space space) { | |
| auto& result = TypedDataView::Handle(TypedDataView::New(class_id, space)); | |
| result.InitializeWith(typed_data, offset_in_bytes, length); | |
| return result.ptr(); | |
| } | |
| bool TypedDataBase::IsExternalOrExternalView() const { | |
| if (IsExternalTypedData()) return true; | |
| if (IsTypedDataView()) { | |
| const auto& backing = | |
| TypedDataBase::Handle(TypedDataView::Cast(*this).typed_data()); | |
| return backing.IsExternalTypedData(); | |
| } | |
| return false; | |
| } | |
| TypedDataViewPtr TypedDataBase::ViewFromTo(intptr_t start, | |
| intptr_t end, | |
| Heap::Space space) const { | |
| const intptr_t len = end - start; | |
| ASSERT(0 <= len); | |
| ASSERT(start < Length()); | |
| ASSERT((start + len) <= Length()); | |
| const intptr_t cid = GetClassId(); | |
| if (IsTypedDataView()) { | |
| const auto& view = TypedDataView::Cast(*this); | |
| const auto& td = TypedDataBase::Handle(view.typed_data()); | |
| const intptr_t view_offset = Smi::Value(view.offset_in_bytes()); | |
| ASSERT(IsTypedDataViewClassId(cid)); | |
| return TypedDataView::New(cid, ExternalTypedData::Cast(td), | |
| view_offset + start, len, Heap::kOld); | |
| } else if (IsExternalTypedData()) { | |
| ASSERT(IsExternalTypedDataClassId(cid)); | |
| ASSERT(IsTypedDataViewClassId(cid - 1)); | |
| return TypedDataView::New(cid - 1, *this, start, len, Heap::kOld); | |
| } | |
| RELEASE_ASSERT(IsTypedData()); | |
| ASSERT(IsExternalTypedDataClassId(cid)); | |
| ASSERT(IsTypedDataViewClassId(cid + 1)); | |
| return TypedDataView::New(cid + 1, *this, start, len, Heap::kOld); | |
| } | |
| const char* TypedDataBase::ToCString() const { | |
| // There are no instances of UntaggedTypedDataBase. | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| const char* TypedDataView::ToCString() const { | |
| const Class& cls = Class::Handle(clazz()); | |
| return cls.ScrubbedNameCString(); | |
| } | |
| const char* ExternalTypedData::ToCString() const { | |
| const Class& cls = Class::Handle(clazz()); | |
| return cls.ScrubbedNameCString(); | |
| } | |
| PointerPtr Pointer::New(uword native_address, Heap::Space space) { | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| IsolateGroup* isolate_group = thread->isolate_group(); | |
| const auto& type_args = TypeArguments::Handle( | |
| zone, isolate_group->object_store()->type_argument_never()); | |
| const Class& cls = | |
| Class::Handle(isolate_group->class_table()->At(kPointerCid)); | |
| cls.EnsureIsAllocateFinalized(thread); | |
| const auto& result = Pointer::Handle(zone, Object::Allocate<Pointer>(space)); | |
| result.SetTypeArguments(type_args); | |
| result.SetNativeAddress(native_address); | |
| return result.ptr(); | |
| } | |
| const char* Pointer::ToCString() const { | |
| return OS::SCreate(Thread::Current()->zone(), "Pointer: address=0x%" Px, | |
| NativeAddress()); | |
| } | |
| DynamicLibraryPtr DynamicLibrary::New(void* handle, | |
| Dart_NativeAssetsDlsymCallback dlsym, | |
| Dart_NativeAssetsDlcloseCallback dlclose, | |
| Heap::Space space) { | |
| const auto& result = | |
| DynamicLibrary::Handle(Object::Allocate<DynamicLibrary>(space)); | |
| ASSERT_EQUAL(result.IsClosed(), false); | |
| result.SetHandle(handle); | |
| result.SetDlsym(dlsym); | |
| result.SetDlclose(dlclose); | |
| return result.ptr(); | |
| } | |
| bool Pointer::IsPointer(const Instance& obj) { | |
| return IsFfiPointerClassId(obj.ptr()->GetClassId()); | |
| } | |
| bool Instance::IsPointer() const { | |
| return Pointer::IsPointer(*this); | |
| } | |
| const char* DynamicLibrary::ToCString() const { | |
| return OS::SCreate(Thread::Current()->zone(), "DynamicLibrary: handle=0x%" Px, | |
| reinterpret_cast<uintptr_t>(GetHandle())); | |
| } | |
| CapabilityPtr Capability::New(uint64_t id, Heap::Space space) { | |
| const auto& result = Capability::Handle(Object::Allocate<Capability>(space)); | |
| result.StoreNonPointer(&result.untag()->id_, id); | |
| return result.ptr(); | |
| } | |
| const char* Capability::ToCString() const { | |
| return "Capability"; | |
| } | |
| ReceivePortPtr ReceivePort::New(Dart_Port id, | |
| const String& debug_name, | |
| Heap::Space space) { | |
| ASSERT(id != ILLEGAL_PORT); | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| const SendPort& send_port = | |
| SendPort::Handle(zone, SendPort::New(id, thread->isolate_group()->id())); | |
| #if !defined(PRODUCT) | |
| const StackTrace& allocation_location_ = | |
| HasStack() ? GetCurrentStackTrace(0) : StackTrace::Handle(); | |
| #endif // !defined(PRODUCT) | |
| const auto& result = | |
| ReceivePort::Handle(zone, Object::Allocate<ReceivePort>(space)); | |
| result.untag()->set_send_port(send_port.ptr()); | |
| result.untag()->set_bitfield( | |
| Smi::New(IsOpen::encode(true) | IsKeepIsolateAlive::encode(true))); | |
| #if !defined(PRODUCT) | |
| result.untag()->set_debug_name(debug_name.ptr()); | |
| result.untag()->set_allocation_location(allocation_location_.ptr()); | |
| #endif // !defined(PRODUCT) | |
| return result.ptr(); | |
| } | |
| const char* ReceivePort::ToCString() const { | |
| return "ReceivePort"; | |
| } | |
| SendPortPtr SendPort::New(Dart_Port id, Heap::Space space) { | |
| return New(id, ILLEGAL_PORT, space); | |
| } | |
| SendPortPtr SendPort::New(Dart_Port id, | |
| Dart_Port origin_id, | |
| Heap::Space space) { | |
| ASSERT(id != ILLEGAL_PORT); | |
| const auto& result = SendPort::Handle(Object::Allocate<SendPort>(space)); | |
| result.StoreNonPointer(&result.untag()->id_, id); | |
| result.StoreNonPointer(&result.untag()->origin_id_, origin_id); | |
| return result.ptr(); | |
| } | |
| const char* SendPort::ToCString() const { | |
| return "SendPort"; | |
| } | |
| static void TransferableTypedDataFinalizer(void* isolate_callback_data, | |
| void* peer) { | |
| delete (reinterpret_cast<TransferableTypedDataPeer*>(peer)); | |
| } | |
| TransferableTypedDataPtr TransferableTypedData::New(uint8_t* data, | |
| intptr_t length) { | |
| auto* const peer = new TransferableTypedDataPeer(data, length); | |
| Thread* thread = Thread::Current(); | |
| const auto& result = | |
| TransferableTypedData::Handle(Object::Allocate<TransferableTypedData>( | |
| thread->heap()->SpaceForExternal(length))); | |
| thread->heap()->SetPeer(result.ptr(), peer); | |
| // Set up finalizer so it frees allocated memory if handle is | |
| // garbage-collected. | |
| FinalizablePersistentHandle* finalizable_ref = | |
| FinalizablePersistentHandle::New(thread->isolate_group(), result, peer, | |
| &TransferableTypedDataFinalizer, length, | |
| /*auto_delete=*/true); | |
| ASSERT(finalizable_ref != nullptr); | |
| peer->set_handle(finalizable_ref); | |
| return result.ptr(); | |
| } | |
| const char* TransferableTypedData::ToCString() const { | |
| return "TransferableTypedData"; | |
| } | |
| bool Closure::CanonicalizeEquals(const Instance& other) const { | |
| if (!other.IsClosure()) return false; | |
| const Closure& other_closure = Closure::Cast(other); | |
| return (instantiator_type_arguments() == | |
| other_closure.instantiator_type_arguments()) && | |
| (function_type_arguments() == | |
| other_closure.function_type_arguments()) && | |
| (delayed_type_arguments() == other_closure.delayed_type_arguments()) && | |
| (function() == other_closure.function()) && | |
| (RawContext() == other_closure.RawContext()); | |
| } | |
| void Closure::CanonicalizeFieldsLocked(Thread* thread) const { | |
| TypeArguments& type_args = TypeArguments::Handle(); | |
| type_args = instantiator_type_arguments(); | |
| if (!type_args.IsNull()) { | |
| type_args = type_args.Canonicalize(thread); | |
| set_instantiator_type_arguments(type_args); | |
| } | |
| type_args = function_type_arguments(); | |
| if (!type_args.IsNull()) { | |
| type_args = type_args.Canonicalize(thread); | |
| set_function_type_arguments(type_args); | |
| } | |
| type_args = delayed_type_arguments(); | |
| if (!type_args.IsNull()) { | |
| type_args = type_args.Canonicalize(thread); | |
| set_delayed_type_arguments(type_args); | |
| } | |
| // Ignore function, context, hash. | |
| } | |
| const char* Closure::ToCString() const { | |
| auto const thread = Thread::Current(); | |
| auto const zone = thread->zone(); | |
| ZoneTextBuffer buffer(zone); | |
| buffer.AddString("Closure: "); | |
| const Function& fun = Function::Handle(zone, function()); | |
| const FunctionType& sig = | |
| FunctionType::Handle(zone, GetInstantiatedSignature(zone)); | |
| sig.Print(kUserVisibleName, &buffer); | |
| if (fun.IsImplicitClosureFunction()) { | |
| buffer.Printf(" from %s", fun.ToCString()); | |
| } | |
| return buffer.buffer(); | |
| } | |
| uword Closure::ComputeHash() const { | |
| Thread* thread = Thread::Current(); | |
| DEBUG_ASSERT(thread->TopErrorHandlerIsExitFrame()); | |
| Zone* zone = thread->zone(); | |
| const Function& func = Function::Handle(zone, function()); | |
| uint32_t result = 0; | |
| if (func.IsImplicitClosureFunction() || func.IsGeneric()) { | |
| // Combine function's hash code, delayed type arguments hash code | |
| // (if generic), and identityHashCode of cached receiver (if implicit | |
| // instance closure). | |
| result = static_cast<uint32_t>(func.Hash()); | |
| if (func.IsGeneric()) { | |
| const TypeArguments& delayed_type_args = | |
| TypeArguments::Handle(zone, delayed_type_arguments()); | |
| result = CombineHashes(result, delayed_type_args.Hash()); | |
| } | |
| if (func.IsImplicitInstanceClosureFunction()) { | |
| const Instance& receiver = | |
| Instance::Handle(zone, GetImplicitClosureReceiver()); | |
| const Integer& receiverHash = | |
| Integer::Handle(zone, receiver.IdentityHashCode(thread)); | |
| result = | |
| CombineHashes(result, static_cast<uint32_t>(receiverHash.Value())); | |
| } | |
| } else { | |
| // Non-implicit closures of non-generic functions are unique, | |
| // so identityHashCode of closure object is good enough. | |
| const Integer& identityHash = | |
| Integer::Handle(zone, this->IdentityHashCode(thread)); | |
| result = static_cast<uint32_t>(identityHash.Value()); | |
| } | |
| return FinalizeHash(result, String::kHashBits); | |
| } | |
| ClosurePtr Closure::New(intptr_t length_and_flags, Heap::Space space) { | |
| const intptr_t num_elements = | |
| UntaggedClosure::LengthBits::decode(length_and_flags); | |
| ASSERT(num_elements >= 0); | |
| if (!IsValidLength(num_elements)) { | |
| // This should be caught before we reach here. | |
| FATAL("Fatal error in Closure::New: invalid num_elements %" Pd "\n", | |
| num_elements); | |
| } | |
| auto raw = Object::Allocate<Closure>(space, num_elements); | |
| NoSafepointScope no_safepoint; | |
| raw->untag()->set_length_and_flags(Smi::New(length_and_flags)); | |
| raw->untag()->set_hash(Smi::New(0)); | |
| if (UntaggedClosure::HasDelayedTypeArgumentsBit::decode(length_and_flags)) { | |
| raw->untag()->set_element(UntaggedClosure::kDelayedTypeArgumentsIndex, | |
| Object::empty_type_arguments().ptr()); | |
| } | |
| return raw; | |
| } | |
| ClosurePtr Closure::New(const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments, | |
| const Function& function, | |
| const Object& context, | |
| Heap::Space space) { | |
| return Closure::New(instantiator_type_arguments, function_type_arguments, | |
| Closure::HasDelayedTypeArgumentsField(function) | |
| ? Object::empty_type_arguments() | |
| : Object::null_type_arguments(), | |
| function, context, space); | |
| } | |
| ClosurePtr Closure::New(const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments, | |
| const TypeArguments& delayed_type_arguments, | |
| const Function& function, | |
| const Object& context, | |
| Heap::Space space) { | |
| ASSERT(instantiator_type_arguments.IsCanonical()); | |
| ASSERT(function_type_arguments.IsCanonical()); | |
| ASSERT(delayed_type_arguments.IsCanonical()); | |
| ASSERT(function.IsClosureFunction()); | |
| ASSERT(FunctionType::Handle(function.signature()).IsCanonical()); | |
| ASSERT( | |
| (function.IsImplicitInstanceClosureFunction() && context.IsInstance()) || | |
| (function.IsNonImplicitClosureFunction() && context.IsContext()) || | |
| context.IsNull()); | |
| const bool has_delayed_type_args = | |
| Closure::HasDelayedTypeArgumentsField(function); | |
| const bool has_instantiator_type_args = | |
| Closure::HasInstantiatorTypeArgumentsField(function); | |
| const bool has_function_type_args = | |
| Closure::HasFunctionTypeArgumentsField(function); | |
| const intptr_t context_index = UntaggedClosure::ContextIndex( | |
| has_delayed_type_args, has_instantiator_type_args, | |
| has_function_type_args); | |
| const intptr_t num_elements = context_index + 1; | |
| const intptr_t length_and_flags = UntaggedClosure::EncodeLengthAndFlags( | |
| has_delayed_type_args, has_instantiator_type_args, has_function_type_args, | |
| num_elements); | |
| const auto& result = Closure::Handle(Closure::New(length_and_flags, space)); | |
| result.set_function(function); | |
| if (has_delayed_type_args) { | |
| result.set_delayed_type_arguments(delayed_type_arguments); | |
| } | |
| if (has_instantiator_type_args) { | |
| result.set_instantiator_type_arguments(instantiator_type_arguments); | |
| } | |
| if (has_function_type_args) { | |
| result.set_function_type_arguments(function_type_arguments); | |
| } | |
| result.SetElementAt(context_index, context); | |
| return result.ptr(); | |
| } | |
| FunctionTypePtr Closure::GetInstantiatedSignature(Zone* zone) const { | |
| const Function& fun = Function::Handle(zone, function()); | |
| FunctionType& sig = FunctionType::Handle(zone, fun.signature()); | |
| TypeArguments& fn_type_args = | |
| TypeArguments::Handle(zone, function_type_arguments()); | |
| const TypeArguments& delayed_type_args = | |
| TypeArguments::Handle(zone, delayed_type_arguments()); | |
| const TypeArguments& inst_type_args = | |
| TypeArguments::Handle(zone, instantiator_type_arguments()); | |
| // We detect the case of a partial tearoff type application and substitute the | |
| // type arguments for the type parameters of the function. | |
| intptr_t num_free_params; | |
| if (!IsGeneric() && fun.IsGeneric()) { | |
| num_free_params = kCurrentAndEnclosingFree; | |
| fn_type_args = delayed_type_args.Prepend( | |
| zone, fn_type_args, sig.NumParentTypeArguments(), | |
| sig.NumTypeParameters() + sig.NumParentTypeArguments()); | |
| } else { | |
| num_free_params = kAllFree; | |
| } | |
| if (num_free_params == kCurrentAndEnclosingFree || !sig.IsInstantiated()) { | |
| sig ^= sig.InstantiateFrom(inst_type_args, fn_type_args, num_free_params, | |
| Heap::kOld); | |
| } | |
| return sig.ptr(); | |
| } | |
| bool StackTrace::skip_sync_start_in_parent_stack() const { | |
| return untag()->skip_sync_start_in_parent_stack; | |
| } | |
| void StackTrace::set_skip_sync_start_in_parent_stack(bool value) const { | |
| StoreNonPointer(&untag()->skip_sync_start_in_parent_stack, value); | |
| } | |
| intptr_t StackTrace::Length() const { | |
| const Array& code_array = Array::Handle(untag()->code_array()); | |
| return code_array.Length(); | |
| } | |
| ObjectPtr StackTrace::CodeAtFrame(intptr_t frame_index) const { | |
| const Array& code_array = Array::Handle(untag()->code_array()); | |
| return code_array.At(frame_index); | |
| } | |
| void StackTrace::SetCodeAtFrame(intptr_t frame_index, | |
| const Object& code) const { | |
| const Array& code_array = Array::Handle(untag()->code_array()); | |
| code_array.SetAt(frame_index, code); | |
| } | |
| uword StackTrace::PcOffsetAtFrame(intptr_t frame_index) const { | |
| const TypedData& pc_offset_array = | |
| TypedData::Handle(untag()->pc_offset_array()); | |
| return pc_offset_array.GetUintPtr(frame_index * kWordSize); | |
| } | |
| void StackTrace::SetPcOffsetAtFrame(intptr_t frame_index, | |
| uword pc_offset) const { | |
| const TypedData& pc_offset_array = | |
| TypedData::Handle(untag()->pc_offset_array()); | |
| pc_offset_array.SetUintPtr(frame_index * kWordSize, pc_offset); | |
| } | |
| void StackTrace::set_async_link(const StackTrace& async_link) const { | |
| untag()->set_async_link(async_link.ptr()); | |
| } | |
| void StackTrace::set_code_array(const Array& code_array) const { | |
| untag()->set_code_array(code_array.ptr()); | |
| } | |
| void StackTrace::set_pc_offset_array(const TypedData& pc_offset_array) const { | |
| untag()->set_pc_offset_array(pc_offset_array.ptr()); | |
| } | |
| void StackTrace::set_expand_inlined(bool value) const { | |
| StoreNonPointer(&untag()->expand_inlined_, value); | |
| } | |
| bool StackTrace::expand_inlined() const { | |
| return untag()->expand_inlined_; | |
| } | |
| StackTracePtr StackTrace::New(const Array& code_array, | |
| const TypedData& pc_offset_array, | |
| Heap::Space space) { | |
| const auto& result = StackTrace::Handle(Object::Allocate<StackTrace>(space)); | |
| result.set_code_array(code_array); | |
| result.set_pc_offset_array(pc_offset_array); | |
| result.set_expand_inlined(true); // default. | |
| ASSERT_EQUAL(result.skip_sync_start_in_parent_stack(), false); | |
| return result.ptr(); | |
| } | |
| StackTracePtr StackTrace::New(const Array& code_array, | |
| const TypedData& pc_offset_array, | |
| const StackTrace& async_link, | |
| bool skip_sync_start_in_parent_stack, | |
| Heap::Space space) { | |
| const auto& result = StackTrace::Handle(Object::Allocate<StackTrace>(space)); | |
| result.set_async_link(async_link); | |
| result.set_code_array(code_array); | |
| result.set_pc_offset_array(pc_offset_array); | |
| result.set_expand_inlined(true); // default. | |
| result.set_skip_sync_start_in_parent_stack(skip_sync_start_in_parent_stack); | |
| return result.ptr(); | |
| } | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| static bool TryPrintNonSymbolicStackFrameBodyRelative( | |
| BaseTextBuffer* buffer, | |
| uword call_addr, | |
| uword instructions, | |
| LoadingUnit* unit = nullptr) { | |
| const TextImage image(reinterpret_cast<const uint8_t*>(instructions)); | |
| if (!image.contains(call_addr)) return false; | |
| if (unit != nullptr) { | |
| ASSERT(!unit->IsNull()); | |
| // Add the unit ID to the stack frame, so the correct loading unit | |
| // information from the header can be checked. | |
| buffer->Printf(" unit %" Pd "", unit->id()); | |
| } | |
| auto const offset = call_addr - image.instructions_address(); | |
| // Only print the relocated address of the call when we know the saved | |
| // debugging information (if any) will have the same relocated address. | |
| // Also only print 'virt' fields for isolate addresses. | |
| if (image.compiled_to_shared_object()) { | |
| const uword relocated_section_start = | |
| image.instructions_relocated_address(); | |
| buffer->Printf(" virt %" Pp "", relocated_section_start + offset); | |
| } | |
| const char* symbol = kSnapshotTextAsmSymbol; | |
| buffer->Printf(" %s+0x%" Px "\n", symbol, offset); | |
| return true; | |
| } | |
| // Prints the best representation(s) for the call address. | |
| static void PrintNonSymbolicStackFrameBody(BaseTextBuffer* buffer, | |
| uword call_addr, | |
| uword isolate_instructions, | |
| const Array& loading_units, | |
| LoadingUnit* unit) { | |
| if (!loading_units.IsNull()) { | |
| // All non-VM stack frames should include the loading unit id. | |
| const intptr_t unit_count = loading_units.Length(); | |
| for (intptr_t i = LoadingUnit::kRootId; i < unit_count; i++) { | |
| *unit ^= loading_units.At(i); | |
| if (!unit->has_instructions_image()) continue; | |
| auto const instructions = | |
| reinterpret_cast<uword>(unit->instructions_image()); | |
| if (TryPrintNonSymbolicStackFrameBodyRelative(buffer, call_addr, | |
| instructions, unit)) { | |
| return; | |
| } | |
| } | |
| } else { | |
| if (TryPrintNonSymbolicStackFrameBodyRelative(buffer, call_addr, | |
| isolate_instructions)) { | |
| return; | |
| } | |
| } | |
| // The stack trace printer should never end up here, since these are not | |
| // addresses within a loading unit or the VM or app isolate instructions | |
| // sections. Thus, make it easy to notice when looking at the stack trace. | |
| buffer->Printf(" <invalid Dart instruction address>\n"); | |
| } | |
| #endif | |
| static void PrintSymbolicStackFrameIndex(BaseTextBuffer* buffer, | |
| intptr_t frame_index) { | |
| buffer->Printf("#%-6" Pd "", frame_index); | |
| } | |
| static void PrintSymbolicStackFrameBody(BaseTextBuffer* buffer, | |
| const char* function_name, | |
| const char* url, | |
| intptr_t line = -1, | |
| intptr_t column = -1) { | |
| buffer->Printf(" %s (%s", function_name, url); | |
| if (line >= 0) { | |
| buffer->Printf(":%" Pd "", line); | |
| if (column >= 0) { | |
| buffer->Printf(":%" Pd "", column); | |
| } | |
| } | |
| buffer->Printf(")\n"); | |
| } | |
| static void PrintSymbolicStackFrame(Zone* zone, | |
| BaseTextBuffer* buffer, | |
| const Function& function, | |
| TokenPosition token_pos_or_line, | |
| intptr_t frame_index, | |
| bool is_line = false) { | |
| ASSERT(!function.IsNull()); | |
| const auto& script = Script::Handle(zone, function.script()); | |
| const char* function_name = function.QualifiedUserVisibleNameCString(); | |
| const char* url = script.IsNull() | |
| ? "Kernel" | |
| : String::Handle(zone, script.url()).ToCString(); | |
| // If the URI starts with "data:application/dart;" this is a URI encoded | |
| // script so we shouldn't print the entire URI because it could be very long. | |
| if (strstr(url, "data:application/dart;") == url) { | |
| url = "<data:application/dart>"; | |
| } | |
| intptr_t line = -1; | |
| intptr_t column = -1; | |
| if (is_line) { | |
| ASSERT(token_pos_or_line.IsNoSource() || token_pos_or_line.IsReal()); | |
| if (token_pos_or_line.IsReal()) { | |
| line = token_pos_or_line.Pos(); | |
| } | |
| } else { | |
| ASSERT(!script.IsNull()); | |
| script.GetTokenLocation(token_pos_or_line, &line, &column); | |
| } | |
| PrintSymbolicStackFrameIndex(buffer, frame_index); | |
| PrintSymbolicStackFrameBody(buffer, function_name, url, line, column); | |
| } | |
| static bool IsVisibleAsFutureListener(const Function& function) { | |
| if (function.is_visible()) { | |
| return true; | |
| } | |
| if (function.IsImplicitClosureFunction()) { | |
| return function.parent_function() == Function::null() || | |
| Function::is_visible(function.parent_function()); | |
| } | |
| return false; | |
| } | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| static void WriteImageBuildId(BaseTextBuffer* buffer, | |
| const char* prefix, | |
| uword image_address) { | |
| const auto& build_id = OS::GetAppBuildId(image_address); | |
| if (build_id.data != nullptr) { | |
| ASSERT(build_id.len > 0); | |
| buffer->AddString(prefix); | |
| buffer->AddString("'"); | |
| for (intptr_t i = 0; i < build_id.len; i++) { | |
| buffer->Printf("%2.2x", build_id.data[i]); | |
| } | |
| buffer->AddString("'"); | |
| } | |
| } | |
| void WriteStackTraceHeaderLoadingUnitEntry(BaseTextBuffer* buffer, | |
| intptr_t id, | |
| uword dso_base, | |
| uword instructions) { | |
| buffer->Printf("loading_unit: %" Pd "", id); | |
| WriteImageBuildId(buffer, ", build_id: ", instructions); | |
| buffer->Printf(", dso_base: %" Px ", instructions: %" Px "\n", dso_base, | |
| instructions); | |
| } | |
| #endif | |
| const char* StackTrace::ToCString() const { | |
| auto const T = Thread::Current(); | |
| auto const zone = T->zone(); | |
| auto& stack_trace = StackTrace::Handle(zone, this->ptr()); | |
| auto& owner = Object::Handle(zone); | |
| auto& function = Function::Handle(zone); | |
| auto& code_object = Object::Handle(zone); | |
| auto& code = Code::Handle(zone); | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| const Array& loading_units = | |
| Array::Handle(T->isolate_group()->object_store()->loading_units()); | |
| auto* const unit = | |
| loading_units.IsNull() ? nullptr : &LoadingUnit::Handle(zone); | |
| #endif | |
| NoSafepointScope no_allocation; | |
| GrowableArray<const Function*> inlined_functions; | |
| GrowableArray<TokenPosition> inlined_token_positions; | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| GrowableArray<void*> addresses(10); | |
| const bool have_footnote_callback = | |
| T->isolate_group()->dwarf_stack_traces() && | |
| Dart::dwarf_stacktrace_footnote_callback() != nullptr; | |
| #endif | |
| ZoneTextBuffer buffer(zone, 1024); | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| auto const isolate_instructions = | |
| reinterpret_cast<uword>(T->isolate_group()->source()->snapshot_text); | |
| #if defined(DEBUG) | |
| if (!loading_units.IsNull()) { | |
| *unit ^= loading_units.At(LoadingUnit::kRootId); | |
| ASSERT(!unit->IsNull()); | |
| ASSERT(unit->has_instructions_image()); | |
| ASSERT(reinterpret_cast<uword>(unit->instructions_image()) == | |
| isolate_instructions); | |
| } | |
| #endif | |
| if (T->isolate_group()->dwarf_stack_traces()) { | |
| // This prologue imitates Android's debuggerd to make it possible to paste | |
| // the stack trace into ndk-stack. | |
| buffer.Printf( | |
| "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"); | |
| OSThread* thread = OSThread::Current(); | |
| buffer.Printf("pid: %" Pd ", tid: %" Pd ", name %s\n", OS::ProcessId(), | |
| OSThread::ThreadIdToIntPtr(thread->id()), thread->name()); | |
| #if defined(DART_COMPRESSED_POINTERS) | |
| const char kCompressedPointers[] = "yes"; | |
| #else | |
| const char kCompressedPointers[] = "no"; | |
| #endif | |
| #if defined(DART_INCLUDE_SIMULATOR) | |
| const char kUsingSimulator[] = "yes"; | |
| #else | |
| const char kUsingSimulator[] = "no"; | |
| #endif | |
| buffer.Printf("os: %s arch: %s comp: %s sim: %s", kHostOperatingSystemName, | |
| kTargetArchitectureName, kCompressedPointers, | |
| kUsingSimulator); | |
| WriteImageBuildId(&buffer, "\nbuild_id: ", isolate_instructions); | |
| buffer.AddString("\n"); | |
| if (!loading_units.IsNull()) { | |
| const intptr_t unit_count = loading_units.Length(); | |
| for (intptr_t i = LoadingUnit::kRootId; i < unit_count; i++) { | |
| *unit ^= loading_units.At(i); | |
| if (!unit->has_instructions_image()) continue; | |
| const uword instructions = | |
| reinterpret_cast<uword>(unit->instructions_image()); | |
| const uword dso_base = OS::GetAppDSOBase(instructions); | |
| WriteStackTraceHeaderLoadingUnitEntry(&buffer, i, dso_base, | |
| instructions); | |
| } | |
| } | |
| // Print the dso_base of the VM and isolate_instructions. We print both here | |
| // as the VM and isolate may be loaded from different snapshot images. | |
| const uword isolate_dso_base = OS::GetAppDSOBase(isolate_instructions); | |
| buffer.Printf("isolate_dso_base: %" Px "", isolate_dso_base); | |
| buffer.Printf(", vm_dso_base: 0\n"); | |
| buffer.Printf("isolate_instructions: %" Px "", | |
| TextImage(isolate_instructions).instructions_address()); | |
| buffer.Printf(", vm_instructions: 0\n"); | |
| } | |
| #endif | |
| // Iterate through the stack frames and create C string description | |
| // for each frame. | |
| intptr_t frame_index = 0; | |
| uint32_t frame_skip = 0; | |
| // If we're already in a gap, don't print multiple gap markers. | |
| bool in_gap = false; | |
| do { | |
| for (intptr_t i = frame_skip; i < stack_trace.Length(); i++) { | |
| code_object = stack_trace.CodeAtFrame(i); | |
| if (code_object.IsNull()) { | |
| // Check for a null function, which indicates a gap in a StackOverflow | |
| // or OutOfMemory trace. | |
| if ((i < (stack_trace.Length() - 1)) && | |
| (stack_trace.CodeAtFrame(i + 1) != Code::null())) { | |
| buffer.AddString("...\n...\n"); | |
| // To account for gap frames. | |
| frame_index += stack_trace.PcOffsetAtFrame(i); | |
| } | |
| continue; | |
| } | |
| if (code_object.ptr() == StubCode::AsynchronousGapMarker().ptr()) { | |
| if (!in_gap) { | |
| buffer.AddString("<asynchronous suspension>\n"); | |
| } | |
| in_gap = true; | |
| continue; | |
| } | |
| uword pc_offset = stack_trace.PcOffsetAtFrame(i); | |
| const bool is_future_listener = | |
| pc_offset == StackTraceUtils::kFutureListenerPcOffset; | |
| // A visible frame ends any gap we might be in. | |
| in_gap = false; | |
| #if defined(DART_DYNAMIC_MODULES) | |
| if (code_object.IsBytecode()) { | |
| const auto& bytecode = Bytecode::Cast(code_object); | |
| // Shared bytecode stubs should never show up in stack traces, as | |
| // they do not have a unique function to which they are attached. | |
| // Usually the bytecode stub is being executed, and so the function | |
| // that should be reported in the stack trace is also on the stack. | |
| if (!bytecode.IsSharedBytecodeStub()) { | |
| function = bytecode.function(); | |
| if (FLAG_show_invisible_frames || function.is_visible()) { | |
| auto const pos = | |
| bytecode.GetTokenIndexOfPC(bytecode.PayloadStart() + pc_offset); | |
| PrintSymbolicStackFrame(zone, &buffer, function, pos, frame_index); | |
| frame_index++; | |
| } | |
| } else { | |
| // One case where this is not true is for future listeners, in which | |
| // case AsyncAwareStackUnwinder::Unwind substitutes the code or | |
| // bytecode of the target function in place of the shared stub. | |
| ASSERT(!is_future_listener); | |
| } | |
| continue; | |
| } | |
| #endif // defined(DART_DYNAMIC_MODULES) | |
| ASSERT(code_object.IsCode()); | |
| code ^= code_object.ptr(); | |
| ASSERT(code.IsFunctionCode()); | |
| owner = code.owner(); | |
| if (owner.IsFunction()) { | |
| function ^= owner.ptr(); | |
| } else { | |
| function = Function::null(); | |
| } | |
| uword pc = code.PayloadStart() + pc_offset; | |
| if (is_future_listener) { | |
| // Note that the value of pc does not make sense for closures registered | |
| // as future listeners, since the pc_offset is a special marker. Uses of | |
| // pc or pc_offset below should special case when is_future_listener is | |
| // true, so both are set to invalid values to detect misuses. | |
| pc = 0; | |
| pc_offset = -code.PayloadStart(); | |
| } | |
| #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 entry point of the closure is used as the | |
| // call address. | |
| const uword call_addr = is_future_listener ? code.EntryPoint() : pc - 1; | |
| if (T->isolate_group()->dwarf_stack_traces()) { | |
| if (have_footnote_callback) { | |
| addresses.Add(reinterpret_cast<void*>(call_addr)); | |
| } | |
| // This output is formatted like Android's debuggerd. Note debuggerd | |
| // prints call addresses instead of return addresses. | |
| buffer.Printf(" #%02" Pd " abs %" Pp "", frame_index, call_addr); | |
| PrintNonSymbolicStackFrameBody(&buffer, call_addr, isolate_instructions, | |
| loading_units, unit); | |
| frame_index++; | |
| continue; | |
| } | |
| if (function.IsNull()) { | |
| in_gap = false; | |
| // We can't print the symbolic information since the owner was not | |
| // retained, so instead print the static symbol + offset like the | |
| // non-symbolic stack traces. | |
| PrintSymbolicStackFrameIndex(&buffer, frame_index); | |
| PrintNonSymbolicStackFrameBody(&buffer, call_addr, isolate_instructions, | |
| loading_units, unit); | |
| frame_index++; | |
| continue; | |
| } | |
| #endif | |
| if (code.is_optimized() && stack_trace.expand_inlined() && | |
| (FLAG_precompiled_mode || !is_future_listener)) { | |
| // Note: In AOT mode EmitFunctionEntrySourcePositionDescriptorIfNeeded | |
| // will take care of emitting a descriptor that would allow us to | |
| // symbolize stack frame with 0 offset. | |
| code.GetInlinedFunctionsAtReturnAddress( | |
| is_future_listener ? 0 : pc_offset, &inlined_functions, | |
| &inlined_token_positions); | |
| ASSERT(inlined_functions.length() >= 1); | |
| for (intptr_t j = inlined_functions.length() - 1; j >= 0; j--) { | |
| function = inlined_functions[j]->ptr(); | |
| auto const pos = inlined_token_positions[j]; | |
| if (is_future_listener && function.IsImplicitClosureFunction()) { | |
| function = function.parent_function(); | |
| } | |
| if (FLAG_show_invisible_frames || function.is_visible()) { | |
| PrintSymbolicStackFrame(zone, &buffer, function, pos, frame_index, | |
| /*is_line=*/FLAG_precompiled_mode); | |
| frame_index++; | |
| } | |
| } | |
| continue; | |
| } | |
| if (FLAG_show_invisible_frames || function.is_visible() || | |
| (is_future_listener && IsVisibleAsFutureListener(function))) { | |
| auto const pos = is_future_listener ? function.token_pos() | |
| : code.GetTokenIndexOfPC(pc); | |
| PrintSymbolicStackFrame(zone, &buffer, function, pos, frame_index); | |
| frame_index++; | |
| } | |
| } | |
| // Follow the link. | |
| frame_skip = stack_trace.skip_sync_start_in_parent_stack() | |
| ? StackTrace::kSyncAsyncCroppedFrames | |
| : 0; | |
| stack_trace = stack_trace.async_link(); | |
| } while (!stack_trace.IsNull()); | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| if (have_footnote_callback) { | |
| char* footnote = Dart::dwarf_stacktrace_footnote_callback()( | |
| &addresses[0], addresses.length()); | |
| if (footnote != nullptr) { | |
| buffer.AddString(footnote); | |
| free(footnote); | |
| } | |
| } | |
| #endif | |
| return buffer.buffer(); | |
| } | |
| static void DwarfStackTracesHandler(bool value) { | |
| FLAG_dwarf_stack_traces_mode = value; | |
| #if defined(PRODUCT) | |
| // We can safely remove function objects in precompiled snapshots if the | |
| // runtime will generate DWARF stack traces and we don't have runtime | |
| // debugging options like the VM service available. | |
| if (value) { | |
| FLAG_retain_function_objects = false; | |
| FLAG_retain_code_objects = false; | |
| } | |
| #endif | |
| } | |
| DEFINE_FLAG_HANDLER(DwarfStackTracesHandler, | |
| dwarf_stack_traces, | |
| "Omit CodeSourceMaps in precompiled snapshots and don't " | |
| "symbolize stack traces in the precompiled runtime."); | |
| SuspendStatePtr SuspendState::New(intptr_t frame_size, | |
| const Instance& function_data, | |
| Heap::Space space) { | |
| ASSERT(frame_size >= 0); | |
| const intptr_t num_elements = frame_size + SuspendState::FrameSizeGrowthGap(); | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| // Include heap object alignment overhead into the frame capacity. | |
| const intptr_t instance_size = SuspendState::InstanceSize(num_elements); | |
| const intptr_t frame_capacity = | |
| instance_size - SuspendState::payload_offset(); | |
| ASSERT(SuspendState::InstanceSize(frame_capacity) == instance_size); | |
| ASSERT(frame_size <= frame_capacity); | |
| #endif | |
| auto raw = Object::Allocate<SuspendState>(space, num_elements); | |
| NoSafepointScope no_safepoint; | |
| ASSERT_EQUAL(raw->untag()->pc_, 0); | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| raw->untag()->frame_capacity_ = frame_capacity; | |
| #endif | |
| raw->untag()->frame_size_ = frame_size; | |
| raw->untag()->set_function_data(function_data.ptr()); | |
| return raw; | |
| } | |
| SuspendStatePtr SuspendState::Clone(Thread* thread, | |
| const SuspendState& src, | |
| Heap::Space space) { | |
| ASSERT(src.pc() != 0); | |
| Zone* zone = thread->zone(); | |
| const intptr_t frame_size = src.frame_size(); | |
| const SuspendState& dst = SuspendState::Handle( | |
| zone, | |
| SuspendState::New(frame_size, Instance::Handle(zone, src.function_data()), | |
| space)); | |
| dst.set_then_callback(Closure::Handle(zone, src.then_callback())); | |
| dst.set_error_callback(Closure::Handle(zone, src.error_callback())); | |
| { | |
| NoSafepointScope no_safepoint; | |
| memmove(dst.payload(), src.payload(), frame_size); | |
| // Update value of :suspend_state variable in the copied frame. | |
| const uword fp = reinterpret_cast<uword>(dst.payload() + frame_size); | |
| *reinterpret_cast<ObjectPtr*>( | |
| LocalVarAddress(fp, runtime_frame_layout.FrameSlotForVariableIndex( | |
| kSuspendStateVarIndex))) = dst.ptr(); | |
| dst.set_pc(src.pc()); | |
| // Trigger write barrier if needed. | |
| if (dst.ptr()->IsOldObject()) { | |
| dst.untag()->EnsureInRememberedSet(thread); | |
| } | |
| if (thread->is_marking()) { | |
| thread->DeferredMarkingStackAddObject(dst.ptr()); | |
| } | |
| } | |
| return dst.ptr(); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| void SuspendState::set_frame_capacity(intptr_t frame_capcity) const { | |
| ASSERT(frame_capcity >= 0); | |
| StoreNonPointer(&untag()->frame_capacity_, frame_capcity); | |
| } | |
| #endif | |
| void SuspendState::set_frame_size(intptr_t frame_size) const { | |
| ASSERT(frame_size >= 0); | |
| StoreNonPointer(&untag()->frame_size_, frame_size); | |
| } | |
| void SuspendState::set_pc(uword pc) const { | |
| StoreNonPointer(&untag()->pc_, pc); | |
| } | |
| void SuspendState::set_function_data(const Instance& function_data) const { | |
| untag()->set_function_data(function_data.ptr()); | |
| } | |
| void SuspendState::set_then_callback(const Closure& then_callback) const { | |
| untag()->set_then_callback(then_callback.ptr()); | |
| } | |
| void SuspendState::set_error_callback(const Closure& error_callback) const { | |
| untag()->set_error_callback(error_callback.ptr()); | |
| } | |
| const char* SuspendState::ToCString() const { | |
| return "SuspendState"; | |
| } | |
| CodePtr SuspendState::GetCodeObject() const { | |
| ASSERT(pc() != 0); | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| NoSafepointScope no_safepoint; | |
| CodePtr code = ReversePc::Lookup(IsolateGroup::Current(), pc(), | |
| /*is_return_address=*/true); | |
| ASSERT(code != Code::null()); | |
| return code; | |
| #else | |
| ObjectPtr code = *(reinterpret_cast<ObjectPtr*>( | |
| untag()->payload() + untag()->frame_size_ + | |
| runtime_frame_layout.code_from_fp * kWordSize)); | |
| return Code::RawCast(code); | |
| #endif // defined(DART_PRECOMPILED_RUNTIME) | |
| } | |
| void RegExp::set_pattern(const String& pattern) const { | |
| untag()->set_pattern(pattern.ptr()); | |
| } | |
| void RegExp::set_bytecode(bool is_one_byte, | |
| bool sticky, | |
| const TypedData& bytecode) const { | |
| if (sticky) { | |
| if (is_one_byte) { | |
| untag()->set_one_byte_sticky<std::memory_order_release>(bytecode.ptr()); | |
| } else { | |
| untag()->set_two_byte_sticky<std::memory_order_release>(bytecode.ptr()); | |
| } | |
| } else { | |
| if (is_one_byte) { | |
| untag()->set_one_byte<std::memory_order_release>(bytecode.ptr()); | |
| } else { | |
| untag()->set_two_byte<std::memory_order_release>(bytecode.ptr()); | |
| } | |
| } | |
| } | |
| void RegExp::set_capture_name_map(const Array& array) const { | |
| untag()->set_capture_name_map<std::memory_order_release>(array.ptr()); | |
| } | |
| RegExpPtr RegExp::New(const String& pattern, RegExpFlags flags) { | |
| const auto& result = RegExp::Handle(Object::Allocate<RegExp>(Heap::kNew)); | |
| result.set_pattern(pattern); | |
| result.set_flags(flags); | |
| result.set_num_bracket_expressions(-1); | |
| result.set_num_registers(/*is_one_byte=*/false, -1); | |
| result.set_num_registers(/*is_one_byte=*/true, -1); | |
| return result.ptr(); | |
| } | |
| const char* FlagsToCString(RegExpFlags flags) { | |
| switch (flags & ~RegExpFlag::kGlobal) { | |
| case RegExpFlag::kIgnoreCase | RegExpFlag::kMultiline | | |
| RegExpFlag::kDotAll | RegExpFlag::kUnicode: | |
| return "imsu"; | |
| case RegExpFlag::kIgnoreCase | RegExpFlag::kMultiline | RegExpFlag::kDotAll: | |
| return "ims"; | |
| case RegExpFlag::kIgnoreCase | RegExpFlag::kMultiline | | |
| RegExpFlag::kUnicode: | |
| return "imu"; | |
| case RegExpFlag::kIgnoreCase | RegExpFlag::kUnicode | RegExpFlag::kDotAll: | |
| return "ius"; | |
| case RegExpFlag::kMultiline | RegExpFlag::kDotAll | RegExpFlag::kUnicode: | |
| return "msu"; | |
| case RegExpFlag::kIgnoreCase | RegExpFlag::kMultiline: | |
| return "im"; | |
| case RegExpFlag::kIgnoreCase | RegExpFlag::kDotAll: | |
| return "is"; | |
| case RegExpFlag::kIgnoreCase | RegExpFlag::kUnicode: | |
| return "iu"; | |
| case RegExpFlag::kMultiline | RegExpFlag::kDotAll: | |
| return "ms"; | |
| case RegExpFlag::kMultiline | RegExpFlag::kUnicode: | |
| return "mu"; | |
| case RegExpFlag::kDotAll | RegExpFlag::kUnicode: | |
| return "su"; | |
| case RegExpFlags(RegExpFlag::kIgnoreCase): | |
| return "i"; | |
| case RegExpFlags(RegExpFlag::kMultiline): | |
| return "m"; | |
| case RegExpFlags(RegExpFlag::kDotAll): | |
| return "s"; | |
| case RegExpFlags(RegExpFlag::kUnicode): | |
| return "u"; | |
| default: | |
| break; | |
| } | |
| return ""; | |
| } | |
| bool RegExp::CanonicalizeEquals(const Instance& other) const { | |
| if (this->ptr() == other.ptr()) { | |
| return true; // "===". | |
| } | |
| if (other.IsNull() || !other.IsRegExp()) { | |
| return false; | |
| } | |
| const RegExp& other_js = RegExp::Cast(other); | |
| // Match the pattern. | |
| const String& str1 = String::Handle(pattern()); | |
| const String& str2 = String::Handle(other_js.pattern()); | |
| if (!str1.Equals(str2)) { | |
| return false; | |
| } | |
| // Match the flags. | |
| if (flags() != other_js.flags()) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| uint32_t RegExp::CanonicalizeHash() const { | |
| // Must agree with RegExpKey::Hash. | |
| return CombineHashes(String::Hash(pattern()), flags()); | |
| } | |
| const char* RegExp::ToCString() const { | |
| const String& str = String::Handle(pattern()); | |
| return OS::SCreate(Thread::Current()->zone(), "RegExp: pattern=%s flags=%s", | |
| str.ToCString(), FlagsToCString(flags())); | |
| } | |
| WeakPropertyPtr WeakProperty::New(Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->weak_property_class() != | |
| Class::null()); | |
| return Object::Allocate<WeakProperty>(space); | |
| } | |
| const char* WeakProperty::ToCString() const { | |
| return "_WeakProperty"; | |
| } | |
| WeakReferencePtr WeakReference::New(Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->weak_reference_class() != | |
| Class::null()); | |
| return Object::Allocate<WeakReference>(space); | |
| } | |
| const char* WeakReference::ToCString() const { | |
| TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments()); | |
| String& type_args_name = String::Handle(type_args.UserVisibleName()); | |
| return OS::SCreate(Thread::Current()->zone(), "_WeakReference%s", | |
| type_args_name.ToCString()); | |
| } | |
| const char* FinalizerBase::ToCString() const { | |
| return "FinalizerBase"; | |
| } | |
| FinalizerPtr Finalizer::New(Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->finalizer_class() != | |
| Class::null()); | |
| ASSERT( | |
| Class::Handle(IsolateGroup::Current()->object_store()->finalizer_class()) | |
| .EnsureIsAllocateFinalized(Thread::Current()) == Error::null()); | |
| return Object::Allocate<Finalizer>(space); | |
| } | |
| const char* Finalizer::ToCString() const { | |
| TypeArguments& type_args = TypeArguments::Handle(GetTypeArguments()); | |
| String& type_args_name = String::Handle(type_args.UserVisibleName()); | |
| return OS::SCreate(Thread::Current()->zone(), "_FinalizerImpl%s", | |
| type_args_name.ToCString()); | |
| } | |
| NativeFinalizerPtr NativeFinalizer::New(Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->native_finalizer_class() != | |
| Class::null()); | |
| ASSERT(Class::Handle( | |
| IsolateGroup::Current()->object_store()->native_finalizer_class()) | |
| .EnsureIsAllocateFinalized(Thread::Current()) == Error::null()); | |
| return Object::Allocate<NativeFinalizer>(space); | |
| } | |
| // Runs the finalizer if not detached, detaches the value and set external size | |
| // to 0. | |
| // TODO(http://dartbug.com/47777): Can this be merged with | |
| // RunNativeFinalizerCallback? | |
| void NativeFinalizer::RunCallback(const FinalizerEntry& entry, | |
| const char* trace_context) const { | |
| Thread* const thread = Thread::Current(); | |
| Zone* const zone = thread->zone(); | |
| IsolateGroup* const group = thread->isolate_group(); | |
| const intptr_t external_size = entry.external_size(); | |
| const auto& token_object = Object::Handle(zone, entry.token()); | |
| const auto& callback_pointer = Pointer::Handle(zone, this->callback()); | |
| const auto callback = reinterpret_cast<NativeFinalizer::Callback>( | |
| callback_pointer.NativeAddress()); | |
| if (token_object.IsFinalizerEntry()) { | |
| // Detached from Dart code. | |
| ASSERT(token_object.ptr() == entry.ptr()); | |
| ASSERT(external_size == 0); | |
| if (FLAG_trace_finalizers) { | |
| THR_Print( | |
| "%s: Not running native finalizer %p callback %p, " | |
| "detached\n", | |
| trace_context, ptr()->untag(), callback); | |
| } | |
| } else { | |
| const auto& token = Pointer::Cast(token_object); | |
| void* peer = reinterpret_cast<void*>(token.NativeAddress()); | |
| if (FLAG_trace_finalizers) { | |
| THR_Print( | |
| "%s: Running native finalizer %p callback %p " | |
| "with token %p\n", | |
| trace_context, ptr()->untag(), callback, peer); | |
| } | |
| entry.set_token(entry); | |
| callback(peer); | |
| if (external_size > 0) { | |
| ASSERT(!entry.value()->IsSmi()); | |
| Heap::Space space = | |
| entry.value()->IsOldObject() ? Heap::kOld : Heap::kNew; | |
| if (FLAG_trace_finalizers) { | |
| THR_Print("%s: Clearing external size %" Pd " bytes in %s space\n", | |
| trace_context, external_size, space == 0 ? "new" : "old"); | |
| } | |
| group->heap()->FreedExternal(external_size, space); | |
| entry.set_external_size(0); | |
| } | |
| } | |
| } | |
| const char* NativeFinalizer::ToCString() const { | |
| const auto& pointer = Pointer::Handle(callback()); | |
| return OS::SCreate(Thread::Current()->zone(), "_NativeFinalizer %s", | |
| pointer.ToCString()); | |
| } | |
| FinalizerEntryPtr FinalizerEntry::New(const FinalizerBase& finalizer, | |
| Heap::Space space) { | |
| ASSERT(IsolateGroup::Current()->object_store()->finalizer_entry_class() != | |
| Class::null()); | |
| const auto& entry = | |
| FinalizerEntry::Handle(Object::Allocate<FinalizerEntry>(space)); | |
| ASSERT_EQUAL(entry.external_size(), 0); | |
| entry.set_finalizer(finalizer); | |
| return entry.ptr(); | |
| } | |
| void FinalizerEntry::set_finalizer(const FinalizerBase& value) const { | |
| untag()->set_finalizer(value.ptr()); | |
| } | |
| const char* FinalizerEntry::ToCString() const { | |
| return "FinalizerEntry"; | |
| } | |
| AbstractTypePtr MirrorReference::GetAbstractTypeReferent() const { | |
| ASSERT(Object::Handle(referent()).IsAbstractType()); | |
| return AbstractType::Cast(Object::Handle(referent())).ptr(); | |
| } | |
| ClassPtr MirrorReference::GetClassReferent() const { | |
| ASSERT(Object::Handle(referent()).IsClass()); | |
| return Class::Cast(Object::Handle(referent())).ptr(); | |
| } | |
| FieldPtr MirrorReference::GetFieldReferent() const { | |
| ASSERT(Object::Handle(referent()).IsField()); | |
| return Field::Cast(Object::Handle(referent())).ptr(); | |
| } | |
| FunctionPtr MirrorReference::GetFunctionReferent() const { | |
| ASSERT(Object::Handle(referent()).IsFunction()); | |
| return Function::Cast(Object::Handle(referent())).ptr(); | |
| } | |
| FunctionTypePtr MirrorReference::GetFunctionTypeReferent() const { | |
| ASSERT(Object::Handle(referent()).IsFunctionType()); | |
| return FunctionType::Cast(Object::Handle(referent())).ptr(); | |
| } | |
| LibraryPtr MirrorReference::GetLibraryReferent() const { | |
| ASSERT(Object::Handle(referent()).IsLibrary()); | |
| return Library::Cast(Object::Handle(referent())).ptr(); | |
| } | |
| TypeParameterPtr MirrorReference::GetTypeParameterReferent() const { | |
| ASSERT(Object::Handle(referent()).IsTypeParameter()); | |
| return TypeParameter::Cast(Object::Handle(referent())).ptr(); | |
| } | |
| MirrorReferencePtr MirrorReference::New(const Object& referent, | |
| Heap::Space space) { | |
| const auto& result = | |
| MirrorReference::Handle(Object::Allocate<MirrorReference>(space)); | |
| result.set_referent(referent); | |
| return result.ptr(); | |
| } | |
| const char* MirrorReference::ToCString() const { | |
| return "_MirrorReference"; | |
| } | |
| UserTagPtr UserTag::MakeActive() const { | |
| Thread* thread = Thread::Current(); | |
| ASSERT(thread != nullptr); | |
| UserTag& old = UserTag::Handle(thread->current_tag()); | |
| thread->set_current_tag(*this); | |
| #if !defined(PRODUCT) | |
| // Notify VM service clients that the current UserTag has changed. | |
| if (Service::profiler_stream.enabled()) { | |
| Isolate* isolate = thread->scheduled_dart_mutator_isolate(); | |
| if (isolate != nullptr) { | |
| ServiceEvent event(isolate, ServiceEvent::kUserTagChanged); | |
| String& name = String::Handle(old.label()); | |
| event.set_previous_tag(name.ToCString()); | |
| name ^= label(); | |
| event.set_updated_tag(name.ToCString()); | |
| Service::HandleEvent(&event); | |
| } | |
| } | |
| #endif // !defined(PRODUCT) | |
| return old.ptr(); | |
| } | |
| UserTagPtr UserTag::New(Thread* thread, | |
| const String& label, | |
| Heap::Space space) { | |
| auto isolate_group = thread->isolate_group(); | |
| // Canonicalize by name. | |
| { | |
| SafepointReadRwLocker ml(thread, isolate_group->tag_table_lock()); | |
| ASSERT(isolate_group->object_store()->tag_table() != | |
| GrowableObjectArray::null()); | |
| UserTag& result = | |
| UserTag::Handle(FindTagInIsolateGroup(isolate_group, thread, label)); | |
| if (!result.IsNull()) { | |
| // Tag already exists, return existing instance. | |
| return result.ptr(); | |
| } | |
| } | |
| SafepointWriteRwLocker mlw(thread, isolate_group->tag_table_lock()); | |
| UserTag& result = | |
| UserTag::Handle(FindTagInIsolateGroup(isolate_group, thread, label)); | |
| if (!result.IsNull()) { | |
| // Tag already exists, return existing instance. | |
| return result.ptr(); | |
| } | |
| if (TagTableIsFull(thread)) { | |
| const String& error = String::Handle(String::NewFormatted( | |
| "UserTag instance limit (%" Pd ") reached.", UserTags::kMaxUserTags)); | |
| const Array& args = Array::Handle(Array::New(1)); | |
| args.SetAt(0, error); | |
| Exceptions::ThrowByType(Exceptions::kUnsupported, args); | |
| } | |
| // No tag with label exists, create and register with thread tag table. | |
| result = Object::Allocate<UserTag>(space); | |
| result.set_label(label); | |
| AddTagToIsolateGroup(thread, result); | |
| return result.ptr(); | |
| } | |
| UserTagPtr UserTag::DefaultTag(Thread* thread) { | |
| Zone* zone = thread->zone(); | |
| if (thread->default_tag() != UserTag::null()) { | |
| // Already created. | |
| return thread->default_tag(); | |
| } | |
| // Create default tag. | |
| const UserTag& result = | |
| UserTag::Handle(zone, UserTag::New(thread, Symbols::Default())); | |
| ASSERT(result.tag() == UserTags::kDefaultUserTag); | |
| thread->set_default_tag(result); | |
| return result.ptr(); | |
| } | |
| UserTagPtr UserTag::FindTagInIsolateGroup(IsolateGroup* isolate_group, | |
| Thread* thread, | |
| const String& label) { | |
| Zone* zone = thread->zone(); | |
| if (isolate_group->object_store()->tag_table() == | |
| GrowableObjectArray::null()) { | |
| return UserTag::null(); | |
| } | |
| const GrowableObjectArray& tag_table = GrowableObjectArray::Handle( | |
| zone, isolate_group->object_store()->tag_table()); | |
| UserTag& other = UserTag::Handle(zone); | |
| String& tag_label = String::Handle(zone); | |
| for (intptr_t i = 0; i < tag_table.Length(); i++) { | |
| other ^= tag_table.At(i); | |
| ASSERT(!other.IsNull()); | |
| tag_label = other.label(); | |
| ASSERT(!tag_label.IsNull()); | |
| if (tag_label.Equals(label)) { | |
| return other.ptr(); | |
| } | |
| } | |
| return UserTag::null(); | |
| } | |
| void UserTag::AddTagToIsolateGroup(Thread* thread, const UserTag& tag) { | |
| auto isolate_group = thread->isolate_group(); | |
| Zone* zone = thread->zone(); | |
| ASSERT(isolate_group->object_store()->tag_table() != | |
| GrowableObjectArray::null()); | |
| const GrowableObjectArray& tag_table = GrowableObjectArray::Handle( | |
| zone, isolate_group->object_store()->tag_table()); | |
| ASSERT(!TagTableIsFull(thread)); | |
| #if defined(DEBUG) | |
| // Verify that no existing tag has the same tag id. | |
| UserTag& other = UserTag::Handle(thread->zone()); | |
| for (intptr_t i = 0; i < tag_table.Length(); i++) { | |
| other ^= tag_table.At(i); | |
| ASSERT(!other.IsNull()); | |
| ASSERT(tag.tag() != other.tag()); | |
| } | |
| #endif | |
| // Generate the UserTag tag id by taking the length of the isolate group's | |
| // tag table + kUserTagIdOffset. | |
| uword tag_id = tag_table.Length() + UserTags::kUserTagIdOffset; | |
| ASSERT(tag_id >= UserTags::kUserTagIdOffset); | |
| ASSERT(tag_id < (UserTags::kUserTagIdOffset + UserTags::kMaxUserTags)); | |
| tag.set_tag(tag_id); | |
| tag_table.Add(tag); | |
| } | |
| bool UserTag::TagTableIsFull(Thread* thread) { | |
| auto isolate_group = thread->isolate_group(); | |
| ASSERT(isolate_group->object_store()->tag_table() != | |
| GrowableObjectArray::null()); | |
| const GrowableObjectArray& tag_table = GrowableObjectArray::Handle( | |
| thread->zone(), isolate_group->object_store()->tag_table()); | |
| ASSERT(tag_table.Length() <= UserTags::kMaxUserTags); | |
| return tag_table.Length() == UserTags::kMaxUserTags; | |
| } | |
| UserTagPtr UserTag::FindTagById(const IsolateGroup* isolate_group, | |
| uword tag_id) { | |
| ASSERT(isolate_group != nullptr); | |
| auto thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| ASSERT(isolate_group->object_store()->tag_table() != | |
| GrowableObjectArray::null()); | |
| const GrowableObjectArray& tag_table = GrowableObjectArray::Handle( | |
| zone, isolate_group->object_store()->tag_table()); | |
| UserTag& tag = UserTag::Handle(zone); | |
| for (intptr_t i = 0; i < tag_table.Length(); i++) { | |
| tag ^= tag_table.At(i); | |
| if (tag.tag() == tag_id) { | |
| return tag.ptr(); | |
| } | |
| } | |
| return UserTag::null(); | |
| } | |
| const char* UserTag::ToCString() const { | |
| const String& tag_label = String::Handle(label()); | |
| return tag_label.ToCString(); | |
| } | |
| void DumpTypeTable(Isolate* isolate) { | |
| OS::PrintErr("canonical types:\n"); | |
| CanonicalTypeSet table(isolate->group()->object_store()->canonical_types()); | |
| table.Dump(); | |
| table.Release(); | |
| } | |
| void DumpFunctionTypeTable(Isolate* isolate) { | |
| OS::PrintErr("canonical function types:\n"); | |
| CanonicalFunctionTypeSet table( | |
| isolate->group()->object_store()->canonical_function_types()); | |
| table.Dump(); | |
| table.Release(); | |
| } | |
| void DumpRecordTypeTable(Isolate* isolate) { | |
| OS::PrintErr("canonical record types:\n"); | |
| CanonicalRecordTypeSet table( | |
| isolate->group()->object_store()->canonical_record_types()); | |
| table.Dump(); | |
| table.Release(); | |
| } | |
| void DumpTypeParameterTable(Isolate* isolate) { | |
| OS::PrintErr("canonical type parameters (cloned from declarations):\n"); | |
| CanonicalTypeParameterSet table( | |
| isolate->group()->object_store()->canonical_type_parameters()); | |
| table.Dump(); | |
| table.Release(); | |
| } | |
| void DumpTypeArgumentsTable(Isolate* isolate) { | |
| OS::PrintErr("canonical type arguments:\n"); | |
| CanonicalTypeArgumentsSet table( | |
| isolate->group()->object_store()->canonical_type_arguments()); | |
| table.Dump(); | |
| table.Release(); | |
| } | |
| EntryPointPragma FindEntryPointPragma(IsolateGroup* IG, | |
| const Array& metadata, | |
| Field* reusable_field_handle, | |
| Object* pragma) { | |
| for (intptr_t i = 0; i < metadata.Length(); i++) { | |
| *pragma = metadata.At(i); | |
| if (pragma->clazz() != IG->object_store()->pragma_class()) { | |
| continue; | |
| } | |
| *reusable_field_handle = IG->object_store()->pragma_name(); | |
| const auto pragma_name = | |
| Instance::Cast(*pragma).GetField(*reusable_field_handle); | |
| if (pragma_name == Symbols::dyn_module_can_be_used_as_type().ptr()) { | |
| return EntryPointPragma::kTypeOnly; | |
| } | |
| if ((pragma_name != Symbols::vm_entry_point().ptr()) && | |
| (pragma_name != Symbols::dyn_module_callable().ptr()) && | |
| (pragma_name != Symbols::dyn_module_implicitly_callable().ptr()) && | |
| (pragma_name != Symbols::dyn_module_extendable().ptr()) && | |
| (pragma_name != Symbols::dyn_module_dynamically_callable().ptr()) && | |
| (pragma_name != | |
| Symbols::dyn_module_implicitly_dynamically_callable().ptr())) { | |
| continue; | |
| } | |
| *reusable_field_handle = IG->object_store()->pragma_options(); | |
| *pragma = Instance::Cast(*pragma).GetField(*reusable_field_handle); | |
| if (pragma->ptr() == Bool::null() || pragma->ptr() == Bool::True().ptr()) { | |
| return EntryPointPragma::kAlways; | |
| break; | |
| } | |
| if (pragma->ptr() == Symbols::get().ptr()) { | |
| return EntryPointPragma::kGetterOnly; | |
| } | |
| if (pragma->ptr() == Symbols::set().ptr()) { | |
| return EntryPointPragma::kSetterOnly; | |
| } | |
| if (pragma->ptr() == Symbols::call().ptr()) { | |
| return EntryPointPragma::kCallOnly; | |
| } | |
| } | |
| return EntryPointPragma::kNever; | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| // Note: see also [NeedsDynamicInvocationForwarder] which ensures that we | |
| // never land in a function which expects parameters in registers from a | |
| // dynamic call site. | |
| intptr_t Function::MaxNumberOfParametersInRegisters(Zone* zone) const { | |
| #if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_ARM64) || \ | |
| defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_RISCV32) || \ | |
| defined(TARGET_ARCH_RISCV64) | |
| if (!FLAG_precompiled_mode) { | |
| return 0; | |
| } | |
| if (!FLAG_use_register_cc) { | |
| return 0; | |
| } | |
| if (IsGeneric()) { | |
| return 0; | |
| } | |
| switch (kind()) { | |
| case UntaggedFunction::kClosureFunction: | |
| case UntaggedFunction::kImplicitClosureFunction: | |
| case UntaggedFunction::kNoSuchMethodDispatcher: | |
| case UntaggedFunction::kInvokeFieldDispatcher: | |
| case UntaggedFunction::kDynamicInvocationForwarder: | |
| case UntaggedFunction::kMethodExtractor: | |
| case UntaggedFunction::kFfiTrampoline: | |
| case UntaggedFunction::kFieldInitializer: | |
| return 0; | |
| default: | |
| break; | |
| } | |
| const auto unboxing_metadata = kernel::UnboxingInfoMetadataOf(*this, zone); | |
| if (unboxing_metadata != nullptr && | |
| unboxing_metadata->must_use_stack_calling_convention) { | |
| return 0; | |
| } | |
| // Getters and setters have fixed signatures. | |
| switch (kind()) { | |
| case UntaggedFunction::kGetterFunction: | |
| case UntaggedFunction::kImplicitGetter: | |
| case UntaggedFunction::kSetterFunction: | |
| case UntaggedFunction::kImplicitSetter: | |
| return num_fixed_parameters(); | |
| default: | |
| break; | |
| } | |
| if (unboxing_metadata != nullptr && | |
| unboxing_metadata->has_overrides_with_less_direct_parameters) { | |
| // Receiver (`this`) can always be passed in the register because it is | |
| // never an optional or named parameter. | |
| return unboxing_metadata->unboxed_args_info.length() + 1; | |
| } | |
| return num_fixed_parameters(); | |
| #endif | |
| return 0; | |
| } | |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) | |
| ErrorPtr Function::VerifyEntryPoint(EntryPointPragma pragma) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| const Library& lib = Library::Handle(); | |
| #else | |
| const Class& cls = Class::Handle(Owner()); | |
| const Library& lib = Library::Handle(cls.library()); | |
| #endif | |
| return dart::VerifyEntryPoint(lib, *this, *this, pragma); | |
| } | |
| ErrorPtr Field::VerifyEntryPoint(EntryPointPragma pragma) const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| const Library& lib = Library::Handle(); | |
| #else | |
| const Class& cls = Class::Handle(Owner()); | |
| const Library& lib = Library::Handle(cls.library()); | |
| #endif | |
| return dart::VerifyEntryPoint(lib, *this, *this, pragma); | |
| } | |
| ErrorPtr Class::VerifyEntryPoint() const { | |
| #if defined(DART_PRECOMPILED_RUNTIME) | |
| const Library& lib = Library::Handle(); | |
| #else | |
| const Library& lib = Library::Handle(library()); | |
| if (lib.IsNull()) return Error::null(); | |
| #endif | |
| return dart::VerifyEntryPoint(lib, *this, *this, EntryPointPragma::kAlways); | |
| } | |
| AbstractTypePtr RecordType::FieldTypeAt(intptr_t index) const { | |
| const Array& field_types = Array::Handle(untag()->field_types()); | |
| return AbstractType::RawCast(field_types.At(index)); | |
| } | |
| void RecordType::SetFieldTypeAt(intptr_t index, | |
| const AbstractType& value) const { | |
| ASSERT(!value.IsNull()); | |
| const Array& field_types = Array::Handle(untag()->field_types()); | |
| field_types.SetAt(index, value); | |
| } | |
| void RecordType::set_field_types(const Array& value) const { | |
| ASSERT(!value.IsNull()); | |
| untag()->set_field_types(value.ptr()); | |
| } | |
| void RecordType::set_shape(RecordShape shape) const { | |
| untag()->set_shape(shape.AsSmi()); | |
| } | |
| ArrayPtr RecordType::GetFieldNames(Thread* thread) const { | |
| return shape().GetFieldNames(thread); | |
| } | |
| void RecordType::Print(NameVisibility name_visibility, | |
| BaseTextBuffer* printer) const { | |
| if (IsNull()) { | |
| printer->AddString("null"); | |
| return; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| AbstractType& type = AbstractType::Handle(zone); | |
| String& name = String::Handle(zone); | |
| const intptr_t num_fields = NumFields(); | |
| const Array& field_names = Array::Handle(zone, GetFieldNames(thread)); | |
| const intptr_t num_positional_fields = num_fields - field_names.Length(); | |
| printer->AddString("("); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| if (i != 0) { | |
| printer->AddString(", "); | |
| } | |
| if (i == num_positional_fields) { | |
| printer->AddString("{"); | |
| } | |
| type = FieldTypeAt(i); | |
| type.PrintName(name_visibility, printer); | |
| if (i >= num_positional_fields) { | |
| printer->AddString(" "); | |
| name ^= field_names.At(i - num_positional_fields); | |
| printer->AddString(name.ToCString()); | |
| } | |
| } | |
| if (num_positional_fields < num_fields) { | |
| printer->AddString("}"); | |
| } | |
| printer->AddString(")"); | |
| printer->AddString(NullabilitySuffix(name_visibility)); | |
| } | |
| const char* RecordType::ToCString() const { | |
| Zone* zone = Thread::Current()->zone(); | |
| ZoneTextBuffer printer(zone); | |
| Print(kInternalName, &printer); | |
| return printer.buffer(); | |
| } | |
| bool RecordType::IsInstantiated(Genericity genericity, | |
| intptr_t num_free_fun_type_params) const { | |
| AbstractType& type = AbstractType::Handle(); | |
| const intptr_t num_fields = NumFields(); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| type = FieldTypeAt(i); | |
| if (!type.IsInstantiated(genericity, num_free_fun_type_params)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| RecordTypePtr RecordType::New(Heap::Space space) { | |
| return Object::Allocate<RecordType>(space); | |
| } | |
| RecordTypePtr RecordType::New(RecordShape shape, | |
| const Array& field_types, | |
| Nullability nullability, | |
| Heap::Space space) { | |
| Zone* Z = Thread::Current()->zone(); | |
| const RecordType& result = RecordType::Handle(Z, RecordType::New(space)); | |
| result.set_shape(shape); | |
| result.set_field_types(field_types); | |
| result.SetHash(0); | |
| result.set_flags(0); | |
| result.set_nullability(nullability); | |
| result.set_type_state(UntaggedAbstractType::kAllocated); | |
| result.InitializeTypeTestingStubNonAtomic( | |
| Code::Handle(Z, TypeTestingStubGenerator::DefaultCodeForType(result))); | |
| return result.ptr(); | |
| } | |
| RecordTypePtr RecordType::ToNullability(Nullability value, | |
| Heap::Space space) const { | |
| if (nullability() == value) { | |
| return ptr(); | |
| } | |
| // Clone record type and set new nullability. | |
| // Always cloning in old space and removing space parameter would not satisfy | |
| // currently existing requests for type instantiation in new space. | |
| Thread* T = Thread::Current(); | |
| Zone* Z = T->zone(); | |
| AbstractType& type = RecordType::Handle( | |
| Z, | |
| RecordType::New(shape(), Array::Handle(Z, field_types()), value, space)); | |
| if (IsFinalized()) { | |
| type.SetIsFinalized(); | |
| if (IsCanonical()) { | |
| type ^= type.Canonicalize(T); | |
| } | |
| } | |
| return RecordType::Cast(type).ptr(); | |
| } | |
| bool RecordType::IsEquivalent( | |
| const Instance& other, | |
| TypeEquality kind, | |
| FunctionTypeMapping* function_type_equivalence) const { | |
| ASSERT(!IsNull()); | |
| if (ptr() == other.ptr()) { | |
| return true; | |
| } | |
| if (!other.IsRecordType()) { | |
| return false; | |
| } | |
| const RecordType& other_type = RecordType::Cast(other); | |
| // Equal record types must have the same shape | |
| // (number of fields and named fields). | |
| if (shape() != other_type.shape()) { | |
| return false; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| if (!IsNullabilityEquivalent(thread, other_type, kind)) { | |
| return false; | |
| } | |
| // Equal record types must have equal field types. | |
| AbstractType& field_type = Type::Handle(zone); | |
| AbstractType& other_field_type = Type::Handle(zone); | |
| const intptr_t num_fields = NumFields(); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| field_type = FieldTypeAt(i); | |
| other_field_type = other_type.FieldTypeAt(i); | |
| if (!field_type.IsEquivalent(other_field_type, kind, | |
| function_type_equivalence)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| uword RecordType::ComputeHash() const { | |
| ASSERT(IsFinalized()); | |
| uint32_t result = 0; | |
| result = CombineHashes(result, static_cast<uint32_t>(nullability())); | |
| result = CombineHashes(result, static_cast<uint32_t>(shape().AsInt())); | |
| AbstractType& type = AbstractType::Handle(); | |
| const intptr_t num_fields = NumFields(); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| type = FieldTypeAt(i); | |
| result = CombineHashes(result, type.Hash()); | |
| } | |
| result = FinalizeHash(result, kHashBits); | |
| SetHash(result); | |
| return result; | |
| } | |
| AbstractTypePtr RecordType::Canonicalize(Thread* thread) const { | |
| ASSERT(IsFinalized()); | |
| Zone* zone = thread->zone(); | |
| AbstractType& type = AbstractType::Handle(zone); | |
| if (IsCanonical()) { | |
| #ifdef DEBUG | |
| // Verify that all fields are allocated in old space and are canonical. | |
| ASSERT(Array::Handle(zone, field_types()).IsOld()); | |
| const intptr_t num_fields = NumFields(); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| type = FieldTypeAt(i); | |
| ASSERT(type.IsOld()); | |
| ASSERT(type.IsCanonical()); | |
| } | |
| #endif | |
| return ptr(); | |
| } | |
| auto isolate_group = thread->isolate_group(); | |
| ObjectStore* object_store = isolate_group->object_store(); | |
| RecordType& rec = RecordType::Handle(zone); | |
| { | |
| SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex()); | |
| CanonicalRecordTypeSet table(zone, object_store->canonical_record_types()); | |
| rec ^= table.GetOrNull(CanonicalRecordTypeKey(*this)); | |
| ASSERT(object_store->canonical_record_types() == table.Release().ptr()); | |
| } | |
| if (rec.IsNull()) { | |
| ASSERT(Array::Handle(zone, field_types()).IsOld()); | |
| const intptr_t num_fields = NumFields(); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| type = FieldTypeAt(i); | |
| if (!type.IsCanonical()) { | |
| type = type.Canonicalize(thread); | |
| SetFieldTypeAt(i, type); | |
| } | |
| } | |
| // Check to see if the record type got added to canonical table as part | |
| // of the canonicalization of its signature types. | |
| SafepointMutexLocker ml(isolate_group->type_canonicalization_mutex()); | |
| CanonicalRecordTypeSet table(zone, object_store->canonical_record_types()); | |
| rec ^= table.GetOrNull(CanonicalRecordTypeKey(*this)); | |
| if (rec.IsNull()) { | |
| // Add this record type into the canonical table of record types. | |
| if (this->IsNew()) { | |
| rec ^= Object::Clone(*this, Heap::kOld); | |
| } else { | |
| rec = this->ptr(); | |
| } | |
| ASSERT(rec.IsOld()); | |
| rec.SetCanonical(); // Mark object as being canonical. | |
| bool present = table.Insert(rec); | |
| ASSERT(!present); | |
| } | |
| object_store->set_canonical_record_types(table.Release()); | |
| } | |
| return rec.ptr(); | |
| } | |
| void RecordType::EnumerateURIs(URIs* uris) const { | |
| AbstractType& type = AbstractType::Handle(); | |
| const intptr_t num_fields = NumFields(); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| type = FieldTypeAt(i); | |
| type.EnumerateURIs(uris); | |
| } | |
| } | |
| void RecordType::PrintName(NameVisibility name_visibility, | |
| BaseTextBuffer* printer) const { | |
| RecordType::Cast(*this).Print(name_visibility, printer); | |
| } | |
| AbstractTypePtr RecordType::InstantiateFrom( | |
| const TypeArguments& instantiator_type_arguments, | |
| const TypeArguments& function_type_arguments, | |
| intptr_t num_free_fun_type_params, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_mapping, | |
| intptr_t num_parent_type_args_adjustment) const { | |
| ASSERT(IsFinalized()); | |
| Zone* zone = Thread::Current()->zone(); | |
| const intptr_t num_fields = NumFields(); | |
| const Array& old_field_types = Array::Handle(zone, field_types()); | |
| const Array& new_field_types = | |
| Array::Handle(zone, Array::New(num_fields, space)); | |
| AbstractType& type = AbstractType::Handle(zone); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| type ^= old_field_types.At(i); | |
| if (!type.IsInstantiated()) { | |
| type = type.InstantiateFrom( | |
| instantiator_type_arguments, function_type_arguments, | |
| num_free_fun_type_params, space, function_type_mapping, | |
| num_parent_type_args_adjustment); | |
| // A returned null type indicates a failed instantiation in dead code that | |
| // must be propagated up to the caller, the optimizing compiler. | |
| if (type.IsNull()) { | |
| return RecordType::null(); | |
| } | |
| } | |
| new_field_types.SetAt(i, type); | |
| } | |
| const auto& rec = RecordType::Handle( | |
| zone, RecordType::New(shape(), new_field_types, nullability(), space)); | |
| rec.SetIsFinalized(); | |
| // Canonicalization is not part of instantiation. | |
| return rec.ptr(); | |
| } | |
| AbstractTypePtr RecordType::UpdateFunctionTypes( | |
| intptr_t num_parent_type_args_adjustment, | |
| intptr_t num_free_fun_type_params, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_mapping) const { | |
| ASSERT(IsFinalized()); | |
| ASSERT(num_parent_type_args_adjustment >= 0); | |
| Zone* zone = Thread::Current()->zone(); | |
| const auto& types = Array::Handle(zone, field_types()); | |
| Array* updated_types = nullptr; | |
| auto& type = AbstractType::Handle(zone); | |
| auto& updated = AbstractType::Handle(zone); | |
| for (intptr_t i = 0, n = NumFields(); i < n; ++i) { | |
| type ^= types.At(i); | |
| updated = type.UpdateFunctionTypes(num_parent_type_args_adjustment, | |
| num_free_fun_type_params, space, | |
| function_type_mapping); | |
| if (type.ptr() != updated.ptr()) { | |
| if (updated_types == nullptr) { | |
| updated_types = &Array::Handle(zone, Array::New(n, space)); | |
| for (intptr_t j = 0; j < i; ++j) { | |
| type ^= types.At(j); | |
| updated_types->SetAt(j, type); | |
| } | |
| } | |
| } | |
| if (updated_types != nullptr) { | |
| updated_types->SetAt(i, updated); | |
| } | |
| } | |
| if (updated_types == nullptr) { | |
| return ptr(); | |
| } | |
| const auto& new_rt = RecordType::Handle( | |
| zone, RecordType::New(shape(), *updated_types, nullability(), space)); | |
| new_rt.SetIsFinalized(); | |
| return new_rt.ptr(); | |
| } | |
| bool RecordType::IsSubtypeOf( | |
| const RecordType& other, | |
| Heap::Space space, | |
| FunctionTypeMapping* function_type_equivalence) const { | |
| if (ptr() == other.ptr()) { | |
| return true; | |
| } | |
| ASSERT(IsFinalized()); | |
| ASSERT(other.IsFinalized()); | |
| const intptr_t num_fields = NumFields(); | |
| if (shape() != other.shape()) { | |
| // Different number of fields or different named fields. | |
| return false; | |
| } | |
| Thread* const thread = Thread::Current(); | |
| if (!IsNullabilityEquivalent(thread, other, TypeEquality::kInSubtypeTest)) { | |
| return false; | |
| } | |
| // Check subtyping of record field types. | |
| Zone* const zone = thread->zone(); | |
| AbstractType& field_type = Type::Handle(zone); | |
| AbstractType& other_field_type = Type::Handle(zone); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| field_type = FieldTypeAt(i); | |
| other_field_type = other.FieldTypeAt(i); | |
| if (!field_type.IsSubtypeOf(other_field_type, space, | |
| function_type_equivalence)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| RecordPtr Record::New(RecordShape shape, Heap::Space space) { | |
| const intptr_t num_fields = shape.num_fields(); | |
| ASSERT(num_fields >= 0); | |
| auto raw = Object::Allocate<Record>(space, num_fields); | |
| NoSafepointScope no_safepoint; | |
| raw->untag()->set_shape(shape.AsSmi()); | |
| return raw; | |
| } | |
| const char* Record::ToCString() const { | |
| if (IsNull()) { | |
| return "Record: null"; | |
| } | |
| Thread* thread = Thread::Current(); | |
| Zone* zone = thread->zone(); | |
| ZoneTextBuffer printer(zone); | |
| const intptr_t num_fields = this->num_fields(); | |
| const Array& field_names = Array::Handle(zone, GetFieldNames(thread)); | |
| const intptr_t num_positional_fields = num_fields - field_names.Length(); | |
| Object& obj = Object::Handle(zone); | |
| printer.AddString("Record ("); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| if (i != 0) { | |
| printer.AddString(", "); | |
| } | |
| if (i >= num_positional_fields) { | |
| obj = field_names.At(i - num_positional_fields); | |
| printer.AddString(obj.ToCString()); | |
| printer.AddString(": "); | |
| } | |
| obj = FieldAt(i); | |
| printer.AddString(obj.ToCString()); | |
| } | |
| printer.AddString(")"); | |
| return printer.buffer(); | |
| } | |
| bool Record::CanonicalizeEquals(const Instance& other) const { | |
| if (this->ptr() == other.ptr()) { | |
| return true; | |
| } | |
| if (!other.IsRecord() || other.IsNull()) { | |
| return false; | |
| } | |
| const Record& other_rec = Record::Cast(other); | |
| if (shape() != other_rec.shape()) { | |
| return false; | |
| } | |
| const intptr_t num_fields = this->num_fields(); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| if (this->FieldAt(i) != other_rec.FieldAt(i)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| uint32_t Record::CanonicalizeHash() const { | |
| Thread* thread = Thread::Current(); | |
| uint32_t hash = thread->heap()->GetCanonicalHash(ptr()); | |
| if (hash != 0) { | |
| return hash; | |
| } | |
| hash = shape().AsInt(); | |
| Instance& element = Instance::Handle(); | |
| const intptr_t num_fields = this->num_fields(); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| element ^= FieldAt(i); | |
| hash = CombineHashes(hash, element.CanonicalizeHash()); | |
| } | |
| hash = FinalizeHash(hash, kHashBits); | |
| thread->heap()->SetCanonicalHash(ptr(), hash); | |
| return hash; | |
| } | |
| void Record::CanonicalizeFieldsLocked(Thread* thread) const { | |
| Zone* zone = thread->zone(); | |
| Instance& obj = Instance::Handle(zone); | |
| const intptr_t num_fields = this->num_fields(); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| obj ^= FieldAt(i); | |
| obj = obj.CanonicalizeLocked(thread); | |
| SetFieldAt(i, obj); | |
| } | |
| } | |
| RecordTypePtr Record::GetRecordType(TypeVisibility visibility) const { | |
| Zone* const zone = Thread::Current()->zone(); | |
| const intptr_t num_fields = this->num_fields(); | |
| const Array& field_types = | |
| Array::Handle(zone, Array::New(num_fields, Heap::kOld)); | |
| Instance& obj = Instance::Handle(zone); | |
| AbstractType& type = AbstractType::Handle(zone); | |
| for (intptr_t i = 0; i < num_fields; ++i) { | |
| obj ^= FieldAt(i); | |
| type = obj.GetType(Heap::kNew, visibility); | |
| field_types.SetAt(i, type); | |
| } | |
| type = RecordType::New(shape(), field_types, Nullability::kNonNullable); | |
| type = ClassFinalizer::FinalizeType(type); | |
| return RecordType::Cast(type).ptr(); | |
| } | |
| intptr_t Record::GetPositionalFieldIndexFromFieldName( | |
| const String& field_name) { | |
| if (field_name.IsOneByteString() && field_name.Length() >= 1 && | |
| field_name.CharAt(0) == '$') { | |
| int64_t value = 0; | |
| const char* cstr = field_name.ToCString(); | |
| if (OS::StringToInt64(cstr + 1 /* skip '$' */, &value)) { | |
| if (value >= 1 && value < kMaxElements) { | |
| return static_cast<intptr_t>(value - 1); | |
| } | |
| } | |
| } | |
| return -1; | |
| } | |
| intptr_t Record::GetFieldIndexByName(Thread* thread, | |
| const String& field_name) const { | |
| ASSERT(field_name.IsSymbol()); | |
| const intptr_t field_index = | |
| Record::GetPositionalFieldIndexFromFieldName(field_name); | |
| const Array& field_names = Array::Handle(GetFieldNames(thread)); | |
| const intptr_t num_positional_fields = num_fields() - field_names.Length(); | |
| if ((field_index >= 0) && (field_index < num_positional_fields)) { | |
| return field_index; | |
| } else { | |
| for (intptr_t i = 0, n = field_names.Length(); i < n; ++i) { | |
| if (field_names.At(i) == field_name.ptr()) { | |
| return num_positional_fields + i; | |
| } | |
| } | |
| } | |
| return -1; | |
| } | |
| class RecordFieldNamesMapTraits { | |
| public: | |
| static const char* Name() { return "RecordFieldNamesMapTraits"; } | |
| static bool ReportStats() { return false; } | |
| static bool IsMatch(const Object& a, const Object& b) { | |
| return Array::Cast(a).CanonicalizeEquals(Array::Cast(b)); | |
| } | |
| static uword Hash(const Object& key) { | |
| return Array::Cast(key).CanonicalizeHash(); | |
| } | |
| static ObjectPtr NewKey(const Array& arr) { return arr.ptr(); } | |
| }; | |
| typedef UnorderedHashMap<RecordFieldNamesMapTraits> RecordFieldNamesMap; | |
| RecordShape RecordShape::Register(Thread* thread, | |
| intptr_t num_fields, | |
| const Array& field_names) { | |
| ASSERT(!field_names.IsNull()); | |
| ASSERT(field_names.IsImmutable()); | |
| ASSERT(field_names.ptr() == Object::empty_array().ptr() || | |
| field_names.Length() > 0); | |
| Zone* zone = thread->zone(); | |
| IsolateGroup* isolate_group = thread->isolate_group(); | |
| ObjectStore* object_store = isolate_group->object_store(); | |
| if (object_store->record_field_names() == Array::null()) { | |
| // First-time initialization. | |
| SafepointWriteRwLocker ml(thread, isolate_group->program_lock()); | |
| if (object_store->record_field_names() == Array::null()) { | |
| // Reserve record field names index 0 for records without named fields. | |
| RecordFieldNamesMap map( | |
| HashTables::New<RecordFieldNamesMap>(16, Heap::kOld)); | |
| map.InsertOrGetValue(Object::empty_array(), | |
| Smi::Handle(zone, Smi::New(0))); | |
| ASSERT(map.NumOccupied() == 1); | |
| object_store->set_record_field_names_map(map.Release()); | |
| const auto& table = Array::Handle(zone, Array::New(16)); | |
| table.SetAt(0, Object::empty_array()); | |
| object_store->set_record_field_names(table); | |
| } | |
| } | |
| #if defined(DART_PRECOMPILER) | |
| const intptr_t kMaxNumFields = compiler::target::RecordShape::kMaxNumFields; | |
| const intptr_t kMaxFieldNamesIndex = | |
| compiler::target::RecordShape::kMaxFieldNamesIndex; | |
| #else | |
| const intptr_t kMaxNumFields = RecordShape::kMaxNumFields; | |
| const intptr_t kMaxFieldNamesIndex = RecordShape::kMaxFieldNamesIndex; | |
| #endif | |
| if (num_fields > kMaxNumFields) { | |
| FATAL("Too many record fields"); | |
| } | |
| if (field_names.ptr() == Object::empty_array().ptr()) { | |
| return RecordShape::ForUnnamed(num_fields); | |
| } | |
| { | |
| SafepointReadRwLocker ml(thread, isolate_group->program_lock()); | |
| RecordFieldNamesMap map(object_store->record_field_names_map()); | |
| Smi& index = Smi::Handle(zone); | |
| index ^= map.GetOrNull(field_names); | |
| ASSERT(map.Release().ptr() == object_store->record_field_names_map()); | |
| if (!index.IsNull()) { | |
| return RecordShape(num_fields, index.Value()); | |
| } | |
| } | |
| SafepointWriteRwLocker ml(thread, isolate_group->program_lock()); | |
| RecordFieldNamesMap map(object_store->record_field_names_map()); | |
| const intptr_t new_index = map.NumOccupied(); | |
| if (new_index > kMaxFieldNamesIndex) { | |
| FATAL("Too many record shapes"); | |
| } | |
| const intptr_t index = Smi::Value(Smi::RawCast(map.InsertOrGetValue( | |
| field_names, Smi::Handle(zone, Smi::New(new_index))))); | |
| ASSERT(index > 0); | |
| if (index == new_index) { | |
| ASSERT(map.NumOccupied() == (new_index + 1)); | |
| Array& table = Array::Handle(zone, object_store->record_field_names()); | |
| intptr_t capacity = table.Length(); | |
| if (index >= table.Length()) { | |
| capacity = capacity + (capacity >> 2); | |
| table = Array::Grow(table, capacity); | |
| object_store->set_record_field_names(table); | |
| } | |
| table.SetAt(index, field_names); | |
| } else { | |
| ASSERT(index < new_index); | |
| } | |
| object_store->set_record_field_names_map(map.Release()); | |
| const RecordShape shape(num_fields, index); | |
| ASSERT(Array::Handle(shape.GetFieldNames(thread)) | |
| .CanonicalizeEquals(field_names)); | |
| ASSERT(shape.num_fields() == num_fields); | |
| return shape; | |
| } | |
| ArrayPtr RecordShape::GetFieldNames(Thread* thread) const { | |
| ObjectStore* object_store = thread->isolate_group()->object_store(); | |
| Array& table = | |
| Array::Handle(thread->zone(), object_store->record_field_names()); | |
| ASSERT(!table.IsNull()); | |
| return Array::RawCast(table.At(field_names_index())); | |
| } | |
| } // namespace dart |