blob: e5065e2fdd5575973938fbe3460875e66b607637 [file] [log] [blame]
// 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_evaluator.h"
#include "vm/compiler/aot/precompiler.h"
#include "vm/compiler/frontend/kernel_to_il.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/dart_entry.h"
#include "vm/longjump.h"
#include "vm/object_store.h"
#if !defined(DART_PRECOMPILED_RUNTIME)
namespace dart {
namespace kernel {
#define Z (zone_)
#define H (translation_helper_)
#define T (type_translator_)
#define I Isolate::Current()
ConstantEvaluator::ConstantEvaluator(KernelReaderHelper* helper,
TypeTranslator* type_translator,
ActiveClass* active_class,
FlowGraphBuilder* flow_graph_builder)
: helper_(helper),
isolate_(Isolate::Current()),
zone_(helper->zone_),
translation_helper_(helper->translation_helper_),
type_translator_(*type_translator),
active_class_(active_class),
flow_graph_builder_(flow_graph_builder),
script_(helper->script()),
result_(Instance::Handle(zone_)) {}
bool ConstantEvaluator::IsCached(intptr_t offset) {
return GetCachedConstant(offset, &result_);
}
RawInstance* ConstantEvaluator::EvaluateExpression(intptr_t offset,
bool reset_position) {
ASSERT(Error::Handle(Z, H.thread()->sticky_error()).IsNull());
if (!GetCachedConstant(offset, &result_)) {
BailoutIfBackgroundCompilation();
ASSERT(IsAllowedToEvaluate());
intptr_t original_offset = helper_->ReaderOffset();
helper_->SetOffset(offset);
uint8_t payload = 0;
Tag tag = helper_->ReadTag(&payload); // read tag.
switch (tag) {
case kVariableGet:
EvaluateVariableGet(/* is_specialized = */ false);
break;
case kSpecializedVariableGet:
EvaluateVariableGet(/* is_specialized = */ true);
break;
case kPropertyGet:
EvaluatePropertyGet();
break;
case kDirectPropertyGet:
EvaluateDirectPropertyGet();
break;
case kStaticGet:
EvaluateStaticGet();
break;
case kMethodInvocation:
EvaluateMethodInvocation();
break;
case kDirectMethodInvocation:
EvaluateDirectMethodInvocation();
break;
case kSuperMethodInvocation:
EvaluateSuperMethodInvocation();
break;
case kStaticInvocation:
case kConstStaticInvocation:
EvaluateStaticInvocation();
break;
case kConstConstructorInvocation:
EvaluateConstructorInvocationInternal();
break;
case kNot:
EvaluateNot();
break;
case kLogicalExpression:
EvaluateLogicalExpression();
break;
case kConditionalExpression:
EvaluateConditionalExpression();
break;
case kStringConcatenation:
EvaluateStringConcatenation();
break;
case kListConcatenation:
case kSetConcatenation:
case kMapConcatenation:
case kInstanceCreation:
// These only occur inside unevaluated constants, so if we decide to
// remove support for late evaluation of environment constants from
// dill files in the VM, an implementation here will not be necessary.
H.ReportError(
script_, TokenPosition::kNoSource,
"Unexpected unevaluated constant, All constant expressions"
" are expected to be evaluated at this point %s (%d)",
Reader::TagName(tag), tag);
break;
case kSymbolLiteral:
EvaluateSymbolLiteral();
break;
case kTypeLiteral:
EvaluateTypeLiteral();
break;
case kAsExpression:
EvaluateAsExpression();
break;
case kConstListLiteral:
EvaluateListLiteralInternal();
break;
case kConstSetLiteral:
// Set literals are currently desugared in the frontend and will not
// reach the VM. See http://dartbug.com/35124 for discussion.
H.ReportError(script_, TokenPosition::kNoSource,
"Unexpected set literal constant, this constant"
" is expected to be evaluated at this point %s (%d)",
Reader::TagName(tag), tag);
break;
case kConstMapLiteral:
EvaluateMapLiteralInternal();
break;
case kLet:
EvaluateLet();
break;
case kBlockExpression: {
UNIMPLEMENTED();
break;
}
case kInstantiation:
EvaluatePartialTearoffInstantiation();
break;
case kBigIntLiteral:
EvaluateBigIntLiteral();
break;
case kStringLiteral:
EvaluateStringLiteral();
break;
case kSpecializedIntLiteral:
EvaluateIntLiteral(payload);
break;
case kNegativeIntLiteral:
EvaluateIntLiteral(true);
break;
case kPositiveIntLiteral:
EvaluateIntLiteral(false);
break;
case kDoubleLiteral:
EvaluateDoubleLiteral();
break;
case kTrueLiteral:
EvaluateBoolLiteral(true);
break;
case kFalseLiteral:
EvaluateBoolLiteral(false);
break;
case kNullLiteral:
EvaluateNullLiteral();
break;
case kConstantExpression:
helper_->ReadPosition();
helper_->SkipDartType();
result_ = EvaluateConstantExpression(helper_->ReadUInt());
break;
case kDeprecated_ConstantExpression:
result_ = EvaluateConstantExpression(helper_->ReadUInt());
break;
default:
H.ReportError(
script_, TokenPosition::kNoSource,
"Not a constant expression: unexpected kernel tag %s (%d)",
Reader::TagName(tag), tag);
}
CacheConstantValue(offset, result_);
if (reset_position) helper_->SetOffset(original_offset);
} else {
if (!reset_position) {
helper_->SetOffset(offset);
helper_->SkipExpression();
}
}
return result_.raw();
}
Instance& ConstantEvaluator::EvaluateListLiteral(intptr_t offset,
bool reset_position) {
if (!GetCachedConstant(offset, &result_)) {
BailoutIfBackgroundCompilation();
ASSERT(IsAllowedToEvaluate());
intptr_t original_offset = helper_->ReaderOffset();
helper_->SetOffset(offset);
helper_->ReadTag(); // skip tag.
EvaluateListLiteralInternal();
CacheConstantValue(offset, result_);
if (reset_position) helper_->SetOffset(original_offset);
}
// We return a new `ZoneHandle` here on purpose: The intermediate language
// instructions do not make a copy of the handle, so we do it.
return Instance::ZoneHandle(Z, result_.raw());
}
Instance& ConstantEvaluator::EvaluateMapLiteral(intptr_t offset,
bool reset_position) {
if (!GetCachedConstant(offset, &result_)) {
BailoutIfBackgroundCompilation();
ASSERT(IsAllowedToEvaluate());
intptr_t original_offset = helper_->ReaderOffset();
helper_->SetOffset(offset);
helper_->ReadTag(); // skip tag.
EvaluateMapLiteralInternal();
CacheConstantValue(offset, result_);
if (reset_position) helper_->SetOffset(original_offset);
}
// We return a new `ZoneHandle` here on purpose: The intermediate language
// instructions do not make a copy of the handle, so we do it.
return Instance::ZoneHandle(Z, result_.raw());
}
Instance& ConstantEvaluator::EvaluateConstructorInvocation(
intptr_t offset,
bool reset_position) {
if (!GetCachedConstant(offset, &result_)) {
BailoutIfBackgroundCompilation();
ASSERT(IsAllowedToEvaluate());
intptr_t original_offset = helper_->ReaderOffset();
helper_->SetOffset(offset);
helper_->ReadTag(); // skip tag.
EvaluateConstructorInvocationInternal();
CacheConstantValue(offset, result_);
if (reset_position) helper_->SetOffset(original_offset);
}
// We return a new `ZoneHandle` here on purpose: The intermediate language
// instructions do not make a copy of the handle, so we do it.
return Instance::ZoneHandle(Z, result_.raw());
}
Instance& ConstantEvaluator::EvaluateStaticInvocation(intptr_t offset,
bool reset_position) {
if (!GetCachedConstant(offset, &result_)) {
BailoutIfBackgroundCompilation();
ASSERT(IsAllowedToEvaluate());
intptr_t original_offset = helper_->ReaderOffset();
helper_->SetOffset(offset);
helper_->ReadTag(); // skip tag.
EvaluateStaticInvocation();
CacheConstantValue(offset, result_);
if (reset_position) helper_->SetOffset(original_offset);
}
// We return a new `ZoneHandle` here on purpose: The intermediate language
// instructions do not make a copy of the handle, so we do it.
return Instance::ZoneHandle(Z, result_.raw());
}
RawObject* ConstantEvaluator::EvaluateExpressionSafe(intptr_t offset) {
LongJumpScope jump;
if (setjmp(*jump.Set()) == 0) {
return EvaluateExpression(offset);
} else {
return H.thread()->StealStickyError();
}
}
RawObject* ConstantEvaluator::EvaluateAnnotations() {
intptr_t list_length = helper_->ReadListLength(); // read list length.
const Array& metadata_values =
Array::Handle(Z, Array::New(list_length, H.allocation_space()));
Instance& value = Instance::Handle(Z);
for (intptr_t i = 0; i < list_length; ++i) {
// this will (potentially) read the expression, but reset the position.
value = EvaluateExpression(helper_->ReaderOffset());
helper_->SkipExpression(); // read (actual) initializer.
metadata_values.SetAt(i, value);
}
return metadata_values.raw();
}
RawInstance* ConstantEvaluator::EvaluateConstantExpression(
intptr_t constant_offset) {
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()->kernel_constants_mutex());
KernelConstantsMap constant_map(H.info().constants());
result_ ^= constant_map.GetOrNull(constant_offset);
ASSERT(constant_map.Release().raw() == H.info().constants());
}
// On miss, evaluate, and insert value.
if (result_.IsNull()) {
result_ = EvaluateConstant(constant_offset);
SafepointMutexLocker ml(H.thread()->isolate()->kernel_constants_mutex());
KernelConstantsMap constant_map(H.info().constants());
auto insert = constant_map.InsertNewOrGetValue(constant_offset, result_);
ASSERT(insert == result_.raw());
H.info().set_constants(constant_map.Release()); // update!
}
return result_.raw();
}
RawInstance* ConstantEvaluator::EvaluateConstant(intptr_t constant_offset) {
// Get reader directly into raw bytes of constant table.
KernelReaderHelper reader(Z, &H, script_, H.constants_table(), 0);
reader.ReadUInt(); // skip variable-sized int for adjusted constant offset
reader.SetOffset(reader.ReaderOffset() + constant_offset);
// 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().raw()
: Object::bool_false().raw();
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()).raw();
break;
case kSymbolConstant: {
Library& library = Library::Handle(Z);
library = Library::InternalLibrary();
const Class& symbol_class =
Class::Handle(Z, library.LookupClass(Symbols::Symbol()));
const Field& 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 Library& corelib = Library::Handle(Z, Library::CoreLibrary());
const Class& list_class =
Class::Handle(Z, corelib.LookupClassAllowPrivate(Symbols::_List()));
// Build type from the raw bytes (needs temporary translator).
TypeTranslator type_translator(&reader, active_class_, true);
TypeArguments& type_arguments =
TypeArguments::ZoneHandle(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, TokenPosition::kNoSource);
type = ClassFinalizer::FinalizeType(*active_class_->klass, 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_offset = reader.ReadUInt();
ASSERT(entry_offset < constant_offset); // DAG!
constant = EvaluateConstantExpression(entry_offset);
array.SetAt(j, constant);
}
instance = array.raw();
break;
}
case kInstanceConstant: {
const NameIndex index = reader.ReadCanonicalNameReference();
const Class& klass = Class::Handle(Z, H.LookupClassByKernelClass(index));
const Object& obj =
Object::Handle(Z, klass.EnsureIsFinalized(H.thread()));
ASSERT(obj.IsNull());
instance = Instance::New(klass, Heap::kOld);
// Build type from the raw bytes (needs temporary translator).
TypeTranslator type_translator(&reader, active_class_, true);
const intptr_t number_of_type_arguments = reader.ReadUInt();
if (klass.NumTypeArguments() > 0) {
TypeArguments& type_arguments = TypeArguments::ZoneHandle(
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.
AbstractType& type = AbstractType::Handle(
Z, Type::New(klass, type_arguments, TokenPosition::kNoSource));
type = ClassFinalizer::FinalizeType(*active_class_->klass, 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_offset = reader.ReadUInt();
ASSERT(entry_offset < constant_offset); // DAG!
constant = EvaluateConstantExpression(entry_offset);
instance.SetField(field, constant);
}
break;
}
case kPartialInstantiationConstant: {
// Recurse into lazily evaluating the "sub" constant
// needed to evaluate the current constant.
const intptr_t entry_offset = reader.ReadUInt();
ASSERT(entry_offset < constant_offset); // DAG!
Instance& constant =
Instance::Handle(Z, EvaluateConstantExpression(entry_offset));
// Happens if the tearoff was in the vmservice library and we have
// [skip_vm_service_library] enabled.
if (constant.IsNull()) {
instance = Instance::null();
break;
}
// Build type from the raw bytes (needs temporary translator).
TypeTranslator type_translator(&reader, active_class_, true);
const intptr_t number_of_type_arguments = reader.ReadUInt();
ASSERT(number_of_type_arguments > 0);
TypeArguments& type_arguments = TypeArguments::ZoneHandle(
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());
}
// Make a copy of the old closure, and set delayed type arguments.
Closure& closure = Closure::Handle(Z, Closure::RawCast(constant.raw()));
Function& function = Function::Handle(Z, closure.function());
TypeArguments& type_arguments2 =
TypeArguments::ZoneHandle(Z, closure.instantiator_type_arguments());
TypeArguments& type_arguments3 =
TypeArguments::ZoneHandle(Z, closure.function_type_arguments());
Context& context = Context::Handle(Z, closure.context());
instance = Closure::New(type_arguments2, Object::null_type_arguments(),
type_arguments3, function, context,
Heap::kOld); // was type_arguments?
break;
}
case kTearOffConstant: {
const NameIndex index = reader.ReadCanonicalNameReference();
Function& function =
Function::Handle(Z, H.LookupStaticMethodByKernelProcedure(index));
function = function.ImplicitClosureFunction();
instance = function.ImplicitStaticClosure();
break;
}
case kTypeLiteralConstant: {
// Build type from the raw bytes (needs temporary translator).
TypeTranslator type_translator(&reader, active_class_, true);
instance = type_translator.BuildType().raw();
break;
}
default:
// Set literals (kSetConstant) are currently desugared in the frontend
// and will not reach the VM. See http://dartbug.com/35124 for some
// discussion. Map constants (kMapConstant ) are already lowered to
// InstanceConstant or ListConstant. 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);
}
void ConstantEvaluator::BailoutIfBackgroundCompilation() {
if (Compiler::IsBackgroundCompilation()) {
Compiler::AbortBackgroundCompilation(
DeoptId::kNone, "Cannot evaluate annotations in background compiler.");
}
}
bool ConstantEvaluator::IsBuildingFlowGraph() const {
return flow_graph_builder_ != nullptr;
}
bool ConstantEvaluator::IsAllowedToEvaluate() const {
return FLAG_precompiled_mode || !IsBuildingFlowGraph() ||
!flow_graph_builder_->optimizing_;
}
void ConstantEvaluator::EvaluateVariableGet(bool is_specialized) {
ASSERT(IsBuildingFlowGraph());
// When we see a [VariableGet] the corresponding [VariableDeclaration] must've
// been executed already. It therefore must have a constant object associated
// with it.
const TokenPosition position = helper_->ReadPosition(); // read position.
const intptr_t variable_kernel_position =
helper_->ReadUInt(); // read kernel position.
if (!is_specialized) {
helper_->ReadUInt(); // read relative variable index.
helper_->SkipOptionalDartType(); // read promoted type.
}
LocalVariable* variable =
flow_graph_builder_->LookupVariable(variable_kernel_position);
if (!variable->IsConst()) {
H.ReportError(script_, position, "Not a constant expression.");
}
result_ = variable->ConstValue()->raw();
}
void ConstantEvaluator::EvaluateGetStringLength(intptr_t expression_offset,
TokenPosition position) {
EvaluateExpression(expression_offset);
if (result_.IsString()) {
const String& str = String::Handle(Z, String::RawCast(result_.raw()));
result_ = Integer::New(str.Length(), H.allocation_space());
} else {
H.ReportError(
script_, position,
"Constant expressions can only call 'length' on string constants.");
}
}
void ConstantEvaluator::EvaluatePropertyGet() {
const TokenPosition position = helper_->ReadPosition(); // read position.
intptr_t expression_offset = helper_->ReaderOffset();
helper_->SkipExpression(); // read receiver.
StringIndex name = helper_->ReadNameAsStringIndex(); // read name.
helper_->SkipCanonicalNameReference(); // read interface_target_reference.
if (H.StringEquals(name, "length")) {
EvaluateGetStringLength(expression_offset, position);
} else {
H.ReportError(
script_, position,
"Constant expressions can only call 'length' on string constants.");
}
}
void ConstantEvaluator::EvaluateDirectPropertyGet() {
TokenPosition position = helper_->ReadPosition(); // read position.
intptr_t expression_offset = helper_->ReaderOffset();
helper_->SkipExpression(); // read receiver.
NameIndex kernel_name =
helper_->ReadCanonicalNameReference(); // read target_reference.
// TODO(vegorov): add check based on the complete canonical name.
if (H.IsGetter(kernel_name) &&
H.StringEquals(H.CanonicalNameString(kernel_name), "length")) {
EvaluateGetStringLength(expression_offset, position);
} else {
H.ReportError(
script_, position,
"Constant expressions can only call 'length' on string constants.");
}
}
void ConstantEvaluator::EvaluateStaticGet() {
TokenPosition position = helper_->ReadPosition(); // read position.
NameIndex target =
helper_->ReadCanonicalNameReference(); // read target_reference.
ASSERT(Error::Handle(Z, H.thread()->sticky_error()).IsNull());
if (H.IsField(target)) {
const Field& field = Field::Handle(Z, H.LookupFieldByKernelField(target));
if (!field.is_const()) {
H.ReportError(script_, position, "Not a constant field.");
}
if (field.StaticValue() == Object::transition_sentinel().raw()) {
if (IsBuildingFlowGraph()) {
flow_graph_builder_->InlineBailout(
"kernel::ConstantEvaluator::EvaluateStaticGet::Cyclic");
}
H.ReportError(script_, position, "Not a constant expression.");
} else if (field.StaticValue() == Object::sentinel().raw()) {
field.SetStaticValue(Object::transition_sentinel());
const Object& value = Object::Handle(Z, field.EvaluateInitializer());
if (value.IsError()) {
field.SetStaticValue(Object::null_instance());
H.ReportError(Error::Cast(value), script_, position,
"Not a constant expression.");
}
Thread* thread = H.thread();
const Error& error =
Error::Handle(thread->zone(), thread->StealStickyError());
if (!error.IsNull()) {
field.SetStaticValue(Object::null_instance());
H.ReportError(error, script_, position, "Not a constant expression.");
}
ASSERT(value.IsNull() || value.IsInstance());
field.SetStaticValue(value.IsNull() ? Instance::null_instance()
: Instance::Cast(value));
result_ = field.StaticValue();
result_ = H.Canonicalize(result_);
field.SetStaticValue(result_, true);
} else {
result_ = field.StaticValue();
}
} else if (H.IsProcedure(target)) {
const Function& function =
Function::ZoneHandle(Z, H.LookupStaticMethodByKernelProcedure(target));
if (H.IsMethod(target)) {
Function& closure_function =
Function::ZoneHandle(Z, function.ImplicitClosureFunction());
result_ = closure_function.ImplicitStaticClosure();
result_ = H.Canonicalize(result_);
} else if (H.IsGetter(target)) {
H.ReportError(script_, position, "Not a constant expression.");
} else {
H.ReportError(script_, position, "Not a constant expression.");
}
}
}
void ConstantEvaluator::EvaluateMethodInvocation() {
TokenPosition position = helper_->ReadPosition(); // read position.
// This method call wasn't cached, so receiver et al. isn't cached either.
const Instance& receiver = Instance::Handle(
Z, EvaluateExpression(helper_->ReaderOffset(), false)); // read receiver.
Class& klass =
Class::Handle(Z, isolate_->class_table()->At(receiver.GetClassId()));
ASSERT(!klass.IsNull());
// Search the superclass chain for the selector.
const String& method_name = helper_->ReadNameAsMethodName(); // read name.
Function& function =
Function::Handle(Z, H.LookupDynamicFunction(klass, method_name));
// The frontend should guarantee that [MethodInvocation]s inside constant
// expressions are always valid.
ASSERT(!function.IsNull());
// Read arguments, run the method and canonicalize the result.
const Object& result = RunMethodCall(position, function, &receiver);
result_ ^= result.raw();
result_ = H.Canonicalize(result_);
helper_->SkipCanonicalNameReference(); // read interface_target_reference.
}
void ConstantEvaluator::EvaluateDirectMethodInvocation() {
TokenPosition position = helper_->ReadPosition(); // read position.
const Instance& receiver = Instance::Handle(
Z, EvaluateExpression(helper_->ReaderOffset(), false)); // read receiver.
NameIndex kernel_name =
helper_->ReadCanonicalNameReference(); // read target_reference.
const Function& function = Function::ZoneHandle(
Z, H.LookupMethodByMember(kernel_name, H.DartProcedureName(kernel_name)));
// Read arguments, run the method and canonicalize the result.
const Object& result = RunMethodCall(position, function, &receiver);
result_ ^= result.raw();
result_ = H.Canonicalize(result_);
}
void ConstantEvaluator::EvaluateSuperMethodInvocation() {
ASSERT(IsBuildingFlowGraph());
TokenPosition position = helper_->ReadPosition(); // read position.
const LocalVariable* receiver_variable =
flow_graph_builder_->parsed_function_->receiver_var();
ASSERT(receiver_variable->IsConst());
const Instance& receiver =
Instance::Handle(Z, receiver_variable->ConstValue()->raw());
ASSERT(!receiver.IsNull());
Class& klass = Class::Handle(Z, active_class_->klass->SuperClass());
ASSERT(!klass.IsNull());
const String& method_name = helper_->ReadNameAsMethodName(); // read name.
Function& function =
Function::Handle(Z, H.LookupDynamicFunction(klass, method_name));
// The frontend should guarantee that [MethodInvocation]s inside constant
// expressions are always valid.
ASSERT(!function.IsNull());
// Read arguments, run the method and canonicalize the result.
const Object& result = RunMethodCall(position, function, &receiver);
result_ ^= result.raw();
result_ = H.Canonicalize(result_);
helper_->SkipCanonicalNameReference(); // read interface_target_reference.
}
void ConstantEvaluator::EvaluateStaticInvocation() {
TokenPosition position = helper_->ReadPosition(); // read position.
NameIndex procedure_reference =
helper_->ReadCanonicalNameReference(); // read procedure reference.
const Function& function = Function::ZoneHandle(
Z, H.LookupStaticMethodByKernelProcedure(procedure_reference));
Class& klass = Class::Handle(Z, function.Owner());
intptr_t argument_count =
helper_->ReadUInt(); // read arguments part #1: arguments count.
// Build the type arguments vector (if necessary).
const TypeArguments* type_arguments =
TranslateTypeArguments(function, &klass); // read argument types.
// read positional and named parameters.
const Object& result =
RunFunction(position, function, argument_count, NULL, type_arguments);
result_ ^= result.raw();
result_ = H.Canonicalize(result_);
}
void ConstantEvaluator::EvaluateConstructorInvocationInternal() {
TokenPosition position = helper_->ReadPosition(); // read position.
NameIndex target = helper_->ReadCanonicalNameReference(); // read target.
const Function& constructor =
Function::Handle(Z, H.LookupConstructorByKernelConstructor(target));
Class& klass = Class::Handle(Z, constructor.Owner());
intptr_t argument_count =
helper_->ReadUInt(); // read arguments part #1: arguments count.
// Build the type arguments vector (if necessary).
const TypeArguments* type_arguments =
TranslateTypeArguments(constructor, &klass); // read argument types.
if (klass.NumTypeArguments() > 0 && !klass.IsGeneric()) {
Type& type = Type::ZoneHandle(Z, T.ReceiverType(klass).raw());
// TODO(27590): Can we move this code into [ReceiverType]?
type ^= ClassFinalizer::FinalizeType(*active_class_->klass, type,
ClassFinalizer::kFinalize);
TypeArguments& canonicalized_type_arguments =
TypeArguments::ZoneHandle(Z, type.arguments());
canonicalized_type_arguments = canonicalized_type_arguments.Canonicalize();
type_arguments = &canonicalized_type_arguments;
}
// Prepare either the instance or the type argument vector for the constructor
// call.
Instance* receiver = NULL;
const TypeArguments* type_arguments_argument = NULL;
if (!constructor.IsFactory()) {
receiver = &Instance::Handle(Z, Instance::New(klass, Heap::kOld));
if (type_arguments != NULL) {
receiver->SetTypeArguments(*type_arguments);
}
} else {
type_arguments_argument = type_arguments;
}
// read positional and named parameters.
const Object& result = RunFunction(position, constructor, argument_count,
receiver, type_arguments_argument);
if (constructor.IsFactory()) {
// Factories return the new object.
result_ ^= result.raw();
} else {
ASSERT(!receiver->IsNull());
result_ = (*receiver).raw();
}
if (I->obfuscate() &&
(result_.clazz() == I->object_store()->symbol_class())) {
Obfuscator::ObfuscateSymbolInstance(H.thread(), result_);
}
result_ = H.Canonicalize(result_);
}
void ConstantEvaluator::EvaluateNot() {
result_ = Bool::Get(!EvaluateBooleanExpressionHere()).raw();
}
void ConstantEvaluator::EvaluateLogicalExpression() {
bool left = EvaluateBooleanExpressionHere(); // read left.
LogicalOperator op = static_cast<LogicalOperator>(helper_->ReadByte());
if (op == kAnd) {
if (left) {
EvaluateBooleanExpressionHere(); // read right.
} else {
helper_->SkipExpression(); // read right.
}
} else {
ASSERT(op == kOr);
if (!left) {
EvaluateBooleanExpressionHere(); // read right.
} else {
helper_->SkipExpression(); // read right.
}
}
}
void ConstantEvaluator::EvaluateAsExpression() {
TokenPosition position = helper_->ReadPosition();
const uint8_t flags = helper_->ReadFlags();
const bool is_type_error = (flags & (1 << 0)) != 0;
// Check that this AsExpression was inserted by the front-end.
if (!is_type_error) {
H.ReportError(
script_, position,
"explicit as operator is not permitted in constant expression");
}
EvaluateExpression(helper_->ReaderOffset(), false);
const AbstractType& type = T.BuildType();
if (!type.IsInstantiated()) {
const String& type_str = String::Handle(type.UserVisibleName());
H.ReportError(
script_, position,
"Not a constant expression: right hand side of an implicit "
"as-expression is expected to be an instantiated type, got %s",
type_str.ToCString());
}
const TypeArguments& instantiator_type_arguments = TypeArguments::Handle();
const TypeArguments& function_type_arguments = TypeArguments::Handle();
if (!result_.IsInstanceOf(type, instantiator_type_arguments,
function_type_arguments)) {
const AbstractType& rtype =
AbstractType::Handle(result_.GetType(Heap::kNew));
const String& result_str = String::Handle(rtype.UserVisibleName());
const String& type_str = String::Handle(type.UserVisibleName());
H.ReportError(
script_, position,
"Not a constant expression: Type '%s' is not a subtype of type '%s'",
result_str.ToCString(), type_str.ToCString());
}
}
void ConstantEvaluator::EvaluateConditionalExpression() {
bool condition = EvaluateBooleanExpressionHere();
if (condition) {
EvaluateExpression(helper_->ReaderOffset(), false); // read then.
helper_->SkipExpression(); // read otherwise.
} else {
helper_->SkipExpression(); // read then.
EvaluateExpression(helper_->ReaderOffset(), false); // read otherwise.
}
helper_->SkipOptionalDartType(); // read unused static type.
}
void ConstantEvaluator::EvaluateStringConcatenation() {
TokenPosition position = helper_->ReadPosition(); // read position.
intptr_t length = helper_->ReadListLength(); // read list length.
bool all_string = true;
const Array& strings =
Array::Handle(Z, Array::New(length, H.allocation_space()));
for (intptr_t i = 0; i < length; ++i) {
EvaluateExpression(helper_->ReaderOffset(),
false); // read ith expression.
strings.SetAt(i, result_);
all_string = all_string && result_.IsString();
}
if (all_string) {
result_ = String::ConcatAll(strings, Heap::kOld);
result_ = H.Canonicalize(result_);
} else {
// Get string interpolation function.
const Class& cls =
Class::Handle(Z, Library::LookupCoreClass(Symbols::StringBase()));
ASSERT(!cls.IsNull());
const Function& func = Function::Handle(
Z, cls.LookupStaticFunction(
Library::PrivateCoreLibName(Symbols::Interpolate())));
ASSERT(!func.IsNull());
// Build argument array to pass to the interpolation function.
const Array& interpolate_arg = Array::Handle(Z, Array::New(1, Heap::kOld));
interpolate_arg.SetAt(0, strings);
// Run and canonicalize.
const Object& result =
RunFunction(position, func, interpolate_arg, Array::null_array());
result_ = H.Canonicalize(String::Cast(result));
}
}
void ConstantEvaluator::EvaluateSymbolLiteral() {
const Class& owner = *active_class_->klass;
const Library& lib = Library::Handle(Z, owner.library());
String& symbol_value = H.DartIdentifier(lib, helper_->ReadStringReference());
const Class& symbol_class =
Class::ZoneHandle(Z, I->object_store()->symbol_class());
ASSERT(!symbol_class.IsNull());
const Function& symbol_constructor = Function::ZoneHandle(
Z, symbol_class.LookupConstructor(Symbols::SymbolCtor()));
ASSERT(!symbol_constructor.IsNull());
result_ ^= EvaluateConstConstructorCall(
symbol_class, TypeArguments::Handle(Z), symbol_constructor, symbol_value);
}
void ConstantEvaluator::EvaluateTypeLiteral() {
const AbstractType& type = T.BuildType();
result_ = type.raw();
}
void ConstantEvaluator::EvaluateListLiteralInternal() {
helper_->ReadPosition(); // read position.
const TypeArguments& type_arguments = T.BuildTypeArguments(1); // read type.
intptr_t length = helper_->ReadListLength(); // read list length.
const Array& const_list =
Array::ZoneHandle(Z, Array::New(length, Heap::kOld));
const_list.SetTypeArguments(type_arguments);
Instance& expression = Instance::Handle(Z);
for (intptr_t i = 0; i < length; ++i) {
expression = EvaluateExpression(helper_->ReaderOffset(),
false); // read ith expression.
const_list.SetAt(i, expression);
}
const_list.MakeImmutable();
result_ = H.Canonicalize(const_list);
}
void ConstantEvaluator::EvaluateMapLiteralInternal() {
helper_->ReadPosition(); // read position.
const TypeArguments& type_arguments =
T.BuildTypeArguments(2); // read key type and value type.
intptr_t length = helper_->ReadListLength(); // read length of entries.
// This MapLiteral wasn't cached, so content isn't cached either.
Array& const_kv_array = Array::Handle(Z, Array::New(2 * length, Heap::kOld));
Instance& temp = Instance::Handle(Z);
for (intptr_t i = 0; i < length; ++i) {
temp = EvaluateExpression(helper_->ReaderOffset(), false); // read key.
const_kv_array.SetAt(2 * i + 0, temp);
temp = EvaluateExpression(helper_->ReaderOffset(), false); // read value.
const_kv_array.SetAt(2 * i + 1, temp);
}
const_kv_array.MakeImmutable();
const_kv_array ^= H.Canonicalize(const_kv_array);
const Class& map_class =
Class::Handle(Z, Library::LookupCoreClass(Symbols::ImmutableMap()));
ASSERT(!map_class.IsNull());
ASSERT(map_class.NumTypeArguments() == 2);
const Field& field =
Field::Handle(Z, map_class.LookupInstanceFieldAllowPrivate(
H.DartSymbolObfuscate("_kvPairs")));
ASSERT(!field.IsNull());
// NOTE: This needs to be kept in sync with `runtime/lib/immutable_map.dart`!
result_ = Instance::New(map_class, Heap::kOld);
ASSERT(!result_.IsNull());
result_.SetTypeArguments(type_arguments);
result_.SetField(field, const_kv_array);
result_ = H.Canonicalize(result_);
}
void ConstantEvaluator::EvaluateLet() {
ASSERT(IsBuildingFlowGraph());
intptr_t kernel_position =
helper_->ReaderOffset() + helper_->data_program_offset_;
LocalVariable* local = flow_graph_builder_->LookupVariable(kernel_position);
// read variable declaration.
VariableDeclarationHelper helper(helper_);
helper.ReadUntilExcluding(VariableDeclarationHelper::kInitializer);
Tag tag = helper_->ReadTag(); // read (first part of) initializer.
if (tag == kNothing) {
local->SetConstValue(Instance::ZoneHandle(Z, Instance::null()));
} else {
local->SetConstValue(Instance::ZoneHandle(
Z, EvaluateExpression(helper_->ReaderOffset(),
false))); // read rest of initializer.
}
EvaluateExpression(helper_->ReaderOffset(), false); // read body
}
void ConstantEvaluator::EvaluatePartialTearoffInstantiation() {
// This method call wasn't cached, so receiver et al. isn't cached either.
const Instance& receiver = Instance::Handle(
Z, EvaluateExpression(helper_->ReaderOffset(), false)); // read receiver.
if (!receiver.IsClosure()) {
H.ReportError(script_, TokenPosition::kNoSource, "Expected closure.");
}
const Closure& old_closure = Closure::Cast(receiver);
// read type arguments.
intptr_t num_type_args = helper_->ReadListLength();
const TypeArguments* type_args = &T.BuildTypeArguments(num_type_args);
if (!type_args->IsNull() && !type_args->IsInstantiated()) {
H.ReportError(
script_, TokenPosition::kNoSource,
"Type arguments in partial instantiations must be instantiated and are "
"therefore not allowed to depend on type parameters.");
}
// Create new closure with the type arguments inserted, and other things
// copied over.
Closure& new_closure = Closure::Handle(
Z,
Closure::New(
TypeArguments::Handle(Z, old_closure.instantiator_type_arguments()),
TypeArguments::Handle(old_closure.function_type_arguments()),
*type_args, Function::Handle(Z, old_closure.function()),
Context::Handle(Z, old_closure.context()), Heap::kOld));
result_ = H.Canonicalize(new_closure);
}
void ConstantEvaluator::EvaluateBigIntLiteral() {
const String& value =
H.DartString(helper_->ReadStringReference()); // read string reference.
result_ = Integer::New(value, Heap::kOld);
if (result_.IsNull()) {
H.ReportError(script_, TokenPosition::kNoSource,
"Integer literal %s is out of range", value.ToCString());
}
result_ = H.Canonicalize(result_);
}
void ConstantEvaluator::EvaluateStringLiteral() {
result_ = H.DartSymbolPlain(helper_->ReadStringReference())
.raw(); // read string reference.
}
void ConstantEvaluator::EvaluateIntLiteral(uint8_t payload) {
int64_t value = static_cast<int32_t>(payload) - SpecializedIntLiteralBias;
result_ = Integer::New(value, Heap::kOld);
result_ = H.Canonicalize(result_);
}
void ConstantEvaluator::EvaluateIntLiteral(bool is_negative) {
int64_t value = is_negative ? -static_cast<int64_t>(helper_->ReadUInt())
: helper_->ReadUInt(); // read value.
result_ = Integer::New(value, Heap::kOld);
result_ = H.Canonicalize(result_);
}
void ConstantEvaluator::EvaluateDoubleLiteral() {
result_ = Double::New(helper_->ReadDouble(), Heap::kOld); // read value.
result_ = H.Canonicalize(result_);
}
void ConstantEvaluator::EvaluateBoolLiteral(bool value) {
result_ = Bool::Get(value).raw();
}
void ConstantEvaluator::EvaluateNullLiteral() {
result_ = Instance::null();
}
// This depends on being about to read the list of positionals on arguments.
const Object& ConstantEvaluator::RunFunction(TokenPosition position,
const Function& function,
intptr_t argument_count,
const Instance* receiver,
const TypeArguments* type_args) {
// We use a kernel2kernel constant evaluator in Dart 2.0 AOT compilation, so
// we should never end up evaluating constants using the VM's constant
// evaluator.
if (FLAG_precompiled_mode) {
UNREACHABLE();
}
// We do not support generic methods yet.
ASSERT((receiver == NULL) || (type_args == NULL));
intptr_t extra_arguments =
(receiver != NULL ? 1 : 0) + (type_args != NULL ? 1 : 0);
// Build up arguments.
const Array& arguments = Array::Handle(
Z, Array::New(extra_arguments + argument_count, H.allocation_space()));
intptr_t pos = 0;
if (receiver != NULL) {
arguments.SetAt(pos++, *receiver);
}
if (type_args != NULL) {
arguments.SetAt(pos++, *type_args);
}
// List of positional.
intptr_t list_length = helper_->ReadListLength(); // read list length.
for (intptr_t i = 0; i < list_length; ++i) {
EvaluateExpression(helper_->ReaderOffset(),
false); // read ith expression.
arguments.SetAt(pos++, result_);
}
// List of named.
list_length = helper_->ReadListLength(); // read list length.
const Array& names =
Array::Handle(Z, Array::New(list_length, H.allocation_space()));
for (intptr_t i = 0; i < list_length; ++i) {
String& name = H.DartSymbolObfuscate(
helper_->ReadStringReference()); // read ith name index.
names.SetAt(i, name);
EvaluateExpression(helper_->ReaderOffset(),
false); // read ith expression.
arguments.SetAt(pos++, result_);
}
return RunFunction(position, function, arguments, names);
}
const Object& ConstantEvaluator::RunFunction(const TokenPosition position,
const Function& function,
const Array& arguments,
const Array& names) {
// We do not support generic methods yet.
const int kTypeArgsLen = 0;
const Array& args_descriptor = Array::Handle(
Z, ArgumentsDescriptor::New(kTypeArgsLen, arguments.Length(), names));
const Object& result = Object::Handle(
Z, DartEntry::InvokeFunction(function, arguments, args_descriptor));
if (result.IsError()) {
H.ReportError(Error::Cast(result), script_, position,
"error evaluating constant constructor");
}
return result;
}
const Object& ConstantEvaluator::RunMethodCall(const TokenPosition position,
const Function& function,
const Instance* receiver) {
intptr_t argument_count = helper_->ReadUInt(); // read arguments count.
// TODO(28109) Support generic methods in the VM or reify them away.
ASSERT(helper_->PeekListLength() == 0);
helper_->SkipListOfDartTypes(); // read list of types.
// Run the method.
return RunFunction(position, function, argument_count, receiver, NULL);
}
RawObject* ConstantEvaluator::EvaluateConstConstructorCall(
const Class& type_class,
const TypeArguments& type_arguments,
const Function& constructor,
const Object& argument) {
// We use a kernel2kernel constant evaluator in Dart 2.0 AOT compilation, so
// we should never end up evaluating constants using the VM's constant
// evaluator.
if (FLAG_precompiled_mode) {
UNREACHABLE();
}
// Factories have one extra argument: the type arguments.
// Constructors have 1 extra arguments: receiver.
const int kTypeArgsLen = 0;
const int kNumArgs = 1;
const int kNumExtraArgs = 1;
const int argument_count = kNumArgs + kNumExtraArgs;
const Array& arg_values =
Array::Handle(Z, Array::New(argument_count, Heap::kOld));
Instance& instance = Instance::Handle(Z);
if (!constructor.IsFactory()) {
instance = Instance::New(type_class, Heap::kOld);
if (!type_arguments.IsNull()) {
ASSERT(type_arguments.IsInstantiated());
instance.SetTypeArguments(
TypeArguments::Handle(Z, type_arguments.Canonicalize()));
}
arg_values.SetAt(0, instance);
} else {
// Prepend type_arguments to list of arguments to factory.
ASSERT(type_arguments.IsZoneHandle());
arg_values.SetAt(0, type_arguments);
}
arg_values.SetAt((0 + kNumExtraArgs), argument);
const Array& args_descriptor =
Array::Handle(Z, ArgumentsDescriptor::New(kTypeArgsLen, argument_count,
Object::empty_array()));
const Object& result = Object::Handle(
Z, DartEntry::InvokeFunction(constructor, arg_values, args_descriptor));
ASSERT(!result.IsError());
if (constructor.IsFactory()) {
// The factory method returns the allocated object.
instance ^= result.raw();
}
if (I->obfuscate() &&
(instance.clazz() == I->object_store()->symbol_class())) {
Obfuscator::ObfuscateSymbolInstance(H.thread(), instance);
}
return H.Canonicalize(instance);
}
const TypeArguments* ConstantEvaluator::TranslateTypeArguments(
const Function& target,
Class* target_klass) {
intptr_t type_count = helper_->ReadListLength(); // read type count.
const TypeArguments* type_arguments = NULL;
if (type_count > 0) {
type_arguments = &T.BuildInstantiatedTypeArguments(
*target_klass, type_count); // read types.
if (!(type_arguments->IsNull() || type_arguments->IsInstantiated())) {
H.ReportError(script_, TokenPosition::kNoSource,
"Type must be constant in const constructor.");
}
} else if (target.IsFactory() && type_arguments == NULL) {
// All factories take a type arguments vector as first argument (independent
// of whether the class is generic or not).
type_arguments = &TypeArguments::ZoneHandle(Z, TypeArguments::null());
}
return type_arguments;
}
bool ConstantEvaluator::EvaluateBooleanExpressionHere() {
EvaluateExpression(helper_->ReaderOffset(), false);
AssertBool();
return result_.raw() == Bool::True().raw();
}
bool ConstantEvaluator::GetCachedConstant(intptr_t kernel_offset,
Instance* value) {
if (!IsBuildingFlowGraph()) return false;
const Function& function = flow_graph_builder_->parsed_function_->function();
if ((function.kind() == RawFunction::kImplicitStaticGetter ||
function.kind() == RawFunction::kStaticFieldInitializer) &&
!I->CanOptimizeImmediately()) {
// Don't cache constants in initializer expressions. They get
// evaluated only once.
return false;
}
bool is_present = false;
ASSERT(!script_.InVMIsolateHeap());
if (script_.compile_time_constants() == Array::null()) {
return false;
}
{
// Any access to constants arrays must be locked since mutator and
// background compiler can access the array at the same time.
SafepointMutexLocker ml(H.thread()->isolate()->kernel_constants_mutex());
KernelConstantsMap constants(script_.compile_time_constants());
*value ^= constants.GetOrNull(kernel_offset + helper_->data_program_offset_,
&is_present);
constants.Release();
}
return is_present;
}
void ConstantEvaluator::CacheConstantValue(intptr_t kernel_offset,
const Instance& value) {
ASSERT(Thread::Current()->IsMutatorThread());
if (!IsBuildingFlowGraph()) return;
const Function& function = flow_graph_builder_->parsed_function_->function();
if ((function.kind() == RawFunction::kImplicitStaticGetter ||
function.kind() == RawFunction::kStaticFieldInitializer) &&
!I->CanOptimizeImmediately()) {
// Don't cache constants in initializer expressions. They get
// evaluated only once.
return;
}
const intptr_t kInitialConstMapSize = 16;
ASSERT(!script_.InVMIsolateHeap());
if (script_.compile_time_constants() == Array::null()) {
const Array& array = Array::Handle(
HashTables::New<KernelConstantsMap>(kInitialConstMapSize, Heap::kNew));
script_.set_compile_time_constants(array);
}
{
// Any access to constants arrays must be locked since mutator and
// background compiler can access the array at the same time.
SafepointMutexLocker ml(H.thread()->isolate()->kernel_constants_mutex());
KernelConstantsMap constants(script_.compile_time_constants());
constants.InsertNewOrGetValue(kernel_offset + helper_->data_program_offset_,
value);
script_.set_compile_time_constants(constants.Release());
}
}
} // namespace kernel
} // namespace dart
#endif // !defined(DART_PRECOMPILED_RUNTIME)