d36adbacaf
The former contents of the VM isolate are now included into each isolate group. This makes each isolate group's heap independent, and in particular allows each heap to be allocated to a separate pointer cage (not done in this CL). The duplicated stubs that allowed PC relative calls are removed, since the originals can now be the target of PC relative calls. The bootstrapping needing to load an AppJIT or AppAOT snapshot is reduced to allocating the oddballs. The code is entirely dropped in the AOT runtime, but the JIT runtime still has it to allow for flags to affect the compilation of the stub code. Further refactoring might be able to remove this for the JIT runtime too, with only gen_snapshot knowing how to bootstrap. Class serialization no longer distinguishes predefined classes. The page containing null is marked as never-evacuate. null, false and true must not move because the compiler relies on their low bits having certain patterns for some optimizations. (Previously, the entire VM isolate heap never moved.) Compaction is disabled for IA32. Due to register pressure, some stub calls must not use a scratch register and embed the address of Code. The page containing the call-through-safepoint stub is frozen when running with --write-protect-code and the stub is created at runtime (instead of loaded from an AppJIT or AppAOT snapshot). This stub must remain executable even during a safepoint, as a foreign call might during return during a safepoint and only block after the stub directs it to the runtime. The snapshot symbols are renamed to kDartSnapshotData and kDartSnapshotText. There is no need to distinguish the VM isolate's snapshot, and snaphots are per isolate group not per isolate. Aliases with the old names are added to ease migration. Some global flags that were automatically set based on the VM isolate's snapshot are now isolate group flags and automatically set by the isolate group's snapshot. TEST=ci Change-Id: Iee82016057d609112e9b021d178fc3d4d18b5044 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500621 Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Tess Strickland <sstrickl@google.com> SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
28463 lines
1018 KiB
C++
28463 lines
1018 KiB
C++
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#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/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::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();
|
|
}
|
|
}
|
|
|
|
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<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)
|
|
Roots::implicit_getter_bytecode().initRO(
|
|
CreateVMInternalBytecode(KernelBytecode::kVMInternal_ImplicitGetter));
|
|
Roots::implicit_setter_bytecode().initRO(
|
|
CreateVMInternalBytecode(KernelBytecode::kVMInternal_ImplicitSetter));
|
|
Roots::implicit_static_getter_bytecode().initRO(CreateVMInternalBytecode(
|
|
KernelBytecode::kVMInternal_ImplicitStaticGetter));
|
|
Roots::implicit_shared_static_getter_bytecode().initRO(
|
|
CreateVMInternalBytecode(
|
|
KernelBytecode::kVMInternal_ImplicitSharedStaticGetter));
|
|
Roots::implicit_static_setter_bytecode().initRO(CreateVMInternalBytecode(
|
|
KernelBytecode::kVMInternal_ImplicitStaticSetter));
|
|
Roots::implicit_shared_static_setter_bytecode().initRO(
|
|
CreateVMInternalBytecode(
|
|
KernelBytecode::kVMInternal_ImplicitSharedStaticSetter));
|
|
Roots::method_extractor_with_ita_bytecode().initRO(CreateVMInternalBytecode(
|
|
KernelBytecode::kVMInternal_MethodExtractorWithITA));
|
|
Roots::method_extractor_without_ita_bytecode().initRO(
|
|
CreateVMInternalBytecode(
|
|
KernelBytecode::kVMInternal_MethodExtractorWithoutITA));
|
|
Roots::invoke_closure_bytecode().initRO(
|
|
CreateVMInternalBytecode(KernelBytecode::kVMInternal_InvokeClosure));
|
|
Roots::invoke_field_bytecode().initRO(
|
|
CreateVMInternalBytecode(KernelBytecode::kVMInternal_InvokeField));
|
|
Roots::nsm_dispatcher_bytecode().initRO(CreateVMInternalBytecode(
|
|
KernelBytecode::kVMInternal_NoSuchMethodDispatcher));
|
|
Roots::dynamic_invocation_forwarder_bytecode().initRO(
|
|
CreateVMInternalBytecode(
|
|
KernelBytecode::kVMInternal_ForwardDynamicInvocation));
|
|
Roots::implicit_static_closure_bytecode().initRO(CreateVMInternalBytecode(
|
|
KernelBytecode::kVMInternal_ImplicitStaticClosure));
|
|
Roots::implicit_instance_closure_bytecode().initRO(CreateVMInternalBytecode(
|
|
KernelBytecode::kVMInternal_ImplicitInstanceClosure));
|
|
Roots::implicit_constructor_closure_bytecode().initRO(
|
|
CreateVMInternalBytecode(
|
|
KernelBytecode::kVMInternal_ImplicitConstructorClosure));
|
|
#else
|
|
Roots::implicit_getter_bytecode().initRO(Bytecode::null());
|
|
Roots::implicit_setter_bytecode().initRO(Bytecode::null());
|
|
Roots::implicit_static_getter_bytecode().initRO(Bytecode::null());
|
|
Roots::implicit_shared_static_getter_bytecode().initRO(Bytecode::null());
|
|
Roots::implicit_static_setter_bytecode().initRO(Bytecode::null());
|
|
Roots::implicit_shared_static_setter_bytecode().initRO(Bytecode::null());
|
|
Roots::method_extractor_with_ita_bytecode().initRO(Bytecode::null());
|
|
Roots::method_extractor_without_ita_bytecode().initRO(Bytecode::null());
|
|
Roots::invoke_closure_bytecode().initRO(Bytecode::null());
|
|
Roots::invoke_field_bytecode().initRO(Bytecode::null());
|
|
Roots::nsm_dispatcher_bytecode().initRO(Bytecode::null());
|
|
Roots::dynamic_invocation_forwarder_bytecode().initRO(Bytecode::null());
|
|
Roots::implicit_static_closure_bytecode().initRO(Bytecode::null());
|
|
Roots::implicit_instance_closure_bytecode().initRO(Bytecode::null());
|
|
Roots::implicit_constructor_closure_bytecode().initRO(Bytecode::null());
|
|
#endif // defined(DART_DYNAMIC_MODULES)
|
|
|
|
Roots::uninitialized_index().initRO(
|
|
TypedData::New(kTypedDataUint32ArrayCid,
|
|
LinkedHashBase::kUninitializedIndexSize, Heap::kOld));
|
|
Roots::uninitialized_data().initRO(Array::New(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());
|
|
ASSERT(!Roots::implicit_getter_bytecode().IsSmi());
|
|
ASSERT(Roots::implicit_getter_bytecode().IsBytecode());
|
|
ASSERT(!Roots::implicit_setter_bytecode().IsSmi());
|
|
ASSERT(Roots::implicit_setter_bytecode().IsBytecode());
|
|
ASSERT(!Roots::implicit_static_getter_bytecode().IsSmi());
|
|
ASSERT(Roots::implicit_static_getter_bytecode().IsBytecode());
|
|
ASSERT(!Roots::implicit_static_setter_bytecode().IsSmi());
|
|
ASSERT(Roots::implicit_static_setter_bytecode().IsBytecode());
|
|
ASSERT(!Roots::method_extractor_with_ita_bytecode().IsSmi());
|
|
ASSERT(Roots::method_extractor_with_ita_bytecode().IsBytecode());
|
|
ASSERT(!Roots::method_extractor_without_ita_bytecode().IsSmi());
|
|
ASSERT(Roots::method_extractor_without_ita_bytecode().IsBytecode());
|
|
ASSERT(!Roots::invoke_closure_bytecode().IsSmi());
|
|
ASSERT(Roots::invoke_closure_bytecode().IsBytecode());
|
|
ASSERT(!Roots::invoke_field_bytecode().IsSmi());
|
|
ASSERT(Roots::invoke_field_bytecode().IsBytecode());
|
|
ASSERT(!Roots::nsm_dispatcher_bytecode().IsSmi());
|
|
ASSERT(Roots::nsm_dispatcher_bytecode().IsBytecode());
|
|
ASSERT(!Roots::dynamic_invocation_forwarder_bytecode().IsSmi());
|
|
ASSERT(Roots::dynamic_invocation_forwarder_bytecode().IsBytecode());
|
|
ASSERT(!Roots::implicit_static_closure_bytecode().IsSmi());
|
|
ASSERT(Roots::implicit_static_closure_bytecode().IsBytecode());
|
|
ASSERT(!Roots::implicit_instance_closure_bytecode().IsSmi());
|
|
ASSERT(Roots::implicit_instance_closure_bytecode().IsBytecode());
|
|
ASSERT(!Roots::implicit_constructor_closure_bytecode().IsSmi());
|
|
ASSERT(Roots::implicit_constructor_closure_bytecode().IsBytecode());
|
|
ASSERT(!Roots::uninitialized_index().IsSmi());
|
|
ASSERT(Roots::uninitialized_index().IsTypedData());
|
|
ASSERT(!Roots::uninitialized_data().IsSmi());
|
|
ASSERT(Roots::uninitialized_data().IsArray());
|
|
}
|
|
|
|
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(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 WorkSet : public StackResource {
|
|
public:
|
|
explicit WorkSet(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_);
|
|
}
|
|
|
|
~WorkSet() {
|
|
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();
|
|
WorkSet 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_);
|
|
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.IsAnyCoreLibrary()) {
|
|
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();
|
|
#endif
|
|
if (attach_bytecode) {
|
|
forwarder.AttachBytecode(Object::dynamic_invocation_forwarder_bytecode());
|
|
}
|
|
#endif
|
|
|
|
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 {
|
|
Zone* zone = Thread::Current()->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()) {
|
|
auto& bound = AbstractType::Handle(zone);
|
|
for (intptr_t i = 0, n = type_params.Length(); i < n; ++i) {
|
|
bound = type_params.BoundAt(i);
|
|
if (!bound.IsTopTypeForSubtyping() &&
|
|
!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.IsTopTypeForSubtyping() &&
|
|
!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());
|
|
ASSERT(value.type_class_id() == kNullCid || value.IsNonNullable());
|
|
untag()->set_declaration_type<std::memory_order_release>(value.ptr());
|
|
}
|
|
|
|
TypePtr Class::DeclarationType() const {
|
|
ASSERT(is_declaration_loaded());
|
|
if (IsNullClass()) {
|
|
return Type::NullType();
|
|
}
|
|
if (IsDynamicClass()) {
|
|
return Type::DynamicType();
|
|
}
|
|
if (IsVoidClass()) {
|
|
return Type::VoidType();
|
|
}
|
|
if (declaration_type() != Type::null()) {
|
|
return declaration_type();
|
|
}
|
|
{
|
|
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.IsTopTypeForSubtyping()) {
|
|
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.IsObjectType() ||
|
|
type.IsNonNullable())) {
|
|
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.
|
|
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());
|
|
|
|
const auto& obj = Object::Handle(data());
|
|
ASSERT(!obj.IsNull());
|
|
const auto& trampoline_data = FfiTrampolineData::Cast(obj);
|
|
|
|
ASSERT(trampoline_data.callback_id() != -1);
|
|
|
|
return trampoline_data.callback_id();
|
|
}
|
|
|
|
void Function::AssignFfiCallbackId(int32_t callback_id) const {
|
|
ASSERT(IsFfiCallbackTrampoline());
|
|
|
|
const auto& obj = Object::Handle(data());
|
|
ASSERT(!obj.IsNull());
|
|
const auto& trampoline_data = FfiTrampolineData::Cast(obj);
|
|
|
|
ASSERT(trampoline_data.callback_id() == -1);
|
|
trampoline_data.set_callback_id(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
|
|
// irregexp function: Array[0] = RegExp
|
|
// Array[1] = Smi string specialization cid
|
|
// native function: Array[0] = String native name
|
|
// Array[1] = Function implicit closure function
|
|
// regular function: Function for implicit closure function
|
|
// 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());
|
|
}
|
|
|
|
void Function::set_name(const String& value) const {
|
|
ASSERT(value.IsSymbol());
|
|
untag()->set_name(value.ptr());
|
|
}
|
|
|
|
void Function::set_owner(const Object& value) const {
|
|
ASSERT(!value.IsNull());
|
|
untag()->set_owner(value.ptr());
|
|
}
|
|
|
|
RegExpPtr Function::regexp() const {
|
|
ASSERT(kind() == UntaggedFunction::kIrregexpFunction);
|
|
const Array& pair = Array::Cast(Object::Handle(data()));
|
|
return RegExp::RawCast(pair.At(0));
|
|
}
|
|
|
|
using StickySpecialization = BitField<intptr_t, bool>;
|
|
using StringSpecializationCid = BitField<intptr_t,
|
|
intptr_t,
|
|
StickySpecialization::kNextBit,
|
|
UntaggedObject::ClassIdTag::bitsize()>;
|
|
|
|
intptr_t Function::string_specialization_cid() const {
|
|
ASSERT(kind() == UntaggedFunction::kIrregexpFunction);
|
|
const Array& pair = Array::Cast(Object::Handle(data()));
|
|
return StringSpecializationCid::decode(Smi::Value(Smi::RawCast(pair.At(1))));
|
|
}
|
|
|
|
bool Function::is_sticky_specialization() const {
|
|
ASSERT(kind() == UntaggedFunction::kIrregexpFunction);
|
|
const Array& pair = Array::Cast(Object::Handle(data()));
|
|
return StickySpecialization::decode(Smi::Value(Smi::RawCast(pair.At(1))));
|
|
}
|
|
|
|
void Function::SetRegExpData(const RegExp& regexp,
|
|
intptr_t string_specialization_cid,
|
|
bool sticky) const {
|
|
ASSERT(kind() == UntaggedFunction::kIrregexpFunction);
|
|
ASSERT(IsStringClassId(string_specialization_cid));
|
|
ASSERT(data() == Object::null());
|
|
const Array& pair = Array::Handle(Array::New(2, Heap::kOld));
|
|
pair.SetAt(0, regexp);
|
|
pair.SetAt(1, Smi::Handle(Smi::New(StickySpecialization::encode(sticky) |
|
|
StringSpecializationCid::encode(
|
|
string_specialization_cid))));
|
|
set_data(pair);
|
|
}
|
|
|
|
StringPtr Function::native_name() const {
|
|
ASSERT(is_native());
|
|
const Object& obj = Object::Handle(data());
|
|
ASSERT(obj.IsArray());
|
|
return String::RawCast(Array::Cast(obj).At(0));
|
|
}
|
|
|
|
void Function::set_native_name(const String& value) const {
|
|
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)
|
|
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)
|
|
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 (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);
|
|
}
|
|
|
|
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 {
|
|
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;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#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.IsTopTypeForSubtyping()) {
|
|
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.IsTopTypeForSubtyping()) 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.IsTopTypeForSubtyping()) {
|
|
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.IsTopTypeForSubtyping()) {
|
|
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);
|
|
}
|
|
|
|
// Force-optimized functions are not debuggable because they cannot
|
|
// deoptimize.
|
|
if (result.ForceOptimize()) {
|
|
result.set_is_debuggable(false);
|
|
}
|
|
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() const {
|
|
// NOTE(turnidge): If you update this function, you probably want to
|
|
// update Class::PatchFieldsAndFunctions() at the same time.
|
|
if (IsDynamicInvocationForwarder()) {
|
|
const Function& target = Function::Handle(ForwardingTarget());
|
|
return target.IsNull() ? Script::null() : target.script();
|
|
}
|
|
if (IsImplicitGetterOrSetter()) {
|
|
const auto& field = Field::Handle(accessor_field());
|
|
return field.IsNull() ? Script::null() : field.Script();
|
|
}
|
|
if (is_eval_function()) {
|
|
const auto& fdata = Array::Handle(Array::RawCast(data()));
|
|
return Script::RawCast(
|
|
fdata.At(static_cast<intptr_t>(EvalFunctionData::kScript)));
|
|
}
|
|
const Object& obj = Object::Handle(untag()->owner());
|
|
if (obj.IsPatchClass()) {
|
|
return PatchClass::Cast(obj).script();
|
|
}
|
|
if (IsClosureFunction()) {
|
|
const Function& function = Function::Handle(parent_function());
|
|
if (function.IsNull()) return Script::null();
|
|
return function.script();
|
|
}
|
|
ASSERT(obj.IsClass());
|
|
return Class::Cast(obj).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 Array& 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
|
|
}
|
|
|
|
ArrayPtr Function::GetCoverageArray() const {
|
|
const Array& arr = Array::Handle(ic_data_array());
|
|
if (arr.IsNull()) {
|
|
return Array::null();
|
|
}
|
|
return Array::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::kIrregexpFunction:
|
|
buffer.AddString(" irregexp-function");
|
|
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);
|
|
}
|
|
|
|
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() const {
|
|
// NOTE(turnidge): If you update this function, you probably want to
|
|
// update Class::PatchFieldsAndFunctions() at the same time.
|
|
const Field& field = Field::Handle(Original());
|
|
ASSERT(field.IsOriginal());
|
|
const Object& obj = Object::Handle(field.untag()->owner());
|
|
if (obj.IsClass()) {
|
|
return Class::Cast(obj).script();
|
|
}
|
|
ASSERT(obj.IsPatchClass());
|
|
return PatchClass::Cast(obj).script();
|
|
}
|
|
|
|
#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_exact_type(const AbstractType& value) const {
|
|
DEBUG_ASSERT(
|
|
IsolateGroup::Current()->program_lock()->IsCurrentThreadWriter());
|
|
ASSERT(IsOriginal());
|
|
untag()->set_exact_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));
|
|
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()) ||
|
|
script_url.StartsWith(Symbols::DartSchemePrivate()));
|
|
}
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
void Script::LoadSourceFromKernel(const uint8_t* kernel_buffer,
|
|
intptr_t kernel_buffer_len) const {
|
|
String& uri = String::Handle(resolved_url());
|
|
String& source = String::Handle(kernel::KernelLoader::FindSourceForScript(
|
|
kernel_buffer, kernel_buffer_len, uri));
|
|
set_source(source);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
bool Library::IsAnyCoreLibrary() const {
|
|
String& url_str = Thread::Current()->StringHandle();
|
|
url_str = url();
|
|
return url_str.StartsWith(Symbols::DartScheme()) ||
|
|
url_str.StartsWith(Symbols::DartSchemePrivate());
|
|
}
|
|
|
|
void Library::set_num_imports(intptr_t value) const {
|
|
if (!Utils::IsUint(16, value)) {
|
|
ReportTooManyImports(*this);
|
|
}
|
|
StoreNonPointer(&untag()->num_imports_, value);
|
|
}
|
|
|
|
void Library::set_name(const String& name) const {
|
|
ASSERT(name.IsSymbol());
|
|
untag()->set_name(name.ptr());
|
|
}
|
|
|
|
void Library::set_url(const String& 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());
|
|
}
|
|
}
|
|
|
|
// Lookup a name in the library's re-export namespace.
|
|
// This lookup can occur from two different threads: background compiler and
|
|
// mutator thread.
|
|
ObjectPtr Library::LookupReExport(const String& name,
|
|
ZoneGrowableArray<intptr_t>* trail) const {
|
|
if (!HasExports()) {
|
|
return Object::null();
|
|
}
|
|
|
|
if (trail == nullptr) {
|
|
trail = new ZoneGrowableArray<intptr_t>();
|
|
}
|
|
Object& obj = Object::Handle();
|
|
|
|
const intptr_t lib_id = this->index();
|
|
ASSERT(lib_id >= 0); // We use -1 to indicate that a cycle was found.
|
|
trail->Add(lib_id);
|
|
const Array& exports = Array::Handle(this->exports());
|
|
Namespace& ns = Namespace::Handle();
|
|
for (int i = 0; i < exports.Length(); i++) {
|
|
ns ^= exports.At(i);
|
|
obj = ns.Lookup(name, trail);
|
|
if (!obj.IsNull()) {
|
|
// The Lookup call above may return a setter x= when we are looking
|
|
// for the name x. Make sure we only return when a matching name
|
|
// is found.
|
|
String& obj_name = String::Handle(obj.DictionaryName());
|
|
if (Field::IsSetterName(obj_name) == Field::IsSetterName(name)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
trail->RemoveLast();
|
|
return obj.ptr();
|
|
}
|
|
|
|
ObjectPtr Library::LookupEntry(const String& name, intptr_t* index) const {
|
|
ASSERT(!IsNull());
|
|
Thread* thread = Thread::Current();
|
|
REUSABLE_ARRAY_HANDLESCOPE(thread);
|
|
REUSABLE_OBJECT_HANDLESCOPE(thread);
|
|
REUSABLE_STRING_HANDLESCOPE(thread);
|
|
Array& dict = thread->ArrayHandle();
|
|
dict = dictionary();
|
|
intptr_t dict_size = dict.Length() - 1;
|
|
*index = name.Hash() % dict_size;
|
|
Object& entry = thread->ObjectHandle();
|
|
String& entry_name = thread->StringHandle();
|
|
entry = dict.At(*index);
|
|
// Search the entry in the hash set.
|
|
while (!entry.IsNull()) {
|
|
entry_name = entry.DictionaryName();
|
|
ASSERT(!entry_name.IsNull());
|
|
if (entry_name.Equals(name)) {
|
|
return entry.ptr();
|
|
}
|
|
*index = (*index + 1) % dict_size;
|
|
entry = dict.At(*index);
|
|
}
|
|
return Object::null();
|
|
}
|
|
|
|
void Library::AddClass(const Class& cls) const {
|
|
ASSERT(!Compiler::IsBackgroundCompilation());
|
|
const String& class_name = String::Handle(cls.Name());
|
|
AddObject(cls, class_name);
|
|
// Link class to this library.
|
|
cls.set_library(*this);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
ObjectPtr Library::LookupLocalOrReExportObject(const String& name) const {
|
|
intptr_t index;
|
|
EnsureTopLevelClassIsFinalized();
|
|
const Object& result = Object::Handle(LookupEntry(name, &index));
|
|
if (!result.IsNull() && !result.IsLibraryPrefix()) {
|
|
return result.ptr();
|
|
}
|
|
return LookupReExport(name);
|
|
}
|
|
|
|
FieldPtr Library::LookupFieldAllowPrivate(const String& name) const {
|
|
EnsureTopLevelClassIsFinalized();
|
|
Object& obj = Object::Handle(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);
|
|
}
|
|
|
|
// Convenience function to determine whether the export list is
|
|
// non-empty.
|
|
bool Library::HasExports() const {
|
|
return exports() != Object::empty_array().ptr();
|
|
}
|
|
|
|
// We add one namespace at a time to the exports array and don't
|
|
// pre-allocate any unused capacity. The assumption is that
|
|
// re-exports are quite rare.
|
|
void Library::AddExport(const Namespace& ns) const {
|
|
Array& exports = Array::Handle(this->exports());
|
|
intptr_t num_exports = exports.Length();
|
|
exports = Array::Grow(exports, num_exports + 1);
|
|
untag()->set_exports(exports.ptr());
|
|
exports.SetAt(num_exports, ns);
|
|
}
|
|
|
|
static ArrayPtr NewDictionary(intptr_t initial_size) {
|
|
const Array& dict = Array::Handle(Array::New(initial_size + 1, Heap::kOld));
|
|
// The last element of the dictionary specifies the number of in use slots.
|
|
dict.SetAt(initial_size, Object::smi_zero());
|
|
return dict.ptr();
|
|
}
|
|
|
|
void Library::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 {
|
|
Object& obj = Object::Handle(LookupLocalOrReExportObject(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 = LookupLocalOrReExportObject(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 = LookupLocalOrReExportObject(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 {
|
|
Object& obj = Object::Handle(LookupLocalOrReExportObject(setter_name));
|
|
const String& internal_setter_name =
|
|
String::Handle(Field::SetterName(setter_name));
|
|
AbstractType& setter_type = AbstractType::Handle();
|
|
AbstractType& argument_type = AbstractType::Handle(value.GetType(Heap::kOld));
|
|
if (obj.IsField()) {
|
|
const Field& field = Field::Cast(obj);
|
|
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 = LookupLocalOrReExportObject(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();
|
|
|
|
// We don't pass any explicit type arguments, which will be understood as
|
|
// using dynamic for any function type arguments by lower layers.
|
|
const int kTypeArgsLen = 0;
|
|
const Array& args_descriptor_array = Array::Handle(
|
|
zone, ArgumentsDescriptor::NewBoxed(kTypeArgsLen, args.Length(),
|
|
arg_names, Heap::kNew));
|
|
ArgumentsDescriptor args_descriptor(args_descriptor_array);
|
|
|
|
auto& function = Function::Handle(zone);
|
|
auto& result =
|
|
Object::Handle(zone, LookupLocalOrReExportObject(function_name));
|
|
if (result.IsFunction()) {
|
|
function ^= result.ptr();
|
|
}
|
|
|
|
if (function.IsNull()) {
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Library prefixes are not exported.
|
|
if (obj.IsNull() || obj.IsLibraryPrefix()) {
|
|
// Lookup in the re-exported symbols.
|
|
obj = lib.LookupReExport(name, trail);
|
|
if (obj.IsNull() && !Field::IsSetterName(name)) {
|
|
// LookupReExport() only returns objects that match the given name.
|
|
// If there is no field/func/getter, try finding a setter.
|
|
const String& setter_name =
|
|
String::Handle(zone, Field::LookupSetterSymbol(name));
|
|
if (!setter_name.IsNull()) {
|
|
obj = lib.LookupReExport(setter_name, trail);
|
|
}
|
|
}
|
|
}
|
|
if (obj.IsNull() || HidesName(name) || obj.IsLibraryPrefix()) {
|
|
return Object::null();
|
|
}
|
|
return obj.ptr();
|
|
}
|
|
|
|
NamespacePtr Namespace::New() {
|
|
ASSERT(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::KindAsStr(UntaggedPcDescriptors::Kind kind) {
|
|
switch (kind) {
|
|
case UntaggedPcDescriptors::kDeopt:
|
|
return "deopt ";
|
|
case UntaggedPcDescriptors::kIcCall:
|
|
return "ic-call";
|
|
case UntaggedPcDescriptors::kUnoptStaticCall:
|
|
return "unopt-call";
|
|
case UntaggedPcDescriptors::kRuntimeCall:
|
|
return "runtime-call";
|
|
case UntaggedPcDescriptors::kOsrEntry:
|
|
return "osr-entry";
|
|
case UntaggedPcDescriptors::kRewind:
|
|
return "rewind";
|
|
case UntaggedPcDescriptors::kBSSRelocation:
|
|
return "bss reloc";
|
|
case UntaggedPcDescriptors::kOther:
|
|
return "other";
|
|
case UntaggedPcDescriptors::kAnyKind:
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
UNREACHABLE();
|
|
return "";
|
|
}
|
|
|
|
void PcDescriptors::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(), KindAsStr(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());
|
|
ASSERT(Object::Handle(ptr()->untag()->name(var_index)).IsString());
|
|
return ptr()->untag()->name(var_index);
|
|
}
|
|
|
|
void LocalVarDescriptors::SetVar(
|
|
intptr_t var_index,
|
|
const String& name,
|
|
UntaggedLocalVarDescriptors::VarInfo* info) const {
|
|
ASSERT(var_index < Length());
|
|
ASSERT(!name.IsNull());
|
|
ptr()->untag()->set_name(var_index, name.ptr());
|
|
ptr()->untag()->data()[var_index] = *info;
|
|
}
|
|
|
|
void LocalVarDescriptors::GetInfo(
|
|
intptr_t var_index,
|
|
UntaggedLocalVarDescriptors::VarInfo* info) const {
|
|
ASSERT(var_index < Length());
|
|
*info = ptr()->untag()->data()[var_index];
|
|
}
|
|
|
|
static int PrintVarInfo(char* buffer,
|
|
int len,
|
|
intptr_t i,
|
|
const String& var_name,
|
|
const UntaggedLocalVarDescriptors::VarInfo& info) {
|
|
const UntaggedLocalVarDescriptors::VarInfoKind kind = info.kind();
|
|
const int32_t index = info.index();
|
|
if (kind == UntaggedLocalVarDescriptors::kContextLevel) {
|
|
return Utils::SNPrint(buffer, len,
|
|
"%2" Pd
|
|
" %-13s level=%-3d"
|
|
" begin=%-3d end=%d\n",
|
|
i, LocalVarDescriptors::KindToCString(kind), index,
|
|
static_cast<int>(info.begin_pos.Pos()),
|
|
static_cast<int>(info.end_pos.Pos()));
|
|
} else if (kind == UntaggedLocalVarDescriptors::kContextVar) {
|
|
return Utils::SNPrint(
|
|
buffer, len,
|
|
"%2" Pd
|
|
" %-13s level=%-3d index=%-3d"
|
|
" begin=%-3d end=%-3d name=%s\n",
|
|
i, LocalVarDescriptors::KindToCString(kind), info.scope_id, index,
|
|
static_cast<int>(info.begin_pos.Pos()),
|
|
static_cast<int>(info.end_pos.Pos()), var_name.ToCString());
|
|
} else {
|
|
return Utils::SNPrint(
|
|
buffer, len,
|
|
"%2" Pd
|
|
" %-13s scope=%-3d index=%-3d"
|
|
" begin=%-3d end=%-3d name=%s\n",
|
|
i, LocalVarDescriptors::KindToCString(kind), info.scope_id, index,
|
|
static_cast<int>(info.begin_pos.Pos()),
|
|
static_cast<int>(info.end_pos.Pos()), var_name.ToCString());
|
|
}
|
|
}
|
|
|
|
const char* LocalVarDescriptors::ToCString() const {
|
|
if (IsNull()) {
|
|
return "LocalVarDescriptors: null";
|
|
}
|
|
if (Length() == 0) {
|
|
return "empty LocalVarDescriptors";
|
|
}
|
|
intptr_t len = 1; // Trailing '\0'.
|
|
String& var_name = String::Handle();
|
|
for (intptr_t i = 0; i < Length(); i++) {
|
|
UntaggedLocalVarDescriptors::VarInfo info;
|
|
var_name = GetName(i);
|
|
GetInfo(i, &info);
|
|
len += PrintVarInfo(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());
|
|
const Function& f = Function::Handle(function());
|
|
ASSERT(!f.IsIrregexpFunction()); // Not yet implemented.
|
|
Compiler::ComputeLocalVarDescriptors(*this);
|
|
}
|
|
return var_descriptors();
|
|
}
|
|
|
|
void Code::set_owner(const Object& owner) const {
|
|
#if defined(DEBUG)
|
|
const auto& unwrapped_owner =
|
|
Object::Handle(WeakSerializationReference::Unwrap(owner));
|
|
ASSERT(unwrapped_owner.IsFunction() || unwrapped_owner.IsClass() ||
|
|
unwrapped_owner.IsAbstractType());
|
|
#endif
|
|
untag()->set_owner(owner.ptr());
|
|
}
|
|
|
|
void Code::set_state_bits(intptr_t bits) const {
|
|
StoreNonPointer(&untag()->state_bits_, bits);
|
|
}
|
|
|
|
void Code::set_is_optimized(bool value) const {
|
|
set_state_bits(OptimizedBit::update(value, untag()->state_bits_));
|
|
}
|
|
|
|
void Code::set_is_force_optimized(bool value) const {
|
|
set_state_bits(ForceOptimizedBit::update(value, untag()->state_bits_));
|
|
}
|
|
|
|
void Code::set_is_alive(bool value) const {
|
|
set_state_bits(AliveBit::update(value, untag()->state_bits_));
|
|
}
|
|
|
|
void Code::set_is_discarded(bool value) const {
|
|
set_state_bits(DiscardedBit::update(value, untag()->state_bits_));
|
|
}
|
|
|
|
void Code::set_compressed_stackmaps(const CompressedStackMaps& maps) const {
|
|
ASSERT(maps.IsOld());
|
|
untag()->set_compressed_stackmaps(maps.ptr());
|
|
}
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
intptr_t Code::num_variables() const {
|
|
ASSERT(!FLAG_precompiled_mode);
|
|
return Smi::Value(Smi::RawCast(untag()->catch_entry()));
|
|
}
|
|
void Code::set_num_variables(intptr_t num_variables) const {
|
|
ASSERT(!FLAG_precompiled_mode);
|
|
untag()->set_catch_entry(Smi::New(num_variables));
|
|
}
|
|
#endif
|
|
|
|
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
|
|
}
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
const char* Bytecode::ToCString() const {
|
|
return Thread::Current()->zone()->PrintToString("Bytecode(%s)",
|
|
QualifiedName());
|
|
}
|
|
|
|
static const char* BytecodeStubName(const Bytecode& bytecode) {
|
|
if (bytecode.ptr() == Object::implicit_getter_bytecode().ptr()) {
|
|
return "[Bytecode Stub] VMInternal_ImplicitGetter";
|
|
} else if (bytecode.ptr() == Object::implicit_setter_bytecode().ptr()) {
|
|
return "[Bytecode Stub] VMInternal_ImplicitSetter";
|
|
} else if (bytecode.ptr() ==
|
|
Object::implicit_static_getter_bytecode().ptr()) {
|
|
return "[Bytecode Stub] VMInternal_ImplicitStaticGetter";
|
|
} else if (bytecode.ptr() ==
|
|
Object::implicit_shared_static_getter_bytecode().ptr()) {
|
|
return "[Bytecode Stub] VMInternal_ImplicitSharedStaticGetter";
|
|
} else if (bytecode.ptr() ==
|
|
Object::implicit_static_setter_bytecode().ptr()) {
|
|
return "[Bytecode Stub] VMInternal_ImplicitStaticSetter";
|
|
} else if (bytecode.ptr() ==
|
|
Object::implicit_shared_static_setter_bytecode().ptr()) {
|
|
return "[Bytecode Stub] VMInternal_ImplicitSharedStaticSetter";
|
|
} else if (bytecode.ptr() ==
|
|
Object::method_extractor_with_ita_bytecode().ptr()) {
|
|
return "[Bytecode Stub] VMInternal_MethodExtractorWithITA";
|
|
} else if (bytecode.ptr() ==
|
|
Object::method_extractor_without_ita_bytecode().ptr()) {
|
|
return "[Bytecode Stub] VMInternal_MethodExtractorWithoutITA";
|
|
} else if (bytecode.ptr() == Object::invoke_closure_bytecode().ptr()) {
|
|
return "[Bytecode Stub] VMInternal_InvokeClosure";
|
|
} else if (bytecode.ptr() == Object::invoke_field_bytecode().ptr()) {
|
|
return "[Bytecode Stub] VMInternal_InvokeField";
|
|
}
|
|
return "[unknown stub]";
|
|
}
|
|
|
|
const char* Bytecode::Name() const {
|
|
Zone* zone = Thread::Current()->zone();
|
|
const Function& fun = Function::Handle(zone, function());
|
|
if (fun.IsNull()) {
|
|
return BytecodeStubName(*this);
|
|
}
|
|
const char* function_name = fun.UserVisibleNameCString();
|
|
return zone->PrintToString("[Bytecode] %s", function_name);
|
|
}
|
|
|
|
const char* Bytecode::QualifiedName() const {
|
|
Zone* zone = Thread::Current()->zone();
|
|
const Function& fun = Function::Handle(zone, function());
|
|
if (fun.IsNull()) {
|
|
return BytecodeStubName(*this);
|
|
}
|
|
const char* function_name = fun.QualifiedScrubbedNameCString();
|
|
return zone->PrintToString("[Bytecode] %s", function_name);
|
|
}
|
|
|
|
const char* Bytecode::FullyQualifiedName() const {
|
|
Zone* zone = Thread::Current()->zone();
|
|
const Function& fun = Function::Handle(zone, function());
|
|
if (fun.IsNull()) {
|
|
return BytecodeStubName(*this);
|
|
}
|
|
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()) {
|
|
switch (field.guarded_cid()) {
|
|
case kDoubleCid:
|
|
return Double::New(*reinterpret_cast<double_t*>(FieldAddr(field)));
|
|
case kFloat32x4Cid:
|
|
return Float32x4::New(
|
|
*reinterpret_cast<simd128_value_t*>(FieldAddr(field)));
|
|
case kFloat64x2Cid:
|
|
return Float64x2::New(
|
|
*reinterpret_cast<simd128_value_t*>(FieldAddr(field)));
|
|
default:
|
|
return Integer::New(*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()) {
|
|
switch (field.guarded_cid()) {
|
|
case kDoubleCid:
|
|
StoreNonPointer(reinterpret_cast<double_t*>(FieldAddr(field)),
|
|
Double::Cast(value).value());
|
|
break;
|
|
case kFloat32x4Cid:
|
|
StoreNonPointer(reinterpret_cast<simd128_value_t*>(FieldAddr(field)),
|
|
Float32x4::Cast(value).value());
|
|
break;
|
|
case kFloat64x2Cid:
|
|
StoreNonPointer(reinterpret_cast<simd128_value_t*>(FieldAddr(field)),
|
|
Float64x2::Cast(value).value());
|
|
break;
|
|
default:
|
|
StoreNonPointer(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;
|
|
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.IsTopTypeForSubtyping()) {
|
|
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.IsTopTypeForSubtyping() ||
|
|
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.IsTopTypeForSubtyping() ||
|
|
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.IsTopTypeForSubtyping()) {
|
|
return true;
|
|
}
|
|
}
|
|
if (IsNull()) {
|
|
if (instantiated_other.IsNullType()) {
|
|
return true;
|
|
}
|
|
if (RuntimeTypeIsSubtypeOfFutureOr(zone, instantiated_other)) {
|
|
return true;
|
|
}
|
|
// At this point, instantiated_other can be a function type.
|
|
return !instantiated_other.IsNonNullable();
|
|
}
|
|
if (!instantiated_other.IsType()) {
|
|
return false;
|
|
}
|
|
// RuntimeType of non-null instance is non-nullable, so there is no need to
|
|
// check nullability of other type.
|
|
return Class::IsSubtypeOf(cls, type_arguments, Nullability::kNonNullable,
|
|
instantiated_other, Heap::kOld);
|
|
}
|
|
|
|
bool Instance::RuntimeTypeIsSubtypeOfFutureOr(Zone* zone,
|
|
const AbstractType& other) const {
|
|
if (other.IsFutureOrType()) {
|
|
const TypeArguments& other_type_arguments =
|
|
TypeArguments::Handle(zone, other.arguments());
|
|
const AbstractType& other_type_arg =
|
|
AbstractType::Handle(zone, other_type_arguments.TypeAtNullSafe(0));
|
|
if (other_type_arg.IsTopTypeForSubtyping()) {
|
|
return true;
|
|
}
|
|
if (Class::Handle(zone, clazz()).IsFutureClass()) {
|
|
const TypeArguments& type_arguments =
|
|
TypeArguments::Handle(zone, GetTypeArguments());
|
|
const AbstractType& type_arg =
|
|
AbstractType::Handle(zone, type_arguments.TypeAtNullSafe(0));
|
|
if (type_arg.IsSubtypeOf(other_type_arg, Heap::kOld)) {
|
|
return true;
|
|
}
|
|
}
|
|
// Retry RuntimeTypeIsSubtypeOf after unwrapping type arg of FutureOr.
|
|
if (RuntimeTypeIsSubtypeOf(other_type_arg, Object::null_type_arguments(),
|
|
Object::null_type_arguments())) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Instance::OperatorEquals(const Instance& other) const {
|
|
// TODO(koda): Optimize for all builtin classes and all classes
|
|
// that do not override operator==.
|
|
return DartLibraryCalls::Equals(*this, other) == Object::bool_true().ptr();
|
|
}
|
|
|
|
bool Instance::IsIdenticalTo(const Instance& other) const {
|
|
if (ptr() == other.ptr()) return true;
|
|
if (IsInteger() && other.IsInteger()) {
|
|
return Integer::Cast(*this).Equals(other);
|
|
}
|
|
if (IsDouble() && other.IsDouble()) {
|
|
double other_value = Double::Cast(other).value();
|
|
return Double::Cast(*this).BitwiseEqualsToDouble(other_value);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
intptr_t* Instance::NativeFieldsDataAddr() const {
|
|
ASSERT(Thread::Current()->no_safepoint_scope_depth() > 0);
|
|
TypedDataPtr native_fields = static_cast<TypedDataPtr>(
|
|
NativeFieldsAddr()->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 {
|
|
// Null can be assigned to legacy and nullable types.
|
|
if (!IsNonNullable()) {
|
|
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 (IsNonNullable()) {
|
|
return unwrapped_type.ptr();
|
|
}
|
|
ASSERT(IsNullable());
|
|
return Type::Cast(unwrapped_type)
|
|
.ToNullability(Nullability::kNullable, space);
|
|
}
|
|
if (cid == kNeverCid && unwrapped_type.IsNonNullable()) {
|
|
ObjectStore* object_store = IsolateGroup::Current()->object_store();
|
|
const Type& future_never_type =
|
|
Type::Handle(zone, object_store->non_nullable_future_never_type());
|
|
ASSERT(!future_never_type.IsNull());
|
|
return future_never_type.ToNullability(nullability(), space);
|
|
}
|
|
if (cid == kNullCid) {
|
|
ObjectStore* object_store = IsolateGroup::Current()->object_store();
|
|
ASSERT(object_store->nullable_future_null_type() != Type::null());
|
|
return object_store->nullable_future_null_type();
|
|
}
|
|
if (IsNullable() && unwrapped_type.IsNullable()) {
|
|
return Type::Cast(*this).ToNullability(Nullability::kNonNullable, space);
|
|
}
|
|
}
|
|
return ptr();
|
|
}
|
|
|
|
bool AbstractType::IsInstantiated(Genericity genericity,
|
|
intptr_t num_free_fun_type_params) 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;
|
|
}
|
|
|
|
bool AbstractType::IsTopTypeForInstanceOf() 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()).IsTopTypeForInstanceOf();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Must be kept in sync with GenerateTypeIsTopTypeForSubtyping in
|
|
// stub_code_compiler.cc if any changes are made.
|
|
bool AbstractType::IsTopTypeForSubtyping() const {
|
|
const classid_t cid = type_class_id();
|
|
if (cid == kDynamicCid || cid == kVoidCid) {
|
|
return true;
|
|
}
|
|
if (cid == kInstanceCid) { // Object type.
|
|
return !IsNonNullable();
|
|
}
|
|
if (cid == kFutureOrCid) {
|
|
// FutureOr<T> where T is a top type behaves as a top type.
|
|
return AbstractType::Handle(UnwrapFutureOr()).IsTopTypeForSubtyping();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool AbstractType::IsIntType() const {
|
|
return HasTypeClass() &&
|
|
(type_class() == Type::Handle(Type::IntType()).type_class());
|
|
}
|
|
|
|
bool AbstractType::IsIntegerImplementationType() const {
|
|
return HasTypeClass() &&
|
|
(type_class() == IsolateGroup::Current()
|
|
->object_store()
|
|
->integer_implementation_class());
|
|
}
|
|
|
|
bool AbstractType::IsDoubleType() const {
|
|
return HasTypeClass() &&
|
|
(type_class() == Type::Handle(Type::Double()).type_class());
|
|
}
|
|
|
|
bool AbstractType::IsFloat32x4Type() const {
|
|
// kFloat32x4Cid refers to the private class and cannot be used here.
|
|
return HasTypeClass() &&
|
|
(type_class() == Type::Handle(Type::Float32x4()).type_class());
|
|
}
|
|
|
|
bool AbstractType::IsFloat64x2Type() const {
|
|
// kFloat64x2Cid refers to the private class and cannot be used here.
|
|
return HasTypeClass() &&
|
|
(type_class() == Type::Handle(Type::Float64x2()).type_class());
|
|
}
|
|
|
|
bool AbstractType::IsInt32x4Type() const {
|
|
// kInt32x4Cid refers to the private class and cannot be used here.
|
|
return HasTypeClass() &&
|
|
(type_class() == Type::Handle(Type::Int32x4()).type_class());
|
|
}
|
|
|
|
bool AbstractType::IsStringType() const {
|
|
return HasTypeClass() &&
|
|
(type_class() == Type::Handle(Type::StringType()).type_class());
|
|
}
|
|
|
|
bool AbstractType::IsDartFunctionType() const {
|
|
return HasTypeClass() &&
|
|
(type_class() == Type::Handle(Type::DartFunctionType()).type_class());
|
|
}
|
|
|
|
bool AbstractType::IsDartClosureType() const {
|
|
return (type_class_id() == kClosureCid);
|
|
}
|
|
|
|
bool AbstractType::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.IsTopTypeForSubtyping()) {
|
|
TRACE_TYPE_CHECKS_VERBOSE(" - result: true (right is top)\n");
|
|
return true;
|
|
}
|
|
// Left bottom type.
|
|
// Any form of Never in weak mode maps to Null and Null is a bottom type in
|
|
// weak mode. In strong mode, Never and Never* are bottom types. Therefore,
|
|
// Never and Never* are bottom types regardless of weak/strong mode.
|
|
// Note that we cannot encounter Never?, as it is normalized to Null.
|
|
if (IsNeverType()) {
|
|
ASSERT(!IsNullable());
|
|
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.IsTopTypeForSubtyping()) {
|
|
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 {
|
|
return memcmp(&untag()->value_, Float32x4::Cast(other).untag()->value_,
|
|
sizeof(simd128_value_t)) == 0;
|
|
}
|
|
|
|
uint32_t Float32x4::CanonicalizeHash() const {
|
|
return HashBytes(reinterpret_cast<const uint8_t*>(&untag()->value_),
|
|
sizeof(simd128_value_t));
|
|
}
|
|
|
|
const char* Float32x4::ToCString() const {
|
|
float _x = x();
|
|
float _y = y();
|
|
float _z = z();
|
|
float _w = w();
|
|
return OS::SCreate(Thread::Current()->zone(), "[%f, %f, %f, %f]", _x, _y, _z,
|
|
_w);
|
|
}
|
|
|
|
Int32x4Ptr Int32x4::New(int32_t v0,
|
|
int32_t v1,
|
|
int32_t v2,
|
|
int32_t v3,
|
|
Heap::Space space) {
|
|
ASSERT(IsolateGroup::Current()->object_store()->int32x4_class() !=
|
|
Class::null());
|
|
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 {
|
|
return memcmp(&untag()->value_, Int32x4::Cast(other).untag()->value_,
|
|
sizeof(simd128_value_t)) == 0;
|
|
}
|
|
|
|
uint32_t Int32x4::CanonicalizeHash() const {
|
|
return HashBytes(reinterpret_cast<const uint8_t*>(&untag()->value_),
|
|
sizeof(simd128_value_t));
|
|
}
|
|
|
|
const char* Int32x4::ToCString() const {
|
|
int32_t _x = x();
|
|
int32_t _y = y();
|
|
int32_t _z = z();
|
|
int32_t _w = w();
|
|
return OS::SCreate(Thread::Current()->zone(), "[%08x, %08x, %08x, %08x]", _x,
|
|
_y, _z, _w);
|
|
}
|
|
|
|
Float64x2Ptr Float64x2::New(double value0, double value1, Heap::Space space) {
|
|
ASSERT(IsolateGroup::Current()->object_store()->float64x2_class() !=
|
|
Class::null());
|
|
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 {
|
|
return memcmp(&untag()->value_, Float64x2::Cast(other).untag()->value_,
|
|
sizeof(simd128_value_t)) == 0;
|
|
}
|
|
|
|
uint32_t Float64x2::CanonicalizeHash() const {
|
|
return HashBytes(reinterpret_cast<const uint8_t*>(&untag()->value_),
|
|
sizeof(simd128_value_t));
|
|
}
|
|
|
|
const char* Float64x2::ToCString() const {
|
|
double _x = x();
|
|
double _y = y();
|
|
return OS::SCreate(Thread::Current()->zone(), "[%f, %f]", _x, _y);
|
|
}
|
|
|
|
const intptr_t
|
|
TypedDataBase::element_size_table[TypedDataBase::kNumElementSizes] = {
|
|
1, // kTypedDataInt8ArrayCid.
|
|
1, // kTypedDataUint8ArrayCid.
|
|
1, // kTypedDataUint8ClampedArrayCid.
|
|
2, // kTypedDataInt16ArrayCid.
|
|
2, // kTypedDataUint16ArrayCid.
|
|
4, // kTypedDataInt32ArrayCid.
|
|
4, // kTypedDataUint32ArrayCid.
|
|
8, // kTypedDataInt64ArrayCid.
|
|
8, // kTypedDataUint64ArrayCid.
|
|
4, // kTypedDataFloat32ArrayCid.
|
|
8, // kTypedDataFloat64ArrayCid.
|
|
16, // kTypedDataFloat32x4ArrayCid.
|
|
16, // kTypedDataInt32x4ArrayCid.
|
|
16, // kTypedDataFloat64x2ArrayCid,
|
|
};
|
|
|
|
bool TypedData::CanonicalizeEquals(const Instance& other) const {
|
|
if (this->ptr() == other.ptr()) {
|
|
// Both handles point to the same raw instance.
|
|
return true;
|
|
}
|
|
|
|
if (!other.IsTypedData() || other.IsNull()) {
|
|
return false;
|
|
}
|
|
|
|
const TypedData& other_typed_data = TypedData::Cast(other);
|
|
|
|
if (this->ElementType() != other_typed_data.ElementType()) {
|
|
return false;
|
|
}
|
|
|
|
const intptr_t len = this->LengthInBytes();
|
|
if (len != other_typed_data.LengthInBytes()) {
|
|
return false;
|
|
}
|
|
NoSafepointScope no_safepoint;
|
|
return (len == 0) ||
|
|
(memcmp(DataAddr(0), other_typed_data.DataAddr(0), len) == 0);
|
|
}
|
|
|
|
uint32_t TypedData::CanonicalizeHash() const {
|
|
const intptr_t len = this->LengthInBytes();
|
|
if (len == 0) {
|
|
return 1;
|
|
}
|
|
uint32_t hash = len;
|
|
for (intptr_t i = 0; i < len; i++) {
|
|
hash = CombineHashes(len, GetUint8(i));
|
|
}
|
|
return FinalizeHash(hash, kHashBits);
|
|
}
|
|
|
|
TypedDataPtr TypedData::New(intptr_t class_id,
|
|
intptr_t len,
|
|
Heap::Space space) {
|
|
if (len < 0 || len > TypedData::MaxElements(class_id)) {
|
|
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,
|
|
bool canBeClosed,
|
|
Heap::Space space) {
|
|
const auto& result =
|
|
DynamicLibrary::Handle(Object::Allocate<DynamicLibrary>(space));
|
|
ASSERT_EQUAL(result.IsClosed(), false);
|
|
result.SetHandle(handle);
|
|
result.SetCanBeClosed(canBeClosed);
|
|
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,
|
|
bool vm,
|
|
LoadingUnit* unit = nullptr) {
|
|
const Image 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 - instructions;
|
|
// 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 (!vm && 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,
|
|
uword vm_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,
|
|
/*vm=*/false, unit)) {
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
if (TryPrintNonSymbolicStackFrameBodyRelative(buffer, call_addr,
|
|
isolate_instructions,
|
|
/*vm=*/false)) {
|
|
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 "", isolate_instructions);
|
|
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;
|
|
}
|
|
|
|
const uword pc_offset = stack_trace.PcOffsetAtFrame(i);
|
|
|
|
// 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);
|
|
function = bytecode.function();
|
|
|
|
if (!function.IsNull() &&
|
|
(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++;
|
|
}
|
|
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();
|
|
}
|
|
const uword pc = code.PayloadStart() + pc_offset;
|
|
|
|
const bool is_future_listener =
|
|
pc_offset == StackTraceUtils::kFutureListenerPcOffset;
|
|
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
// When printing non-symbolic frames, we normally print call
|
|
// addresses, not return addresses, by subtracting one from the PC to
|
|
// get an address within the preceding instruction.
|
|
//
|
|
// The one exception is a normal closure registered as a listener on a
|
|
// future. In this case, the returned pc_offset will be pointing to the
|
|
// entry pooint of the function, which will be invoked when the future
|
|
// completes. To make things more uniform stack unwinding code offets
|
|
// pc_offset by 1 for such cases.
|
|
const uword call_addr = 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,
|
|
/*vm_instructions=*/0, 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,
|
|
/*vm_instructions=*/0, 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:
|
|
case UntaggedFunction::kIrregexpFunction:
|
|
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
|