83e4aad58b
The element data array in the VM's hash map/set implementation always has a power-of-two length, growing exponentially as entries/elements are added. For constant maps/sets, this is not necessary, and the unused parts of these data arrays is just wasted space. This changes the data arrays for constant maps and sets to only contain the actual entries/elements of the map/set. Tested: ci Change-Id: I061eb44b6c74c87157ef920143bf1885582f1b9a Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-debug-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/237140 Reviewed-by: Daco Harkes <dacoharkes@google.com>
491 lines
20 KiB
C++
491 lines
20 KiB
C++
// Copyright (c) 2018, 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/compiler/frontend/constant_reader.h"
|
|
|
|
#include "vm/object_store.h"
|
|
|
|
namespace dart {
|
|
namespace kernel {
|
|
|
|
#define Z (zone_)
|
|
#define H (translation_helper_)
|
|
|
|
// Note: If changing how the constants are saved in the binary (and thus how
|
|
// they are read here) be aware that there's also some reading going on in
|
|
// KernelLoader::ReadVMAnnotations which then also has to be updated!
|
|
|
|
ConstantReader::ConstantReader(KernelReaderHelper* helper,
|
|
ActiveClass* active_class)
|
|
: helper_(helper),
|
|
zone_(helper->zone_),
|
|
translation_helper_(helper->translation_helper_),
|
|
active_class_(active_class),
|
|
script_(helper->script()),
|
|
result_(Object::Handle(zone_)) {}
|
|
|
|
InstancePtr ConstantReader::ReadConstantInitializer() {
|
|
Tag tag = helper_->ReadTag(); // read tag.
|
|
switch (tag) {
|
|
case kSomething:
|
|
return ReadConstantExpression();
|
|
default:
|
|
H.ReportError(script_, TokenPosition::kNoSource,
|
|
"Not a constant expression: unexpected kernel tag %s (%d)",
|
|
Reader::TagName(tag), tag);
|
|
}
|
|
return Instance::RawCast(result_.ptr());
|
|
}
|
|
|
|
InstancePtr ConstantReader::ReadConstantExpression() {
|
|
Tag tag = helper_->ReadTag(); // read tag.
|
|
switch (tag) {
|
|
case kConstantExpression:
|
|
helper_->ReadPosition();
|
|
helper_->SkipDartType();
|
|
result_ = ReadConstant(helper_->ReadUInt());
|
|
break;
|
|
case kInvalidExpression: {
|
|
helper_->ReadPosition(); // Skip position.
|
|
const String& message = H.DartString(helper_->ReadStringReference());
|
|
// Invalid expression message has pointer to the source code, no need to
|
|
// report it twice.
|
|
H.ReportError(helper_->script(), TokenPosition::kNoSource, "%s",
|
|
message.ToCString());
|
|
break;
|
|
}
|
|
default:
|
|
H.ReportError(script_, TokenPosition::kNoSource,
|
|
"Not a constant expression: unexpected kernel tag %s (%d)",
|
|
Reader::TagName(tag), tag);
|
|
}
|
|
return Instance::RawCast(result_.ptr());
|
|
}
|
|
|
|
ObjectPtr ConstantReader::ReadAnnotations() {
|
|
intptr_t list_length = helper_->ReadListLength(); // read list length.
|
|
const auto& metadata_values =
|
|
Array::Handle(Z, ImmutableArray::New(list_length, H.allocation_space()));
|
|
Instance& value = Instance::Handle(Z);
|
|
for (intptr_t i = 0; i < list_length; ++i) {
|
|
// This will read the expression.
|
|
value = ReadConstantExpression();
|
|
metadata_values.SetAt(i, value);
|
|
}
|
|
return H.Canonicalize(metadata_values);
|
|
}
|
|
|
|
InstancePtr ConstantReader::ReadConstant(intptr_t constant_index) {
|
|
ASSERT(!H.constants().IsNull());
|
|
ASSERT(!H.constants_table().IsNull()); // raw bytes
|
|
|
|
// For kernel-level cache (in contrast with script-level caching),
|
|
// we need to access the raw constants array inside the shared
|
|
// KernelProgramInfo directly, so that all scripts will see the
|
|
// results after new insertions. These accesses at kernel-level
|
|
// must be locked since mutator and background compiler can
|
|
// access the array at the same time.
|
|
{
|
|
SafepointMutexLocker ml(
|
|
H.thread()->isolate_group()->kernel_constants_mutex());
|
|
const auto& constants_array = Array::Handle(Z, H.info().constants());
|
|
ASSERT(constant_index < constants_array.Length());
|
|
result_ = constants_array.At(constant_index);
|
|
}
|
|
|
|
// On miss, evaluate, and insert value.
|
|
if (result_.ptr() == Object::sentinel().ptr()) {
|
|
LeaveCompilerScope cs(H.thread());
|
|
result_ = ReadConstantInternal(constant_index);
|
|
SafepointMutexLocker ml(
|
|
H.thread()->isolate_group()->kernel_constants_mutex());
|
|
const auto& constants_array = Array::Handle(Z, H.info().constants());
|
|
ASSERT(constant_index < constants_array.Length());
|
|
constants_array.SetAt(constant_index, result_);
|
|
}
|
|
return Instance::RawCast(result_.ptr());
|
|
}
|
|
|
|
bool ConstantReader::IsInstanceConstant(intptr_t constant_index,
|
|
const Class& clazz) {
|
|
// Get reader directly into raw bytes of constant table/constant mapping.
|
|
KernelReaderHelper reader(Z, &H, script_, H.constants_table(), 0);
|
|
NavigateToIndex(&reader, constant_index);
|
|
|
|
// Peek for an instance of the given clazz.
|
|
if (reader.ReadByte() == kInstanceConstant) {
|
|
const NameIndex index = reader.ReadCanonicalNameReference();
|
|
return H.LookupClassByKernelClass(index) == clazz.ptr();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
intptr_t ConstantReader::NumConstants() {
|
|
ASSERT(!H.constants_table().IsNull());
|
|
KernelReaderHelper reader(Z, &H, script_, H.constants_table(), 0);
|
|
return NumConstants(&reader);
|
|
}
|
|
|
|
intptr_t ConstantReader::NumConstants(KernelReaderHelper* reader) {
|
|
// Get reader directly into raw bytes of constant table/constant mapping.
|
|
// Get the length of the constants (at the end of the mapping).
|
|
reader->SetOffset(reader->ReaderSize() - 4);
|
|
return reader->ReadUInt32();
|
|
}
|
|
|
|
intptr_t ConstantReader::NavigateToIndex(KernelReaderHelper* reader,
|
|
intptr_t constant_index) {
|
|
const intptr_t num_constants = NumConstants(reader);
|
|
|
|
// Get the binary offset of the constant at the wanted index.
|
|
reader->SetOffset(reader->ReaderSize() - 4 - (num_constants * 4) +
|
|
(constant_index * 4));
|
|
const intptr_t constant_offset = reader->ReadUInt32();
|
|
|
|
reader->SetOffset(constant_offset);
|
|
|
|
return constant_offset;
|
|
}
|
|
|
|
InstancePtr ConstantReader::ReadConstantInternal(intptr_t constant_index) {
|
|
// Get reader directly into raw bytes of constant table/constant mapping.
|
|
bool null_safety = H.thread()->isolate_group()->null_safety();
|
|
KernelReaderHelper reader(Z, &H, script_, H.constants_table(), 0);
|
|
const intptr_t constant_offset = NavigateToIndex(&reader, constant_index);
|
|
|
|
// No function types returned as part of any types built should reference
|
|
// free parent type args, ensured by clearing the enclosing function type.
|
|
ActiveEnclosingFunctionScope scope(active_class_, nullptr);
|
|
// Construct constant from raw bytes.
|
|
Instance& instance = Instance::Handle(Z);
|
|
const intptr_t constant_tag = reader.ReadByte();
|
|
switch (constant_tag) {
|
|
case kNullConstant:
|
|
instance = Instance::null();
|
|
break;
|
|
case kBoolConstant:
|
|
instance = reader.ReadByte() == 1 ? Object::bool_true().ptr()
|
|
: Object::bool_false().ptr();
|
|
break;
|
|
case kIntConstant: {
|
|
uint8_t payload = 0;
|
|
Tag integer_tag = reader.ReadTag(&payload); // read tag.
|
|
switch (integer_tag) {
|
|
case kBigIntLiteral: {
|
|
const String& value = H.DartString(reader.ReadStringReference());
|
|
instance = Integer::New(value, Heap::kOld);
|
|
break;
|
|
}
|
|
case kSpecializedIntLiteral: {
|
|
const int64_t value =
|
|
static_cast<int32_t>(payload) - SpecializedIntLiteralBias;
|
|
instance = Integer::New(value, Heap::kOld);
|
|
break;
|
|
}
|
|
case kNegativeIntLiteral: {
|
|
const int64_t value = -static_cast<int64_t>(reader.ReadUInt());
|
|
instance = Integer::New(value, Heap::kOld);
|
|
break;
|
|
}
|
|
case kPositiveIntLiteral: {
|
|
const int64_t value = reader.ReadUInt();
|
|
instance = Integer::New(value, Heap::kOld);
|
|
break;
|
|
}
|
|
default:
|
|
H.ReportError(
|
|
script_, TokenPosition::kNoSource,
|
|
"Cannot lazily read integer: unexpected kernel tag %s (%d)",
|
|
Reader::TagName(integer_tag), integer_tag);
|
|
}
|
|
break;
|
|
}
|
|
case kDoubleConstant:
|
|
instance = Double::New(reader.ReadDouble(), Heap::kOld);
|
|
break;
|
|
case kStringConstant:
|
|
instance = H.DartSymbolPlain(reader.ReadStringReference()).ptr();
|
|
break;
|
|
case kSymbolConstant: {
|
|
Library& library = Library::Handle(Z);
|
|
library = Library::InternalLibrary();
|
|
const auto& symbol_class =
|
|
Class::Handle(Z, library.LookupClass(Symbols::Symbol()));
|
|
const auto& symbol_name_field = Field::Handle(
|
|
Z, symbol_class.LookupInstanceFieldAllowPrivate(Symbols::_name()));
|
|
ASSERT(!symbol_name_field.IsNull());
|
|
const NameIndex index = reader.ReadCanonicalNameReference();
|
|
if (index == -1) {
|
|
library = Library::null();
|
|
} else {
|
|
library = H.LookupLibraryByKernelLibrary(index);
|
|
}
|
|
const String& symbol =
|
|
H.DartIdentifier(library, reader.ReadStringReference());
|
|
instance = Instance::New(symbol_class, Heap::kOld);
|
|
instance.SetField(symbol_name_field, symbol);
|
|
break;
|
|
}
|
|
case kListConstant: {
|
|
const auto& list_class = Class::Handle(
|
|
Z, H.isolate_group()->object_store()->immutable_array_class());
|
|
ASSERT(!list_class.IsNull());
|
|
// Build type from the raw bytes (needs temporary translator).
|
|
TypeTranslator type_translator(
|
|
&reader, this, active_class_, /* finalize = */ true,
|
|
active_class_->RequireConstCanonicalTypeErasure(null_safety),
|
|
/* in_constant_context = */ true);
|
|
auto& type_arguments =
|
|
TypeArguments::Handle(Z, TypeArguments::New(1, Heap::kOld));
|
|
AbstractType& type = type_translator.BuildType();
|
|
type_arguments.SetTypeAt(0, type);
|
|
// Instantiate class.
|
|
type = Type::New(list_class, type_arguments);
|
|
type = ClassFinalizer::FinalizeType(type, ClassFinalizer::kCanonicalize);
|
|
type_arguments = type.arguments();
|
|
// Fill array with constant elements.
|
|
const intptr_t length = reader.ReadUInt();
|
|
const Array& array =
|
|
Array::Handle(Z, ImmutableArray::New(length, Heap::kOld));
|
|
array.SetTypeArguments(type_arguments);
|
|
Instance& constant = Instance::Handle(Z);
|
|
for (intptr_t j = 0; j < length; ++j) {
|
|
// Recurse into lazily evaluating all "sub" constants
|
|
// needed to evaluate the current constant.
|
|
const intptr_t entry_index = reader.ReadUInt();
|
|
ASSERT(entry_index < constant_offset); // DAG!
|
|
constant = ReadConstant(entry_index);
|
|
array.SetAt(j, constant);
|
|
}
|
|
instance = array.ptr();
|
|
break;
|
|
}
|
|
case kMapConstant: {
|
|
const auto& map_class = Class::Handle(
|
|
Z,
|
|
H.isolate_group()->object_store()->immutable_linked_hash_map_class());
|
|
ASSERT(!map_class.IsNull());
|
|
|
|
// Build types from the raw bytes (needs temporary translator).
|
|
TypeTranslator type_translator(
|
|
&reader, this, active_class_, /* finalize = */ true,
|
|
active_class_->RequireConstCanonicalTypeErasure(null_safety),
|
|
/* in_constant_context = */ true);
|
|
auto& type_arguments =
|
|
TypeArguments::Handle(Z, TypeArguments::New(2, Heap::kOld));
|
|
AbstractType& type = type_translator.BuildType();
|
|
type_arguments.SetTypeAt(0, type);
|
|
type = type_translator.BuildType().ptr();
|
|
type_arguments.SetTypeAt(1, type);
|
|
|
|
// Instantiate class.
|
|
type = Type::New(map_class, type_arguments);
|
|
type = ClassFinalizer::FinalizeType(type, ClassFinalizer::kCanonicalize);
|
|
type_arguments = type.arguments();
|
|
|
|
// Fill map with constant elements.
|
|
const auto& map = LinkedHashMap::Handle(
|
|
Z, ImmutableLinkedHashMap::NewUninitialized(Heap::kOld));
|
|
ASSERT_EQUAL(map.GetClassId(), kImmutableLinkedHashMapCid);
|
|
map.SetTypeArguments(type_arguments);
|
|
const intptr_t length = reader.ReadUInt();
|
|
const intptr_t used_data = (length << 1);
|
|
map.set_used_data(used_data);
|
|
|
|
const auto& data = Array::Handle(Z, Array::New(used_data));
|
|
map.set_data(data);
|
|
|
|
map.set_deleted_keys(0);
|
|
map.ComputeAndSetHashMask();
|
|
|
|
Instance& constant = Instance::Handle(Z);
|
|
for (intptr_t j = 0; j < used_data; ++j) {
|
|
// Recurse into lazily evaluating all "sub" constants
|
|
// needed to evaluate the current constant.
|
|
const intptr_t entry_index = reader.ReadUInt();
|
|
ASSERT(entry_index < constant_offset); // DAG!
|
|
constant = ReadConstant(entry_index);
|
|
data.SetAt(j, constant);
|
|
}
|
|
|
|
instance = map.ptr();
|
|
break;
|
|
}
|
|
case kSetConstant: {
|
|
const auto& set_class = Class::Handle(
|
|
Z,
|
|
H.isolate_group()->object_store()->immutable_linked_hash_set_class());
|
|
ASSERT(!set_class.IsNull());
|
|
|
|
// Build types from the raw bytes (needs temporary translator).
|
|
TypeTranslator type_translator(
|
|
&reader, this, active_class_, /* finalize = */ true,
|
|
active_class_->RequireConstCanonicalTypeErasure(null_safety),
|
|
/* in_constant_context = */ true);
|
|
auto& type_arguments =
|
|
TypeArguments::Handle(Z, TypeArguments::New(1, Heap::kOld));
|
|
AbstractType& type = type_translator.BuildType();
|
|
type_arguments.SetTypeAt(0, type);
|
|
|
|
// Instantiate class.
|
|
type = Type::New(set_class, type_arguments);
|
|
type = ClassFinalizer::FinalizeType(type, ClassFinalizer::kCanonicalize);
|
|
type_arguments = type.arguments();
|
|
|
|
// Fill set with constant elements.
|
|
const auto& set = LinkedHashSet::Handle(
|
|
Z, ImmutableLinkedHashSet::NewUninitialized(Heap::kOld));
|
|
ASSERT_EQUAL(set.GetClassId(), kImmutableLinkedHashSetCid);
|
|
set.SetTypeArguments(type_arguments);
|
|
const intptr_t length = reader.ReadUInt();
|
|
const intptr_t used_data = length;
|
|
set.set_used_data(used_data);
|
|
|
|
const auto& data = Array::Handle(Z, Array::New(used_data));
|
|
set.set_data(data);
|
|
|
|
set.set_deleted_keys(0);
|
|
set.ComputeAndSetHashMask();
|
|
|
|
Instance& constant = Instance::Handle(Z);
|
|
for (intptr_t j = 0; j < used_data; ++j) {
|
|
// Recurse into lazily evaluating all "sub" constants
|
|
// needed to evaluate the current constant.
|
|
const intptr_t entry_index = reader.ReadUInt();
|
|
ASSERT(entry_index < constant_offset); // DAG!
|
|
constant = ReadConstant(entry_index);
|
|
data.SetAt(j, constant);
|
|
}
|
|
|
|
instance = set.ptr();
|
|
break;
|
|
}
|
|
case kInstanceConstant: {
|
|
const NameIndex index = reader.ReadCanonicalNameReference();
|
|
const auto& klass = Class::Handle(Z, H.LookupClassByKernelClass(index));
|
|
if (!klass.is_declaration_loaded()) {
|
|
FATAL1(
|
|
"Trying to evaluate an instance constant whose references class "
|
|
"%s is not loaded yet.",
|
|
klass.ToCString());
|
|
}
|
|
const auto& obj =
|
|
Object::Handle(Z, klass.EnsureIsAllocateFinalized(H.thread()));
|
|
ASSERT(obj.IsNull());
|
|
ASSERT(klass.is_enum_class() || klass.is_const());
|
|
instance = Instance::New(klass, Heap::kOld);
|
|
// Build type from the raw bytes (needs temporary translator).
|
|
TypeTranslator type_translator(
|
|
&reader, this, active_class_, /* finalize = */ true,
|
|
active_class_->RequireConstCanonicalTypeErasure(null_safety),
|
|
/* in_constant_context = */ true);
|
|
const intptr_t number_of_type_arguments = reader.ReadUInt();
|
|
if (klass.NumTypeArguments() > 0) {
|
|
auto& type_arguments = TypeArguments::Handle(
|
|
Z, TypeArguments::New(number_of_type_arguments, Heap::kOld));
|
|
for (intptr_t j = 0; j < number_of_type_arguments; ++j) {
|
|
type_arguments.SetTypeAt(j, type_translator.BuildType());
|
|
}
|
|
// Instantiate class.
|
|
auto& type = AbstractType::Handle(Z, Type::New(klass, type_arguments));
|
|
type =
|
|
ClassFinalizer::FinalizeType(type, ClassFinalizer::kCanonicalize);
|
|
type_arguments = type.arguments();
|
|
instance.SetTypeArguments(type_arguments);
|
|
} else {
|
|
ASSERT(number_of_type_arguments == 0);
|
|
}
|
|
// Set the fields.
|
|
const intptr_t number_of_fields = reader.ReadUInt();
|
|
Field& field = Field::Handle(Z);
|
|
Instance& constant = Instance::Handle(Z);
|
|
for (intptr_t j = 0; j < number_of_fields; ++j) {
|
|
field = H.LookupFieldByKernelField(reader.ReadCanonicalNameReference());
|
|
// Recurse into lazily evaluating all "sub" constants
|
|
// needed to evaluate the current constant.
|
|
const intptr_t entry_index = reader.ReadUInt();
|
|
ASSERT(entry_index < constant_offset); // DAG!
|
|
constant = ReadConstant(entry_index);
|
|
instance.SetField(field, constant);
|
|
}
|
|
break;
|
|
}
|
|
case kInstantiationConstant: {
|
|
// Recurse into lazily evaluating the "sub" constant
|
|
// needed to evaluate the current constant.
|
|
const intptr_t entry_index = reader.ReadUInt();
|
|
ASSERT(entry_index < constant_offset); // DAG!
|
|
const auto& constant = Instance::Handle(Z, ReadConstant(entry_index));
|
|
ASSERT(!constant.IsNull());
|
|
|
|
// Build type from the raw bytes (needs temporary translator).
|
|
TypeTranslator type_translator(
|
|
&reader, this, active_class_, /* finalize = */ true,
|
|
active_class_->RequireConstCanonicalTypeErasure(null_safety),
|
|
/* in_constant_context = */ true);
|
|
const intptr_t number_of_type_arguments = reader.ReadUInt();
|
|
ASSERT(number_of_type_arguments > 0);
|
|
auto& type_arguments = TypeArguments::Handle(
|
|
Z, TypeArguments::New(number_of_type_arguments, Heap::kOld));
|
|
for (intptr_t j = 0; j < number_of_type_arguments; ++j) {
|
|
type_arguments.SetTypeAt(j, type_translator.BuildType());
|
|
}
|
|
type_arguments = type_arguments.Canonicalize(Thread::Current(), nullptr);
|
|
// Make a copy of the old closure, and set delayed type arguments.
|
|
Closure& closure = Closure::Handle(Z, Closure::RawCast(constant.ptr()));
|
|
Function& function = Function::Handle(Z, closure.function());
|
|
const auto& type_arguments2 =
|
|
TypeArguments::Handle(Z, closure.instantiator_type_arguments());
|
|
// The function type arguments are used for type parameters from enclosing
|
|
// closures. Though inner closures cannot be constants. We should
|
|
// therefore see `null here.
|
|
ASSERT(closure.function_type_arguments() == TypeArguments::null());
|
|
Context& context = Context::Handle(Z, closure.context());
|
|
instance = Closure::New(type_arguments2, Object::null_type_arguments(),
|
|
type_arguments, function, context, Heap::kOld);
|
|
break;
|
|
}
|
|
case kStaticTearOffConstant:
|
|
case kConstructorTearOffConstant:
|
|
case kRedirectingFactoryTearOffConstant: {
|
|
const NameIndex index = reader.ReadCanonicalNameReference();
|
|
Function& function = Function::Handle(Z);
|
|
if (H.IsConstructor(index)) {
|
|
function = H.LookupConstructorByKernelConstructor(index);
|
|
} else {
|
|
function = H.LookupStaticMethodByKernelProcedure(index);
|
|
}
|
|
function = function.ImplicitClosureFunction();
|
|
instance = function.ImplicitStaticClosure();
|
|
break;
|
|
}
|
|
case kTypeLiteralConstant: {
|
|
// Build type from the raw bytes (needs temporary translator).
|
|
// Const canonical type erasure is not applied to constant type literals.
|
|
// However, CFE must ensure that constant type literals can be
|
|
// canonicalized to an identical representant independently of the null
|
|
// safety mode currently in use (sound or unsound) or migration state of
|
|
// the declaring library (legacy or opted-in).
|
|
TypeTranslator type_translator(&reader, this, active_class_,
|
|
/* finalize = */ true,
|
|
/* apply_canonical_type_erasure = */ false,
|
|
/* in_constant_context = */ true);
|
|
instance = type_translator.BuildType().ptr();
|
|
break;
|
|
}
|
|
default:
|
|
// We should never see unevaluated constants (kUnevaluatedConstant) in
|
|
// the constant table, they should have been fully evaluated before we
|
|
// get them.
|
|
H.ReportError(script_, TokenPosition::kNoSource,
|
|
"Cannot lazily read constant: unexpected kernel tag (%" Pd
|
|
")",
|
|
constant_tag);
|
|
}
|
|
return H.Canonicalize(instance);
|
|
}
|
|
|
|
} // namespace kernel
|
|
} // namespace dart
|